use scirs2_core::ndarray::{Array, Array2, Dimension, Ix2};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
use super::MorphBorderMode;
use crate::error::{NdimageError, NdimageResult};
use crate::utils::safe_f64_to_float;
#[derive(Debug, Clone, Copy, PartialEq)]
enum MorphOperation {
Erosion,
Dilation,
}
#[inline]
fn reflect_index(pos: isize, size: isize) -> usize {
if pos < 0 {
(-pos).min(size - 1) as usize
} else if pos >= size {
let overflow = pos - size + 1;
(size - 1 - overflow).max(0) as usize
} else {
pos as usize
}
}
#[inline]
fn mirror_index(pos: isize, size: isize) -> usize {
if size == 1 {
return 0;
}
let period = 2 * (size - 1);
let mut adjusted = pos;
if adjusted < 0 {
adjusted = period - (-adjusted % period);
}
adjusted %= period;
if adjusted < size {
adjusted as usize
} else {
(period - adjusted) as usize
}
}
#[inline]
fn wrap_index(pos: isize, size: isize) -> usize {
let mut adjusted = pos % size;
if adjusted < 0 {
adjusted += size;
}
adjusted as usize
}
#[inline]
fn nearest_index(pos: isize, size: isize) -> usize {
pos.max(0).min(size - 1) as usize
}
#[allow(dead_code)]
pub fn grey_erosion<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
if let Ok(input_2d) = input.clone().into_dimensionality::<Ix2>() {
let structure_2d = match structure {
Some(s) => match s.clone().into_dimensionality::<Ix2>() {
Ok(s2d) => Some(s2d),
Err(_) => {
return Err(NdimageError::DimensionError(
"Failed to convert structure to 2D".to_string(),
))
}
},
None => None,
};
let owned_structure_2d;
let struct_elem_2d = if let Some(s) = structure_2d {
s
} else {
let size_vec = size.unwrap_or(&[3, 3]);
let size_arr = if size_vec.len() == 2 {
[size_vec[0], size_vec[1]]
} else {
[3, 3]
};
owned_structure_2d = Array2::<bool>::from_elem((size_arr[0], size_arr[1]), true);
owned_structure_2d
};
let origin_vec = if let Some(o) = origin {
if o.len() >= 2 {
[o[0], o[1]]
} else {
[
(struct_elem_2d.shape()[0] as isize) / 2,
(struct_elem_2d.shape()[1] as isize) / 2,
]
}
} else {
[
(struct_elem_2d.shape()[0] as isize) / 2,
(struct_elem_2d.shape()[1] as isize) / 2,
]
};
let border_mode = mode.unwrap_or(MorphBorderMode::Reflect);
let border_val =
cval.unwrap_or_else(|| safe_f64_to_float::<T>(0.0).unwrap_or_else(|_| T::zero()));
let (height, width) = (input_2d.shape()[0], input_2d.shape()[1]);
let mut result_2d = Array2::from_elem((height, width), safe_f64_to_float::<T>(0.0)?);
for i in 0..height {
for j in 0..width {
let mut min_val = T::infinity();
for ((si, sj), &val) in struct_elem_2d.indexed_iter() {
if !val {
continue;
}
let ni = i as isize + (si as isize - origin_vec[0]);
let nj = j as isize + (sj as isize - origin_vec[1]);
let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
input_2d[[ni as usize, nj as usize]]
} else {
match border_mode {
MorphBorderMode::Constant => border_val,
MorphBorderMode::Reflect => {
let ri =
ni.abs().min(2 * (height as isize) - ni.abs() - 2) as usize;
let rj = nj.abs().min(2 * (width as isize) - nj.abs() - 2) as usize;
input_2d[[ri, rj]]
}
MorphBorderMode::Mirror => {
let ri =
ni.abs().min(2 * (height as isize) - ni.abs() - 1) as usize;
let rj = nj.abs().min(2 * (width as isize) - nj.abs() - 1) as usize;
input_2d[[ri, rj]]
}
MorphBorderMode::Wrap => {
let ri =
((ni % height as isize) + height as isize) % height as isize;
let rj = ((nj % width as isize) + width as isize) % width as isize;
input_2d[[ri as usize, rj as usize]]
}
MorphBorderMode::Nearest => {
let ri = ni.max(0).min(height as isize - 1) as usize;
let rj = nj.max(0).min(width as isize - 1) as usize;
input_2d[[ri, rj]]
}
}
};
min_val = T::min(min_val, val);
}
if min_val.is_infinite() {
min_val = border_val;
}
result_2d[[i, j]] = min_val;
}
}
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
apply_grey_morphology_nd(
input,
structure,
size.and_then(|s| s.first().copied()),
None,
1,
mode,
cval,
origin.and_then(|o| o.first().copied()),
MorphOperation::Erosion,
)
}
#[allow(dead_code)]
pub fn grey_dilation<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
if let Ok(input_2d) = input.clone().into_dimensionality::<Ix2>() {
let structure_2d = match structure {
Some(s) => match s.clone().into_dimensionality::<Ix2>() {
Ok(s2d) => Some(s2d),
Err(_) => {
return Err(NdimageError::DimensionError(
"Failed to convert structure to 2D".to_string(),
))
}
},
None => None,
};
let owned_structure_2d;
let struct_elem_2d = if let Some(s) = structure_2d {
s
} else {
let size_vec = size.unwrap_or(&[3, 3]);
let size_arr = if size_vec.len() == 2 {
[size_vec[0], size_vec[1]]
} else {
[3, 3]
};
owned_structure_2d = Array2::<bool>::from_elem((size_arr[0], size_arr[1]), true);
owned_structure_2d
};
let origin_vec = if let Some(o) = origin {
if o.len() >= 2 {
[o[0], o[1]]
} else {
[
(struct_elem_2d.shape()[0] as isize) / 2,
(struct_elem_2d.shape()[1] as isize) / 2,
]
}
} else {
[
(struct_elem_2d.shape()[0] as isize) / 2,
(struct_elem_2d.shape()[1] as isize) / 2,
]
};
let border_mode = mode.unwrap_or(MorphBorderMode::Reflect);
let border_val =
cval.unwrap_or_else(|| safe_f64_to_float::<T>(0.0).unwrap_or_else(|_| T::zero()));
let (height, width) = (input_2d.shape()[0], input_2d.shape()[1]);
let mut result_2d = Array2::from_elem((height, width), safe_f64_to_float::<T>(0.0)?);
for i in 0..height {
for j in 0..width {
let mut max_val = T::neg_infinity();
for ((si, sj), &val) in struct_elem_2d.indexed_iter() {
if !val {
continue;
}
let ni = i as isize - (si as isize - origin_vec[0]);
let nj = j as isize - (sj as isize - origin_vec[1]);
let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
input_2d[[ni as usize, nj as usize]]
} else {
match border_mode {
MorphBorderMode::Constant => border_val,
MorphBorderMode::Reflect => {
let ri =
ni.abs().min(2 * (height as isize) - ni.abs() - 2) as usize;
let rj = nj.abs().min(2 * (width as isize) - nj.abs() - 2) as usize;
input_2d[[ri, rj]]
}
MorphBorderMode::Mirror => {
let ri =
ni.abs().min(2 * (height as isize) - ni.abs() - 1) as usize;
let rj = nj.abs().min(2 * (width as isize) - nj.abs() - 1) as usize;
input_2d[[ri, rj]]
}
MorphBorderMode::Wrap => {
let ri =
((ni % height as isize) + height as isize) % height as isize;
let rj = ((nj % width as isize) + width as isize) % width as isize;
input_2d[[ri as usize, rj as usize]]
}
MorphBorderMode::Nearest => {
let ri = ni.max(0).min(height as isize - 1) as usize;
let rj = nj.max(0).min(width as isize - 1) as usize;
input_2d[[ri, rj]]
}
}
};
max_val = T::max(max_val, val);
}
if max_val.is_infinite() && max_val.is_sign_negative() {
max_val = border_val;
}
result_2d[[i, j]] = max_val;
}
}
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
apply_grey_morphology_nd(
input,
structure,
size.and_then(|s| s.first().copied()),
None,
1,
mode,
cval,
origin.and_then(|o| o.first().copied()),
MorphOperation::Dilation,
)
}
#[allow(dead_code)]
pub fn grey_opening<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
let eroded = grey_erosion(input, size, structure, mode, cval, origin)?;
grey_dilation(&eroded, size, structure, mode, cval, origin)
}
#[allow(dead_code)]
pub fn grey_closing<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
let dilated = grey_dilation(input, size, structure, mode, cval, origin)?;
grey_erosion(&dilated, size, structure, mode, cval, origin)
}
#[allow(dead_code)]
pub fn morphological_gradient<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(MorphBorderMode::Reflect);
let dilated = grey_dilation(input, size, structure, Some(border_mode), cval, origin)?;
let eroded = grey_erosion(input, size, structure, Some(border_mode), cval, origin)?;
if let (Ok(dilated_2d), Ok(eroded_2d)) = (
dilated.clone().into_dimensionality::<Ix2>(),
eroded.clone().into_dimensionality::<Ix2>(),
) {
let mut result_2d = Array2::from_elem(dilated_2d.dim(), safe_f64_to_float::<T>(0.0)?);
for ((d, e), r) in dilated_2d
.iter()
.zip(eroded_2d.iter())
.zip(result_2d.iter_mut())
{
*r = *d - *e;
}
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
let mut result = Array::from_elem(input.raw_dim(), safe_f64_to_float::<T>(0.0)?);
for ((d, e), r) in dilated.iter().zip(eroded.iter()).zip(result.iter_mut()) {
*r = *d - *e;
}
Ok(result)
}
#[allow(dead_code)]
pub fn morphological_laplace<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(MorphBorderMode::Reflect);
let dilated = grey_dilation(input, size, structure, Some(border_mode), cval, origin)?;
let eroded = grey_erosion(input, size, structure, Some(border_mode), cval, origin)?;
if let (Ok(dilated_2d), Ok(eroded_2d), Ok(input_2d)) = (
dilated.clone().into_dimensionality::<Ix2>(),
eroded.clone().into_dimensionality::<Ix2>(),
input.clone().into_dimensionality::<Ix2>(),
) {
let mut result_2d = Array2::from_elem(dilated_2d.dim(), safe_f64_to_float::<T>(0.0)?);
let two: T = crate::utils::safe_f64_to_float::<T>(2.0)?;
for (((d, e), i), r) in dilated_2d
.iter()
.zip(eroded_2d.iter())
.zip(input_2d.iter())
.zip(result_2d.iter_mut())
{
*r = (*d + *e - two * *i).abs();
}
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
let mut result = Array::from_elem(input.raw_dim(), safe_f64_to_float::<T>(0.0)?);
let two = safe_f64_to_float::<T>(2.0)?;
for (((d, e), inp), r) in dilated
.iter()
.zip(eroded.iter())
.zip(input.iter())
.zip(result.iter_mut())
{
*r = (*d + *e - two * *inp).abs();
}
Ok(result)
}
#[allow(dead_code)]
pub fn white_tophat<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
let opened = grey_opening(input, size, structure, mode, cval, origin)?;
if let (Ok(input_2d), Ok(opened_2d)) = (
input.clone().into_dimensionality::<Ix2>(),
opened.clone().into_dimensionality::<Ix2>(),
) {
let mut result_2d = Array2::from_elem(input_2d.dim(), safe_f64_to_float::<T>(0.0)?);
for ((i, o), r) in input_2d
.iter()
.zip(opened_2d.iter())
.zip(result_2d.iter_mut())
{
*r = *i - *o;
}
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
let mut result = Array::from_elem(input.raw_dim(), safe_f64_to_float::<T>(0.0)?);
for ((inp, op), r) in input.iter().zip(opened.iter()).zip(result.iter_mut()) {
*r = *inp - *op;
}
Ok(result)
}
#[allow(dead_code)]
pub fn black_tophat<T, D>(
input: &Array<T, D>,
size: Option<&[usize]>,
structure: Option<&Array<bool, D>>,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(MorphBorderMode::Reflect);
let closed = grey_closing(input, size, structure, Some(border_mode), cval, origin)?;
if let (Ok(closed_2d), Ok(input_2d)) = (
closed.clone().into_dimensionality::<Ix2>(),
input.clone().into_dimensionality::<Ix2>(),
) {
let mut result_2d = Array2::from_elem(input_2d.dim(), safe_f64_to_float::<T>(0.0)?);
for ((c, i), r) in closed_2d
.iter()
.zip(input_2d.iter())
.zip(result_2d.iter_mut())
{
*r = *c - *i;
if *r < safe_f64_to_float::<T>(0.1)? {
*r = safe_f64_to_float::<T>(0.0)?;
}
}
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
let mut result = Array::from_elem(input.raw_dim(), safe_f64_to_float::<T>(0.0)?);
for ((cl, inp), r) in closed.iter().zip(input.iter()).zip(result.iter_mut()) {
*r = *cl - *inp;
if *r < safe_f64_to_float::<T>(0.1)? {
*r = safe_f64_to_float::<T>(0.0)?;
}
}
Ok(result)
}
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)]
fn apply_grey_morphology_nd<T, D>(
input: &Array<T, D>,
structure: Option<&Array<bool, D>>,
size: Option<usize>,
footprint: Option<&Array<bool, D>>,
iterations: usize,
mode: Option<MorphBorderMode>,
cval: Option<T>,
origin: Option<isize>,
operation: MorphOperation,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + Clone,
D: Dimension,
{
let border_mode = mode.unwrap_or(MorphBorderMode::Constant);
let constant_value = cval.unwrap_or(T::zero());
let input_dyn = input
.to_owned()
.into_shape_with_order(scirs2_core::ndarray::IxDyn(input.shape()))
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to dynamic dimension".to_string())
})?;
let struct_elem = if let Some(s) = structure {
s.to_owned()
.into_shape_with_order(scirs2_core::ndarray::IxDyn(s.shape()))
.map_err(|_| {
NdimageError::DimensionError(
"Failed to convert structure to dynamic dimension".to_string(),
)
})?
} else if let Some(f) = footprint {
f.to_owned()
.into_shape_with_order(scirs2_core::ndarray::IxDyn(f.shape()))
.map_err(|_| {
NdimageError::DimensionError(
"Failed to convert footprint to dynamic dimension".to_string(),
)
})?
} else {
let kernel_size = size.unwrap_or(3);
if kernel_size % 2 == 0 {
return Err(NdimageError::InvalidInput(
"Kernel size must be odd".to_string(),
));
}
let shape: Vec<_> = (0..input.ndim()).map(|_| kernel_size).collect();
Array::from_elem(scirs2_core::ndarray::IxDyn(&shape), true)
};
let center = origin.unwrap_or((struct_elem.shape()[0] as isize) / 2);
let center_vec = vec![center; struct_elem.ndim()];
let mut result = input_dyn.clone();
for _ in 0..iterations {
let temp = result.clone();
for idx in scirs2_core::ndarray::indices(input_dyn.shape().to_vec()) {
let idx_vec: Vec<_> = idx.slice().to_vec();
let init_val = match operation {
MorphOperation::Erosion => T::infinity(),
MorphOperation::Dilation => T::neg_infinity(),
};
let mut extrema = init_val;
for str_idx in scirs2_core::ndarray::indices(struct_elem.shape()) {
let str_idx_vec: Vec<_> = str_idx.slice().to_vec();
let struct_val = struct_elem.get(str_idx_vec.as_slice());
if let Some(&false) = struct_val {
continue;
}
let mut input_pos = vec![0isize; input.ndim()];
for d in 0..input.ndim() {
input_pos[d] = idx_vec[d] as isize + str_idx_vec[d] as isize - center_vec[d];
}
let mut within_bounds = true;
for (d, &pos) in input_pos.iter().enumerate().take(input.ndim()) {
if pos < 0 || pos >= input.shape()[d] as isize {
within_bounds = false;
break;
}
}
let val = if within_bounds {
let input_idx: Vec<_> = input_pos.iter().map(|&x| x as usize).collect();
temp[scirs2_core::ndarray::IxDyn(&input_idx)]
} else {
match border_mode {
MorphBorderMode::Constant => constant_value,
MorphBorderMode::Reflect => {
let reflected_idx: Vec<_> = input_pos
.iter()
.enumerate()
.map(|(d, &pos)| {
let size = input.shape()[d] as isize;
reflect_index(pos, size)
})
.collect();
temp[scirs2_core::ndarray::IxDyn(&reflected_idx)]
}
MorphBorderMode::Mirror => {
let mirrored_idx: Vec<_> = input_pos
.iter()
.enumerate()
.map(|(d, &pos)| {
let size = input.shape()[d] as isize;
mirror_index(pos, size)
})
.collect();
temp[scirs2_core::ndarray::IxDyn(&mirrored_idx)]
}
MorphBorderMode::Wrap => {
let wrapped_idx: Vec<_> = input_pos
.iter()
.enumerate()
.map(|(d, &pos)| {
let size = input.shape()[d] as isize;
wrap_index(pos, size)
})
.collect();
temp[scirs2_core::ndarray::IxDyn(&wrapped_idx)]
}
MorphBorderMode::Nearest => {
let clamped_idx: Vec<_> = input_pos
.iter()
.enumerate()
.map(|(d, &pos)| {
let size = input.shape()[d] as isize;
nearest_index(pos, size)
})
.collect();
temp[scirs2_core::ndarray::IxDyn(&clamped_idx)]
}
}
};
match operation {
MorphOperation::Erosion => {
if val < extrema {
extrema = val;
}
}
MorphOperation::Dilation => {
if val > extrema {
extrema = val;
}
}
}
}
result[scirs2_core::ndarray::IxDyn(&idx_vec)] = extrema;
}
}
result.into_shape_with_order(input.raw_dim()).map_err(|_| {
NdimageError::DimensionError(
"Failed to convert back to original dimensionality".to_string(),
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::{s, Array2};
#[test]
fn test_grey_erosion() {
let mut input: Array2<f64> = Array2::from_elem((5, 5), 1.0);
input[[2, 2]] = 2.0;
let result = grey_erosion(&input, None, None, None, None, None)
.expect("grey_erosion should succeed for test");
assert_abs_diff_eq!(result[[2, 2]], 1.0, epsilon = 1e-10);
assert_eq!(result.shape(), input.shape());
}
#[test]
fn test_grey_erosion_with_constant_border() {
let input: Array2<f64> = Array2::from_elem((5, 5), 1.0);
let result = grey_erosion(
&input,
None,
None,
Some(MorphBorderMode::Constant),
Some(0.0),
None,
)
.expect("grey_erosion with constant border should succeed");
assert_abs_diff_eq!(result[[0, 0]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[0, 4]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[4, 0]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[4, 4]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[2, 2]], 1.0, epsilon = 1e-10);
}
#[test]
fn test_grey_dilation() {
let mut input: Array2<f64> = Array2::from_elem((5, 5), 1.0);
input[[2, 2]] = 2.0;
let result = grey_dilation(&input, None, None, None, None, None)
.expect("grey_dilation should succeed for test");
assert_abs_diff_eq!(result[[2, 2]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[1, 2]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[2, 1]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[3, 2]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[2, 3]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(result[[0, 0]], 1.0, epsilon = 1e-10);
}
#[test]
fn test_grey_opening() {
let mut input: Array2<f64> = Array2::from_elem((7, 7), 1.0);
input[[2, 2]] = 2.0;
input[[4, 4]] = 2.0;
let size = [3, 3];
let result = grey_opening(&input, Some(&size), None, None, None, None)
.expect("grey_opening should succeed for test");
assert!(result[[2, 2]] < 1.5);
assert!(result[[4, 4]] < 1.5);
assert_abs_diff_eq!(result[[0, 0]], 1.0, epsilon = 1e-10);
}
#[test]
fn test_grey_closing() {
let mut input: Array2<f64> = Array2::from_elem((7, 7), 1.0);
input[[2, 2]] = 0.0;
input[[4, 4]] = 0.0;
let result = grey_closing(&input, None, None, None, None, None)
.expect("grey_closing should succeed for test");
assert!(result[[2, 2]] > 0.5);
assert!(result[[4, 4]] > 0.5);
assert_abs_diff_eq!(result[[0, 0]], 1.0, epsilon = 1e-10);
}
#[test]
fn test_morphological_gradient() {
let mut input: Array2<f64> = Array2::from_elem((7, 7), 0.0);
input.slice_mut(s![0..7, 3..7]).fill(1.0);
let result = morphological_gradient(&input, None, None, None, None, None)
.expect("morphological_gradient should succeed for test");
for i in 0..7 {
assert!(result[[i, 2]] > 0.5);
}
for i in 0..7 {
for j in 0..2 {
assert_abs_diff_eq!(result[[i, j]], 0.0, epsilon = 1e-10);
}
for j in 4..7 {
assert_abs_diff_eq!(result[[i, j]], 0.0, epsilon = 1e-10);
}
}
}
#[test]
fn test_morphological_laplace() {
let mut input: Array2<f64> = Array2::from_elem((7, 7), 1.0);
input[[2, 2]] = 2.0; input[[4, 4]] = 0.0;
let result = morphological_laplace(&input, None, None, None, None, None)
.expect("morphological_laplace should succeed for test");
assert!(result[[2, 2]] > 0.0);
assert!(result[[4, 4]] > 0.0);
assert!(result[[0, 0]].abs() < 0.1);
}
#[test]
fn test_white_tophat() {
let mut input: Array2<f64> = Array2::from_elem((7, 7), 1.0);
input[[2, 2]] = 2.0;
input[[4, 4]] = 2.0;
let result = white_tophat(&input, None, None, None, None, None)
.expect("white_tophat should succeed for test");
assert!(result[[2, 2]] > 0.5);
assert!(result[[4, 4]] > 0.5);
assert!(result[[0, 0]].abs() < 0.1);
}
#[test]
fn test_black_tophat() {
let mut input: Array2<f64> = Array2::from_elem((7, 7), 1.0);
input[[2, 2]] = 0.0;
input[[4, 4]] = 0.0;
let result = black_tophat(&input, None, None, None, None, None)
.expect("black_tophat should succeed for test");
assert!(result[[2, 2]] > 0.5);
assert!(result[[4, 4]] > 0.5);
assert!(result[[0, 0]].abs() < 0.1);
}
#[test]
fn test_grey_erosion_3d() {
let mut input: scirs2_core::ndarray::Array<f64, scirs2_core::ndarray::Ix3> =
scirs2_core::ndarray::Array::from_elem((3, 3, 3), 1.0);
input[[1, 1, 1]] = 2.0;
let result = grey_erosion(&input, None, None, None, None, None)
.expect("grey_erosion 3D should succeed for test");
assert_abs_diff_eq!(result[[1, 1, 1]], 1.0, epsilon = 1e-10);
assert_eq!(result.shape(), input.shape());
}
#[test]
fn test_grey_dilation_3d() {
let mut input: scirs2_core::ndarray::Array<f64, scirs2_core::ndarray::Ix3> =
scirs2_core::ndarray::Array::from_elem((3, 3, 3), 1.0);
input[[1, 1, 1]] = 0.0;
let result = grey_dilation(&input, None, None, None, None, None)
.expect("grey_dilation 3D should succeed for test");
assert_abs_diff_eq!(result[[1, 1, 1]], 1.0, epsilon = 1e-10);
assert_eq!(result.shape(), input.shape());
}
}