iris/segmentation/
watershed.rs1use crate::error::{IrisError, Result};
2use crate::image::Image;
3use burn::tensor::{Int, Tensor, TensorData, backend::Backend};
4
5pub fn watershed<B: Backend>(
18 image: &Image<B>,
19 markers: &Tensor<B, 2, Int>,
20) -> Result<Tensor<B, 2, Int>> {
21 let gray = image.grayscale()?;
22 let img_dims = gray.tensor.dims();
23 let h = img_dims[1];
24 let w = img_dims[2];
25 let mk_dims = markers.dims();
26
27 if mk_dims[0] != h || mk_dims[1] != w {
28 return Err(IrisError::DimensionMismatch {
29 expected: vec![h, w],
30 actual: mk_dims.to_vec(),
31 });
32 }
33
34 let device = gray.tensor.device();
35 let img_data = gray.tensor.clone().into_data();
36 let img_vals: Vec<f32> = img_data.iter::<f32>().collect();
37
38 let mk_data = markers.clone().into_data();
39 let mk_vals: Vec<i32> = mk_data.iter::<i32>().collect();
40
41 if mk_vals.iter().all(|&v| v == 0) {
43 return Err(IrisError::InvalidParameter(
44 "Markers must contain at least one positive seed".into(),
45 ));
46 }
47
48 let mut labels: Vec<i32> = mk_vals.clone();
50
51 let mut queue: Vec<(f32, usize, usize)> = Vec::new();
53
54 for y in 0..h {
56 for x in 0..w {
57 let idx = y * w + x;
58 if labels[idx] > 0 {
59 queue.push((img_vals[idx], y, x));
60 }
61 }
62 }
63 queue.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
65 let neighbors = [(1_isize, 0_isize), (-1, 0), (0, 1), (0, -1)];
69
70 while let Some((_intensity, cy, cx)) = queue.pop() {
71 let cidx = cy * w + cx;
72 let c_label = labels[cidx];
73 if c_label == 0 {
74 continue;
75 }
76
77 for &(dx, dy) in &neighbors {
78 let nx = cx as isize + dx;
79 let ny = cy as isize + dy;
80 if nx < 0 || nx >= w as isize || ny < 0 || ny >= h as isize {
81 continue;
82 }
83 let ux = nx as usize;
84 let uy = ny as usize;
85 let nidx = uy * w + ux;
86
87 if labels[nidx] == 0 {
88 labels[nidx] = c_label;
90 let new_entry = (img_vals[nidx], uy, ux);
92 match queue.binary_search_by(|a| {
93 a.0.partial_cmp(&new_entry.0)
94 .unwrap_or(std::cmp::Ordering::Equal)
95 }) {
96 Ok(pos) | Err(pos) => queue.insert(pos, new_entry),
97 }
98 } else if labels[nidx] != c_label {
99 }
104 }
105 }
106
107 let out_data = TensorData::new(labels, [h, w]);
108 let out_tensor = Tensor::<B, 2, Int>::from_data(out_data, &device);
109 Ok(out_tensor)
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::test_helpers::{TestBackend, test_device};
116
117 #[test]
118 fn test_watershed_basic() {
119 let device = test_device();
120
121 let mut img_vals = vec![0.3f32; 8 * 8];
123 for y in 0..8 {
125 for x in 0..4 {
126 img_vals[y * 8 + x] = 0.2;
127 }
128 }
129 for y in 0..8 {
131 for x in 4..8 {
132 img_vals[y * 8 + x] = 0.8;
133 }
134 }
135 let img_tensor =
136 Tensor::<TestBackend, 3>::from_data(TensorData::new(img_vals, [1, 8, 8]), &device);
137 let img = Image::new(img_tensor);
138
139 let mut mk_vals = vec![0i32; 8 * 8];
141 mk_vals[0] = 1;
142 mk_vals[7 * 8 + 7] = 2;
143 let mk_tensor =
144 Tensor::<TestBackend, 2, Int>::from_data(TensorData::new(mk_vals, [8, 8]), &device);
145
146 let result = watershed(&img, &mk_tensor).unwrap();
147 assert_eq!(result.dims(), [8, 8]);
148
149 let out_data = result.clone().into_data();
151 let out_vals: Vec<i32> = out_data.iter::<i32>().collect();
152 assert_eq!(out_vals[0], 1); assert_eq!(out_vals[63], 2); assert!(out_vals.iter().all(|&v| v > 0));
157 }
158
159 #[test]
160 fn test_watershed_no_markers_error() {
161 let device = test_device();
162 let img_tensor = Tensor::<TestBackend, 3>::from_data(
163 TensorData::new(vec![0.5f32; 16], [1, 4, 4]),
164 &device,
165 );
166 let img = Image::new(img_tensor);
167
168 let mk_tensor = Tensor::<TestBackend, 2, Int>::from_data(
169 TensorData::new(vec![0i32; 16], [4, 4]),
170 &device,
171 );
172
173 assert!(watershed(&img, &mk_tensor).is_err());
174 }
175}