1#![allow(dead_code)]
2#[derive(Debug, Clone, PartialEq)]
22pub struct ConfidenceMap {
23 pub width: usize,
25 pub height: usize,
27 pub data: Vec<f64>,
29}
30
31impl ConfidenceMap {
32 pub fn new(width: usize, height: usize) -> Self {
34 Self {
35 width,
36 height,
37 data: vec![0.0; width * height],
38 }
39 }
40
41 pub fn filled(width: usize, height: usize, value: f64) -> Self {
43 let v = value.clamp(0.0, 1.0);
44 Self {
45 width,
46 height,
47 data: vec![v; width * height],
48 }
49 }
50
51 pub fn len(&self) -> usize {
53 self.data.len()
54 }
55
56 pub fn is_empty(&self) -> bool {
58 self.data.is_empty()
59 }
60
61 pub fn get(&self, x: usize, y: usize) -> Option<f64> {
63 if x < self.width && y < self.height {
64 Some(self.data[y * self.width + x])
65 } else {
66 None
67 }
68 }
69
70 pub fn set(&mut self, x: usize, y: usize, value: f64) -> bool {
73 if x < self.width && y < self.height {
74 self.data[y * self.width + x] = value.clamp(0.0, 1.0);
75 true
76 } else {
77 false
78 }
79 }
80
81 #[allow(clippy::cast_precision_loss)]
84 pub fn gaussian_blur(&self, radius: usize) -> Self {
85 if radius == 0 || self.is_empty() {
86 return self.clone();
87 }
88 let kernel_size = 2 * radius + 1;
89 let sigma = radius as f64 / 2.0;
90 let mut kernel = vec![0.0f64; kernel_size];
91 let mut sum = 0.0;
92 for i in 0..kernel_size {
93 let x = i as f64 - radius as f64;
94 let val = (-x * x / (2.0 * sigma * sigma)).exp();
95 kernel[i] = val;
96 sum += val;
97 }
98 for v in &mut kernel {
99 *v /= sum;
100 }
101
102 let mut temp = vec![0.0f64; self.data.len()];
104 for y in 0..self.height {
105 for x in 0..self.width {
106 let mut acc = 0.0;
107 for k in 0..kernel_size {
108 let sx = (x as isize + k as isize - radius as isize)
109 .max(0)
110 .min(self.width as isize - 1) as usize;
111 acc += self.data[y * self.width + sx] * kernel[k];
112 }
113 temp[y * self.width + x] = acc;
114 }
115 }
116
117 let mut result = vec![0.0f64; self.data.len()];
119 for y in 0..self.height {
120 for x in 0..self.width {
121 let mut acc = 0.0;
122 for k in 0..kernel_size {
123 let sy = (y as isize + k as isize - radius as isize)
124 .max(0)
125 .min(self.height as isize - 1) as usize;
126 acc += temp[sy * self.width + x] * kernel[k];
127 }
128 result[y * self.width + x] = acc.clamp(0.0, 1.0);
129 }
130 }
131
132 Self {
133 width: self.width,
134 height: self.height,
135 data: result,
136 }
137 }
138
139 #[allow(clippy::cast_precision_loss)]
141 pub fn statistics(&self) -> ConfidenceStats {
142 if self.data.is_empty() {
143 return ConfidenceStats {
144 min: 0.0,
145 max: 0.0,
146 mean: 0.0,
147 std_dev: 0.0,
148 median: 0.0,
149 };
150 }
151 let n = self.data.len() as f64;
152 let min = self.data.iter().cloned().fold(f64::INFINITY, f64::min);
153 let max = self.data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
154 let mean = self.data.iter().sum::<f64>() / n;
155 let variance = self.data.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
156 let std_dev = variance.sqrt();
157 let mut sorted = self.data.clone();
158 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
159 let median = if sorted.len() % 2 == 0 {
160 let mid = sorted.len() / 2;
161 (sorted[mid - 1] + sorted[mid]) / 2.0
162 } else {
163 sorted[sorted.len() / 2]
164 };
165 ConfidenceStats {
166 min,
167 max,
168 mean,
169 std_dev,
170 median,
171 }
172 }
173}
174
175#[derive(Debug, Clone, PartialEq)]
177pub struct ConfidenceStats {
178 pub min: f64,
180 pub max: f64,
182 pub mean: f64,
184 pub std_dev: f64,
186 pub median: f64,
188}
189
190#[derive(Debug, Clone)]
192pub struct ConfidenceThresholder {
193 pub threshold: f64,
195}
196
197impl ConfidenceThresholder {
198 pub fn new(threshold: f64) -> Self {
200 Self {
201 threshold: threshold.clamp(0.0, 1.0),
202 }
203 }
204
205 pub fn apply(&self, map: &ConfidenceMap) -> Vec<bool> {
207 map.data.iter().map(|&v| v >= self.threshold).collect()
208 }
209
210 pub fn count_accepted(&self, map: &ConfidenceMap) -> usize {
212 map.data.iter().filter(|&&v| v >= self.threshold).count()
213 }
214
215 #[allow(clippy::cast_precision_loss)]
217 pub fn acceptance_ratio(&self, map: &ConfidenceMap) -> f64 {
218 if map.is_empty() {
219 return 0.0;
220 }
221 self.count_accepted(map) as f64 / map.data.len() as f64
222 }
223}
224
225pub fn merge_max(a: &ConfidenceMap, b: &ConfidenceMap) -> Option<ConfidenceMap> {
227 if a.width != b.width || a.height != b.height {
228 return None;
229 }
230 let data: Vec<f64> = a
231 .data
232 .iter()
233 .zip(b.data.iter())
234 .map(|(&va, &vb)| va.max(vb))
235 .collect();
236 Some(ConfidenceMap {
237 width: a.width,
238 height: a.height,
239 data,
240 })
241}
242
243pub fn merge_min(a: &ConfidenceMap, b: &ConfidenceMap) -> Option<ConfidenceMap> {
245 if a.width != b.width || a.height != b.height {
246 return None;
247 }
248 let data: Vec<f64> = a
249 .data
250 .iter()
251 .zip(b.data.iter())
252 .map(|(&va, &vb)| va.min(vb))
253 .collect();
254 Some(ConfidenceMap {
255 width: a.width,
256 height: a.height,
257 data,
258 })
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_new_map_zeroed() {
267 let m = ConfidenceMap::new(4, 3);
268 assert_eq!(m.width, 4);
269 assert_eq!(m.height, 3);
270 assert_eq!(m.len(), 12);
271 assert!(m.data.iter().all(|&v| v == 0.0));
272 }
273
274 #[test]
275 fn test_filled_clamped() {
276 let m = ConfidenceMap::filled(2, 2, 1.5);
277 assert!(m.data.iter().all(|&v| (v - 1.0).abs() < 1e-12));
278 }
279
280 #[test]
281 fn test_get_set() {
282 let mut m = ConfidenceMap::new(3, 3);
283 assert!(m.set(1, 2, 0.75));
284 assert!((m.get(1, 2).expect("get should succeed") - 0.75).abs() < 1e-12);
285 }
286
287 #[test]
288 fn test_get_out_of_bounds() {
289 let m = ConfidenceMap::new(2, 2);
290 assert!(m.get(5, 0).is_none());
291 }
292
293 #[test]
294 fn test_set_out_of_bounds() {
295 let mut m = ConfidenceMap::new(2, 2);
296 assert!(!m.set(5, 0, 0.5));
297 }
298
299 #[test]
300 fn test_set_clamps_value() {
301 let mut m = ConfidenceMap::new(2, 2);
302 m.set(0, 0, 2.0);
303 assert!((m.get(0, 0).expect("get should succeed") - 1.0).abs() < 1e-12);
304 m.set(0, 0, -1.0);
305 assert!(m.get(0, 0).expect("get should succeed").abs() < 1e-12);
306 }
307
308 #[test]
309 fn test_is_empty() {
310 let m = ConfidenceMap::new(0, 0);
311 assert!(m.is_empty());
312 let m2 = ConfidenceMap::new(1, 1);
313 assert!(!m2.is_empty());
314 }
315
316 #[test]
317 fn test_statistics_uniform() {
318 let m = ConfidenceMap::filled(3, 3, 0.5);
319 let s = m.statistics();
320 assert!((s.min - 0.5).abs() < 1e-12);
321 assert!((s.max - 0.5).abs() < 1e-12);
322 assert!((s.mean - 0.5).abs() < 1e-12);
323 assert!(s.std_dev < 1e-12);
324 assert!((s.median - 0.5).abs() < 1e-12);
325 }
326
327 #[test]
328 fn test_statistics_varied() {
329 let mut m = ConfidenceMap::new(3, 1);
330 m.set(0, 0, 0.0);
331 m.set(1, 0, 0.5);
332 m.set(2, 0, 1.0);
333 let s = m.statistics();
334 assert!(s.min.abs() < 1e-12);
335 assert!((s.max - 1.0).abs() < 1e-12);
336 assert!((s.mean - 0.5).abs() < 1e-12);
337 assert!((s.median - 0.5).abs() < 1e-12);
338 }
339
340 #[test]
341 fn test_thresholder() {
342 let mut m = ConfidenceMap::new(2, 2);
343 m.set(0, 0, 0.3);
344 m.set(1, 0, 0.7);
345 m.set(0, 1, 0.5);
346 m.set(1, 1, 0.9);
347 let t = ConfidenceThresholder::new(0.5);
348 let mask = t.apply(&m);
349 assert!(!mask[0]); assert!(mask[1]); assert!(mask[2]); assert!(mask[3]); assert_eq!(t.count_accepted(&m), 3);
354 assert!((t.acceptance_ratio(&m) - 0.75).abs() < 1e-12);
355 }
356
357 #[test]
358 fn test_merge_max() {
359 let a = ConfidenceMap {
360 width: 2,
361 height: 1,
362 data: vec![0.3, 0.8],
363 };
364 let b = ConfidenceMap {
365 width: 2,
366 height: 1,
367 data: vec![0.5, 0.6],
368 };
369 let merged = merge_max(&a, &b).expect("merged should be valid");
370 assert!((merged.data[0] - 0.5).abs() < 1e-12);
371 assert!((merged.data[1] - 0.8).abs() < 1e-12);
372 }
373
374 #[test]
375 fn test_merge_min() {
376 let a = ConfidenceMap {
377 width: 2,
378 height: 1,
379 data: vec![0.3, 0.8],
380 };
381 let b = ConfidenceMap {
382 width: 2,
383 height: 1,
384 data: vec![0.5, 0.6],
385 };
386 let merged = merge_min(&a, &b).expect("merged should be valid");
387 assert!((merged.data[0] - 0.3).abs() < 1e-12);
388 assert!((merged.data[1] - 0.6).abs() < 1e-12);
389 }
390
391 #[test]
392 fn test_merge_mismatched_dimensions() {
393 let a = ConfidenceMap::new(2, 2);
394 let b = ConfidenceMap::new(3, 2);
395 assert!(merge_max(&a, &b).is_none());
396 assert!(merge_min(&a, &b).is_none());
397 }
398
399 #[test]
400 fn test_gaussian_blur_preserves_uniform() {
401 let m = ConfidenceMap::filled(5, 5, 0.7);
402 let blurred = m.gaussian_blur(1);
403 for v in &blurred.data {
404 assert!((v - 0.7).abs() < 1e-6);
405 }
406 }
407}