use crate::error::{NdimageError, NdimageResult};
use scirs2_core::ndarray::Array2;
use scirs2_core::numeric::{Float, NumAssign};
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WatershedNeighborhood {
Conn4,
Conn8,
}
impl Default for WatershedNeighborhood {
fn default() -> Self {
WatershedNeighborhood::Conn8
}
}
#[derive(Debug, Clone)]
pub struct CompactWatershedConfig {
pub connectivity: WatershedNeighborhood,
pub compactness: f64,
pub watershed_line: bool,
}
impl Default for CompactWatershedConfig {
fn default() -> Self {
CompactWatershedConfig {
connectivity: WatershedNeighborhood::Conn8,
compactness: 0.0,
watershed_line: false,
}
}
}
#[derive(Debug, Clone)]
pub struct OversegmentationConfig {
pub min_region_area: usize,
pub h_minima: f64,
pub max_regions: usize,
pub connectivity: WatershedNeighborhood,
}
impl Default for OversegmentationConfig {
fn default() -> Self {
OversegmentationConfig {
min_region_area: 1,
h_minima: 0.0,
max_regions: 0,
connectivity: WatershedNeighborhood::Conn8,
}
}
}
#[derive(Debug, Clone)]
pub struct DamResult {
pub labels: Array2<i32>,
pub dam_mask: Array2<bool>,
pub num_regions: usize,
}
const WSHED: i32 = -1;
const IN_QUEUE: i32 = -2;
fn neighbor_offsets(conn: WatershedNeighborhood) -> &'static [(isize, isize)] {
match conn {
WatershedNeighborhood::Conn4 => &[(-1, 0), (0, -1), (0, 1), (1, 0)],
WatershedNeighborhood::Conn8 => &[
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
],
}
}
#[inline]
fn in_bounds(r: isize, c: isize, rows: usize, cols: usize) -> bool {
r >= 0 && r < rows as isize && c >= 0 && c < cols as isize
}
#[derive(Clone, Debug)]
struct CompactEntry {
row: usize,
col: usize,
intensity: f64,
distance: f64,
priority: f64,
order: u64,
}
impl PartialEq for CompactEntry {
fn eq(&self, other: &Self) -> bool {
self.row == other.row && self.col == other.col
}
}
impl Eq for CompactEntry {}
impl PartialOrd for CompactEntry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CompactEntry {
fn cmp(&self, other: &Self) -> Ordering {
match other
.priority
.partial_cmp(&self.priority)
.unwrap_or(Ordering::Equal)
{
Ordering::Equal => other.order.cmp(&self.order),
ord => ord,
}
}
}
pub fn compact_watershed<T>(
image: &Array2<T>,
markers: &Array2<i32>,
config: &CompactWatershedConfig,
) -> NdimageResult<Array2<i32>>
where
T: Float + NumAssign + std::fmt::Debug + 'static,
{
if image.shape() != markers.shape() {
return Err(NdimageError::DimensionError(
"Image and markers must have the same shape".to_string(),
));
}
let rows = image.nrows();
let cols = image.ncols();
if rows == 0 || cols == 0 {
return Ok(markers.clone());
}
let offsets = neighbor_offsets(config.connectivity);
let compact = config.compactness;
let mut output = markers.clone();
let mut dist = Array2::<f64>::from_elem((rows, cols), f64::INFINITY);
let mut seed_centers: HashMap<i32, (f64, f64)> = HashMap::new();
let mut seed_counts: HashMap<i32, usize> = HashMap::new();
for r in 0..rows {
for c in 0..cols {
let lbl = markers[[r, c]];
if lbl > 0 {
let entry = seed_centers.entry(lbl).or_insert((0.0, 0.0));
entry.0 += r as f64;
entry.1 += c as f64;
*seed_counts.entry(lbl).or_insert(0) += 1;
}
}
}
for (lbl, center) in seed_centers.iter_mut() {
let cnt = *seed_counts.get(lbl).unwrap_or(&1) as f64;
center.0 /= cnt;
center.1 /= cnt;
}
let mut queue = BinaryHeap::new();
let mut insertion_order: u64 = 0;
for r in 0..rows {
for c in 0..cols {
let lbl = markers[[r, c]];
if lbl > 0 {
dist[[r, c]] = 0.0;
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if in_bounds(nr, nc, rows, cols) {
let nr = nr as usize;
let nc = nc as usize;
if output[[nr, nc]] == 0 {
output[[nr, nc]] = IN_QUEUE;
let intensity = image[[nr, nc]].to_f64().unwrap_or(f64::INFINITY);
let spatial_dist = if let Some(¢er) = seed_centers.get(&lbl) {
((nr as f64 - center.0).powi(2) + (nc as f64 - center.1).powi(2))
.sqrt()
} else {
0.0
};
let priority = intensity + compact * spatial_dist;
queue.push(CompactEntry {
row: nr,
col: nc,
intensity,
distance: spatial_dist,
priority,
order: insertion_order,
});
insertion_order += 1;
}
}
}
}
}
}
while let Some(entry) = queue.pop() {
let r = entry.row;
let c = entry.col;
let mut neighbor_labels: HashMap<i32, (usize, f64)> = HashMap::new(); let mut _has_wshed_neighbor = false;
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if in_bounds(nr, nc, rows, cols) {
let nr = nr as usize;
let nc = nc as usize;
let nlbl = output[[nr, nc]];
if nlbl > 0 {
let nd = dist[[nr, nc]];
let e = neighbor_labels.entry(nlbl).or_insert((0, f64::INFINITY));
e.0 += 1;
if nd < e.1 {
e.1 = nd;
}
} else if nlbl == WSHED && config.watershed_line {
_has_wshed_neighbor = true;
}
}
}
if neighbor_labels.is_empty() {
output[[r, c]] = 0;
continue;
}
let distinct: Vec<i32> = neighbor_labels.keys().copied().collect();
let assigned_label;
if config.watershed_line && distinct.len() > 1 {
output[[r, c]] = WSHED;
assigned_label = WSHED;
} else {
let best_label = if compact > 0.0 {
let mut best = distinct[0];
let mut best_cost = f64::INFINITY;
for &lbl in &distinct {
let spatial = if let Some(¢er) = seed_centers.get(&lbl) {
((r as f64 - center.0).powi(2) + (c as f64 - center.1).powi(2)).sqrt()
} else {
0.0
};
let cost = entry.intensity + compact * spatial;
if cost < best_cost {
best_cost = cost;
best = lbl;
}
}
best
} else {
neighbor_labels
.iter()
.max_by_key(|&(_, (count, _))| count)
.map(|(&lbl, _)| lbl)
.unwrap_or(0)
};
if best_label > 0 {
output[[r, c]] = best_label;
assigned_label = best_label;
let spatial = if let Some(¢er) = seed_centers.get(&best_label) {
((r as f64 - center.0).powi(2) + (c as f64 - center.1).powi(2)).sqrt()
} else {
0.0
};
dist[[r, c]] = spatial;
} else {
output[[r, c]] = 0;
continue;
}
}
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if in_bounds(nr, nc, rows, cols) {
let nr = nr as usize;
let nc = nc as usize;
if output[[nr, nc]] == 0 {
output[[nr, nc]] = IN_QUEUE;
let intensity = image[[nr, nc]].to_f64().unwrap_or(f64::INFINITY);
let spatial_dist = if assigned_label > 0 {
if let Some(¢er) = seed_centers.get(&assigned_label) {
((nr as f64 - center.0).powi(2) + (nc as f64 - center.1).powi(2)).sqrt()
} else {
0.0
}
} else {
0.0
};
let priority = intensity + compact * spatial_dist;
queue.push(CompactEntry {
row: nr,
col: nc,
intensity,
distance: spatial_dist,
priority,
order: insertion_order,
});
insertion_order += 1;
}
}
}
}
for val in output.iter_mut() {
if *val == IN_QUEUE {
*val = 0;
}
}
Ok(output)
}
pub fn extract_dams(
labels: &Array2<i32>,
connectivity: WatershedNeighborhood,
) -> NdimageResult<DamResult> {
let rows = labels.nrows();
let cols = labels.ncols();
if rows == 0 || cols == 0 {
return Ok(DamResult {
labels: labels.clone(),
dam_mask: Array2::from_elem((rows, cols), false),
num_regions: 0,
});
}
let offsets = neighbor_offsets(connectivity);
let mut dam_mask = Array2::from_elem((rows, cols), false);
let mut out = labels.clone();
let mut unique_labels = std::collections::HashSet::new();
for r in 0..rows {
for c in 0..cols {
let lbl = labels[[r, c]];
if lbl <= 0 {
continue;
}
unique_labels.insert(lbl);
let mut is_dam = false;
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if in_bounds(nr, nc, rows, cols) {
let nlbl = labels[[nr as usize, nc as usize]];
if nlbl > 0 && nlbl != lbl {
is_dam = true;
break;
}
}
}
if is_dam {
dam_mask[[r, c]] = true;
out[[r, c]] = WSHED;
}
}
}
Ok(DamResult {
labels: out,
dam_mask,
num_regions: unique_labels.len(),
})
}
pub fn merge_small_regions<T>(
image: &Array2<T>,
labels: &Array2<i32>,
config: &OversegmentationConfig,
) -> NdimageResult<Array2<i32>>
where
T: Float + NumAssign + std::fmt::Debug + 'static,
{
if image.shape() != labels.shape() {
return Err(NdimageError::DimensionError(
"Image and labels must have the same shape".to_string(),
));
}
let rows = image.nrows();
let cols = image.ncols();
if rows == 0 || cols == 0 {
return Ok(labels.clone());
}
let mut region_area: HashMap<i32, usize> = HashMap::new();
let mut region_sum: HashMap<i32, f64> = HashMap::new();
for r in 0..rows {
for c in 0..cols {
let lbl = labels[[r, c]];
if lbl <= 0 {
continue;
}
*region_area.entry(lbl).or_insert(0) += 1;
let val = image[[r, c]].to_f64().unwrap_or(0.0);
*region_sum.entry(lbl).or_insert(0.0) += val;
}
}
let offsets = neighbor_offsets(config.connectivity);
let mut adjacency: HashMap<i32, HashMap<i32, usize>> = HashMap::new();
for r in 0..rows {
for c in 0..cols {
let lbl = labels[[r, c]];
if lbl <= 0 {
continue;
}
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if in_bounds(nr, nc, rows, cols) {
let nlbl = labels[[nr as usize, nc as usize]];
if nlbl > 0 && nlbl != lbl {
*adjacency.entry(lbl).or_default().entry(nlbl).or_insert(0) += 1;
}
}
}
}
}
let mut merge_map: HashMap<i32, i32> = HashMap::new();
let mut regions_by_area: Vec<(i32, usize)> =
region_area.iter().map(|(&k, &v)| (k, v)).collect();
regions_by_area.sort_by_key(|&(_, area)| area);
for &(lbl, area) in ®ions_by_area {
if area >= config.min_region_area {
continue; }
let my_mean = region_sum.get(&lbl).copied().unwrap_or(0.0) / (area.max(1) as f64);
let best_neighbor = if let Some(neighbors) = adjacency.get(&lbl) {
let mut best: Option<(i32, f64)> = None;
for (&nlbl, _) in neighbors {
let mut final_lbl = nlbl;
while let Some(&merged_to) = merge_map.get(&final_lbl) {
if merged_to == final_lbl {
break;
}
final_lbl = merged_to;
}
if final_lbl == lbl {
continue; }
let n_area = region_area.get(&final_lbl).copied().unwrap_or(1);
let n_mean =
region_sum.get(&final_lbl).copied().unwrap_or(0.0) / (n_area.max(1) as f64);
let diff = (my_mean - n_mean).abs();
match best {
None => best = Some((final_lbl, diff)),
Some((_, best_diff)) if diff < best_diff => {
best = Some((final_lbl, diff));
}
_ => {}
}
}
best.map(|(lbl, _)| lbl)
} else {
None
};
if let Some(target) = best_neighbor {
merge_map.insert(lbl, target);
let my_sum = region_sum.get(&lbl).copied().unwrap_or(0.0);
*region_sum.entry(target).or_insert(0.0) += my_sum;
*region_area.entry(target).or_insert(0) += area;
}
}
let all_labels: Vec<i32> = merge_map.keys().copied().collect();
for lbl in all_labels {
let mut target = lbl;
let mut visited = std::collections::HashSet::new();
while let Some(&next) = merge_map.get(&target) {
if next == target || visited.contains(&next) {
break;
}
visited.insert(target);
target = next;
}
merge_map.insert(lbl, target);
}
let mut output = labels.clone();
for r in 0..rows {
for c in 0..cols {
let lbl = output[[r, c]];
if lbl > 0 {
if let Some(&target) = merge_map.get(&lbl) {
output[[r, c]] = target;
}
}
}
}
if config.max_regions > 0 {
let mut unique: std::collections::HashSet<i32> = std::collections::HashSet::new();
for &v in output.iter() {
if v > 0 {
unique.insert(v);
}
}
if unique.len() > config.max_regions {
let mut remaining = unique.len();
while remaining > config.max_regions {
let mut areas: HashMap<i32, usize> = HashMap::new();
for &v in output.iter() {
if v > 0 {
*areas.entry(v).or_insert(0) += 1;
}
}
let smallest = areas
.iter()
.min_by_key(|&(_, &area)| area)
.map(|(&lbl, _)| lbl);
if let Some(small_lbl) = smallest {
let mut neighbor_boundary: HashMap<i32, usize> = HashMap::new();
for r in 0..rows {
for c in 0..cols {
if output[[r, c]] != small_lbl {
continue;
}
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if in_bounds(nr, nc, rows, cols) {
let nlbl = output[[nr as usize, nc as usize]];
if nlbl > 0 && nlbl != small_lbl {
*neighbor_boundary.entry(nlbl).or_insert(0) += 1;
}
}
}
}
}
let best = neighbor_boundary
.iter()
.max_by_key(|&(_, &count)| count)
.map(|(&lbl, _)| lbl);
if let Some(target) = best {
for val in output.iter_mut() {
if *val == small_lbl {
*val = target;
}
}
remaining -= 1;
} else {
break; }
} else {
break;
}
}
}
}
Ok(output)
}
pub fn h_minima_transform<T>(image: &Array2<T>, h: f64) -> NdimageResult<Array2<f64>>
where
T: Float + NumAssign + std::fmt::Debug + 'static,
{
if h < 0.0 {
return Err(NdimageError::InvalidInput(
"h must be non-negative".to_string(),
));
}
let rows = image.nrows();
let cols = image.ncols();
if rows == 0 || cols == 0 {
return Ok(Array2::zeros((rows, cols)));
}
let img_f64: Array2<f64> = image.mapv(|x| x.to_f64().unwrap_or(0.0));
let marker: Array2<f64> = img_f64.mapv(|x| x - h);
let result = morphological_reconstruction_by_dilation_2d(&marker, &img_f64, 200)?;
Ok(result)
}
#[allow(dead_code)]
fn morphological_reconstruction_by_erosion_2d(
marker: &Array2<f64>,
mask: &Array2<f64>,
max_iterations: usize,
) -> NdimageResult<Array2<f64>> {
let rows = marker.nrows();
let cols = marker.ncols();
let mut result = marker.clone();
for _iter in 0..max_iterations {
let mut changed = false;
for r in 0..rows {
for c in 0..cols {
let mut val = result[[r, c]];
if r > 0 && result[[r - 1, c]] < val {
val = result[[r - 1, c]];
}
if c > 0 && result[[r, c - 1]] < val {
val = result[[r, c - 1]];
}
let mask_val = mask[[r, c]];
if val < mask_val {
val = mask_val;
}
if (val - result[[r, c]]).abs() > 1e-15 {
result[[r, c]] = val;
changed = true;
}
}
}
for r in (0..rows).rev() {
for c in (0..cols).rev() {
let mut val = result[[r, c]];
if r + 1 < rows && result[[r + 1, c]] < val {
val = result[[r + 1, c]];
}
if c + 1 < cols && result[[r, c + 1]] < val {
val = result[[r, c + 1]];
}
let mask_val = mask[[r, c]];
if val < mask_val {
val = mask_val;
}
if (val - result[[r, c]]).abs() > 1e-15 {
result[[r, c]] = val;
changed = true;
}
}
}
if !changed {
break;
}
}
Ok(result)
}
fn morphological_reconstruction_by_dilation_2d(
marker: &Array2<f64>,
mask: &Array2<f64>,
max_iterations: usize,
) -> NdimageResult<Array2<f64>> {
let rows = marker.nrows();
let cols = marker.ncols();
let mut result = Array2::zeros((rows, cols));
for r in 0..rows {
for c in 0..cols {
result[[r, c]] = marker[[r, c]].min(mask[[r, c]]);
}
}
for _iter in 0..max_iterations {
let mut changed = false;
for r in 0..rows {
for c in 0..cols {
let mut val = result[[r, c]];
if r > 0 && result[[r - 1, c]] > val {
val = result[[r - 1, c]];
}
if c > 0 && result[[r, c - 1]] > val {
val = result[[r, c - 1]];
}
if r > 0 && c > 0 && result[[r - 1, c - 1]] > val {
val = result[[r - 1, c - 1]];
}
if r > 0 && c + 1 < cols && result[[r - 1, c + 1]] > val {
val = result[[r - 1, c + 1]];
}
let mask_val = mask[[r, c]];
if val > mask_val {
val = mask_val;
}
if (val - result[[r, c]]).abs() > 1e-15 {
result[[r, c]] = val;
changed = true;
}
}
}
for r in (0..rows).rev() {
for c in (0..cols).rev() {
let mut val = result[[r, c]];
if r + 1 < rows && result[[r + 1, c]] > val {
val = result[[r + 1, c]];
}
if c + 1 < cols && result[[r, c + 1]] > val {
val = result[[r, c + 1]];
}
if r + 1 < rows && c + 1 < cols && result[[r + 1, c + 1]] > val {
val = result[[r + 1, c + 1]];
}
if r + 1 < rows && c > 0 && result[[r + 1, c - 1]] > val {
val = result[[r + 1, c - 1]];
}
let mask_val = mask[[r, c]];
if val > mask_val {
val = mask_val;
}
if (val - result[[r, c]]).abs() > 1e-15 {
result[[r, c]] = val;
changed = true;
}
}
}
if !changed {
break;
}
}
Ok(result)
}
pub fn auto_markers<T>(
image: &Array2<T>,
connectivity: WatershedNeighborhood,
threshold: Option<f64>,
) -> NdimageResult<Array2<i32>>
where
T: Float + NumAssign + std::fmt::Debug + 'static,
{
let rows = image.nrows();
let cols = image.ncols();
if rows == 0 || cols == 0 {
return Ok(Array2::zeros((rows, cols)));
}
let offsets = neighbor_offsets(connectivity);
let thresh = threshold.unwrap_or(0.0);
let mut markers = Array2::<i32>::zeros((rows, cols));
let mut next_label = 1i32;
for r in 0..rows {
for c in 0..cols {
let val = image[[r, c]].to_f64().unwrap_or(f64::INFINITY);
let mut is_minimum = true;
let mut min_neighbor = f64::INFINITY;
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if in_bounds(nr, nc, rows, cols) {
let nval = image[[nr as usize, nc as usize]]
.to_f64()
.unwrap_or(f64::INFINITY);
if nval <= val {
is_minimum = false;
break;
}
if nval < min_neighbor {
min_neighbor = nval;
}
}
}
if is_minimum {
let depth = if min_neighbor.is_finite() {
min_neighbor - val
} else {
f64::INFINITY
};
if depth >= thresh {
markers[[r, c]] = next_label;
next_label += 1;
}
}
}
}
Ok(markers)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_compact_watershed_basic() {
let image = array![
[0.1, 0.2, 0.9, 0.2, 0.1],
[0.1, 0.1, 0.9, 0.1, 0.1],
[0.1, 0.2, 0.9, 0.2, 0.1],
];
let markers = array![[0, 0, 0, 0, 0], [1, 0, 0, 0, 2], [0, 0, 0, 0, 0],];
let config = CompactWatershedConfig::default();
let result = compact_watershed(&image, &markers, &config).expect("should succeed");
assert_eq!(result[[1, 0]], 1);
assert_eq!(result[[1, 4]], 2);
for &v in result.iter() {
assert!(v > 0);
}
}
#[test]
fn test_compact_watershed_with_compactness() {
let image = array![
[0.1, 0.1, 0.5, 0.1, 0.1],
[0.1, 0.1, 0.5, 0.1, 0.1],
[0.1, 0.1, 0.5, 0.1, 0.1],
];
let markers = array![[0, 0, 0, 0, 0], [1, 0, 0, 0, 2], [0, 0, 0, 0, 0],];
let config = CompactWatershedConfig {
compactness: 1.0,
..Default::default()
};
let result = compact_watershed(&image, &markers, &config).expect("should succeed");
assert_eq!(result[[1, 0]], 1);
assert_eq!(result[[1, 4]], 2);
}
#[test]
fn test_compact_watershed_shape_mismatch() {
let image = array![[0.1, 0.2], [0.3, 0.4]];
let markers = array![[0, 0, 0], [1, 0, 2]];
let config = CompactWatershedConfig::default();
let result = compact_watershed(&image, &markers, &config);
assert!(result.is_err());
}
#[test]
fn test_compact_watershed_single_marker() {
let image = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let markers = array![[1, 0, 0], [0, 0, 0], [0, 0, 0]];
let config = CompactWatershedConfig::default();
let result = compact_watershed(&image, &markers, &config).expect("should succeed");
for &v in result.iter() {
assert_eq!(v, 1);
}
}
#[test]
fn test_compact_watershed_watershed_line() {
let image = array![
[1.0, 2.0, 9.0, 2.0, 1.0],
[1.0, 2.0, 9.0, 2.0, 1.0],
[1.0, 2.0, 9.0, 2.0, 1.0],
];
let markers = array![[1, 0, 0, 0, 2], [0, 0, 0, 0, 0], [1, 0, 0, 0, 2],];
let config = CompactWatershedConfig {
watershed_line: true,
connectivity: WatershedNeighborhood::Conn4,
..Default::default()
};
let result = compact_watershed(&image, &markers, &config).expect("should succeed");
assert_eq!(result[[0, 0]], 1);
assert_eq!(result[[0, 4]], 2);
}
#[test]
fn test_compact_watershed_empty() {
let image: Array2<f64> = Array2::zeros((0, 0));
let markers: Array2<i32> = Array2::zeros((0, 0));
let config = CompactWatershedConfig::default();
let result = compact_watershed(&image, &markers, &config).expect("empty should succeed");
assert_eq!(result.len(), 0);
}
#[test]
fn test_extract_dams_basic() {
let labels = array![[1, 1, 2, 2], [1, 1, 2, 2], [3, 3, 4, 4], [3, 3, 4, 4],];
let result = extract_dams(&labels, WatershedNeighborhood::Conn4).expect("should succeed");
assert!(result.num_regions >= 2);
assert!(!result.dam_mask[[0, 0]]);
assert!(!result.dam_mask[[3, 3]]);
}
#[test]
fn test_extract_dams_single_region() {
let labels = Array2::from_elem((4, 4), 1i32);
let result = extract_dams(&labels, WatershedNeighborhood::Conn8).expect("should succeed");
for &v in result.dam_mask.iter() {
assert!(!v);
}
assert_eq!(result.num_regions, 1);
}
#[test]
fn test_merge_small_regions_basic() {
let image = array![
[0.1, 0.1, 0.9, 0.9],
[0.1, 0.1, 0.9, 0.9],
[0.1, 0.1, 0.9, 0.9],
[0.1, 0.1, 0.9, 0.9],
];
let labels = array![
[1, 1, 2, 2],
[1, 3, 2, 2], [1, 1, 2, 2],
[1, 1, 2, 2],
];
let config = OversegmentationConfig {
min_region_area: 3,
..Default::default()
};
let result = merge_small_regions(&image, &labels, &config).expect("should succeed");
assert_ne!(result[[1, 1]], 3);
assert_eq!(result[[1, 1]], 1);
}
#[test]
fn test_merge_small_regions_max_regions() {
let image = Array2::<f64>::from_elem((6, 6), 0.5);
let mut labels = Array2::<i32>::zeros((6, 6));
for r in 0..3 {
for c in 0..3 {
labels[[r, c]] = 1;
}
}
for r in 0..3 {
for c in 3..6 {
labels[[r, c]] = 2;
}
}
for r in 3..6 {
for c in 0..3 {
labels[[r, c]] = 3;
}
}
for r in 3..6 {
for c in 3..6 {
labels[[r, c]] = 4;
}
}
let config = OversegmentationConfig {
min_region_area: 1,
max_regions: 2,
..Default::default()
};
let result = merge_small_regions(&image, &labels, &config).expect("should succeed");
let mut unique = std::collections::HashSet::new();
for &v in result.iter() {
if v > 0 {
unique.insert(v);
}
}
assert!(
unique.len() <= 2,
"Expected <= 2 regions, got {}",
unique.len()
);
}
#[test]
fn test_h_minima_transform() {
let image = array![
[5.0, 5.0, 5.0, 5.0, 5.0],
[5.0, 3.0, 5.0, 1.0, 5.0],
[5.0, 5.0, 5.0, 5.0, 5.0],
];
let result = h_minima_transform(&image, 1.5).expect("should succeed");
assert!((result[[0, 0]] - 3.5).abs() < 1e-10);
assert!(result[[1, 1]] < result[[0, 0]]); assert!(result[[1, 3]] < result[[0, 0]]);
let result2 = h_minima_transform(&image, 3.0).expect("should succeed");
let plateau_level = result2[[0, 0]];
assert!((plateau_level - 2.0).abs() < 1e-10);
assert!(
(result2[[1, 1]] - plateau_level).abs() < 1e-10,
"expected (1,1) to be suppressed to plateau {}, got {}",
plateau_level,
result2[[1, 1]]
);
assert!(
result2[[1, 3]] < plateau_level,
"expected (1,3) to still be a minimum (below plateau {}), got {}",
plateau_level,
result2[[1, 3]]
);
assert!((result2[[1, 3]] - 1.0).abs() < 1e-10);
}
#[test]
fn test_auto_markers_basic() {
let image = array![
[5.0, 5.0, 5.0, 5.0, 5.0],
[5.0, 1.0, 5.0, 2.0, 5.0],
[5.0, 5.0, 5.0, 5.0, 5.0],
];
let markers =
auto_markers(&image, WatershedNeighborhood::Conn4, None).expect("should succeed");
assert!(markers[[1, 1]] > 0);
assert!(markers[[1, 3]] > 0);
assert_ne!(markers[[1, 1]], markers[[1, 3]]);
assert_eq!(markers[[0, 0]], 0);
}
#[test]
fn test_auto_markers_with_threshold() {
let image = array![
[5.0, 5.0, 5.0, 5.0, 5.0],
[5.0, 4.5, 5.0, 1.0, 5.0],
[5.0, 5.0, 5.0, 5.0, 5.0],
];
let markers =
auto_markers(&image, WatershedNeighborhood::Conn4, Some(1.0)).expect("should succeed");
assert_eq!(markers[[1, 1]], 0);
assert!(markers[[1, 3]] > 0);
}
#[test]
fn test_auto_markers_empty() {
let image: Array2<f64> = Array2::zeros((0, 0));
let markers =
auto_markers(&image, WatershedNeighborhood::Conn4, None).expect("empty should succeed");
assert_eq!(markers.len(), 0);
}
#[test]
fn test_compact_watershed_conn4() {
let image = array![[0.1, 0.9, 0.1], [0.9, 0.9, 0.9], [0.1, 0.9, 0.1],];
let markers = array![[1, 0, 2], [0, 0, 0], [3, 0, 4],];
let config = CompactWatershedConfig {
connectivity: WatershedNeighborhood::Conn4,
compactness: 0.0,
watershed_line: false,
};
let result = compact_watershed(&image, &markers, &config).expect("should succeed");
assert_eq!(result[[0, 0]], 1);
assert_eq!(result[[0, 2]], 2);
assert_eq!(result[[2, 0]], 3);
assert_eq!(result[[2, 2]], 4);
}
}