1use crate::Grid;
8
9pub fn create_sphere_mask(
23 grid: &Grid,
24 center_x: f64, center_y: f64, center_z: f64,
25 radius: f64,
26) -> Vec<u8> {
27 let (nx, ny, nz) = grid.dims;
28 let mut mask = vec![0u8; nx * ny * nz];
29 let r2 = radius * radius;
30
31 for k in 0..nz {
32 for j in 0..ny {
33 for i in 0..nx {
34 let dx = i as f64 - center_x;
35 let dy = j as f64 - center_y;
36 let dz = k as f64 - center_z;
37 if dx * dx + dy * dy + dz * dz <= r2 {
38 mask[i + j * nx + k * nx * ny] = 1;
39 }
40 }
41 }
42 }
43
44 mask
45}
46
47#[inline]
49pub fn apply_mask_zero(data: &mut [f64], mask: &[u8]) {
50 for i in 0..data.len() {
51 if mask[i] == 0 {
52 data[i] = 0.0;
53 }
54 }
55}
56
57pub const SCANNER_FILL_MIN_FRACTION: f64 = 0.05;
64
65#[derive(Clone, Debug, PartialEq)]
67pub struct ScannerMaskedPhase {
68 pub has_data: Vec<u8>,
70 pub fill_value: Option<f64>,
72 pub n_masked: usize,
74}
75
76pub fn scanner_masked_phase(phases: &[&[f64]]) -> ScannerMaskedPhase {
97 let n = phases.first().map_or(0, |p| p.len());
98 let all_data = || ScannerMaskedPhase { has_data: vec![1; n], fill_value: None, n_masked: 0 };
99 if n == 0 || phases.iter().any(|p| p.len() != n) {
100 return all_data();
101 }
102 let same_in_every_echo = |i: usize| {
103 let v = phases[0][i].to_bits();
104 phases[1..].iter().all(|p| p[i].to_bits() == v)
105 };
106 let mut counts: std::collections::HashMap<u64, usize> = std::collections::HashMap::new();
108 for i in 0..n {
109 if phases[0][i].is_finite() && same_in_every_echo(i) {
110 *counts.entry(phases[0][i].to_bits()).or_insert(0) += 1;
111 }
112 }
113 let Some((bits, count)) = counts.into_iter().max_by(|a, b| {
115 a.1.cmp(&b.1).then_with(|| f64::from_bits(b.0).total_cmp(&f64::from_bits(a.0)))
116 }) else {
117 return all_data();
118 };
119 if (count as f64) < SCANNER_FILL_MIN_FRACTION * n as f64 {
120 return all_data();
121 }
122 let has_data: Vec<u8> = (0..n)
123 .map(|i| u8::from(!(phases[0][i].to_bits() == bits && same_in_every_echo(i))))
124 .collect();
125 ScannerMaskedPhase { has_data, fill_value: Some(f64::from_bits(bits)), n_masked: count }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 fn grid(nx: usize, ny: usize, nz: usize) -> Grid {
133 Grid::new(nx, ny, nz, 1.0, 1.0, 1.0)
134 }
135
136 fn noisy(n: usize, seed: u64) -> Vec<f64> {
137 (0..n).map(|i| {
139 let h = (i as u64).wrapping_mul(2654435761).wrapping_add(seed.wrapping_mul(40503)) % 4096;
140 -std::f64::consts::PI + h as f64 * (2.0 * std::f64::consts::PI / 4096.0)
141 }).collect()
142 }
143
144 #[test]
145 fn scanner_fill_value_is_found_in_every_echo() {
146 let n = 4000;
147 let fill = -std::f64::consts::PI;
148 let mut echoes: Vec<Vec<f64>> = (0..3).map(|e| noisy(n, e + 1)).collect();
149 for e in echoes.iter_mut() {
150 for v in e[..1500].iter_mut() { *v = fill; }
151 }
152 echoes[2][1499] = 0.25;
154 let refs: Vec<&[f64]> = echoes.iter().map(|e| e.as_slice()).collect();
155 let r = scanner_masked_phase(&refs);
156 assert_eq!(r.fill_value, Some(fill));
157 assert_eq!(r.n_masked, 1499);
158 assert!(r.has_data[..1499].iter().all(|&h| h == 0));
159 assert!(r.has_data[1499..].iter().all(|&h| h == 1));
160 }
161
162 #[test]
163 fn single_echo_uses_the_dominant_value_and_noise_has_none() {
164 let n = 4000;
165 let mut one = noisy(n, 7);
166 let r = scanner_masked_phase(&[&one]);
167 assert_eq!((r.fill_value, r.n_masked), (None, 0));
168 assert!(r.has_data.iter().all(|&h| h == 1));
169 for v in one[..150].iter_mut() { *v = 0.0; }
171 assert_eq!(scanner_masked_phase(&[&one]).n_masked, 0);
172 for v in one[..400].iter_mut() { *v = 0.0; }
173 let r = scanner_masked_phase(&[&one]);
174 assert_eq!((r.fill_value, r.n_masked), (Some(0.0), 400 + one[400..].iter().filter(|&&v| v == 0.0).count()));
175 let (a, b) = (noisy(n, 3), noisy(n, 4));
177 assert_eq!(scanner_masked_phase(&[&a, &b]).n_masked, 0);
178 }
179
180 #[test]
181 fn test_sphere_mask_basic() {
182 let mask = create_sphere_mask(&grid(10, 10, 10), 5.0, 5.0, 5.0, 3.0);
183 assert_eq!(mask.len(), 1000);
184
185 assert_eq!(mask[5 + 5 * 10 + 5 * 100], 1);
187
188 assert_eq!(mask[0], 0);
190
191 let count: usize = mask.iter().map(|&m| m as usize).sum();
193 assert!(count > 50 && count < 200, "Sphere voxel count {} seems wrong", count);
194 }
195
196 #[test]
197 fn test_sphere_mask_non_cubic() {
198 let mask = create_sphere_mask(&grid(20, 10, 5), 10.0, 5.0, 2.5, 2.0);
199 assert_eq!(mask.len(), 1000);
200
201 assert_eq!(mask[10 + 5 * 20 + 2 * 20 * 10], 1);
203 }
204
205 #[test]
206 fn test_sphere_mask_zero_radius() {
207 let mask = create_sphere_mask(&grid(5, 5, 5), 2.0, 2.0, 2.0, 0.0);
208 let count: usize = mask.iter().map(|&m| m as usize).sum();
210 assert_eq!(count, 1);
211 }
212
213 #[test]
214 fn test_apply_mask_zero() {
215 let mask = vec![1, 0, 1, 0, 1];
216 let mut data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
217 apply_mask_zero(&mut data, &mask);
218 assert_eq!(data, vec![1.0, 0.0, 3.0, 0.0, 5.0]);
219 }
220}