pineapple_core/mp/
intensity.rs

1// Copyright (c) 2025, Tom Ouellette
2// Licensed under the BSD 3-Clause License
3
4use std::cmp::Ordering;
5use std::ops::Deref;
6
7use num::{FromPrimitive, ToPrimitive};
8
9use crate::im::PineappleViewBuffer;
10
11#[inline]
12pub fn intensity_min<T>(pixels: &[T], channels: usize) -> Vec<f32>
13where
14    T: ToPrimitive,
15{
16    let mut min = vec![f32::INFINITY; channels];
17    for pixel in pixels.chunks_exact(channels) {
18        for (i, v) in pixel.iter().enumerate() {
19            let v = v.to_f32().unwrap();
20            if v < min[i] && v > 0. {
21                min[i] = v;
22            }
23        }
24    }
25
26    for v in min.iter_mut() {
27        if *v == f32::INFINITY {
28            *v = 0.0;
29        }
30    }
31
32    min
33}
34
35#[inline]
36pub fn intensity_max<T>(pixels: &[T], channels: usize) -> Vec<f32>
37where
38    T: ToPrimitive,
39{
40    let mut max = vec![f32::NEG_INFINITY; channels];
41    for pixel in pixels.chunks_exact(channels) {
42        for (i, v) in pixel.iter().enumerate() {
43            let v = v.to_f32().unwrap();
44            if v > max[i] && v > 0. {
45                max[i] = v;
46            }
47        }
48    }
49
50    for v in max.iter_mut() {
51        if *v == f32::NEG_INFINITY {
52            *v = 0.0;
53        }
54    }
55
56    max
57}
58
59#[inline]
60pub fn intensity_mean<T>(pixels: &[T], channels: usize) -> Vec<f32>
61where
62    T: ToPrimitive,
63{
64    let mut n = vec![0; channels];
65    let mut mean = vec![0.0; channels];
66    for pixel in pixels.chunks_exact(channels) {
67        for (i, v) in pixel.iter().enumerate() {
68            let v = v.to_f32().unwrap();
69            if v > 0. {
70                n[i] += 1;
71                mean[i] += v;
72            }
73        }
74    }
75
76    for i in 0..channels {
77        if n[i] > 0 {
78            mean[i] *= 1.0 / n[i] as f32;
79        }
80    }
81
82    mean
83}
84
85#[inline]
86pub fn intensity_sum<T>(pixels: &[T], channels: usize) -> Vec<f32>
87where
88    T: ToPrimitive,
89{
90    let mut sum = vec![0.0; channels];
91    for pixel in pixels.chunks_exact(channels) {
92        for (i, v) in pixel.iter().enumerate() {
93            sum[i] += v.to_f32().unwrap();
94        }
95    }
96
97    sum
98}
99
100#[inline]
101pub fn intensity_std<T>(pixels: &[T], channels: usize) -> Vec<f32>
102where
103    T: ToPrimitive,
104{
105    let mut n = vec![0; channels];
106    let mean = intensity_mean(pixels, channels);
107
108    let mut std = vec![0.0; channels];
109    for pixel in pixels.chunks_exact(channels) {
110        for (i, v) in pixel.iter().enumerate() {
111            let v = v.to_f32().unwrap();
112            if v > 0. {
113                n[i] += 1;
114                std[i] += (v - mean[i]) * (v - mean[i]);
115            }
116        }
117    }
118
119    for i in 0..channels {
120        if n[i] > 0 {
121            std[i] = (std[i] * 1.0 / n[i] as f32).sqrt()
122        }
123    }
124
125    std
126}
127
128#[inline]
129pub fn intensity_median<T>(pixels: &[T]) -> f32
130where
131    T: Copy + Into<f32> + PartialOrd + ToPrimitive,
132{
133    let mut pixels: Vec<f32> = pixels
134        .to_vec()
135        .iter()
136        .map(|x| x.to_f32().unwrap())
137        .filter(|x| *x > 0.)
138        .collect();
139
140    pixels.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
141
142    let len = pixels.len();
143    let mid = len / 2;
144
145    if len.is_multiple_of(2) {
146        let left = pixels[mid - 1];
147        let right = pixels[mid];
148        (left + right) / 2.0
149    } else {
150        pixels[mid]
151    }
152}
153
154#[inline]
155pub fn intensity_mad<T>(pixels: &[T]) -> f32
156where
157    T: Copy + Into<f32> + PartialOrd + ToPrimitive,
158{
159    let median = intensity_median(pixels);
160    let mut mad = Vec::new();
161    for pixel in pixels.iter() {
162        let pixel = pixel.to_f32().unwrap();
163        if pixel > 0. {
164            mad.push((pixel - median).abs());
165        }
166    }
167
168    mad.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
169    let mid = mad.len() / 2;
170    if mad.len() % 2 == 0 {
171        (mad[mid] + mad[mid - 1]) / 2.0
172    } else {
173        mad[mid]
174    }
175}
176
177#[inline]
178#[allow(clippy::all)]
179pub fn descriptors<T>(pixels: &[T], channels: usize) -> Vec<f32>
180where
181    T: ToPrimitive,
182{
183    let mut n = vec![0; channels];
184
185    // Initial allocation for all intensity measurements. The
186    // intensity min, max, sum, mean, and standard deviation
187    // are stored in chunks that span the number of channels.
188    // The last two spots are for median and mad descriptors.
189    let mut results = vec![0.0; channels * 5 + 2];
190
191    for i in 0..channels {
192        results[i + 0 * channels] = f32::INFINITY;
193        results[i + 1 * channels] = f32::NEG_INFINITY;
194    }
195
196    let mut store: Vec<f32> = Vec::with_capacity(pixels.len());
197
198    for pixel in pixels.chunks_exact(channels) {
199        for (i, v) in pixel.iter().enumerate() {
200            let v = v.to_f32().unwrap();
201
202            if v > 0. {
203                n[i] += 1;
204
205                // Intensity minimum
206                results[i + 0 * channels] = results[i + 0 * channels].min(v);
207
208                // Intensity maximum
209                results[i + 1 * channels] = results[i + 1 * channels].max(v);
210
211                // Intensity sum/integrated
212                results[i + 2 * channels] += v;
213            }
214
215            store.push(v);
216        }
217    }
218
219    for v in results.iter_mut().take(channels * 2) {
220        if *v == f32::NEG_INFINITY || *v == f32::INFINITY {
221            *v = 0.
222        }
223    }
224
225    // Intensity mean
226    for i in 0..channels {
227        if n[i] > 0 {
228            results[i + 3 * channels] = results[i + 2 * channels] * 1.0 / n[i] as f32;
229        }
230    }
231
232    // Intensity standard deviation
233    for pixel in store.chunks_exact(channels) {
234        for (i, &v) in pixel.iter().enumerate() {
235            results[i + 4 * channels] += (v - results[i + 3 * channels]).powi(2);
236        }
237    }
238
239    for i in 0..channels {
240        if n[i] > 0 {
241            results[i + 4 * channels] = (results[i + 4 * channels] * 1.0 / n[i] as f32).sqrt();
242        }
243    }
244
245    store.retain(|v| *v > 0.);
246
247    if store.is_empty() {
248        return results;
249    }
250
251    store.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
252
253    let n = store.len();
254    let mid = n / 2;
255    let len = results.len();
256
257    // Intensity median
258    results[len - 2] = if n % 2 == 0 {
259        (store[mid - 1] + store[mid]) / 2.0
260    } else {
261        store[mid]
262    };
263
264    store
265        .iter_mut()
266        .for_each(|pixel| *pixel = (*pixel - results[len - 2]).abs());
267
268    store.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
269
270    // Intensity median absolute deviation
271    results[len - 1] = if n % 2 == 0 {
272        (store[mid] + store[mid - 1]) / 2.0
273    } else {
274        store[mid]
275    };
276
277    results
278}
279
280#[inline]
281#[allow(clippy::all)]
282pub fn objects<T, Container>(object: &PineappleViewBuffer<T, Container>) -> Vec<f32>
283where
284    T: ToPrimitive + FromPrimitive,
285    Container: Deref<Target = [T]>,
286{
287    let c = object.channels();
288    let mut n = vec![0; c];
289
290    // Initial allocation for all intensity measurements. The
291    // intensity min, max, sum, mean, and standard deviation
292    // are stored in chunks that span the number of channels.
293    // The last two spots are for median and mad descriptors.
294    let mut results = vec![0.0; c * 5 + 2];
295
296    for i in 0..c {
297        results[i + 0 * c] = f32::INFINITY;
298        results[i + 1 * c] = f32::NEG_INFINITY;
299    }
300
301    let mut store: Vec<f32> = Vec::with_capacity(object.len());
302
303    for pixel in object.iter_pixels() {
304        for (i, v) in pixel.iter().enumerate() {
305            let v = v.to_f32().unwrap();
306
307            if v > 0. {
308                n[i] += 1;
309
310                // Intensity minimum
311                results[i + 0 * c] = results[i + 0 * c].min(v);
312
313                // Intensity maximum
314                results[i + 1 * c] = results[i + 1 * c].max(v);
315
316                // Intensity sum/integrated
317                results[i + 2 * c] += v;
318            }
319
320            store.push(v);
321        }
322    }
323
324    for v in results.iter_mut().take(c * 2) {
325        if *v == f32::NEG_INFINITY || *v == f32::INFINITY {
326            *v = 0.
327        }
328    }
329
330    // Intensity mean
331    for i in 0..c {
332        if n[i] > 0 {
333            results[i + 3 * c] = results[i + 2 * c] * 1.0 / n[i] as f32;
334        }
335    }
336
337    // Intensity standard deviation
338    for pixel in store.chunks_exact(c) {
339        for (i, &v) in pixel.iter().enumerate() {
340            if v > 0. {
341                results[i + 4 * c] += (v - results[i + 3 * c]).powi(2);
342            }
343        }
344    }
345
346    for i in 0..c {
347        if n[i] > 0 {
348            results[i + 4 * c] = (results[i + 4 * c] * 1.0 / n[i] as f32).sqrt();
349        }
350    }
351
352    store.retain(|x| *x > 0.);
353
354    if store.is_empty() {
355        return results;
356    }
357
358    store.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
359
360    let n = store.len();
361    let mid = n / 2;
362    let len = results.len();
363
364    // Intensity median
365    results[len - 2] = if n % 2 == 0 {
366        (store[mid - 1] + store[mid]) / 2.0
367    } else {
368        store[mid]
369    };
370
371    store
372        .iter_mut()
373        .for_each(|pixel| *pixel = (*pixel - results[len - 2]).abs());
374
375    store.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
376
377    // Intensity median absolute deviation
378    results[len - 1] = if n % 2 == 0 {
379        (store[mid] + store[mid - 1]) / 2.0
380    } else {
381        store[mid]
382    };
383
384    results
385}
386
387#[cfg(test)]
388mod test {
389
390    use super::*;
391    use crate::im::PineappleBuffer;
392
393    fn test_pixels() -> (Vec<u8>, usize) {
394        let channels = 3;
395        let pixels: Vec<u8> = vec![0, 1, 2, 0, 1, 3, 0, 1, 4, 0, 1, 5];
396        (pixels, channels)
397    }
398
399    fn test_object() -> PineappleBuffer<u8, Vec<u8>> {
400        let pixels: Vec<u8> = vec![0, 1, 2, 0, 1, 3, 0, 1, 4, 0, 1, 5];
401        PineappleBuffer::new(2, 2, 3, pixels).unwrap()
402    }
403
404    #[test]
405    fn test_intensity_min() {
406        let (pixels, channels) = test_pixels();
407        let min = intensity_min(&pixels, channels);
408        assert_eq!(min, vec![0.0, 1.0, 2.0]);
409    }
410
411    #[test]
412    fn test_intensity_max() {
413        let (pixels, channels) = test_pixels();
414        let max = intensity_max(&pixels, channels);
415        assert_eq!(max, vec![0.0, 1.0, 5.0]);
416    }
417
418    #[test]
419    fn test_intensity_mean() {
420        let (pixels, channels) = test_pixels();
421        let mean = intensity_mean(&pixels, channels);
422        assert_eq!(mean, vec![0.0, 1.0, 3.5]);
423    }
424
425    #[test]
426    fn test_intensity_sum() {
427        let (pixels, channels) = test_pixels();
428        let sum = intensity_sum(&pixels, channels);
429        assert_eq!(sum, vec![0.0, 4.0, 14.0]);
430    }
431
432    #[test]
433    fn test_intensity_std() {
434        let (pixels, channels) = test_pixels();
435        let std = intensity_std(&pixels, channels);
436        assert_eq!(std, vec![0.0, 0.0, 1.118034]);
437    }
438
439    #[test]
440    fn test_intensity_median() {
441        let (pixels, _) = test_pixels();
442        let median = intensity_median(&pixels);
443        assert_eq!(median, 1.5);
444    }
445
446    #[test]
447    fn test_intensity_mad() {
448        let (pixels, _) = test_pixels();
449        let mad = intensity_mad(&pixels);
450        assert_eq!(mad, 0.5);
451    }
452
453    #[test]
454    fn test_descriptors() {
455        let (pixels, channels) = test_pixels();
456
457        let min = intensity_min(&pixels, channels);
458        let max = intensity_max(&pixels, channels);
459        let sum = intensity_sum(&pixels, channels);
460        let mean = intensity_mean(&pixels, channels);
461        let std = intensity_std(&pixels, channels);
462        let median = intensity_median(&pixels);
463        let mad = intensity_mad(&pixels);
464
465        let results = descriptors(&pixels, channels);
466
467        for i in 0..3 {
468            assert_eq!(min[i], results[i]);
469            assert_eq!(max[i], results[i + 3]);
470            assert_eq!(sum[i], results[i + 6]);
471            assert_eq!(mean[i], results[i + 9]);
472            assert_eq!(std[i], results[i + 12]);
473        }
474
475        assert_eq!(median, results[3 + 12]);
476        assert_eq!(mad, results[3 + 13]);
477    }
478
479    #[test]
480    fn test_objects() {
481        let (pixels, channels) = test_pixels();
482
483        let min = intensity_min(&pixels, channels);
484        let max = intensity_max(&pixels, channels);
485        let sum = intensity_sum(&pixels, channels);
486        let mean = intensity_mean(&pixels, channels);
487        let std = intensity_std(&pixels, channels);
488        let median = intensity_median(&pixels);
489        let mad = intensity_mad(&pixels);
490
491        let buffer = test_object();
492        let object = buffer.crop_view(0, 0, 2, 2);
493        let results = objects(&object);
494
495        for i in 0..3 {
496            assert_eq!(min[i], results[i]);
497            assert_eq!(max[i], results[i + 3]);
498            assert_eq!(sum[i], results[i + 6]);
499            assert_eq!(mean[i], results[i + 9]);
500            assert_eq!(std[i], results[i + 12]);
501        }
502
503        assert_eq!(median, results[3 + 12]);
504        assert_eq!(mad, results[3 + 13]);
505    }
506}