fpm_rs/metrics/complex_field/
statistics.rs1use num_complex::Complex64;
4
5use crate::{
6 Array2, Result,
7 backend::{Backend, CpuBackend, FftDirection},
8};
9
10#[derive(Clone, Debug, PartialEq)]
22pub struct RadialFourierSpectrum {
23 pub radius_px: Vec<f64>,
24 pub power: Vec<f64>,
25 pub sample_count: Vec<usize>,
26}
27
28pub fn radial_fourier_spectrum(field: &Array2<Complex64>) -> Result<RadialFourierSpectrum> {
33 let shape = field.shape();
34 let mut spectrum = field.as_slice().to_vec();
35 let backend = CpuBackend::new(shape, shape)?;
36 let mut column_scratch = vec![Complex64::default(); shape.0];
37 backend.fft2(
38 &mut spectrum,
39 shape,
40 FftDirection::Forward,
41 &mut column_scratch,
42 )?;
43
44 let center_row = shape.0 / 2;
45 let center_column = shape.1 / 2;
46 let max_radius = ((center_row as f64).hypot(center_column as f64)).floor() as usize;
47 let mut power = vec![0.0; max_radius + 1];
48 let mut sample_count = vec![0_usize; max_radius + 1];
49 let normalization = field.len() as f64;
50
51 for row in 0..shape.0 {
52 let frequency_row = if row <= shape.0 / 2 {
53 row as isize
54 } else {
55 row as isize - shape.0 as isize
56 };
57 for column in 0..shape.1 {
58 let frequency_column = if column <= shape.1 / 2 {
59 column as isize
60 } else {
61 column as isize - shape.1 as isize
62 };
63 let radius = ((frequency_row * frequency_row + frequency_column * frequency_column)
64 as f64)
65 .sqrt()
66 .floor() as usize;
67 power[radius] += spectrum[row * shape.1 + column].norm_sqr() * normalization;
68 sample_count[radius] += 1;
69 }
70 }
71
72 for (value, count) in power.iter_mut().zip(&sample_count) {
73 *value /= *count as f64;
74 }
75
76 Ok(RadialFourierSpectrum {
77 radius_px: (0..power.len()).map(|bin| bin as f64).collect(),
78 power,
79 sample_count,
80 })
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn constant_field_has_power_only_at_dc() {
89 let field = Array2::from_vec((4, 4), vec![Complex64::new(1.0, 0.0); 16]).unwrap();
90
91 let spectrum = radial_fourier_spectrum(&field).unwrap();
92
93 assert_eq!(spectrum.radius_px, vec![0.0, 1.0, 2.0]);
94 assert_eq!(spectrum.sample_count, vec![1, 8, 7]);
95 assert!((spectrum.power[0] - 16.0).abs() < 1e-12);
96 assert!(spectrum.power[1..].iter().all(|value| value.abs() < 1e-12));
97 }
98}