use crate::error::{NdimageError, NdimageResult};
use scirs2_core::ndarray::Array2;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::collections::VecDeque;
use std::fmt::Debug;
pub fn morphological_reconstruction_by_dilation<T>(
marker: &Array2<T>,
mask: &Array2<T>,
connectivity: Option<usize>,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Copy + 'static,
{
if marker.shape() != mask.shape() {
return Err(NdimageError::DimensionError(
"Marker and mask must have the same shape".into(),
));
}
let rows = marker.nrows();
let cols = marker.ncols();
if rows == 0 || cols == 0 {
return Ok(marker.clone());
}
let conn = connectivity.unwrap_or(8);
if conn != 4 && conn != 8 {
return Err(NdimageError::InvalidInput(
"Connectivity must be 4 or 8".into(),
));
}
for r in 0..rows {
for c in 0..cols {
if marker[[r, c]] > mask[[r, c]] {
return Err(NdimageError::InvalidInput(
"Marker must be pointwise <= mask".into(),
));
}
}
}
let mut result = marker.clone();
let offsets_4: &[(isize, isize)] = &[(-1, 0), (0, -1), (0, 1), (1, 0)];
let offsets_8: &[(isize, isize)] = &[
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
];
let offsets = if conn == 4 { offsets_4 } else { offsets_8 };
let forward_offsets_4: &[(isize, isize)] = &[(-1, 0), (0, -1)];
let forward_offsets_8: &[(isize, isize)] = &[(-1, -1), (-1, 0), (-1, 1), (0, -1)];
let fwd = if conn == 4 {
forward_offsets_4
} else {
forward_offsets_8
};
for r in 0..rows {
for c in 0..cols {
let mut max_val = result[[r, c]];
for &(dr, dc) in fwd {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let nv = result[[nr as usize, nc as usize]];
if nv > max_val {
max_val = nv;
}
}
}
result[[r, c]] = if max_val < mask[[r, c]] {
max_val
} else {
mask[[r, c]]
};
}
}
let backward_offsets_4: &[(isize, isize)] = &[(1, 0), (0, 1)];
let backward_offsets_8: &[(isize, isize)] = &[(0, 1), (1, -1), (1, 0), (1, 1)];
let bwd = if conn == 4 {
backward_offsets_4
} else {
backward_offsets_8
};
let mut queue = VecDeque::new();
for r in (0..rows).rev() {
for c in (0..cols).rev() {
let mut max_val = result[[r, c]];
for &(dr, dc) in bwd {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let nv = result[[nr as usize, nc as usize]];
if nv > max_val {
max_val = nv;
}
}
}
result[[r, c]] = if max_val < mask[[r, c]] {
max_val
} else {
mask[[r, c]]
};
for &(dr, dc) in bwd {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let nu = nr as usize;
let ncu = nc as usize;
if result[[nu, ncu]] < result[[r, c]] && result[[nu, ncu]] < mask[[nu, ncu]] {
queue.push_back((r, c));
break;
}
}
}
}
}
while let Some((r, c)) = queue.pop_front() {
for &(dr, dc) in offsets {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let nu = nr as usize;
let ncu = nc as usize;
if result[[nu, ncu]] < result[[r, c]] && result[[nu, ncu]] != mask[[nu, ncu]] {
let new_val = if result[[r, c]] < mask[[nu, ncu]] {
result[[r, c]]
} else {
mask[[nu, ncu]]
};
if new_val > result[[nu, ncu]] {
result[[nu, ncu]] = new_val;
queue.push_back((nu, ncu));
}
}
}
}
}
Ok(result)
}
pub fn morphological_reconstruction_by_erosion<T>(
marker: &Array2<T>,
mask: &Array2<T>,
connectivity: Option<usize>,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Copy + 'static,
{
if marker.shape() != mask.shape() {
return Err(NdimageError::DimensionError(
"Marker and mask must have the same shape".into(),
));
}
let rows = marker.nrows();
let cols = marker.ncols();
if rows == 0 || cols == 0 {
return Ok(marker.clone());
}
let conn = connectivity.unwrap_or(8);
if conn != 4 && conn != 8 {
return Err(NdimageError::InvalidInput(
"Connectivity must be 4 or 8".into(),
));
}
let neg_marker = marker.mapv(|v| T::zero() - v);
let neg_mask = mask.mapv(|v| T::zero() - v);
let neg_result = morphological_reconstruction_by_dilation(&neg_marker, &neg_mask, Some(conn))?;
Ok(neg_result.mapv(|v| T::zero() - v))
}
pub fn fill_holes_2d(input: &Array2<bool>) -> NdimageResult<Array2<bool>> {
let rows = input.nrows();
let cols = input.ncols();
if rows == 0 || cols == 0 {
return Ok(input.clone());
}
let complement = input.mapv(|v| !v);
let mut marker = Array2::from_elem((rows, cols), false);
for c in 0..cols {
marker[[0, c]] = complement[[0, c]];
marker[[rows - 1, c]] = complement[[rows - 1, c]];
}
for r in 0..rows {
marker[[r, 0]] = complement[[r, 0]];
marker[[r, cols - 1]] = complement[[r, cols - 1]];
}
let marker_f64 = marker.mapv(|v| if v { 1.0f64 } else { 0.0f64 });
let mask_f64 = complement.mapv(|v| if v { 1.0f64 } else { 0.0f64 });
let reconstructed = morphological_reconstruction_by_dilation(&marker_f64, &mask_f64, Some(8))?;
let result = reconstructed.mapv(|v| v < 0.5);
Ok(result)
}
pub fn morphological_gradient_2d_fast<T>(
input: &Array2<T>,
size: Option<usize>,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Copy + 'static,
{
let sz = size.unwrap_or(3);
if sz == 0 || sz % 2 == 0 {
return Err(NdimageError::InvalidInput(
"Structuring element size must be odd and > 0".into(),
));
}
let rows = input.nrows();
let cols = input.ncols();
let half = (sz / 2) as isize;
let mut dilated = Array2::from_elem((rows, cols), T::neg_infinity());
let mut eroded = Array2::from_elem((rows, cols), T::infinity());
for r in 0..rows {
for c in 0..cols {
for dr in -half..=half {
for dc in -half..=half {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let val = input[[nr as usize, nc as usize]];
if val > dilated[[r, c]] {
dilated[[r, c]] = val;
}
if val < eroded[[r, c]] {
eroded[[r, c]] = val;
}
}
}
}
}
}
let mut result = Array2::zeros((rows, cols));
for r in 0..rows {
for c in 0..cols {
result[[r, c]] = dilated[[r, c]] - eroded[[r, c]];
}
}
Ok(result)
}
pub fn white_tophat_2d_fast<T>(input: &Array2<T>, size: Option<usize>) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Copy + 'static,
{
let sz = size.unwrap_or(3);
if sz == 0 || sz % 2 == 0 {
return Err(NdimageError::InvalidInput(
"Structuring element size must be odd and > 0".into(),
));
}
let rows = input.nrows();
let cols = input.ncols();
let half = (sz / 2) as isize;
let mut eroded = Array2::from_elem((rows, cols), T::infinity());
for r in 0..rows {
for c in 0..cols {
for dr in -half..=half {
for dc in -half..=half {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let val = input[[nr as usize, nc as usize]];
if val < eroded[[r, c]] {
eroded[[r, c]] = val;
}
}
}
}
}
}
let mut opened = Array2::from_elem((rows, cols), T::neg_infinity());
for r in 0..rows {
for c in 0..cols {
for dr in -half..=half {
for dc in -half..=half {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let val = eroded[[nr as usize, nc as usize]];
if val > opened[[r, c]] {
opened[[r, c]] = val;
}
}
}
}
}
}
let mut result = Array2::zeros((rows, cols));
for r in 0..rows {
for c in 0..cols {
let diff = input[[r, c]] - opened[[r, c]];
result[[r, c]] = if diff > T::zero() { diff } else { T::zero() };
}
}
Ok(result)
}
pub fn black_tophat_2d_fast<T>(input: &Array2<T>, size: Option<usize>) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Copy + 'static,
{
let sz = size.unwrap_or(3);
if sz == 0 || sz % 2 == 0 {
return Err(NdimageError::InvalidInput(
"Structuring element size must be odd and > 0".into(),
));
}
let rows = input.nrows();
let cols = input.ncols();
let half = (sz / 2) as isize;
let mut dilated = Array2::from_elem((rows, cols), T::neg_infinity());
for r in 0..rows {
for c in 0..cols {
for dr in -half..=half {
for dc in -half..=half {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let val = input[[nr as usize, nc as usize]];
if val > dilated[[r, c]] {
dilated[[r, c]] = val;
}
}
}
}
}
}
let mut closed = Array2::from_elem((rows, cols), T::infinity());
for r in 0..rows {
for c in 0..cols {
for dr in -half..=half {
for dc in -half..=half {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nr < rows as isize && nc >= 0 && nc < cols as isize {
let val = dilated[[nr as usize, nc as usize]];
if val < closed[[r, c]] {
closed[[r, c]] = val;
}
}
}
}
}
}
let mut result = Array2::zeros((rows, cols));
for r in 0..rows {
for c in 0..cols {
let diff = closed[[r, c]] - input[[r, c]];
result[[r, c]] = if diff > T::zero() { diff } else { T::zero() };
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_reconstruction_by_dilation_basic() {
let mask: Array2<f64> = array![
[0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 1.0, 1.0, 0.0],
[0.0, 1.0, 0.5, 1.0, 0.0],
[0.0, 1.0, 1.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0],
];
let mut marker: Array2<f64> = Array2::zeros((5, 5));
marker[[1, 1]] = 1.0;
let result = morphological_reconstruction_by_dilation(&marker, &mask, Some(8))
.expect("reconstruction should succeed");
assert_abs_diff_eq!(result[[1, 1]], 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[1, 2]], 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[1, 3]], 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[2, 1]], 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[2, 2]], 0.5, epsilon = 1e-10);
assert_abs_diff_eq!(result[[0, 0]], 0.0, epsilon = 1e-10);
}
#[test]
fn test_reconstruction_by_dilation_shape_mismatch() {
let marker: Array2<f64> = Array2::zeros((3, 3));
let mask: Array2<f64> = Array2::zeros((4, 4));
let result = morphological_reconstruction_by_dilation(&marker, &mask, None);
assert!(result.is_err());
}
#[test]
fn test_reconstruction_by_dilation_marker_exceeds_mask() {
let marker: Array2<f64> = Array2::from_elem((3, 3), 2.0);
let mask: Array2<f64> = Array2::from_elem((3, 3), 1.0);
let result = morphological_reconstruction_by_dilation(&marker, &mask, None);
assert!(result.is_err());
}
#[test]
fn test_reconstruction_by_erosion_basic() {
let mask: Array2<f64> = array![[1.0, 1.0, 1.0], [1.0, 0.0, 1.0], [1.0, 1.0, 1.0],];
let marker: Array2<f64> = Array2::from_elem((3, 3), 1.0);
let result = morphological_reconstruction_by_erosion(&marker, &mask, Some(8))
.expect("erosion reconstruction should succeed");
assert_abs_diff_eq!(result[[1, 1]], 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[0, 0]], 1.0, epsilon = 1e-10);
let mask2: Array2<f64> = array![[0.2, 0.2, 0.2], [0.2, 0.2, 0.2], [0.2, 0.2, 0.2],];
let marker2: Array2<f64> = array![[0.2, 0.2, 0.2], [0.2, 1.0, 0.2], [0.2, 0.2, 0.2],];
let result2 = morphological_reconstruction_by_erosion(&marker2, &mask2, Some(8))
.expect("erosion reconstruction should succeed");
assert_abs_diff_eq!(result2[[1, 1]], 0.2, epsilon = 1e-10);
assert_abs_diff_eq!(result2[[0, 0]], 0.2, epsilon = 1e-10);
}
#[test]
fn test_fill_holes_2d_basic() {
let input = array![
[false, false, false, false, false],
[false, true, true, true, false],
[false, true, false, true, false],
[false, true, true, true, false],
[false, false, false, false, false],
];
let filled = fill_holes_2d(&input).expect("fill_holes_2d should succeed");
assert!(filled[[2, 2]]);
assert!(!filled[[0, 0]]);
assert!(!filled[[0, 4]]);
assert!(!filled[[4, 0]]);
assert!(!filled[[4, 4]]);
assert!(filled[[1, 1]]);
assert!(filled[[1, 2]]);
}
#[test]
fn test_fill_holes_2d_no_holes() {
let input = array![
[false, false, false],
[false, true, false],
[false, false, false],
];
let filled = fill_holes_2d(&input).expect("no holes should succeed");
assert_eq!(input, filled);
}
#[test]
fn test_fill_holes_2d_all_background() {
let input = Array2::from_elem((4, 4), false);
let filled = fill_holes_2d(&input).expect("all background should succeed");
assert_eq!(input, filled);
}
#[test]
fn test_fill_holes_2d_empty() {
let input = Array2::<bool>::from_elem((0, 0), false);
let filled = fill_holes_2d(&input).expect("empty should succeed");
assert_eq!(filled.len(), 0);
}
#[test]
fn test_fill_holes_2d_multiple_holes() {
let input = array![
[false, false, false, false, false, false, false],
[false, true, true, true, true, true, false],
[false, true, false, true, false, true, false],
[false, true, true, true, true, true, false],
[false, false, false, false, false, false, false],
];
let filled = fill_holes_2d(&input).expect("multiple holes should succeed");
assert!(filled[[2, 2]]);
assert!(filled[[2, 4]]);
assert!(!filled[[0, 0]]);
}
#[test]
fn test_morphological_gradient_2d_fast_basic() {
let input: Array2<f64> = array![
[0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 1.0, 1.0, 0.0],
[0.0, 1.0, 1.0, 1.0, 0.0],
[0.0, 1.0, 1.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0],
];
let grad =
morphological_gradient_2d_fast(&input, Some(3)).expect("gradient should succeed");
assert_abs_diff_eq!(grad[[2, 2]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(grad[[1, 1]], 1.0, epsilon = 1e-10);
}
#[test]
fn test_morphological_gradient_invalid_size() {
let input: Array2<f64> = Array2::zeros((3, 3));
let result = morphological_gradient_2d_fast(&input, Some(2));
assert!(result.is_err());
}
#[test]
fn test_white_tophat_2d_fast_basic() {
let mut input: Array2<f64> = Array2::zeros((7, 7));
input[[3, 3]] = 1.0;
let wth = white_tophat_2d_fast(&input, Some(5)).expect("white tophat should succeed");
assert!(wth[[3, 3]] > 0.0);
}
#[test]
fn test_white_tophat_flat_image() {
let input: Array2<f64> = Array2::from_elem((5, 5), 5.0);
let wth = white_tophat_2d_fast(&input, Some(3)).expect("flat tophat should succeed");
for &v in wth.iter() {
assert_abs_diff_eq!(v, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_black_tophat_2d_fast_basic() {
let mut input: Array2<f64> = Array2::from_elem((7, 7), 1.0);
input[[3, 3]] = 0.0;
let bth = black_tophat_2d_fast(&input, Some(5)).expect("black tophat should succeed");
assert!(bth[[3, 3]] > 0.0);
}
#[test]
fn test_black_tophat_flat_image() {
let input: Array2<f64> = Array2::from_elem((5, 5), 3.0);
let bth = black_tophat_2d_fast(&input, Some(3)).expect("flat btophat should succeed");
for &v in bth.iter() {
assert_abs_diff_eq!(v, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_reconstruction_4_connectivity() {
let mask: Array2<f64> = array![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],];
let mut marker: Array2<f64> = Array2::zeros((3, 3));
marker[[0, 0]] = 1.0;
let result = morphological_reconstruction_by_dilation(&marker, &mask, Some(4))
.expect("4-conn reconstruction should succeed");
assert_abs_diff_eq!(result[[0, 0]], 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[1, 1]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[2, 2]], 0.0, epsilon = 1e-10);
}
}