Skip to main content

fpm_rs/metrics/intensity/
statistics.rs

1//! Statistics calculated from one intensity image.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, Serialize, Deserialize)]
6pub struct IntensityStatistics {
7    pub mean: f64,
8    pub std: f64,
9    pub min: f64,
10    pub max: f64,
11    pub sum: f64,
12    pub saturated_pixels: usize,
13    pub zero_pixels: usize,
14}
15
16pub fn intensity_statistics(
17    values: &[f64],
18    saturation_value: Option<f64>,
19) -> crate::Result<IntensityStatistics> {
20    if values.is_empty() {
21        return Err(crate::Error::InvalidShape(
22            "intensity statistics require non-empty values".into(),
23        ));
24    }
25    let mut sum = 0.0;
26    let mut sum_squares = 0.0;
27    let mut min = f64::INFINITY;
28    let mut max = f64::NEG_INFINITY;
29    let mut saturated_pixels = 0;
30    let mut zero_pixels = 0;
31    for &value in values {
32        if !value.is_finite() {
33            return Err(crate::Error::Numerical(
34                "intensity statistics contain a non-finite value".into(),
35            ));
36        }
37        sum += value;
38        sum_squares += value * value;
39        min = min.min(value);
40        max = max.max(value);
41        zero_pixels += usize::from(value == 0.0);
42        saturated_pixels += usize::from(saturation_value.is_some_and(|limit| value >= limit));
43    }
44    let count = values.len() as f64;
45    let mean = sum / count;
46    Ok(IntensityStatistics {
47        mean,
48        std: (sum_squares / count - mean * mean).max(0.0).sqrt(),
49        min,
50        max,
51        sum,
52        saturated_pixels,
53        zero_pixels,
54    })
55}