grana_object/
intensity.rs1#[derive(Clone, Copy, Debug, PartialEq)]
2pub struct SampleStatistics {
3 mean: f32,
4 minimum: f32,
5 maximum: f32,
6 deviation: f32,
7 sum: f64,
8}
9
10impl SampleStatistics {
11 pub fn new(mean: f32, minimum: f32, maximum: f32, deviation: f32, sum: f64) -> Self {
12 assert!(
13 [mean, minimum, maximum, deviation]
14 .iter()
15 .all(|value| value.is_finite()),
16 "intensity statistics are finite"
17 );
18 assert!(
19 minimum <= mean && mean <= maximum,
20 "intensity statistics are ordered"
21 );
22 assert!(deviation >= 0.0, "an intensity deviation is non negative");
23 assert!(
24 sum.is_finite() && sum >= 0.0,
25 "an intensity sum is a non negative total"
26 );
27 Self {
28 mean,
29 minimum,
30 maximum,
31 deviation,
32 sum,
33 }
34 }
35
36 pub fn mean(self) -> f32 {
37 self.mean
38 }
39
40 pub fn minimum(self) -> f32 {
41 self.minimum
42 }
43
44 pub fn maximum(self) -> f32 {
45 self.maximum
46 }
47
48 pub fn deviation(self) -> f32 {
49 self.deviation
50 }
51
52 pub fn sum(self) -> f64 {
53 self.sum
54 }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq)]
58pub struct SampleAccumulator {
59 count: u64,
60 mean: f64,
61 squared: f64,
62 minimum: f32,
63 maximum: f32,
64 sum: f64,
65}
66
67impl SampleAccumulator {
68 pub fn new() -> Self {
69 Self {
70 count: 0,
71 mean: 0.0,
72 squared: 0.0,
73 minimum: f32::INFINITY,
74 maximum: f32::NEG_INFINITY,
75 sum: 0.0,
76 }
77 }
78
79 pub fn push(&mut self, value: f32) {
80 if !value.is_finite() {
81 return;
82 }
83 self.count += 1;
84 self.sum += f64::from(value);
85 self.minimum = self.minimum.min(value);
86 self.maximum = self.maximum.max(value);
87 let delta = f64::from(value) - self.mean;
88 self.mean += delta / self.count as f64;
89 self.squared += delta * (f64::from(value) - self.mean);
90 }
91
92 pub fn merge(&mut self, other: &Self) {
93 if other.count == 0 {
94 return;
95 }
96 if self.count == 0 {
97 *self = *other;
98 return;
99 }
100 let total = self.count + other.count;
101 let delta = other.mean - self.mean;
102 self.mean += delta * other.count as f64 / total as f64;
103 self.squared +=
104 other.squared + delta * delta * (self.count as f64 * other.count as f64 / total as f64);
105 self.count = total;
106 self.sum += other.sum;
107 self.minimum = self.minimum.min(other.minimum);
108 self.maximum = self.maximum.max(other.maximum);
109 }
110
111 pub fn statistics(self) -> SampleStatistics {
112 if self.count == 0 {
113 return SampleStatistics::new(0.0, 0.0, 0.0, 0.0, 0.0);
114 }
115 let mean = self
116 .mean
117 .clamp(f64::from(self.minimum), f64::from(self.maximum)) as f32;
118 let deviation = (self.squared / self.count as f64).max(0.0).sqrt() as f32;
119 SampleStatistics::new(mean, self.minimum, self.maximum, deviation, self.sum)
120 }
121}
122
123impl Default for SampleAccumulator {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129#[derive(Clone, Debug, PartialEq)]
130pub struct IntensityTable {
131 channels: usize,
132 values: Vec<SampleStatistics>,
133}
134
135impl IntensityTable {
136 pub fn new(channels: usize, values: Vec<SampleStatistics>) -> Self {
137 assert!(
138 channels > 0,
139 "an intensity table measures at least one channel"
140 );
141 assert_eq!(
142 values.len() % channels,
143 0,
144 "every object is measured in every channel"
145 );
146 Self { channels, values }
147 }
148
149 pub fn empty(channels: usize) -> Self {
150 Self::new(channels, Vec::new())
151 }
152
153 pub fn channels(&self) -> usize {
154 self.channels
155 }
156
157 pub fn objects(&self) -> usize {
158 self.values.len() / self.channels
159 }
160
161 pub fn is_empty(&self) -> bool {
162 self.values.is_empty()
163 }
164
165 pub fn object(&self, object: usize, channel: usize) -> SampleStatistics {
166 assert!(
167 object < self.objects() && channel < self.channels,
168 "the measured object lies inside the intensity table"
169 );
170 self.values[object * self.channels + channel]
171 }
172}