Skip to main content

grana_object/
intensity.rs

1#[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}