1use scirs2_core::ndarray::{Array, Dimension, IxDyn};
4use std::collections::HashMap;
5
6use super::Connectivity;
7use crate::error::{NdimageError, NdimageResult};
8
9struct UnionFind {
11 parent: Vec<usize>,
12 rank: Vec<usize>,
13}
14
15impl UnionFind {
16 fn new(size: usize) -> Self {
17 UnionFind {
18 parent: (0..size).collect(),
19 rank: vec![0; size],
20 }
21 }
22
23 fn find(&mut self, x: usize) -> usize {
24 if self.parent[x] != x {
25 self.parent[x] = self.find(self.parent[x]); }
27 self.parent[x]
28 }
29
30 fn union(&mut self, x: usize, y: usize) {
31 let root_x = self.find(x);
32 let root_y = self.find(y);
33
34 if root_x != root_y {
35 if self.rank[root_x] < self.rank[root_y] {
37 self.parent[root_x] = root_y;
38 } else if self.rank[root_x] > self.rank[root_y] {
39 self.parent[root_y] = root_x;
40 } else {
41 self.parent[root_y] = root_x;
42 self.rank[root_x] += 1;
43 }
44 }
45 }
46
47 fn get_component_mapping(&mut self) -> HashMap<usize, usize> {
48 let mut mapping = HashMap::new();
49 let mut next_label = 1;
50
51 for i in 0..self.parent.len() {
52 let root = self.find(i);
53 if !mapping.contains_key(&root) {
54 mapping.insert(root, next_label);
55 next_label += 1;
56 }
57 }
58
59 mapping
60 }
61}
62
63#[allow(dead_code)]
65fn get_neighbors(
66 position: &[usize],
67 shape: &[usize],
68 connectivity: Connectivity,
69) -> Vec<Vec<usize>> {
70 let ndim = position.len();
71 let mut neighbors = Vec::new();
72
73 match connectivity {
74 Connectivity::Face => {
75 for dim in 0..ndim {
77 if position[dim] > 0 {
79 let mut neighbor = position.to_vec();
80 neighbor[dim] -= 1;
81 neighbors.push(neighbor);
82 }
83 if position[dim] + 1 < shape[dim] {
85 let mut neighbor = position.to_vec();
86 neighbor[dim] += 1;
87 neighbors.push(neighbor);
88 }
89 }
90 }
91 Connectivity::FaceEdge => {
92 let offsets = generate_face_edge_offsets(ndim);
94 for offset in offsets {
95 let mut neighbor = Vec::with_capacity(ndim);
96 let mut valid = true;
97
98 for (i, &pos) in position.iter().enumerate() {
99 let new_pos = (pos as isize) + offset[i];
100 if new_pos < 0 || new_pos >= shape[i] as isize {
101 valid = false;
102 break;
103 }
104 neighbor.push(new_pos as usize);
105 }
106
107 if valid && neighbor != position {
108 neighbors.push(neighbor);
109 }
110 }
111 }
112 Connectivity::Full => {
113 let offsets = generate_all_offsets(ndim);
115 for offset in offsets {
116 let mut neighbor = Vec::with_capacity(ndim);
117 let mut valid = true;
118
119 for (i, &pos) in position.iter().enumerate() {
120 let new_pos = (pos as isize) + offset[i];
121 if new_pos < 0 || new_pos >= shape[i] as isize {
122 valid = false;
123 break;
124 }
125 neighbor.push(new_pos as usize);
126 }
127
128 if valid && neighbor != position {
129 neighbors.push(neighbor);
130 }
131 }
132 }
133 }
134
135 neighbors
136}
137
138#[allow(dead_code)]
140fn generate_all_offsets(ndim: usize) -> Vec<Vec<isize>> {
141 let mut offsets = Vec::new();
142 let total_combinations = 3_usize.pow(ndim as u32);
143
144 for i in 0..total_combinations {
145 let mut offset = Vec::with_capacity(ndim);
146 let mut temp = i;
147
148 for _ in 0..ndim {
149 let val = (temp % 3) as isize - 1; offset.push(val);
151 temp /= 3;
152 }
153
154 if !offset.iter().all(|&x| x == 0) {
156 offsets.push(offset);
157 }
158 }
159
160 offsets
161}
162
163#[allow(dead_code)]
165fn generate_face_edge_offsets(ndim: usize) -> Vec<Vec<isize>> {
166 let mut offsets = Vec::new();
167 let total_combinations = 3_usize.pow(ndim as u32);
168
169 for i in 0..total_combinations {
170 let mut offset = Vec::with_capacity(ndim);
171 let mut temp = i;
172 for _ in 0..ndim {
173 let val = (temp % 3) as isize - 1; offset.push(val);
175 temp /= 3;
176 }
177
178 if offset.iter().all(|&x| x == 0) {
180 continue;
181 }
182
183 let non_zero_count = offset.iter().filter(|&&x| x != 0).count();
186
187 if non_zero_count <= 2 {
191 offsets.push(offset);
192 }
193 }
194
195 offsets
196}
197
198#[allow(dead_code)]
200fn ravel_index(indices: &[usize], shape: &[usize]) -> usize {
201 let mut flat_index = 0;
202 let mut stride = 1;
203
204 for i in (0..indices.len()).rev() {
205 flat_index += indices[i] * stride;
206 stride *= shape[i];
207 }
208
209 flat_index
210}
211
212#[allow(dead_code)]
214fn unravel_index(_flatindex: usize, shape: &[usize]) -> Vec<usize> {
215 let mut indices = vec![0; shape.len()];
216 let mut remaining = _flatindex;
217
218 for i in 0..shape.len() {
220 let stride: usize = shape[(i + 1)..].iter().product();
221 indices[i] = remaining / stride;
222 remaining %= stride;
223 }
224
225 indices
226}
227
228#[allow(dead_code)]
241pub fn label<D>(
242 input: &Array<bool, D>,
243 structure: Option<&Array<bool, D>>,
244 connectivity: Option<Connectivity>,
245 background: Option<bool>,
246) -> NdimageResult<(Array<usize, D>, usize)>
247where
248 D: Dimension,
249{
250 if input.ndim() == 0 {
252 return Err(NdimageError::InvalidInput(
253 "Input array cannot be 0-dimensional".into(),
254 ));
255 }
256
257 let conn = connectivity.unwrap_or(Connectivity::Face);
258 let bg = background.unwrap_or(false);
259
260 if let Some(struct_elem) = structure {
262 if struct_elem.ndim() != input.ndim() {
263 return Err(NdimageError::DimensionError(format!(
264 "Structure must have same rank as input (got {} expected {})",
265 struct_elem.ndim(),
266 input.ndim()
267 )));
268 }
269 }
270
271 let shape = input.shape();
272 let total_elements: usize = shape.iter().product();
273
274 if total_elements == 0 {
275 let output = Array::<usize, D>::zeros(input.raw_dim());
276 return Ok((output, 0));
277 }
278
279 let mut uf = UnionFind::new(total_elements);
281
282 let input_dyn = input.clone().into_dyn();
284
285 for flat_idx in 0..total_elements {
287 let indices = unravel_index(flat_idx, shape);
288 let current_pixel = input_dyn[IxDyn(&indices)];
289
290 if current_pixel == !bg {
292 let neighbors = get_neighbors(&indices, shape, conn);
294
295 for neighbor_indices in neighbors {
296 let neighbor_pixel = input_dyn[IxDyn(&neighbor_indices)];
297
298 if neighbor_pixel == current_pixel {
300 let neighbor_flat_idx = ravel_index(&neighbor_indices, shape);
301 uf.union(flat_idx, neighbor_flat_idx);
302 }
303 }
304 }
305 }
306
307 let component_mapping = uf.get_component_mapping();
309
310 let mut output = Array::<usize, D>::zeros(input.raw_dim());
312 let mut num_labels = 0;
313
314 let mut output_dyn = output.clone().into_dyn();
316 for flat_idx in 0..total_elements {
317 let indices = unravel_index(flat_idx, shape);
318 let pixel = input_dyn[IxDyn(&indices)];
319
320 if pixel == !bg {
321 let root = uf.find(flat_idx);
322 if let Some(&label) = component_mapping.get(&root) {
323 output_dyn[IxDyn(&indices)] = label;
324 num_labels = num_labels.max(label);
325 }
326 }
327 }
328
329 output = output_dyn.into_dimensionality::<D>().map_err(|_| {
331 NdimageError::DimensionError("Failed to convert back to original dimension type".into())
332 })?;
333
334 Ok((output, num_labels))
335}
336
337#[allow(dead_code)]
349pub fn find_boundaries<D>(
350 input: &Array<usize, D>,
351 connectivity: Option<Connectivity>,
352 mode: Option<&str>,
353) -> NdimageResult<Array<bool, D>>
354where
355 D: Dimension,
356{
357 if input.ndim() == 0 {
359 return Err(NdimageError::InvalidInput(
360 "Input array cannot be 0-dimensional".into(),
361 ));
362 }
363
364 let conn = connectivity.unwrap_or(Connectivity::Face);
365 let mode_str = mode.unwrap_or("outer");
366
367 if mode_str != "inner" && mode_str != "outer" && mode_str != "thick" {
369 return Err(NdimageError::InvalidInput(format!(
370 "Mode must be 'inner', 'outer', or 'thick', got '{}'",
371 mode_str
372 )));
373 }
374
375 let shape = input.shape();
376 let total_elements: usize = shape.iter().product();
377 let mut output = Array::<bool, D>::from_elem(input.raw_dim(), false);
378
379 if total_elements == 0 {
380 return Ok(output);
381 }
382
383 let input_dyn = input.clone().into_dyn();
385 let mut output_dyn = output.clone().into_dyn();
386
387 for flat_idx in 0..total_elements {
389 let indices = unravel_index(flat_idx, shape);
390 let current_label = input_dyn[IxDyn(&indices)];
391
392 if mode_str == "inner" && current_label == 0 {
394 continue;
395 }
396
397 let neighbors = get_neighbors(&indices, shape, conn);
399 let mut is_boundary = false;
400
401 for neighbor_indices in neighbors {
402 let neighbor_label = input_dyn[IxDyn(&neighbor_indices)];
403
404 match mode_str {
405 "inner"
406 if current_label != 0
408 && (neighbor_label == 0 || neighbor_label != current_label)
409 => {
410 is_boundary = true;
411 break;
412 }
413 "outer"
414 if current_label == 0 && neighbor_label != 0 => {
416 is_boundary = true;
417 break;
418 }
419 "thick"
420 if current_label != neighbor_label => {
422 is_boundary = true;
423 break;
424 }
425 _ => {} }
427 }
428
429 if is_boundary {
430 output_dyn[IxDyn(&indices)] = true;
431 }
432 }
433
434 output = output_dyn.into_dimensionality::<D>().map_err(|_| {
436 NdimageError::DimensionError("Failed to convert back to original dimension type".into())
437 })?;
438
439 Ok(output)
440}
441
442#[allow(dead_code)]
454pub fn remove_small_objects<D>(
455 input: &Array<bool, D>,
456 min_size: usize,
457 connectivity: Option<Connectivity>,
458) -> NdimageResult<Array<bool, D>>
459where
460 D: Dimension,
461{
462 if input.ndim() == 0 {
464 return Err(NdimageError::InvalidInput(
465 "Input array cannot be 0-dimensional".into(),
466 ));
467 }
468
469 if min_size == 0 {
470 return Err(NdimageError::InvalidInput(
471 "min_size must be greater than 0".into(),
472 ));
473 }
474
475 let conn = connectivity.unwrap_or(Connectivity::Face);
476
477 let (labeled, num_labels) = label(input, None, Some(conn), None)?;
479
480 if num_labels == 0 {
481 return Ok(Array::<bool, D>::from_elem(input.raw_dim(), false));
482 }
483
484 let mut component_sizes = vec![0; num_labels + 1];
486 for &label_val in labeled.iter() {
487 if label_val > 0 {
488 component_sizes[label_val] += 1;
489 }
490 }
491
492 let mut output = Array::<bool, D>::from_elem(input.raw_dim(), false);
494 let shape = input.shape();
495 let total_elements: usize = shape.iter().product();
496
497 let labeled_dyn = labeled.clone().into_dyn();
499 let mut output_dyn = output.clone().into_dyn();
500
501 for flat_idx in 0..total_elements {
502 let indices = unravel_index(flat_idx, shape);
503 let label_val = labeled_dyn[IxDyn(&indices)];
504
505 if label_val > 0 && component_sizes[label_val] >= min_size {
506 output_dyn[IxDyn(&indices)] = true;
507 }
508 }
509
510 output = output_dyn.into_dimensionality::<D>().map_err(|_| {
512 NdimageError::DimensionError("Failed to convert back to original dimension type".into())
513 })?;
514
515 Ok(output)
516}
517
518#[allow(dead_code)]
530pub fn remove_small_holes<D>(
531 input: &Array<bool, D>,
532 min_size: usize,
533 connectivity: Option<Connectivity>,
534) -> NdimageResult<Array<bool, D>>
535where
536 D: Dimension,
537{
538 if input.ndim() == 0 {
540 return Err(NdimageError::InvalidInput(
541 "Input array cannot be 0-dimensional".into(),
542 ));
543 }
544
545 if min_size == 0 {
546 return Err(NdimageError::InvalidInput(
547 "min_size must be greater than 0".into(),
548 ));
549 }
550
551 let conn = connectivity.unwrap_or(Connectivity::Face);
552
553 let mut inverted = input.clone();
560 for pixel in inverted.iter_mut() {
561 *pixel = !*pixel;
562 }
563
564 let filtered_inverted = remove_small_objects(&inverted, min_size, Some(conn))?;
566
567 let mut output = filtered_inverted;
569 for pixel in output.iter_mut() {
570 *pixel = !*pixel;
571 }
572
573 Ok(output)
574}
575
576#[derive(Debug, Clone, PartialEq, Eq)]
581pub struct BoundingBox2D {
582 pub label: usize,
584 pub min_row: usize,
586 pub max_row: usize,
588 pub min_col: usize,
590 pub max_col: usize,
592}
593
594impl BoundingBox2D {
595 pub fn width(&self) -> usize {
597 self.max_col - self.min_col
598 }
599
600 pub fn height(&self) -> usize {
602 self.max_row - self.min_row
603 }
604
605 pub fn area(&self) -> usize {
607 self.width() * self.height()
608 }
609}
610
611pub fn label_2d(
642 input: &scirs2_core::ndarray::Array2<bool>,
643 connectivity: Option<Connectivity>,
644) -> NdimageResult<(scirs2_core::ndarray::Array2<usize>, usize)> {
645 use scirs2_core::ndarray::Array2;
646
647 let conn = connectivity.unwrap_or(Connectivity::Face);
648 let rows = input.nrows();
649 let cols = input.ncols();
650
651 if rows == 0 || cols == 0 {
652 return Ok((Array2::zeros((rows, cols)), 0));
653 }
654
655 let total = rows * cols;
656 let mut uf = UnionFind::new(total);
657
658 let use_diag = matches!(conn, Connectivity::Full | Connectivity::FaceEdge);
660
661 for r in 0..rows {
663 for c in 0..cols {
664 if !input[[r, c]] {
665 continue;
666 }
667
668 let idx = r * cols + c;
669
670 if r > 0 && input[[r - 1, c]] {
672 uf.union(idx, (r - 1) * cols + c);
673 }
674
675 if c > 0 && input[[r, c - 1]] {
677 uf.union(idx, r * cols + (c - 1));
678 }
679
680 if use_diag {
681 if r > 0 && c > 0 && input[[r - 1, c - 1]] {
683 uf.union(idx, (r - 1) * cols + (c - 1));
684 }
685 if r > 0 && c + 1 < cols && input[[r - 1, c + 1]] {
687 uf.union(idx, (r - 1) * cols + (c + 1));
688 }
689 }
690 }
691 }
692
693 let mut root_to_label: HashMap<usize, usize> = HashMap::new();
695 let mut next_label = 1usize;
696 let mut output = Array2::zeros((rows, cols));
697
698 for r in 0..rows {
699 for c in 0..cols {
700 if !input[[r, c]] {
701 continue;
702 }
703 let idx = r * cols + c;
704 let root = uf.find(idx);
705 let lbl = match root_to_label.get(&root) {
706 Some(&l) => l,
707 None => {
708 let l = next_label;
709 root_to_label.insert(root, l);
710 next_label += 1;
711 l
712 }
713 };
714 output[[r, c]] = lbl;
715 }
716 }
717
718 let num_labels = next_label - 1;
719 Ok((output, num_labels))
720}
721
722pub fn find_objects_2d(
753 labeled: &scirs2_core::ndarray::Array2<usize>,
754) -> NdimageResult<Vec<BoundingBox2D>> {
755 let rows = labeled.nrows();
756 let cols = labeled.ncols();
757
758 if rows == 0 || cols == 0 {
759 return Ok(Vec::new());
760 }
761
762 let mut bbox_map: HashMap<usize, (usize, usize, usize, usize)> = HashMap::new();
764
765 for r in 0..rows {
766 for c in 0..cols {
767 let lbl = labeled[[r, c]];
768 if lbl == 0 {
769 continue;
770 }
771 let entry = bbox_map.entry(lbl).or_insert((r, r, c, c));
772 if r < entry.0 {
773 entry.0 = r;
774 }
775 if r > entry.1 {
776 entry.1 = r;
777 }
778 if c < entry.2 {
779 entry.2 = c;
780 }
781 if c > entry.3 {
782 entry.3 = c;
783 }
784 }
785 }
786
787 let mut result: Vec<BoundingBox2D> = bbox_map
788 .into_iter()
789 .map(|(lbl, (min_r, max_r, min_c, max_c))| BoundingBox2D {
790 label: lbl,
791 min_row: min_r,
792 max_row: max_r + 1, min_col: min_c,
794 max_col: max_c + 1, })
796 .collect();
797
798 result.sort_by_key(|b| b.label);
799 Ok(result)
800}
801
802pub fn count_labels_2d(labeled: &scirs2_core::ndarray::Array2<usize>) -> HashMap<usize, usize> {
812 let mut counts: HashMap<usize, usize> = HashMap::new();
813 for &lbl in labeled.iter() {
814 if lbl > 0 {
815 *counts.entry(lbl).or_insert(0) += 1;
816 }
817 }
818 counts
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824 use scirs2_core::ndarray::{array, Array2};
825
826 #[test]
827 fn test_label() {
828 let input = Array2::from_elem((3, 3), true);
829 let (result, _num_labels) = label(&input, None, None, None).expect("Operation failed");
830 assert_eq!(result.shape(), input.shape());
831 }
832
833 #[test]
834 fn test_find_boundaries() {
835 let input = Array2::from_elem((3, 3), 1);
836 let result = find_boundaries(&input, None, None).expect("Operation failed");
837 assert_eq!(result.shape(), input.shape());
838 }
839
840 #[test]
841 fn test_remove_small_objects() {
842 let input = Array2::from_elem((3, 3), true);
843 let result = remove_small_objects(&input, 1, None).expect("Operation failed");
844 assert_eq!(result.shape(), input.shape());
845 }
846
847 #[test]
848 fn test_label_2d_two_components() {
849 let input = array![
850 [true, true, false, false],
851 [true, true, false, false],
852 [false, false, true, true],
853 [false, false, true, true],
854 ];
855 let (labeled, num) = label_2d(&input, None).expect("label_2d should succeed");
856 assert_eq!(num, 2);
857 let l1 = labeled[[0, 0]];
859 let l2 = labeled[[2, 2]];
860 assert_ne!(l1, 0);
861 assert_ne!(l2, 0);
862 assert_ne!(l1, l2);
863 assert_eq!(labeled[[0, 0]], labeled[[0, 1]]);
865 assert_eq!(labeled[[0, 0]], labeled[[1, 0]]);
866 assert_eq!(labeled[[0, 0]], labeled[[1, 1]]);
867 }
868
869 #[test]
870 fn test_label_2d_single_component_8conn() {
871 let input = array![
873 [true, false, false],
874 [false, true, false],
875 [false, false, true],
876 ];
877 let (labeled, num) =
878 label_2d(&input, Some(Connectivity::Full)).expect("label_2d 8-conn should succeed");
879 assert_eq!(num, 1);
880 assert_eq!(labeled[[0, 0]], labeled[[1, 1]]);
881 assert_eq!(labeled[[1, 1]], labeled[[2, 2]]);
882 }
883
884 #[test]
885 fn test_label_2d_multiple_components_4conn() {
886 let input = array![
888 [true, false, false],
889 [false, true, false],
890 [false, false, true],
891 ];
892 let (labeled, num) =
893 label_2d(&input, Some(Connectivity::Face)).expect("label_2d 4-conn should succeed");
894 assert_eq!(num, 3);
895 let l0 = labeled[[0, 0]];
897 let l1 = labeled[[1, 1]];
898 let l2 = labeled[[2, 2]];
899 assert_ne!(l0, l1);
900 assert_ne!(l1, l2);
901 assert_ne!(l0, l2);
902 }
903
904 #[test]
905 fn test_label_2d_empty() {
906 let input = Array2::from_elem((3, 3), false);
907 let (labeled, num) = label_2d(&input, None).expect("empty should succeed");
908 assert_eq!(num, 0);
909 for &v in labeled.iter() {
910 assert_eq!(v, 0);
911 }
912 }
913
914 #[test]
915 fn test_label_2d_all_foreground() {
916 let input = Array2::from_elem((4, 4), true);
917 let (labeled, num) = label_2d(&input, None).expect("all-foreground should succeed");
918 assert_eq!(num, 1);
919 let expected_label = labeled[[0, 0]];
920 for &v in labeled.iter() {
921 assert_eq!(v, expected_label);
922 }
923 }
924
925 #[test]
926 fn test_find_objects_2d_basic() {
927 let input = array![
928 [true, true, false, false],
929 [true, true, false, false],
930 [false, false, true, true],
931 [false, false, true, true],
932 ];
933 let (labeled, _) = label_2d(&input, None).expect("label_2d should succeed");
934 let objects = find_objects_2d(&labeled).expect("find_objects_2d should succeed");
935 assert_eq!(objects.len(), 2);
936
937 let obj1 = &objects[0];
939 assert_eq!(obj1.min_row, 0);
940 assert_eq!(obj1.max_row, 2);
941 assert_eq!(obj1.min_col, 0);
942 assert_eq!(obj1.max_col, 2);
943 assert_eq!(obj1.width(), 2);
944 assert_eq!(obj1.height(), 2);
945
946 let obj2 = &objects[1];
948 assert_eq!(obj2.min_row, 2);
949 assert_eq!(obj2.max_row, 4);
950 assert_eq!(obj2.min_col, 2);
951 assert_eq!(obj2.max_col, 4);
952 }
953
954 #[test]
955 fn test_find_objects_2d_no_objects() {
956 let labeled = Array2::<usize>::zeros((5, 5));
957 let objects = find_objects_2d(&labeled).expect("no objects should succeed");
958 assert!(objects.is_empty());
959 }
960
961 #[test]
962 fn test_find_objects_2d_single_pixel() {
963 let mut labeled = Array2::<usize>::zeros((5, 5));
964 labeled[[2, 3]] = 1;
965 let objects = find_objects_2d(&labeled).expect("single pixel should succeed");
966 assert_eq!(objects.len(), 1);
967 assert_eq!(objects[0].min_row, 2);
968 assert_eq!(objects[0].max_row, 3);
969 assert_eq!(objects[0].min_col, 3);
970 assert_eq!(objects[0].max_col, 4);
971 assert_eq!(objects[0].area(), 1);
972 }
973
974 #[test]
975 fn test_count_labels_2d() {
976 let labeled = array![[0, 1, 1, 0], [0, 1, 0, 2], [3, 0, 0, 2], [3, 3, 0, 0],];
977 let counts = count_labels_2d(&labeled);
978 assert_eq!(counts.get(&1), Some(&3));
979 assert_eq!(counts.get(&2), Some(&2));
980 assert_eq!(counts.get(&3), Some(&3));
981 assert_eq!(counts.get(&0), None); }
983
984 #[test]
985 fn test_label_2d_l_shape() {
986 let input = array![
988 [true, false, false],
989 [true, false, false],
990 [true, true, true],
991 ];
992 let (labeled, num) = label_2d(&input, None).expect("L-shape should succeed");
993 assert_eq!(num, 1);
994 let expected = labeled[[0, 0]];
995 assert_eq!(labeled[[1, 0]], expected);
996 assert_eq!(labeled[[2, 0]], expected);
997 assert_eq!(labeled[[2, 1]], expected);
998 assert_eq!(labeled[[2, 2]], expected);
999 }
1000}