iris/segmentation/
components.rs1use crate::core::types::Rect;
2use crate::error::Result;
3use crate::image::Image;
4use burn::tensor::{Int, Tensor, TensorData, backend::Backend};
5
6#[derive(Clone, Debug, PartialEq)]
8pub struct ComponentStats {
9 pub label: usize,
10 pub bbox: Rect<usize>,
11 pub area: usize,
12 pub centroid: (f64, f64),
13}
14
15impl<B: Backend> Image<B> {
16 pub fn connected_components_with_stats(
19 &self,
20 ) -> Result<(Tensor<B, 2, Int>, Vec<ComponentStats>)> {
21 let gray = self.grayscale()?;
22 let dims = gray.tensor.dims();
23 let h = dims[1];
24 let w = dims[2];
25
26 let device = gray.tensor.device();
27 let tensor_data = gray.tensor.clone().into_data();
28 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
29
30 let mut labels = vec![0usize; h * w];
31 let mut stats = Vec::new();
32 let mut current_label = 0;
33
34 for y in 0..h {
36 for x in 0..w {
37 let idx = y * w + x;
38 if flat_vals[idx] > 0.5 && labels[idx] == 0 {
39 current_label += 1;
40
41 let mut area = 0;
43 let mut min_x = x;
44 let mut max_x = x;
45 let mut min_y = y;
46 let mut max_y = y;
47 let mut sum_x = 0;
48 let mut sum_y = 0;
49
50 let mut queue = std::collections::VecDeque::new();
52 queue.push_back((x, y));
53 labels[idx] = current_label;
54
55 while let Some((cx, cy)) = queue.pop_front() {
56 area += 1;
57 min_x = min_x.min(cx);
58 max_x = max_x.max(cx);
59 min_y = min_y.min(cy);
60 max_y = max_y.max(cy);
61 sum_x += cx;
62 sum_y += cy;
63
64 let neighbors = [
66 (cx as isize + 1, cy as isize),
67 (cx as isize - 1, cy as isize),
68 (cx as isize, cy as isize + 1),
69 (cx as isize, cy as isize - 1),
70 ];
71
72 for &(nx, ny) in &neighbors {
73 if nx >= 0 && nx < w as isize && ny >= 0 && ny < h as isize {
74 let nidx = (ny as usize) * w + (nx as usize);
75 if flat_vals[nidx] > 0.5 && labels[nidx] == 0 {
76 labels[nidx] = current_label;
77 queue.push_back((nx as usize, ny as usize));
78 }
79 }
80 }
81 }
82
83 stats.push(ComponentStats {
84 label: current_label,
85 bbox: Rect::new(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1),
86 area,
87 centroid: (sum_x as f64 / area as f64, sum_y as f64 / area as f64),
88 });
89 }
90 }
91 }
92
93 let labels_i32: Vec<i32> = labels.iter().map(|&l| l as i32).collect();
95 let labels_data = TensorData::new(labels_i32, [h, w]);
96 let labels_tensor = Tensor::<B, 2, Int>::from_data(labels_data, &device);
97
98 Ok((labels_tensor, stats))
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::test_helpers::{TestBackend, test_device};
106
107 #[test]
108 fn test_connected_components() {
109 let device = test_device();
110 let mut flat_data = vec![0.0f32; 5 * 5];
112 flat_data[0] = 1.0;
113 flat_data[1] = 1.0;
114 flat_data[23] = 1.0;
115 flat_data[24] = 1.0;
116
117 let tensor =
118 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 5, 5]), &device);
119 let img = Image::new(tensor);
120
121 let (labels, stats) = img.connected_components_with_stats().unwrap();
122 assert_eq!(labels.dims(), [5, 5]);
123 assert_eq!(stats.len(), 2);
124 assert_eq!(stats[0].area, 2);
125 assert_eq!(stats[1].area, 2);
126 }
127}