use scirs2_core::ndarray::{Array, Dimension};
use super::dilation::binary_dilation;
use super::erosion::binary_erosion;
use crate::error::{NdimageError, NdimageResult};
#[allow(dead_code)]
pub fn binary_opening<D>(
input: &Array<bool, D>,
structure: Option<&Array<bool, D>>,
iterations: Option<usize>,
mask: Option<&Array<bool, D>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
brute_force: Option<bool>,
) -> NdimageResult<Array<bool, D>>
where
D: Dimension + 'static,
{
let eroded = binary_erosion(
input,
structure,
iterations,
mask,
border_value,
origin,
brute_force,
)?;
binary_dilation(
&eroded,
structure,
iterations,
mask,
border_value,
origin,
brute_force,
)
}
#[allow(dead_code)]
pub fn binary_closing<D>(
input: &Array<bool, D>,
structure: Option<&Array<bool, D>>,
iterations: Option<usize>,
mask: Option<&Array<bool, D>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
brute_force: Option<bool>,
) -> NdimageResult<Array<bool, D>>
where
D: Dimension + 'static,
{
let dilated = binary_dilation(
input,
structure,
iterations,
mask,
border_value,
origin,
brute_force,
)?;
binary_erosion(
&dilated,
structure,
iterations,
mask,
border_value,
origin,
brute_force,
)
}
#[allow(dead_code)]
pub fn binary_fill_holes<D>(
input: &Array<bool, D>,
_structure: Option<&Array<bool, D>>,
_origin: Option<&[isize]>,
) -> NdimageResult<Array<bool, D>>
where
D: Dimension + 'static,
{
Ok(input.clone())
}
#[allow(dead_code)]
pub fn binary_hit_or_miss<D>(
input: &Array<bool, D>,
structure1: Option<&Array<bool, D>>,
structure2: Option<&Array<bool, D>>,
mask: Option<&Array<bool, D>>,
border_value: Option<bool>,
origin1: Option<&[isize]>,
origin2: Option<&[isize]>,
) -> NdimageResult<Array<bool, D>>
where
D: Dimension + 'static,
{
match input.ndim() {
1 => {
let input_1d = input
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| NdimageError::DimensionError("Failed to convert to 1D".to_string()))?;
let result_1d = binary_hit_or_miss_1d(
&input_1d,
structure1,
structure2,
mask,
border_value,
origin1,
origin2,
)?;
Ok(result_1d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert from 1D".to_string())
})?)
}
2 => {
let input_2d = input
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| NdimageError::DimensionError("Failed to convert to 2D".to_string()))?;
let result_2d = binary_hit_or_miss_2d(
&input_2d,
structure1,
structure2,
mask,
border_value,
origin1,
origin2,
)?;
Ok(result_2d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert from 2D".to_string())
})?)
}
_ => {
let input_dyn = input
.clone()
.into_dimensionality::<scirs2_core::ndarray::IxDyn>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to IxDyn".to_string())
})?;
let ndim = input.ndim();
let s1_dyn: scirs2_core::ndarray::Array<bool, scirs2_core::ndarray::IxDyn> =
if let Some(s) = structure1 {
s.clone()
.into_dimensionality::<scirs2_core::ndarray::IxDyn>()
.map_err(|_| {
NdimageError::DimensionError(
"Failed to convert structure1 to IxDyn".to_string(),
)
})?
} else {
let side = vec![3usize; ndim];
let mut cross = scirs2_core::ndarray::Array::from_elem(
scirs2_core::ndarray::IxDyn(&side),
false,
);
let center_idx: Vec<usize> = vec![1; ndim];
cross[scirs2_core::ndarray::IxDyn(¢er_idx)] = true;
for ax in 0..ndim {
let mut lo = center_idx.clone();
lo[ax] = 0;
cross[scirs2_core::ndarray::IxDyn(&lo)] = true;
let mut hi = center_idx.clone();
hi[ax] = 2;
cross[scirs2_core::ndarray::IxDyn(&hi)] = true;
}
cross
};
let s2_dyn: scirs2_core::ndarray::Array<bool, scirs2_core::ndarray::IxDyn> =
if let Some(s) = structure2 {
s.clone()
.into_dimensionality::<scirs2_core::ndarray::IxDyn>()
.map_err(|_| {
NdimageError::DimensionError(
"Failed to convert structure2 to IxDyn".to_string(),
)
})?
} else {
s1_dyn.mapv(|v| !v)
};
let border = border_value.unwrap_or(false);
let center1: Vec<isize> = if let Some(o) = origin1 {
o.to_vec()
} else {
s1_dyn.shape().iter().map(|&s| s as isize / 2).collect()
};
let center2: Vec<isize> = if let Some(o) = origin2 {
o.to_vec()
} else {
s2_dyn.shape().iter().map(|&s| s as isize / 2).collect()
};
let input_shape = input_dyn.shape().to_vec();
let mut result_dyn = scirs2_core::ndarray::Array::from_elem(
scirs2_core::ndarray::IxDyn(&input_shape),
false,
);
for out_idx in scirs2_core::ndarray::indices(scirs2_core::ndarray::IxDyn(&input_shape))
{
let pos: Vec<isize> = out_idx.slice().iter().map(|&x| x as isize).collect();
if let Some(m) = mask {
let m_dyn = m
.clone()
.into_dimensionality::<scirs2_core::ndarray::IxDyn>()
.map_err(|_| {
NdimageError::DimensionError(
"Failed to convert mask to IxDyn".to_string(),
)
})?;
if !m_dyn[out_idx.clone()] {
continue;
}
}
let mut fg_hit = true;
'fg: for s_idx in scirs2_core::ndarray::indices(s1_dyn.raw_dim()) {
if !s1_dyn[s_idx.clone()] {
continue;
}
let src: Vec<isize> = s_idx
.slice()
.iter()
.enumerate()
.map(|(ax, &si)| pos[ax] + si as isize - center1[ax])
.collect();
let val = if src
.iter()
.enumerate()
.all(|(ax, &c)| c >= 0 && c < input_shape[ax] as isize)
{
let idx_vec: Vec<usize> = src.iter().map(|&c| c as usize).collect();
input_dyn[scirs2_core::ndarray::IxDyn(&idx_vec)]
} else {
border
};
if !val {
fg_hit = false;
break 'fg;
}
}
let mut bg_miss = true;
if fg_hit {
'bg: for s_idx in scirs2_core::ndarray::indices(s2_dyn.raw_dim()) {
if !s2_dyn[s_idx.clone()] {
continue;
}
let src: Vec<isize> = s_idx
.slice()
.iter()
.enumerate()
.map(|(ax, &si)| pos[ax] + si as isize - center2[ax])
.collect();
let val = if src
.iter()
.enumerate()
.all(|(ax, &c)| c >= 0 && c < input_shape[ax] as isize)
{
let idx_vec: Vec<usize> = src.iter().map(|&c| c as usize).collect();
input_dyn[scirs2_core::ndarray::IxDyn(&idx_vec)]
} else {
border
};
if val {
bg_miss = false;
break 'bg;
}
}
}
result_dyn[out_idx] = fg_hit && bg_miss;
}
result_dyn.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert result from IxDyn".to_string())
})
}
}
}
#[allow(dead_code)]
fn binary_hit_or_miss_1d<D>(
input: &Array<bool, scirs2_core::ndarray::Ix1>,
_structure1: Option<&Array<bool, D>>,
_structure2: Option<&Array<bool, D>>,
_mask: Option<&Array<bool, D>>,
_border_value: Option<bool>,
_origin1: Option<&[isize]>,
_origin2: Option<&[isize]>,
) -> NdimageResult<Array<bool, scirs2_core::ndarray::Ix1>>
where
D: Dimension + 'static,
{
Ok(input.clone())
}
#[allow(dead_code)]
fn binary_hit_or_miss_2d<D>(
input: &Array<bool, scirs2_core::ndarray::Ix2>,
structure1: Option<&Array<bool, D>>,
structure2: Option<&Array<bool, D>>,
mask: Option<&Array<bool, D>>,
border_value: Option<bool>,
origin1: Option<&[isize]>,
origin2: Option<&[isize]>,
) -> NdimageResult<Array<bool, scirs2_core::ndarray::Ix2>>
where
D: Dimension + 'static,
{
use scirs2_core::ndarray::Array2;
let border = border_value.unwrap_or(false);
let (rows, cols) = input.dim();
let default_structure1 = if let Some(s) = structure1 {
if s.ndim() != 2 {
return Err(NdimageError::DimensionError(
"Foreground structure must be 2D for 2D input".into(),
));
}
s.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert structure to 2D".to_string())
})?
} else {
let mut cross = Array2::from_elem((3, 3), false);
cross[[1, 0]] = true; cross[[1, 1]] = true; cross[[1, 2]] = true; cross[[0, 1]] = true; cross[[2, 1]] = true; cross
};
let default_structure2 = if let Some(s) = structure2 {
if s.ndim() != 2 {
return Err(NdimageError::DimensionError(
"Background structure must be 2D for 2D input".into(),
));
}
s.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert structure to 2D".to_string())
})?
} else {
let mut complement = Array2::from_elem(default_structure1.raw_dim(), false);
for ((i, j), &val) in default_structure1.indexed_iter() {
complement[[i, j]] = !val;
}
complement
};
let center1 = if let Some(orig) = origin1 {
if orig.len() != 2 {
return Err(NdimageError::InvalidInput(
"Origin must be 2D for 2D structure".into(),
));
}
[orig[0], orig[1]]
} else {
[
default_structure1.nrows() as isize / 2,
default_structure1.ncols() as isize / 2,
]
};
let center2 = if let Some(orig) = origin2 {
if orig.len() != 2 {
return Err(NdimageError::InvalidInput(
"Origin must be 2D for 2D structure".into(),
));
}
[orig[0], orig[1]]
} else {
[
default_structure2.nrows() as isize / 2,
default_structure2.ncols() as isize / 2,
]
};
let mut result = Array2::from_elem((rows, cols), false);
for i in 0..rows {
for j in 0..cols {
if let Some(m) = mask {
if m.ndim() != 2 {
return Err(NdimageError::DimensionError(
"Mask must be 2D for 2D input".into(),
));
}
let m_2d = m
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert mask to 2D".to_string())
})?;
if !m_2d[[i, j]] {
continue;
}
}
let mut foreground_hit = true;
for (si, sj) in scirs2_core::ndarray::indices(default_structure1.dim()) {
if !default_structure1[[si, sj]] {
continue; }
let input_i = i as isize + si as isize - center1[0];
let input_j = j as isize + sj as isize - center1[1];
let val = if input_i >= 0
&& input_i < rows as isize
&& input_j >= 0
&& input_j < cols as isize
{
input[[input_i as usize, input_j as usize]]
} else {
border
};
if !val {
foreground_hit = false;
break;
}
}
let mut background_miss = true;
if foreground_hit {
for (si, sj) in scirs2_core::ndarray::indices(default_structure2.dim()) {
if !default_structure2[[si, sj]] {
continue; }
let input_i = i as isize + si as isize - center2[0];
let input_j = j as isize + sj as isize - center2[1];
let val = if input_i >= 0
&& input_i < rows as isize
&& input_j >= 0
&& input_j < cols as isize
{
input[[input_i as usize, input_j as usize]]
} else {
border
};
if val {
background_miss = false;
break;
}
}
}
result[[i, j]] = foreground_hit && background_miss;
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
#[test]
fn test_binary_erosion() {
let input = Array2::from_elem((5, 5), true);
let result = binary_erosion(&input, None, None, None, None, None, None)
.expect("binary_erosion should succeed for test");
assert_eq!(result.shape(), input.shape());
assert!(result[[2, 2]]); assert!(result[[1, 1]]); assert!(result[[1, 3]]);
assert!(result[[3, 1]]);
assert!(result[[3, 3]]);
assert!(!result[[0, 2]]); assert!(!result[[2, 0]]); assert!(!result[[4, 2]]); assert!(!result[[2, 4]]); }
#[test]
fn test_binary_erosion_with_multiple_iterations() {
let input = Array2::from_elem((5, 5), true);
let result = binary_erosion(&input, None, Some(2), None, None, None, None)
.expect("binary_erosion with iterations should succeed for test");
assert!(result[[2, 2]]);
assert!(!result[[1, 1]]);
assert!(!result[[1, 3]]);
assert!(!result[[3, 1]]);
assert!(!result[[3, 3]]);
}
#[test]
fn test_binary_dilation() {
let mut input = Array2::from_elem((5, 5), false);
input[[2, 2]] = true;
let result = binary_dilation(&input, None, None, None, None, None, None)
.expect("binary_dilation should succeed for test");
assert!(result[[2, 2]]); assert!(result[[1, 2]]); assert!(result[[2, 1]]); assert!(result[[3, 2]]); assert!(result[[2, 3]]);
assert!(!result[[0, 0]]);
assert!(!result[[0, 4]]);
assert!(!result[[4, 0]]);
assert!(!result[[4, 4]]);
}
#[test]
fn test_binary_opening() {
let mut input = Array2::from_elem((7, 7), false);
input[[1, 1]] = true;
input[[1, 2]] = true;
input[[2, 1]] = true;
input[[2, 2]] = true;
input[[4, 4]] = true;
input[[4, 5]] = true;
input[[4, 6]] = true;
input[[5, 4]] = true;
input[[5, 5]] = true;
input[[5, 6]] = true;
input[[6, 4]] = true;
input[[6, 5]] = true;
input[[6, 6]] = true;
let result = binary_opening(&input, None, None, None, None, None, None)
.expect("binary_opening should succeed for test");
assert!(result[[5, 5]]);
assert!(!result[[1, 1]]);
assert!(!result[[1, 2]]);
assert!(!result[[2, 1]]);
assert!(!result[[2, 2]]);
}
#[test]
fn test_binary_closing() {
let mut input = Array2::from_elem((5, 5), false);
input[[1, 1]] = true;
input[[1, 2]] = true;
input[[1, 3]] = true;
input[[2, 1]] = true;
input[[2, 3]] = true;
input[[3, 1]] = true;
input[[3, 2]] = true;
input[[3, 3]] = true;
let result = binary_closing(&input, None, None, None, None, None, None)
.expect("binary_closing should succeed for test");
assert!(result[[2, 2]]);
assert!(result[[1, 1]]);
assert!(result[[1, 2]]);
assert!(result[[1, 3]]);
assert!(result[[2, 1]]);
assert!(result[[2, 3]]);
assert!(result[[3, 1]]);
assert!(result[[3, 2]]);
assert!(result[[3, 3]]);
}
#[test]
fn test_binary_erosion_3d() {
let mut input: scirs2_core::ndarray::Array<bool, scirs2_core::ndarray::Ix3> =
scirs2_core::ndarray::Array::from_elem((3, 3, 3), false);
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
input[[i, j, k]] = true;
}
}
}
let result = binary_erosion(&input, None, None, None, None, None, None)
.expect("binary_erosion 3D should succeed for test");
assert!(result[[1, 1, 1]]);
assert!(!result[[0, 0, 0]]);
assert!(!result[[0, 1, 1]]);
assert!(!result[[1, 0, 1]]);
assert!(!result[[1, 1, 0]]);
assert_eq!(result.shape(), input.shape());
}
#[test]
fn test_binary_dilation_3d() {
let mut input: scirs2_core::ndarray::Array<bool, scirs2_core::ndarray::Ix3> =
scirs2_core::ndarray::Array::from_elem((3, 3, 3), false);
input[[1, 1, 1]] = true;
let result = binary_dilation(&input, None, None, None, None, None, None)
.expect("binary_dilation 3D should succeed for test");
assert!(result[[1, 1, 1]]);
assert!(result[[0, 1, 1]]); assert!(result[[2, 1, 1]]); assert!(result[[1, 0, 1]]); assert!(result[[1, 2, 1]]); assert!(result[[1, 1, 0]]); assert!(result[[1, 1, 2]]);
assert!(!result[[0, 0, 0]]);
assert!(!result[[2, 2, 2]]);
assert_eq!(result.shape(), input.shape());
}
#[test]
fn test_binary_hit_or_miss_3d_basic() {
use scirs2_core::ndarray::Array3;
let mut input = Array3::from_elem((5, 5, 5), false);
input[[2, 2, 2]] = true;
let mut fg = Array3::from_elem((3, 3, 3), false);
fg[[1, 1, 1]] = true;
let bg = Array3::from_elem((3, 3, 3), false);
let result = binary_hit_or_miss(
&input,
Some(&fg),
Some(&bg),
None::<&Array3<bool>>,
None,
None,
None,
)
.expect("binary_hit_or_miss 3D should succeed");
assert_eq!(result.shape(), input.shape(), "Shape must be preserved");
assert!(
result[[2, 2, 2]],
"Centre of isolated foreground should be detected"
);
let n_true = result.iter().filter(|&&v| v).count();
assert_eq!(n_true, 1, "Exactly one voxel should be detected");
}
#[test]
fn test_binary_hit_or_miss_3d_all_foreground() {
use scirs2_core::ndarray::Array3;
let input = Array3::from_elem((5, 5, 5), true);
let result = binary_hit_or_miss(
&input,
None::<&Array3<bool>>,
None::<&Array3<bool>>,
None::<&Array3<bool>>,
None,
None,
None,
)
.expect("binary_hit_or_miss 3D all-foreground should succeed");
assert_eq!(result.shape(), input.shape());
let n_true = result.iter().filter(|&&v| v).count();
assert_eq!(
n_true, 0,
"No voxel should be detected when entire volume is foreground"
);
}
#[test]
fn test_binary_hit_or_miss_3d_custom_structures() {
use scirs2_core::ndarray::Array3;
let mut input = Array3::from_elem((5, 5, 5), false);
input[[2, 2, 2]] = true;
let mut fg = Array3::from_elem((3, 3, 3), false);
fg[[1, 1, 1]] = true;
let mut bg = Array3::from_elem((3, 3, 3), false);
bg[[0, 1, 1]] = true; bg[[2, 1, 1]] = true; bg[[1, 0, 1]] = true; bg[[1, 2, 1]] = true; bg[[1, 1, 0]] = true; bg[[1, 1, 2]] = true;
let result = binary_hit_or_miss(
&input,
Some(&fg),
Some(&bg),
None::<&Array3<bool>>,
None,
None,
None,
)
.expect("binary_hit_or_miss 3D custom should succeed");
assert_eq!(result.shape(), input.shape());
assert!(
result[[2, 2, 2]],
"Isolated foreground point should be detected"
);
let n_true = result.iter().filter(|&&v| v).count();
assert_eq!(n_true, 1, "Exactly one voxel should be detected");
}
}