Skip to main content

fpm_rs/metrics/intensity/
single.rs

1//! Statistics calculated from one intensity image.
2
3use serde::{Deserialize, Serialize};
4
5/// Summary statistics calculated from one intensity image.
6#[derive(Clone, Debug, Serialize, Deserialize)]
7pub struct IntensityStats {
8    /// Arithmetic mean intensity.
9    pub mean: f64,
10    /// Population standard deviation of intensity.
11    pub std: f64,
12    /// Minimum intensity.
13    pub min: f64,
14    /// Maximum intensity.
15    pub max: f64,
16    /// Sum of intensities.
17    pub sum: f64,
18    /// Pixels at or above the optional saturation threshold, or zero when absent.
19    pub saturated_pixels: usize,
20    /// Pixels exactly equal to zero.
21    pub zero_pixels: usize,
22}
23
24/// Calculate summary statistics for a non-empty intensity image.
25pub fn stats(values: &[f64], saturation_value: Option<f64>) -> crate::Result<IntensityStats> {
26    if values.is_empty() {
27        return Err(crate::Error::InvalidShape(
28            "intensity statistics require non-empty values".into(),
29        ));
30    }
31    let mut sum = 0.0;
32    let mut sum_squares = 0.0;
33    let mut min = f64::INFINITY;
34    let mut max = f64::NEG_INFINITY;
35    let mut saturated_pixels = 0;
36    let mut zero_pixels = 0;
37    for &value in values {
38        if !value.is_finite() {
39            return Err(crate::Error::Numerical(
40                "intensity statistics contain a non-finite value".into(),
41            ));
42        }
43        sum += value;
44        sum_squares += value * value;
45        min = min.min(value);
46        max = max.max(value);
47        zero_pixels += usize::from(value == 0.0);
48        saturated_pixels += usize::from(saturation_value.is_some_and(|limit| value >= limit));
49    }
50    let count = values.len() as f64;
51    let mean = sum / count;
52    Ok(IntensityStats {
53        mean,
54        std: (sum_squares / count - mean * mean).max(0.0).sqrt(),
55        min,
56        max,
57        sum,
58        saturated_pixels,
59        zero_pixels,
60    })
61}