use crate::error::{IrisError, Result};
use crate::image::Image;
use burn::tensor::{Int, Tensor, TensorData, backend::Backend};
pub fn watershed<B: Backend>(
image: &Image<B>,
markers: &Tensor<B, 2, Int>,
) -> Result<Tensor<B, 2, Int>> {
let gray = image.grayscale()?;
let img_dims = gray.tensor.dims();
let h = img_dims[1];
let w = img_dims[2];
let mk_dims = markers.dims();
if mk_dims[0] != h || mk_dims[1] != w {
return Err(IrisError::DimensionMismatch {
expected: vec![h, w],
actual: mk_dims.to_vec(),
});
}
let device = gray.tensor.device();
let img_data = gray.tensor.clone().into_data();
let img_vals: Vec<f32> = img_data.iter::<f32>().collect();
let mk_data = markers.clone().into_data();
let mk_vals: Vec<i32> = mk_data.iter::<i32>().collect();
if mk_vals.iter().all(|&v| v == 0) {
return Err(IrisError::InvalidParameter(
"Markers must contain at least one positive seed".into(),
));
}
let mut labels: Vec<i32> = mk_vals.clone();
let mut queue: Vec<(f32, usize, usize)> = Vec::new();
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
if labels[idx] > 0 {
queue.push((img_vals[idx], y, x));
}
}
}
queue.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
let neighbors = [(1_isize, 0_isize), (-1, 0), (0, 1), (0, -1)];
while let Some((_intensity, cy, cx)) = queue.pop() {
let cidx = cy * w + cx;
let c_label = labels[cidx];
if c_label == 0 {
continue;
}
for &(dx, dy) in &neighbors {
let nx = cx as isize + dx;
let ny = cy as isize + dy;
if nx < 0 || nx >= w as isize || ny < 0 || ny >= h as isize {
continue;
}
let ux = nx as usize;
let uy = ny as usize;
let nidx = uy * w + ux;
if labels[nidx] == 0 {
labels[nidx] = c_label;
let new_entry = (img_vals[nidx], uy, ux);
match queue.binary_search_by(|a| {
a.0.partial_cmp(&new_entry.0)
.unwrap_or(std::cmp::Ordering::Equal)
}) {
Ok(pos) | Err(pos) => queue.insert(pos, new_entry),
}
} else if labels[nidx] != c_label {
}
}
}
let out_data = TensorData::new(labels, [h, w]);
let out_tensor = Tensor::<B, 2, Int>::from_data(out_data, &device);
Ok(out_tensor)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::{TestBackend, test_device};
#[test]
fn test_watershed_basic() {
let device = test_device();
let mut img_vals = vec![0.3f32; 8 * 8];
for y in 0..8 {
for x in 0..4 {
img_vals[y * 8 + x] = 0.2;
}
}
for y in 0..8 {
for x in 4..8 {
img_vals[y * 8 + x] = 0.8;
}
}
let img_tensor =
Tensor::<TestBackend, 3>::from_data(TensorData::new(img_vals, [1, 8, 8]), &device);
let img = Image::new(img_tensor);
let mut mk_vals = vec![0i32; 8 * 8];
mk_vals[0] = 1;
mk_vals[7 * 8 + 7] = 2;
let mk_tensor =
Tensor::<TestBackend, 2, Int>::from_data(TensorData::new(mk_vals, [8, 8]), &device);
let result = watershed(&img, &mk_tensor).unwrap();
assert_eq!(result.dims(), [8, 8]);
let out_data = result.clone().into_data();
let out_vals: Vec<i32> = out_data.iter::<i32>().collect();
assert_eq!(out_vals[0], 1); assert_eq!(out_vals[63], 2);
assert!(out_vals.iter().all(|&v| v > 0));
}
#[test]
fn test_watershed_no_markers_error() {
let device = test_device();
let img_tensor = Tensor::<TestBackend, 3>::from_data(
TensorData::new(vec![0.5f32; 16], [1, 4, 4]),
&device,
);
let img = Image::new(img_tensor);
let mk_tensor = Tensor::<TestBackend, 2, Int>::from_data(
TensorData::new(vec![0i32; 16], [4, 4]),
&device,
);
assert!(watershed(&img, &mk_tensor).is_err());
}
}