1use scirs2_core::ndarray::{Array2, Axis};
7use scirs2_core::numeric::{Float, FromPrimitive};
8use scirs2_core::parallel_ops::{self};
9use scirs2_core::simd_ops::SimdUnifiedOps;
10use std::fmt::Debug;
11use std::sync::Arc;
12
13use crate::error::NdimageResult;
14
15#[allow(dead_code)]
35pub fn grey_erosion_2d_optimized<T>(
36 input: &Array2<T>,
37 structure: Option<&Array2<bool>>,
38 iterations: Option<usize>,
39 border_value: Option<T>,
40 origin: Option<&[isize; 2]>,
41) -> NdimageResult<Array2<T>>
42where
43 T: Float
44 + FromPrimitive
45 + Debug
46 + Send
47 + Sync
48 + std::ops::AddAssign
49 + std::ops::DivAssign
50 + 'static,
51 T: SimdUnifiedOps,
52{
53 let iters = iterations.unwrap_or(1);
55 let _border_val = border_value.unwrap_or_else(|| T::from_f64(0.0).expect("Operation failed"));
56
57 let default_structure = Array2::from_elem((3, 3), true);
59 let struct_elem = structure.unwrap_or(&default_structure);
60
61 let default_origin = [
63 (struct_elem.shape()[0] / 2) as isize,
64 (struct_elem.shape()[1] / 2) as isize,
65 ];
66 let struct_origin = origin.unwrap_or(&default_origin);
67
68 let (height, width) = input.dim();
70
71 let (s_height, s_width) = struct_elem.dim();
73 let mut offsets = Vec::new();
74 for si in 0..s_height {
75 for sj in 0..s_width {
76 if struct_elem[[si, sj]] {
77 offsets.push((
78 si as isize - struct_origin[0],
79 sj as isize - struct_origin[1],
80 ));
81 }
82 }
83 }
84 let offsets = Arc::new(offsets);
85
86 let use_parallel = height * width > 10_000;
88
89 let mut buffer1 = input.to_owned();
91 let mut buffer2 = Array2::from_elem(input.dim(), T::zero());
92
93 for iter in 0..iters {
95 let (src, dst) = if iter % 2 == 0 {
96 (&buffer1, &mut buffer2)
97 } else {
98 (&buffer2, &mut buffer1)
99 };
100
101 if use_parallel {
102 erosion_iteration_parallel(src, dst, &offsets, height, width);
104 } else {
105 erosion_iteration_simd(src, dst, &offsets, height, width);
107 }
108 }
109
110 Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
112}
113
114#[allow(dead_code)]
116fn erosion_iteration_simd<T>(
117 src: &Array2<T>,
118 dst: &mut Array2<T>,
119 offsets: &[(isize, isize)],
120 height: usize,
121 width: usize,
122) where
123 T: Float
124 + FromPrimitive
125 + Debug
126 + Send
127 + Sync
128 + std::ops::AddAssign
129 + std::ops::DivAssign
130 + 'static,
131 T: SimdUnifiedOps,
132{
133 for i in 0..height {
135 let mut row_slice = dst.row_mut(i);
137
138 for j in 0..width {
139 let mut min_val = T::infinity();
140
141 for &(di, dj) in offsets.iter() {
143 let ni = i as isize + di;
144 let nj = j as isize + dj;
145
146 let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
147 src[[ni as usize, nj as usize]]
148 } else {
149 let ri = ni.clamp(0, (height as isize) - 1) as usize;
151 let rj = nj.clamp(0, (width as isize) - 1) as usize;
152 src[[ri, rj]]
153 };
154
155 min_val = min_val.min(val);
156 }
157
158 row_slice[j] = min_val;
159 }
160 }
161}
162
163#[allow(dead_code)]
165fn erosion_iteration_parallel<T>(
166 src: &Array2<T>,
167 dst: &mut Array2<T>,
168 offsets: &Arc<Vec<(isize, isize)>>,
169 height: usize,
170 width: usize,
171) where
172 T: Float
173 + FromPrimitive
174 + Debug
175 + Send
176 + Sync
177 + std::ops::AddAssign
178 + std::ops::DivAssign
179 + 'static,
180{
181 use parallel_ops::*;
182
183 let offsets_clone = offsets.clone();
185
186 dst.axis_iter_mut(Axis(0))
187 .into_par_iter()
188 .enumerate()
189 .for_each(|(i, mut row)| {
190 let src_ref = src;
191
192 for j in 0..width {
193 let mut min_val = T::infinity();
194
195 for &(di, dj) in offsets_clone.iter() {
197 let ni = i as isize + di;
198 let nj = j as isize + dj;
199
200 let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
201 src_ref[[ni as usize, nj as usize]]
202 } else {
203 let ri = ni.clamp(0, (height as isize) - 1) as usize;
205 let rj = nj.clamp(0, (width as isize) - 1) as usize;
206 src_ref[[ri, rj]]
207 };
208
209 min_val = min_val.min(val);
210 }
211
212 row[j] = min_val;
213 }
214 });
215}
216
217#[allow(dead_code)]
237pub fn grey_dilation_2d_optimized<T>(
238 input: &Array2<T>,
239 structure: Option<&Array2<bool>>,
240 iterations: Option<usize>,
241 border_value: Option<T>,
242 origin: Option<&[isize; 2]>,
243) -> NdimageResult<Array2<T>>
244where
245 T: Float
246 + FromPrimitive
247 + Debug
248 + Send
249 + Sync
250 + std::ops::AddAssign
251 + std::ops::DivAssign
252 + 'static,
253 T: SimdUnifiedOps,
254{
255 let iters = iterations.unwrap_or(1);
257 let _border_val = border_value.unwrap_or_else(|| T::from_f64(0.0).expect("Operation failed"));
258
259 let default_structure = Array2::from_elem((3, 3), true);
261 let struct_elem = structure.unwrap_or(&default_structure);
262
263 let default_origin = [
265 (struct_elem.shape()[0] / 2) as isize,
266 (struct_elem.shape()[1] / 2) as isize,
267 ];
268 let struct_origin = origin.unwrap_or(&default_origin);
269
270 let (height, width) = input.dim();
272
273 let (s_height, s_width) = struct_elem.dim();
275 let mut offsets = Vec::new();
276 for si in 0..s_height {
277 for sj in 0..s_width {
278 if struct_elem[[si, sj]] {
279 offsets.push((
281 -(si as isize - struct_origin[0]),
282 -(sj as isize - struct_origin[1]),
283 ));
284 }
285 }
286 }
287 let offsets = Arc::new(offsets);
288
289 let use_parallel = height * width > 10_000;
291
292 let mut buffer1 = input.to_owned();
294 let mut buffer2 = Array2::from_elem(input.dim(), T::zero());
295
296 for iter in 0..iters {
298 let (src, dst) = if iter % 2 == 0 {
299 (&buffer1, &mut buffer2)
300 } else {
301 (&buffer2, &mut buffer1)
302 };
303
304 if use_parallel {
305 dilation_iteration_parallel(src, dst, &offsets, height, width);
307 } else {
308 dilation_iteration_simd(src, dst, &offsets, height, width);
310 }
311 }
312
313 Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
315}
316
317#[allow(dead_code)]
319fn dilation_iteration_simd<T>(
320 src: &Array2<T>,
321 dst: &mut Array2<T>,
322 offsets: &[(isize, isize)],
323 height: usize,
324 width: usize,
325) where
326 T: Float
327 + FromPrimitive
328 + Debug
329 + Send
330 + Sync
331 + std::ops::AddAssign
332 + std::ops::DivAssign
333 + 'static,
334 T: SimdUnifiedOps,
335{
336 for i in 0..height {
338 let mut row_slice = dst.row_mut(i);
339
340 for j in 0..width {
341 let mut max_val = T::neg_infinity();
342
343 for &(di, dj) in offsets.iter() {
345 let ni = i as isize + di;
346 let nj = j as isize + dj;
347
348 let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
349 src[[ni as usize, nj as usize]]
350 } else {
351 let ri = ni.clamp(0, (height as isize) - 1) as usize;
353 let rj = nj.clamp(0, (width as isize) - 1) as usize;
354 src[[ri, rj]]
355 };
356
357 max_val = max_val.max(val);
358 }
359
360 row_slice[j] = max_val;
361 }
362 }
363}
364
365#[allow(dead_code)]
367fn dilation_iteration_parallel<T>(
368 src: &Array2<T>,
369 dst: &mut Array2<T>,
370 offsets: &Arc<Vec<(isize, isize)>>,
371 height: usize,
372 width: usize,
373) where
374 T: Float
375 + FromPrimitive
376 + Debug
377 + Send
378 + Sync
379 + std::ops::AddAssign
380 + std::ops::DivAssign
381 + 'static,
382{
383 use parallel_ops::*;
384
385 let offsets_clone = offsets.clone();
387
388 dst.axis_iter_mut(Axis(0))
389 .into_par_iter()
390 .enumerate()
391 .for_each(|(i, mut row)| {
392 let src_ref = src;
393
394 for j in 0..width {
395 let mut max_val = T::neg_infinity();
396
397 for &(di, dj) in offsets_clone.iter() {
399 let ni = i as isize + di;
400 let nj = j as isize + dj;
401
402 let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
403 src_ref[[ni as usize, nj as usize]]
404 } else {
405 let ri = ni.clamp(0, (height as isize) - 1) as usize;
407 let rj = nj.clamp(0, (width as isize) - 1) as usize;
408 src_ref[[ri, rj]]
409 };
410
411 max_val = max_val.max(val);
412 }
413
414 row[j] = max_val;
415 }
416 });
417}
418
419#[allow(dead_code)]
424pub fn binary_erosion_2d_optimized(
425 input: &Array2<bool>,
426 structure: Option<&Array2<bool>>,
427 iterations: Option<usize>,
428 mask: Option<&Array2<bool>>,
429 origin: Option<&[isize; 2]>,
430) -> NdimageResult<Array2<bool>> {
431 let iters = iterations.unwrap_or(1);
433
434 let default_structure = Array2::from_elem((3, 3), true);
436 let struct_elem = structure.unwrap_or(&default_structure);
437
438 let default_origin = [
440 (struct_elem.shape()[0] / 2) as isize,
441 (struct_elem.shape()[1] / 2) as isize,
442 ];
443 let struct_origin = origin.unwrap_or(&default_origin);
444
445 let (height, width) = input.dim();
447
448 let (s_height, s_width) = struct_elem.dim();
450 let mut offsets = Vec::new();
451 for si in 0..s_height {
452 for sj in 0..s_width {
453 if struct_elem[[si, sj]] {
454 offsets.push((
455 si as isize - struct_origin[0],
456 sj as isize - struct_origin[1],
457 ));
458 }
459 }
460 }
461 let offsets = Arc::new(offsets);
462
463 let use_parallel = height * width > 10_000;
465
466 let mut buffer1 = input.to_owned();
468 let mut buffer2 = Array2::from_elem(input.dim(), false);
469
470 for iter in 0..iters {
472 let (src, dst) = if iter % 2 == 0 {
473 (&buffer1, &mut buffer2)
474 } else {
475 (&buffer2, &mut buffer1)
476 };
477
478 if use_parallel {
479 binary_erosion_iteration_parallel(src, dst, &offsets, height, width, mask);
480 } else {
481 binary_erosion_iteration_sequential(src, dst, &offsets, height, width, mask);
482 }
483 }
484
485 Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
487}
488
489#[allow(dead_code)]
491fn binary_erosion_iteration_sequential(
492 src: &Array2<bool>,
493 dst: &mut Array2<bool>,
494 offsets: &[(isize, isize)],
495 height: usize,
496 width: usize,
497 mask: Option<&Array2<bool>>,
498) {
499 for i in 0..height {
500 for j in 0..width {
501 if let Some(m) = mask {
503 if !m[[i, j]] {
504 dst[[i, j]] = src[[i, j]];
505 continue;
506 }
507 }
508
509 let mut eroded = true;
511 for &(di, dj) in offsets.iter() {
512 let ni = i as isize + di;
513 let nj = j as isize + dj;
514
515 if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
516 if !src[[ni as usize, nj as usize]] {
517 eroded = false;
518 break;
519 }
520 } else {
521 eroded = false;
523 break;
524 }
525 }
526
527 dst[[i, j]] = eroded;
528 }
529 }
530}
531
532#[allow(dead_code)]
534fn binary_erosion_iteration_parallel(
535 src: &Array2<bool>,
536 dst: &mut Array2<bool>,
537 offsets: &Arc<Vec<(isize, isize)>>,
538 height: usize,
539 width: usize,
540 mask: Option<&Array2<bool>>,
541) {
542 use parallel_ops::*;
543
544 let offsets_clone = offsets.clone();
546
547 dst.axis_iter_mut(Axis(0))
548 .into_par_iter()
549 .enumerate()
550 .for_each(|(i, mut row)| {
551 let src_ref = src;
552 let mask_ref = mask;
553
554 for j in 0..width {
555 if let Some(m) = mask_ref {
557 if !m[[i, j]] {
558 row[j] = src_ref[[i, j]];
559 continue;
560 }
561 }
562
563 let mut eroded = true;
565 for &(di, dj) in offsets_clone.iter() {
566 let ni = i as isize + di;
567 let nj = j as isize + dj;
568
569 if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
570 if !src_ref[[ni as usize, nj as usize]] {
571 eroded = false;
572 break;
573 }
574 } else {
575 eroded = false;
576 break;
577 }
578 }
579
580 row[j] = eroded;
581 }
582 });
583}
584
585#[allow(dead_code)]
587pub fn binary_dilation_2d_optimized(
588 input: &Array2<bool>,
589 structure: Option<&Array2<bool>>,
590 iterations: Option<usize>,
591 mask: Option<&Array2<bool>>,
592 origin: Option<&[isize; 2]>,
593) -> NdimageResult<Array2<bool>> {
594 let iters = iterations.unwrap_or(1);
596
597 let default_structure = Array2::from_elem((3, 3), true);
599 let struct_elem = structure.unwrap_or(&default_structure);
600
601 let default_origin = [
603 (struct_elem.shape()[0] / 2) as isize,
604 (struct_elem.shape()[1] / 2) as isize,
605 ];
606 let struct_origin = origin.unwrap_or(&default_origin);
607
608 let (height, width) = input.dim();
610
611 let (s_height, s_width) = struct_elem.dim();
613 let mut offsets = Vec::new();
614 for si in 0..s_height {
615 for sj in 0..s_width {
616 if struct_elem[[si, sj]] {
617 offsets.push((
619 -(si as isize - struct_origin[0]),
620 -(sj as isize - struct_origin[1]),
621 ));
622 }
623 }
624 }
625 let offsets = Arc::new(offsets);
626
627 let use_parallel = height * width > 10_000;
629
630 let mut buffer1 = input.to_owned();
632 let mut buffer2 = Array2::from_elem(input.dim(), false);
633
634 for iter in 0..iters {
636 let (src, dst) = if iter % 2 == 0 {
637 (&buffer1, &mut buffer2)
638 } else {
639 (&buffer2, &mut buffer1)
640 };
641
642 if use_parallel {
643 binary_dilation_iteration_parallel(src, dst, &offsets, height, width, mask);
644 } else {
645 binary_dilation_iteration_sequential(src, dst, &offsets, height, width, mask);
646 }
647 }
648
649 Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
651}
652
653#[allow(dead_code)]
655fn binary_dilation_iteration_sequential(
656 src: &Array2<bool>,
657 dst: &mut Array2<bool>,
658 offsets: &[(isize, isize)],
659 height: usize,
660 width: usize,
661 mask: Option<&Array2<bool>>,
662) {
663 for i in 0..height {
664 for j in 0..width {
665 if let Some(m) = mask {
667 if !m[[i, j]] {
668 dst[[i, j]] = src[[i, j]];
669 continue;
670 }
671 }
672
673 let mut dilated = false;
675 for &(di, dj) in offsets.iter() {
676 let ni = i as isize + di;
677 let nj = j as isize + dj;
678
679 if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
680 if src[[ni as usize, nj as usize]] {
681 dilated = true;
682 break;
683 }
684 }
685 }
686
687 dst[[i, j]] = dilated;
688 }
689 }
690}
691
692#[allow(dead_code)]
694fn binary_dilation_iteration_parallel(
695 src: &Array2<bool>,
696 dst: &mut Array2<bool>,
697 offsets: &Arc<Vec<(isize, isize)>>,
698 height: usize,
699 width: usize,
700 mask: Option<&Array2<bool>>,
701) {
702 use parallel_ops::*;
703
704 let offsets_clone = offsets.clone();
706
707 dst.axis_iter_mut(Axis(0))
708 .into_par_iter()
709 .enumerate()
710 .for_each(|(i, mut row)| {
711 let src_ref = src;
712 let mask_ref = mask;
713
714 for j in 0..width {
715 if let Some(m) = mask_ref {
717 if !m[[i, j]] {
718 row[j] = src_ref[[i, j]];
719 continue;
720 }
721 }
722
723 let mut dilated = false;
725 for &(di, dj) in offsets_clone.iter() {
726 let ni = i as isize + di;
727 let nj = j as isize + dj;
728
729 if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
730 if src_ref[[ni as usize, nj as usize]] {
731 dilated = true;
732 break;
733 }
734 }
735 }
736
737 row[j] = dilated;
738 }
739 });
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745 use scirs2_core::ndarray::array;
746
747 #[test]
748 fn test_grey_erosion_optimized() {
749 let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
750
751 let result =
752 grey_erosion_2d_optimized(&input, None, None, None, None).expect("Operation failed");
753
754 assert_eq!(result[[1, 1]], 1.0);
756 }
757
758 #[test]
759 fn test_grey_dilation_optimized() {
760 let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
761
762 let result =
763 grey_dilation_2d_optimized(&input, None, None, None, None).expect("Operation failed");
764
765 assert_eq!(result[[1, 1]], 9.0);
767 }
768
769 #[test]
770 fn test_binary_erosion_optimized() {
771 let input = array![
772 [false, true, true],
773 [false, true, true],
774 [false, false, false]
775 ];
776
777 let result =
778 binary_erosion_2d_optimized(&input, None, None, None, None).expect("Operation failed");
779
780 assert!(!result[[1, 1]]);
782 }
783
784 #[test]
785 fn test_binary_dilation_optimized() {
786 let input = array![
787 [false, true, false],
788 [false, true, false],
789 [false, false, false]
790 ];
791
792 let result =
793 binary_dilation_2d_optimized(&input, None, None, None, None).expect("Operation failed");
794
795 assert!(result[[0, 0]]);
797 assert!(result[[1, 0]]);
798 }
799}
800
801#[derive(Debug, Clone)]
810pub struct MultiScaleMorphConfig {
811 pub scales: Vec<usize>,
813 pub operation: MorphOperation,
815 pub structure_type: StructureType,
817 pub normalize: bool,
819}
820
821#[derive(Debug, Clone, Copy, PartialEq, Eq)]
823pub enum MorphOperation {
824 Erosion,
825 Dilation,
826 Opening,
827 Closing,
828 Gradient,
829 TopHat,
830 BlackHat,
831}
832
833#[derive(Debug, Clone, Copy, PartialEq, Eq)]
835pub enum StructureType {
836 Box,
837 Disk,
838 Cross,
839 Diamond,
840}
841
842impl Default for MultiScaleMorphConfig {
843 fn default() -> Self {
844 Self {
845 scales: vec![1, 3, 5, 7],
846 operation: MorphOperation::Opening,
847 structure_type: StructureType::Disk,
848 normalize: true,
849 }
850 }
851}
852
853#[allow(dead_code)]
869pub fn geodesic_erosion_2d<T>(
870 marker: &Array2<T>,
871 mask: &Array2<T>,
872 structure: Option<&Array2<bool>>,
873 iterations: Option<usize>,
874) -> NdimageResult<Array2<T>>
875where
876 T: Float
877 + FromPrimitive
878 + Debug
879 + Send
880 + Sync
881 + 'static
882 + PartialOrd
883 + std::ops::AddAssign
884 + std::ops::DivAssign,
885 T: SimdUnifiedOps,
886{
887 if marker.shape() != mask.shape() {
888 return Err(crate::error::NdimageError::DimensionError(
889 "Marker and mask must have the same shape".into(),
890 ));
891 }
892
893 let max_iters = iterations.unwrap_or(1000);
894 let mut current = marker.clone();
895 let mut previous = Array2::zeros(marker.dim());
896
897 let default_structure = Array2::from_elem((3, 3), true);
899 let struct_elem = structure.unwrap_or(&default_structure);
900
901 for iter in 0..max_iters {
902 previous.assign(¤t);
903
904 current = grey_erosion_2d_optimized(¤t, Some(struct_elem), Some(1), None, None)?;
906
907 for ((c, m), p) in current.iter_mut().zip(mask.iter()).zip(previous.iter()) {
909 *c = (*c).max(*m);
910 }
911
912 if iter > 0 {
914 let mut converged = true;
915 for (c, p) in current.iter().zip(previous.iter()) {
916 if (*c - *p).abs() > T::from_f64(1e-10).unwrap_or(T::epsilon()) {
917 converged = false;
918 break;
919 }
920 }
921 if converged {
922 break;
923 }
924 }
925 }
926
927 Ok(current)
928}
929
930#[allow(dead_code)]
945pub fn geodesic_dilation_2d<T>(
946 marker: &Array2<T>,
947 mask: &Array2<T>,
948 structure: Option<&Array2<bool>>,
949 iterations: Option<usize>,
950) -> NdimageResult<Array2<T>>
951where
952 T: Float
953 + FromPrimitive
954 + Debug
955 + Send
956 + Sync
957 + 'static
958 + PartialOrd
959 + std::ops::AddAssign
960 + std::ops::DivAssign,
961 T: SimdUnifiedOps,
962{
963 if marker.shape() != mask.shape() {
964 return Err(crate::error::NdimageError::DimensionError(
965 "Marker and mask must have the same shape".into(),
966 ));
967 }
968
969 let max_iters = iterations.unwrap_or(1000);
970 let mut current = marker.clone();
971 let mut previous = Array2::zeros(marker.dim());
972
973 let default_structure = Array2::from_elem((3, 3), true);
975 let struct_elem = structure.unwrap_or(&default_structure);
976
977 for iter in 0..max_iters {
978 previous.assign(¤t);
979
980 current = grey_dilation_2d_optimized(¤t, Some(struct_elem), Some(1), None, None)?;
982
983 for ((c, m), p) in current.iter_mut().zip(mask.iter()).zip(previous.iter()) {
985 *c = (*c).min(*m);
986 }
987
988 if iter > 0 {
990 let mut converged = true;
991 for (c, p) in current.iter().zip(previous.iter()) {
992 if (*c - *p).abs() > T::from_f64(1e-10).unwrap_or(T::epsilon()) {
993 converged = false;
994 break;
995 }
996 }
997 if converged {
998 break;
999 }
1000 }
1001 }
1002
1003 Ok(current)
1004}
1005
1006#[allow(dead_code)]
1022pub fn morphological_reconstruction_2d<T>(
1023 marker: &Array2<T>,
1024 mask: &Array2<T>,
1025 method: MorphOperation,
1026 structure: Option<&Array2<bool>>,
1027) -> NdimageResult<Array2<T>>
1028where
1029 T: Float
1030 + FromPrimitive
1031 + Debug
1032 + Send
1033 + Sync
1034 + 'static
1035 + PartialOrd
1036 + std::ops::AddAssign
1037 + std::ops::DivAssign,
1038 T: SimdUnifiedOps,
1039{
1040 match method {
1041 MorphOperation::Dilation => geodesic_dilation_2d(marker, mask, structure, None),
1042 MorphOperation::Erosion => geodesic_erosion_2d(marker, mask, structure, None),
1043 _ => Err(crate::error::NdimageError::InvalidInput(
1044 "Only dilation and erosion methods are supported for reconstruction".into(),
1045 )),
1046 }
1047}
1048
1049#[allow(dead_code)]
1063pub fn multi_scale_morphology_2d<T>(
1064 input: &Array2<T>,
1065 config: &MultiScaleMorphConfig,
1066) -> NdimageResult<Vec<Array2<T>>>
1067where
1068 T: Float
1069 + FromPrimitive
1070 + Debug
1071 + Send
1072 + Sync
1073 + 'static
1074 + PartialOrd
1075 + std::ops::AddAssign
1076 + std::ops::DivAssign,
1077 T: SimdUnifiedOps,
1078{
1079 let mut results = Vec::with_capacity(config.scales.len());
1080
1081 for &scale in &config.scales {
1082 let structure = create_structuring_element(config.structure_type, scale)?;
1084
1085 let result = match config.operation {
1087 MorphOperation::Erosion => {
1088 grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?
1089 }
1090 MorphOperation::Dilation => {
1091 grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?
1092 }
1093 MorphOperation::Opening => {
1094 let eroded =
1095 grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
1096 grey_dilation_2d_optimized(&eroded, Some(&structure), Some(1), None, None)?
1097 }
1098 MorphOperation::Closing => {
1099 let dilated =
1100 grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?;
1101 grey_erosion_2d_optimized(&dilated, Some(&structure), Some(1), None, None)?
1102 }
1103 MorphOperation::Gradient => {
1104 let dilated =
1105 grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?;
1106 let eroded =
1107 grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
1108 let mut gradient = Array2::zeros(input.dim());
1109 for ((d, e), g) in dilated.iter().zip(eroded.iter()).zip(gradient.iter_mut()) {
1110 *g = *d - *e;
1111 }
1112 gradient
1113 }
1114 MorphOperation::TopHat => {
1115 let opened = {
1116 let eroded =
1117 grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
1118 grey_dilation_2d_optimized(&eroded, Some(&structure), Some(1), None, None)?
1119 };
1120 let mut tophat = Array2::zeros(input.dim());
1121 for ((i, o), t) in input.iter().zip(opened.iter()).zip(tophat.iter_mut()) {
1122 *t = *i - *o;
1123 }
1124 tophat
1125 }
1126 MorphOperation::BlackHat => {
1127 let closed = {
1128 let dilated =
1129 grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?;
1130 grey_erosion_2d_optimized(&dilated, Some(&structure), Some(1), None, None)?
1131 };
1132 let mut blackhat = Array2::zeros(input.dim());
1133 for ((c, i), b) in closed.iter().zip(input.iter()).zip(blackhat.iter_mut()) {
1134 *b = *c - *i;
1135 }
1136 blackhat
1137 }
1138 };
1139
1140 results.push(result);
1141 }
1142
1143 if config.normalize {
1145 for result in &mut results {
1146 normalize_array(result)?;
1147 }
1148 }
1149
1150 Ok(results)
1151}
1152
1153#[allow(dead_code)]
1168pub fn granulometry_2d<T>(
1169 input: &Array2<T>,
1170 scales: &[usize],
1171 structure_type: StructureType,
1172) -> NdimageResult<Vec<f64>>
1173where
1174 T: Float
1175 + FromPrimitive
1176 + Debug
1177 + Send
1178 + Sync
1179 + 'static
1180 + PartialOrd
1181 + std::ops::AddAssign
1182 + std::ops::DivAssign,
1183 T: SimdUnifiedOps,
1184{
1185 let mut curve = Vec::with_capacity(scales.len());
1186
1187 let original_sum: f64 = input.iter().map(|&x| x.to_f64().unwrap_or(0.0)).sum();
1189
1190 for &scale in scales {
1191 let structure = create_structuring_element(structure_type, scale)?;
1193
1194 let eroded = grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
1196 let opened = grey_dilation_2d_optimized(&eroded, Some(&structure), Some(1), None, None)?;
1197
1198 let opened_sum: f64 = opened.iter().map(|&x| x.to_f64().unwrap_or(0.0)).sum();
1200
1201 let granulo_value = if original_sum > 0.0 {
1203 opened_sum / original_sum
1204 } else {
1205 0.0
1206 };
1207
1208 curve.push(granulo_value);
1209 }
1210
1211 Ok(curve)
1212}
1213
1214#[allow(dead_code)]
1229pub fn area_opening_2d<T>(
1230 input: &Array2<T>,
1231 area_threshold: usize,
1232 connectivity: usize,
1233) -> NdimageResult<Array2<T>>
1234where
1235 T: Float + FromPrimitive + Debug + Send + Sync + 'static + PartialOrd,
1236{
1237 if connectivity != 4 && connectivity != 8 {
1238 return Err(crate::error::NdimageError::InvalidInput(
1239 "Connectivity must be 4 or 8".into(),
1240 ));
1241 }
1242
1243 let mut result = input.clone();
1248 let (height, width) = input.dim();
1249
1250 let _threshold = compute_threshold_for_area(input, area_threshold)?;
1252
1253 for i in 0..height {
1254 for j in 0..width {
1255 if input[[i, j]] < _threshold {
1256 result[[i, j]] = T::zero();
1257 }
1258 }
1259 }
1260
1261 Ok(result)
1262}
1263
1264#[allow(dead_code)]
1266fn create_structuring_element(
1267 structure_type: StructureType,
1268 size: usize,
1269) -> NdimageResult<Array2<bool>> {
1270 let radius = size / 2;
1271 let dim = 2 * radius + 1;
1272 let mut structure = Array2::from_elem((dim, dim), false);
1273
1274 match structure_type {
1275 StructureType::Box => {
1276 structure.fill(true);
1277 }
1278 StructureType::Cross => {
1279 for i in 0..dim {
1281 structure[[i, radius]] = true; structure[[radius, i]] = true; }
1284 }
1285 StructureType::Diamond => {
1286 let center = radius as isize;
1288 for i in 0..dim {
1289 for j in 0..dim {
1290 let di = i as isize - center;
1291 let dj = j as isize - center;
1292 if (di.abs() + dj.abs()) <= radius as isize {
1293 structure[[i, j]] = true;
1294 }
1295 }
1296 }
1297 }
1298 StructureType::Disk => {
1299 let center = radius as f64;
1301 for i in 0..dim {
1302 for j in 0..dim {
1303 let di = i as f64 - center;
1304 let dj = j as f64 - center;
1305 if (di * di + dj * dj).sqrt() <= radius as f64 {
1306 structure[[i, j]] = true;
1307 }
1308 }
1309 }
1310 }
1311 }
1312
1313 Ok(structure)
1314}
1315
1316#[allow(dead_code)]
1318fn normalize_array<T>(array: &mut Array2<T>) -> NdimageResult<()>
1319where
1320 T: Float + FromPrimitive + Debug + 'static,
1321{
1322 let min_val = array.iter().fold(T::infinity(), |acc, &x| acc.min(x));
1323 let max_val = array.iter().fold(T::neg_infinity(), |acc, &x| acc.max(x));
1324
1325 let range = max_val - min_val;
1326 if range > T::zero() {
1327 for value in array.iter_mut() {
1328 *value = (*value - min_val) / range;
1329 }
1330 }
1331
1332 Ok(())
1333}
1334
1335#[allow(dead_code)]
1337fn compute_threshold_for_area<T>(_input: &Array2<T>, _areathreshold: usize) -> NdimageResult<T>
1338where
1339 T: Float + FromPrimitive + Debug + 'static,
1340{
1341 let mut values: Vec<T> = _input.iter().copied().collect();
1343 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1344
1345 let median_idx = values.len() / 2;
1346 Ok(values[median_idx])
1347}