1use crate::error::{VisionError, VisionResult};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum Connectivity {
24 Four,
26 Eight,
28}
29
30#[derive(Debug, Clone)]
32pub struct ComponentLabels {
33 pub labels: Vec<u32>,
36 pub num_components: usize,
38 pub height: usize,
40 pub width: usize,
42 sizes: Vec<usize>,
44}
45
46impl ComponentLabels {
47 #[must_use]
49 pub fn sizes(&self) -> &[usize] {
50 &self.sizes
51 }
52
53 #[must_use]
56 pub fn size_of(&self, label: u32) -> usize {
57 if label == 0 || (label as usize) > self.num_components {
58 0
59 } else {
60 self.sizes[label as usize - 1]
61 }
62 }
63
64 #[must_use]
67 pub fn bounding_boxes(&self) -> Vec<[usize; 4]> {
68 let mut boxes = vec![[usize::MAX, usize::MAX, 0usize, 0usize]; self.num_components];
69 for y in 0..self.height {
70 for x in 0..self.width {
71 let label = self.labels[y * self.width + x];
72 if label == 0 {
73 continue;
74 }
75 let bb = &mut boxes[label as usize - 1];
76 bb[0] = bb[0].min(x);
77 bb[1] = bb[1].min(y);
78 bb[2] = bb[2].max(x);
79 bb[3] = bb[3].max(y);
80 }
81 }
82 boxes
83 }
84}
85
86struct UnionFind {
89 parent: Vec<u32>,
90 rank: Vec<u32>,
91}
92
93impl UnionFind {
94 fn new() -> Self {
95 Self {
97 parent: vec![0],
98 rank: vec![0],
99 }
100 }
101
102 fn make_set(&mut self) -> u32 {
104 let id = self.parent.len() as u32;
105 self.parent.push(id);
106 self.rank.push(0);
107 id
108 }
109
110 fn find(&mut self, mut x: u32) -> u32 {
112 while self.parent[x as usize] != x {
113 let grandparent = self.parent[self.parent[x as usize] as usize];
114 self.parent[x as usize] = grandparent;
115 x = grandparent;
116 }
117 x
118 }
119
120 fn union(&mut self, a: u32, b: u32) {
122 let ra = self.find(a);
123 let rb = self.find(b);
124 if ra == rb {
125 return;
126 }
127 let (high, low) = if self.rank[ra as usize] < self.rank[rb as usize] {
128 (rb, ra)
129 } else {
130 (ra, rb)
131 };
132 self.parent[low as usize] = high;
133 if self.rank[high as usize] == self.rank[low as usize] {
134 self.rank[high as usize] += 1;
135 }
136 }
137}
138
139#[inline]
141fn validate_gray(img: &[f32], h: usize, w: usize) -> VisionResult<()> {
142 if h == 0 || w == 0 {
143 return Err(VisionError::InvalidImageSize {
144 height: h,
145 width: w,
146 channels: 1,
147 });
148 }
149 if img.len() != h * w {
150 return Err(VisionError::DimensionMismatch {
151 expected: h * w,
152 got: img.len(),
153 });
154 }
155 Ok(())
156}
157
158pub fn connected_components(
168 img: &[f32],
169 h: usize,
170 w: usize,
171 connectivity: Connectivity,
172) -> VisionResult<ComponentLabels> {
173 validate_gray(img, h, w)?;
174
175 let mut uf = UnionFind::new();
176 let mut provisional = vec![0u32; h * w];
177
178 let mut neighbours: Vec<u32> = Vec::with_capacity(4);
180 for y in 0..h {
181 for x in 0..w {
182 let idx = y * w + x;
183 if img[idx] <= 0.0 {
184 continue;
185 }
186 neighbours.clear();
187 if x > 0 && provisional[idx - 1] != 0 {
189 neighbours.push(provisional[idx - 1]);
190 }
191 if y > 0 && provisional[idx - w] != 0 {
193 neighbours.push(provisional[idx - w]);
194 }
195 if connectivity == Connectivity::Eight {
196 if x > 0 && y > 0 && provisional[idx - w - 1] != 0 {
198 neighbours.push(provisional[idx - w - 1]);
199 }
200 if x + 1 < w && y > 0 && provisional[idx - w + 1] != 0 {
202 neighbours.push(provisional[idx - w + 1]);
203 }
204 }
205
206 match neighbours.split_first() {
207 None => {
208 provisional[idx] = uf.make_set();
209 }
210 Some((&head, tail)) => {
211 let mut root = uf.find(head);
212 for &other in tail {
213 let r = uf.find(other);
214 if r < root {
215 root = r;
216 }
217 }
218 uf.union(root, head);
219 for &other in tail {
220 uf.union(root, other);
221 }
222 provisional[idx] = root;
223 }
224 }
225 }
226 }
227
228 let mut remap = vec![0u32; uf.parent.len()];
230 let mut labels = vec![0u32; h * w];
231 let mut sizes: Vec<usize> = Vec::new();
232 let mut num_components = 0u32;
233 for (idx, &prov) in provisional.iter().enumerate() {
234 if prov == 0 {
235 continue;
236 }
237 let root = uf.find(prov);
238 let mut compact = remap[root as usize];
239 if compact == 0 {
240 num_components += 1;
241 compact = num_components;
242 remap[root as usize] = compact;
243 sizes.push(0);
244 }
245 labels[idx] = compact;
246 sizes[compact as usize - 1] += 1;
247 }
248
249 Ok(ComponentLabels {
250 labels,
251 num_components: num_components as usize,
252 height: h,
253 width: w,
254 sizes,
255 })
256}
257
258#[cfg(test)]
261mod tests {
262 use super::*;
263
264 fn set_block(img: &mut [f32], w: usize, r0: usize, r1: usize, c0: usize, c1: usize) {
265 for y in r0..r1 {
266 for x in c0..c1 {
267 img[y * w + x] = 1.0;
268 }
269 }
270 }
271
272 #[test]
273 fn single_blob_one_component() {
274 let mut img = vec![0.0_f32; 8 * 8];
275 set_block(&mut img, 8, 2, 5, 2, 5); let cc = connected_components(&img, 8, 8, Connectivity::Eight).expect("cc");
277 assert_eq!(cc.num_components, 1);
278 assert_eq!(cc.size_of(1), 9);
279 }
280
281 #[test]
282 fn two_disjoint_blobs_two_components() {
283 let mut img = vec![0.0_f32; 8 * 8];
284 set_block(&mut img, 8, 0, 2, 0, 2);
285 set_block(&mut img, 8, 5, 7, 5, 7);
286 let cc = connected_components(&img, 8, 8, Connectivity::Eight).expect("cc");
287 assert_eq!(cc.num_components, 2);
288 assert_eq!(cc.size_of(1), 4);
289 assert_eq!(cc.size_of(2), 4);
290 }
291
292 #[test]
293 fn diagonal_pair_four_vs_eight_connectivity() {
294 let mut img = vec![0.0_f32; 4 * 4];
296 let at = |y: usize, x: usize| y * 4 + x;
297 img[at(1, 1)] = 1.0;
298 img[at(2, 2)] = 1.0;
299 let cc4 = connected_components(&img, 4, 4, Connectivity::Four).expect("cc4");
300 assert_eq!(cc4.num_components, 2, "4-connectivity splits the diagonal");
301 let cc8 = connected_components(&img, 4, 4, Connectivity::Eight).expect("cc8");
302 assert_eq!(cc8.num_components, 1, "8-connectivity joins the diagonal");
303 }
304
305 #[test]
306 fn all_background_zero_components() {
307 let img = vec![0.0_f32; 5 * 5];
308 let cc = connected_components(&img, 5, 5, Connectivity::Eight).expect("cc");
309 assert_eq!(cc.num_components, 0);
310 assert!(cc.labels.iter().all(|&l| l == 0));
311 assert!(cc.sizes().is_empty());
312 }
313
314 #[test]
315 fn all_foreground_single_component() {
316 let img = vec![1.0_f32; 5 * 5];
317 let cc = connected_components(&img, 5, 5, Connectivity::Four).expect("cc");
318 assert_eq!(cc.num_components, 1);
319 assert_eq!(cc.size_of(1), 25);
320 }
321
322 #[test]
323 fn u_shape_is_single_component() {
324 let mut img = vec![0.0_f32; 5 * 5];
326 for y in 0..5 {
328 img[y * 5] = 1.0;
329 img[y * 5 + 4] = 1.0;
330 }
331 for x in 0..5 {
332 img[4 * 5 + x] = 1.0;
333 }
334 let cc = connected_components(&img, 5, 5, Connectivity::Four).expect("cc");
335 assert_eq!(cc.num_components, 1, "the U is one connected region");
336 assert_eq!(cc.size_of(1), 5 + 5 + 3); }
338
339 #[test]
340 fn sizes_sum_equals_foreground_count() {
341 let mut img = vec![0.0_f32; 6 * 6];
342 set_block(&mut img, 6, 0, 2, 0, 2);
343 set_block(&mut img, 6, 3, 5, 3, 6);
344 let foreground: usize = img.iter().filter(|&&v| v > 0.0).count();
345 let cc = connected_components(&img, 6, 6, Connectivity::Eight).expect("cc");
346 let total: usize = cc.sizes().iter().sum();
347 assert_eq!(total, foreground);
348 }
349
350 #[test]
351 fn bounding_boxes_match_blob_extent() {
352 let mut img = vec![0.0_f32; 8 * 8];
353 set_block(&mut img, 8, 2, 5, 3, 7); let cc = connected_components(&img, 8, 8, Connectivity::Eight).expect("cc");
355 let boxes = cc.bounding_boxes();
356 assert_eq!(boxes.len(), 1);
357 assert_eq!(boxes[0], [3, 2, 6, 4]);
359 }
360
361 #[test]
362 fn size_of_out_of_range_is_zero() {
363 let img = vec![1.0_f32; 4 * 4];
364 let cc = connected_components(&img, 4, 4, Connectivity::Eight).expect("cc");
365 assert_eq!(cc.size_of(0), 0);
366 assert_eq!(cc.size_of(99), 0);
367 }
368
369 #[test]
370 fn wrong_size_errors() {
371 let img = vec![0.0_f32; 10];
372 assert!(matches!(
373 connected_components(&img, 8, 8, Connectivity::Four),
374 Err(VisionError::DimensionMismatch { .. })
375 ));
376 }
377
378 #[test]
379 fn zero_dim_errors() {
380 let img: Vec<f32> = vec![];
381 assert!(matches!(
382 connected_components(&img, 0, 4, Connectivity::Four),
383 Err(VisionError::InvalidImageSize { .. })
384 ));
385 }
386
387 #[test]
388 fn checkerboard_four_connectivity_many_components() {
389 let mut img = vec![0.0_f32; 4 * 4];
391 let mut expected = 0;
392 for y in 0..4 {
393 for x in 0..4 {
394 if (y + x) % 2 == 0 {
395 img[y * 4 + x] = 1.0;
396 expected += 1;
397 }
398 }
399 }
400 let cc = connected_components(&img, 4, 4, Connectivity::Four).expect("cc");
401 assert_eq!(cc.num_components, expected);
402 let cc8 = connected_components(&img, 4, 4, Connectivity::Eight).expect("cc8");
404 assert_eq!(cc8.num_components, 1);
405 }
406}