1use crate::error::{SpatialError, SpatialResult};
37use scirs2_core::ndarray::{Array1, Array2, Array3, ArrayView2, ArrayView3};
38use scirs2_core::numeric::Float;
39use std::collections::VecDeque;
40
41#[derive(Debug, Clone, Copy, PartialEq)]
43pub enum DistanceMetric {
44 Euclidean,
46 Manhattan,
48 Chebyshev,
50 Chamfer34,
52 Chamfer5711,
54}
55
56pub fn euclidean_distance_transform<T: Float>(
80 binary: &ArrayView2<i32>,
81 metric: DistanceMetric,
82) -> SpatialResult<Array2<T>> {
83 let (rows, cols) = binary.dim();
84
85 if rows == 0 || cols == 0 {
86 return Err(SpatialError::ValueError(
87 "Input array must be non-empty".to_string(),
88 ));
89 }
90
91 match metric {
92 DistanceMetric::Euclidean => euclidean_dt_2d(binary),
93 DistanceMetric::Manhattan => manhattan_dt_2d(binary),
94 DistanceMetric::Chebyshev => chebyshev_dt_2d(binary),
95 DistanceMetric::Chamfer34 => chamfer_dt_2d(binary, &[3.0, 4.0]),
96 DistanceMetric::Chamfer5711 => chamfer_dt_2d(binary, &[5.0, 7.0, 11.0]),
97 }
98}
99
100fn euclidean_dt_2d<T: Float>(binary: &ArrayView2<i32>) -> SpatialResult<Array2<T>> {
102 let (rows, cols) = binary.dim();
103 let mut distances = Array2::from_elem((rows, cols), T::infinity());
104
105 for i in 0..rows {
107 for j in 0..cols {
108 if binary[[i, j]] != 0 {
109 distances[[i, j]] = T::zero();
110 }
111 }
112 }
113
114 for i in 0..rows {
116 let mut min_dist = T::infinity();
117 for j in 0..cols {
118 if binary[[i, j]] != 0 {
119 min_dist = T::zero();
120 } else {
121 min_dist = min_dist + T::one();
122 }
123 distances[[i, j]] = min_dist.min(distances[[i, j]]);
124 }
125
126 let mut min_dist = T::infinity();
128 for j in (0..cols).rev() {
129 if binary[[i, j]] != 0 {
130 min_dist = T::zero();
131 } else {
132 min_dist = min_dist + T::one();
133 }
134 distances[[i, j]] = min_dist.min(distances[[i, j]]);
135 }
136 }
137
138 for j in 0..cols {
140 for i in 1..rows {
142 let up_dist = distances[[i - 1, j]] + T::one();
143 distances[[i, j]] = up_dist.min(distances[[i, j]]);
144 }
145
146 for i in (0..rows - 1).rev() {
148 let down_dist = distances[[i + 1, j]] + T::one();
149 distances[[i, j]] = down_dist.min(distances[[i, j]]);
150 }
151 }
152
153 Ok(distances)
154}
155
156fn manhattan_dt_2d<T: Float>(binary: &ArrayView2<i32>) -> SpatialResult<Array2<T>> {
158 let (rows, cols) = binary.dim();
159 let mut distances = Array2::from_elem(
160 (rows, cols),
161 T::from(rows + cols).expect("conversion failed"),
162 );
163
164 for i in 0..rows {
166 for j in 0..cols {
167 if binary[[i, j]] != 0 {
168 distances[[i, j]] = T::zero();
169 }
170 }
171 }
172
173 for i in 0..rows {
175 for j in 0..cols {
176 if binary[[i, j]] == 0 {
177 let mut min_dist = distances[[i, j]];
178
179 if i > 0 {
180 min_dist = min_dist.min(distances[[i - 1, j]] + T::one());
181 }
182 if j > 0 {
183 min_dist = min_dist.min(distances[[i, j - 1]] + T::one());
184 }
185
186 distances[[i, j]] = min_dist;
187 }
188 }
189 }
190
191 for i in (0..rows).rev() {
193 for j in (0..cols).rev() {
194 if binary[[i, j]] == 0 {
195 let mut min_dist = distances[[i, j]];
196
197 if i < rows - 1 {
198 min_dist = min_dist.min(distances[[i + 1, j]] + T::one());
199 }
200 if j < cols - 1 {
201 min_dist = min_dist.min(distances[[i, j + 1]] + T::one());
202 }
203
204 distances[[i, j]] = min_dist;
205 }
206 }
207 }
208
209 Ok(distances)
210}
211
212fn chebyshev_dt_2d<T: Float>(binary: &ArrayView2<i32>) -> SpatialResult<Array2<T>> {
214 let (rows, cols) = binary.dim();
215 let mut distances = Array2::from_elem(
216 (rows, cols),
217 T::from(rows.max(cols)).expect("conversion failed"),
218 );
219
220 let mut queue = VecDeque::new();
222
223 for i in 0..rows {
225 for j in 0..cols {
226 if binary[[i, j]] != 0 {
227 distances[[i, j]] = T::zero();
228 queue.push_back((i, j, T::zero()));
229 }
230 }
231 }
232
233 let neighbors = [
235 (-1, -1),
236 (-1, 0),
237 (-1, 1),
238 (0, -1),
239 (0, 1),
240 (1, -1),
241 (1, 0),
242 (1, 1),
243 ];
244
245 while let Some((i, j, dist)) = queue.pop_front() {
246 for &(di, dj) in &neighbors {
247 let ni = i as isize + di;
248 let nj = j as isize + dj;
249
250 if ni >= 0 && ni < rows as isize && nj >= 0 && nj < cols as isize {
251 let ni = ni as usize;
252 let nj = nj as usize;
253
254 let new_dist = dist + T::one();
255 if new_dist < distances[[ni, nj]] {
256 distances[[ni, nj]] = new_dist;
257 queue.push_back((ni, nj, new_dist));
258 }
259 }
260 }
261 }
262
263 Ok(distances)
264}
265
266fn chamfer_dt_2d<T: Float>(binary: &ArrayView2<i32>, weights: &[f64]) -> SpatialResult<Array2<T>> {
268 let (rows, cols) = binary.dim();
269 let max_val = T::from(rows * cols).expect("conversion failed");
270 let mut distances = Array2::from_elem((rows, cols), max_val);
271
272 for i in 0..rows {
274 for j in 0..cols {
275 if binary[[i, j]] != 0 {
276 distances[[i, j]] = T::zero();
277 }
278 }
279 }
280
281 let w1 = T::from(weights[0]).expect("conversion failed");
282 let w2 =
283 T::from(weights.get(1).copied().unwrap_or(weights[0] * 1.4)).expect("conversion failed");
284
285 for i in 0..rows {
287 for j in 0..cols {
288 if binary[[i, j]] == 0 {
289 let mut min_dist = distances[[i, j]];
290
291 if i > 0 {
293 min_dist = min_dist.min(distances[[i - 1, j]] + w1);
294 }
295 if j > 0 {
296 min_dist = min_dist.min(distances[[i, j - 1]] + w1);
297 }
298
299 if i > 0 && j > 0 {
301 min_dist = min_dist.min(distances[[i - 1, j - 1]] + w2);
302 }
303 if i > 0 && j < cols - 1 {
304 min_dist = min_dist.min(distances[[i - 1, j + 1]] + w2);
305 }
306
307 distances[[i, j]] = min_dist;
308 }
309 }
310 }
311
312 for i in (0..rows).rev() {
314 for j in (0..cols).rev() {
315 if binary[[i, j]] == 0 {
316 let mut min_dist = distances[[i, j]];
317
318 if i < rows - 1 {
320 min_dist = min_dist.min(distances[[i + 1, j]] + w1);
321 }
322 if j < cols - 1 {
323 min_dist = min_dist.min(distances[[i, j + 1]] + w1);
324 }
325
326 if i < rows - 1 && j < cols - 1 {
328 min_dist = min_dist.min(distances[[i + 1, j + 1]] + w2);
329 }
330 if i < rows - 1 && j > 0 {
331 min_dist = min_dist.min(distances[[i + 1, j - 1]] + w2);
332 }
333
334 distances[[i, j]] = min_dist;
335 }
336 }
337 }
338
339 Ok(distances)
340}
341
342pub fn feature_transform(binary: &ArrayView2<i32>) -> SpatialResult<Array2<(usize, usize)>> {
354 let (rows, cols) = binary.dim();
355 let mut features = Array2::from_elem((rows, cols), (usize::MAX, usize::MAX));
356 let mut distances = Array2::from_elem((rows, cols), f64::INFINITY);
357
358 for i in 0..rows {
360 for j in 0..cols {
361 if binary[[i, j]] != 0 {
362 features[[i, j]] = (i, j);
363 distances[[i, j]] = 0.0;
364 }
365 }
366 }
367
368 for i in 0..rows {
370 for j in 0..cols {
371 if binary[[i, j]] == 0 {
372 let neighbors = [
373 (i.saturating_sub(1), j),
374 (i, j.saturating_sub(1)),
375 (i.saturating_sub(1), j.saturating_sub(1)),
376 ];
377
378 for &(ni, nj) in &neighbors {
379 if ni < rows && nj < cols && features[[ni, nj]] != (usize::MAX, usize::MAX) {
380 let (fi, fj) = features[[ni, nj]];
381 let dist = (((i as isize - fi as isize).pow(2)
382 + (j as isize - fj as isize).pow(2))
383 as f64)
384 .sqrt();
385
386 if dist < distances[[i, j]] {
387 distances[[i, j]] = dist;
388 features[[i, j]] = (fi, fj);
389 }
390 }
391 }
392 }
393 }
394 }
395
396 for i in (0..rows).rev() {
398 for j in (0..cols).rev() {
399 if binary[[i, j]] == 0 {
400 let neighbors = [
401 ((i + 1).min(rows - 1), j),
402 (i, (j + 1).min(cols - 1)),
403 ((i + 1).min(rows - 1), (j + 1).min(cols - 1)),
404 ];
405
406 for &(ni, nj) in &neighbors {
407 if features[[ni, nj]] != (usize::MAX, usize::MAX) {
408 let (fi, fj) = features[[ni, nj]];
409 let dist = (((i as isize - fi as isize).pow(2)
410 + (j as isize - fj as isize).pow(2))
411 as f64)
412 .sqrt();
413
414 if dist < distances[[i, j]] {
415 distances[[i, j]] = dist;
416 features[[i, j]] = (fi, fj);
417 }
418 }
419 }
420 }
421 }
422 }
423
424 Ok(features)
425}
426
427pub fn euclidean_distance_transform_3d<T: Float>(
440 binary: &ArrayView3<i32>,
441 metric: DistanceMetric,
442) -> SpatialResult<Array3<T>> {
443 let (depth, rows, cols) = binary.dim();
444
445 if depth == 0 || rows == 0 || cols == 0 {
446 return Err(SpatialError::ValueError(
447 "Input array must be non-empty".to_string(),
448 ));
449 }
450
451 let mut distances = Array3::from_elem((depth, rows, cols), T::infinity());
453
454 for i in 0..depth {
456 for j in 0..rows {
457 for k in 0..cols {
458 if binary[[i, j, k]] != 0 {
459 distances[[i, j, k]] = T::zero();
460 }
461 }
462 }
463 }
464
465 let max_iterations = (depth + rows + cols) / 2;
467 for _iter in 0..max_iterations {
468 let mut changed = false;
469
470 for i in 0..depth {
471 for j in 0..rows {
472 for k in 0..cols {
473 if binary[[i, j, k]] == 0 {
474 let mut min_dist = distances[[i, j, k]];
475
476 if i > 0 {
478 min_dist = min_dist.min(distances[[i - 1, j, k]] + T::one());
479 }
480 if i < depth - 1 {
481 min_dist = min_dist.min(distances[[i + 1, j, k]] + T::one());
482 }
483 if j > 0 {
484 min_dist = min_dist.min(distances[[i, j - 1, k]] + T::one());
485 }
486 if j < rows - 1 {
487 min_dist = min_dist.min(distances[[i, j + 1, k]] + T::one());
488 }
489 if k > 0 {
490 min_dist = min_dist.min(distances[[i, j, k - 1]] + T::one());
491 }
492 if k < cols - 1 {
493 min_dist = min_dist.min(distances[[i, j, k + 1]] + T::one());
494 }
495
496 if min_dist < distances[[i, j, k]] {
497 distances[[i, j, k]] = min_dist;
498 changed = true;
499 }
500 }
501 }
502 }
503 }
504
505 if !changed {
506 break;
507 }
508 }
509
510 Ok(distances)
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use approx::assert_relative_eq;
517 use scirs2_core::ndarray::array;
518
519 #[test]
520 fn test_euclidean_distance_transform_2d() {
521 let binary = array![[1, 0, 0], [0, 0, 0], [0, 0, 1]];
522
523 let distances: Array2<f64> =
524 euclidean_distance_transform(&binary.view(), DistanceMetric::Euclidean)
525 .expect("Failed to compute");
526
527 assert_eq!(distances.dim(), (3, 3));
529
530 assert_relative_eq!(distances[[0, 0]], 0.0, epsilon = 1e-10);
532 assert_relative_eq!(distances[[2, 2]], 0.0, epsilon = 1e-10);
533
534 assert!(distances[[1, 1]] > 1.0);
536 }
537
538 #[test]
539 fn test_manhattan_distance_transform() {
540 let binary = array![[1, 0, 0], [0, 0, 0], [0, 0, 1]];
541
542 let distances: Array2<f64> =
543 euclidean_distance_transform(&binary.view(), DistanceMetric::Manhattan)
544 .expect("Failed to compute");
545
546 assert_relative_eq!(distances[[0, 0]], 0.0, epsilon = 1e-10);
548 assert_relative_eq!(distances[[2, 2]], 0.0, epsilon = 1e-10);
549
550 assert_relative_eq!(distances[[1, 1]], 2.0, epsilon = 0.1);
552 }
553
554 #[test]
555 fn test_feature_transform() {
556 let binary = array![[1, 0, 0], [0, 0, 0], [0, 0, 1]];
557
558 let features = feature_transform(&binary.view()).expect("Failed to compute");
559
560 assert_eq!(features[[0, 0]], (0, 0));
562 assert_eq!(features[[2, 2]], (2, 2));
563
564 assert!(
566 features[[1, 1]] == (0, 0) || features[[1, 1]] == (2, 2),
567 "Pixel (1,1) should point to one of the features"
568 );
569 }
570
571 #[test]
572 fn test_chamfer_distance_transform() {
573 let binary = array![[1, 0, 0], [0, 0, 0], [0, 0, 0]];
574
575 let distances: Array2<f64> =
576 euclidean_distance_transform(&binary.view(), DistanceMetric::Chamfer34)
577 .expect("Failed to compute");
578
579 assert_relative_eq!(distances[[0, 0]], 0.0, epsilon = 1e-10);
581
582 assert!(distances[[0, 1]] > 0.0);
584 assert!(distances[[0, 2]] > distances[[0, 1]]);
585 }
586
587 #[test]
588 fn test_3d_distance_transform() {
589 let binary = array![[[1, 0], [0, 0]], [[0, 0], [0, 0]]];
590
591 let distances: Array3<f64> =
592 euclidean_distance_transform_3d(&binary.view(), DistanceMetric::Euclidean)
593 .expect("Failed to compute");
594
595 assert_relative_eq!(distances[[0, 0, 0]], 0.0, epsilon = 1e-10);
597
598 for i in 0..2 {
600 for j in 0..2 {
601 for k in 0..2 {
602 if i != 0 || j != 0 || k != 0 {
603 assert!(distances[[i, j, k]] > 0.0);
604 }
605 }
606 }
607 }
608 }
609
610 #[test]
611 fn test_empty_input() {
612 let binary = array![[0, 0], [0, 0]];
613
614 let distances: Array2<f64> =
615 euclidean_distance_transform(&binary.view(), DistanceMetric::Euclidean)
616 .expect("Failed to compute");
617
618 for &d in distances.iter() {
620 assert!(d > 1.0);
621 }
622 }
623
624 #[test]
625 fn test_all_features() {
626 let binary = array![[1, 1], [1, 1]];
627
628 let distances: Array2<f64> =
629 euclidean_distance_transform(&binary.view(), DistanceMetric::Euclidean)
630 .expect("Failed to compute");
631
632 for &d in distances.iter() {
634 assert_relative_eq!(d, 0.0, epsilon = 1e-10);
635 }
636 }
637}