1use std::{error::Error, fmt};
7
8use crate::representation::{EventFrame, EventFrameData, RepresentationKind};
9
10impl EventFrame {
11 pub fn connected_components(&self, connectivity: u8) -> Result<EventFrame, ClusterError> {
15 if connectivity != 4 && connectivity != 8 {
16 return Err(ClusterError::InvalidConnectivity(connectivity));
17 }
18 let (channels, height, width) = self.shape();
19 let plane = width
20 .checked_mul(height)
21 .ok_or(ClusterError::SizeOverflow)?;
22
23 let foreground = foreground_mask(self.data(), plane, channels);
24 let mut labels = vec![0_u64; plane];
25 let mut next = 0_u64;
26 let mut stack = Vec::new();
27 let neighbours = neighbour_offsets(connectivity);
28
29 for start in 0..plane {
30 if !foreground[start] || labels[start] != 0 {
31 continue;
32 }
33 next += 1;
34 labels[start] = next;
35 stack.push(start);
36 while let Some(index) = stack.pop() {
37 let (x, y) = (index % width, index / width);
38 for &(dx, dy) in neighbours {
39 let (nx, ny) = (x as isize + dx, y as isize + dy);
40 if nx < 0 || ny < 0 || nx >= width as isize || ny >= height as isize {
41 continue;
42 }
43 let neighbour = ny as usize * width + nx as usize;
44 if foreground[neighbour] && labels[neighbour] == 0 {
45 labels[neighbour] = next;
46 stack.push(neighbour);
47 }
48 }
49 }
50 }
51
52 Ok(EventFrame::from_parts(
53 EventFrameData::U64(labels),
54 width,
55 height,
56 RepresentationKind::Labels,
57 vec!["labels".to_owned()],
58 ))
59 }
60}
61
62fn foreground_mask(data: &EventFrameData, plane: usize, channels: usize) -> Vec<bool> {
64 let mut mask = vec![false; plane];
65 let mut mark = |nonzero: &dyn Fn(usize) -> bool| {
66 for c in 0..channels {
67 let base = c * plane;
68 for (i, slot) in mask.iter_mut().enumerate() {
69 if nonzero(base + i) {
70 *slot = true;
71 }
72 }
73 }
74 };
75 match data {
76 EventFrameData::U8(v) => mark(&|k| v[k] != 0),
77 EventFrameData::U16(v) => mark(&|k| v[k] != 0),
78 EventFrameData::U64(v) => mark(&|k| v[k] != 0),
79 EventFrameData::F32(v) => mark(&|k| v[k] != 0.0),
80 }
81 mask
82}
83
84fn neighbour_offsets(connectivity: u8) -> &'static [(isize, isize)] {
85 const FOUR: [(isize, isize); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
86 const EIGHT: [(isize, isize); 8] = [
87 (-1, -1),
88 (0, -1),
89 (1, -1),
90 (-1, 0),
91 (1, 0),
92 (-1, 1),
93 (0, 1),
94 (1, 1),
95 ];
96 if connectivity == 4 {
97 &FOUR
98 } else {
99 &EIGHT
100 }
101}
102
103#[derive(Debug, PartialEq, Eq)]
104pub enum ClusterError {
105 SizeOverflow,
106 InvalidConnectivity(u8),
107}
108
109impl fmt::Display for ClusterError {
110 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 Self::SizeOverflow => formatter.write_str("label field dimensions are too large"),
113 Self::InvalidConnectivity(value) => {
114 write!(formatter, "connectivity must be 4 or 8, got {value}")
115 }
116 }
117 }
118}
119
120impl Error for ClusterError {}
121
122#[cfg(test)]
123mod tests {
124 use super::ClusterError;
125 use crate::representation::{EventFrame, EventFrameData, RepresentationKind};
126
127 fn frame(width: usize, height: usize, pixels: &[u8]) -> EventFrame {
128 EventFrame::from_parts(
129 EventFrameData::U8(pixels.to_vec()),
130 width,
131 height,
132 RepresentationKind::Count,
133 vec!["count".to_owned()],
134 )
135 }
136
137 fn labels(frame: &EventFrame) -> Vec<u64> {
138 let EventFrameData::U64(values) = frame.data() else {
139 panic!("labels are u64");
140 };
141 values.clone()
142 }
143
144 #[test]
145 fn rejects_bad_connectivity() {
146 let f = frame(2, 2, &[0, 0, 0, 0]);
147 assert_eq!(
148 f.connected_components(6).unwrap_err(),
149 ClusterError::InvalidConnectivity(6)
150 );
151 }
152
153 #[test]
154 fn empty_frame_is_all_background() {
155 let out = frame(3, 3, &[0; 9]).connected_components(8).unwrap();
156 assert_eq!(out.shape(), (1, 3, 3));
157 assert!(labels(&out).iter().all(|&l| l == 0));
158 }
159
160 #[test]
161 fn two_diagonal_blobs_split_under_4_but_merge_under_8() {
162 let pixels = [1, 0, 0, 1];
164 let four = frame(2, 2, &pixels).connected_components(4).unwrap();
165 assert_eq!(labels(&four), vec![1, 0, 0, 2]);
166 let eight = frame(2, 2, &pixels).connected_components(8).unwrap();
167 assert_eq!(labels(&eight), vec![1, 0, 0, 1]);
168 }
169
170 #[test]
171 fn foreground_is_any_nonzero_channel() {
172 let data = EventFrameData::U8(vec![0, 0, 0, 1]);
174 let f = EventFrame::from_parts(
175 data,
176 2,
177 1,
178 RepresentationKind::Polarity,
179 vec!["a".to_owned(), "b".to_owned()],
180 );
181 assert_eq!(labels(&f.connected_components(4).unwrap()), vec![0, 1]);
182 }
183}