use scirs2_core::ndarray::{Array, Array1, Array2, Dimension, Ix1, Ix2, IxDyn};
use super::structuring::generate_binary_structure_dyn;
use super::utils::get_structure_center_dyn;
use crate::error::{NdimageError, NdimageResult};
#[allow(dead_code)]
pub fn binary_erosion<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,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
match input.ndim() {
1 => {
if let Ok(input_1d) = input.clone().into_dimensionality::<Ix1>() {
let structure_1d = match structure {
Some(s) => {
if let Ok(s1d) = s.clone().into_dimensionality::<Ix1>() {
Some(s1d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert structure to 1D".to_string(),
));
}
}
None => None,
};
let mask_1d = match mask {
Some(m) => {
if let Ok(m1d) = m.clone().into_dimensionality::<Ix1>() {
Some(m1d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert mask to 1D".to_string(),
));
}
}
None => None,
};
let result_1d = binary_erosion1d(
&input_1d,
structure_1d.as_ref(),
iterations,
mask_1d.as_ref(),
border_value,
origin,
brute_force,
)?;
return result_1d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
}
2 => {
if let Ok(input_2d) = input.clone().into_dimensionality::<Ix2>() {
let structure_2d = match structure {
Some(s) => {
if let Ok(s2d) = s.clone().into_dimensionality::<Ix2>() {
Some(s2d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert structure to 2D".to_string(),
));
}
}
None => None,
};
let mask_2d = match mask {
Some(m) => {
if let Ok(m2d) = m.clone().into_dimensionality::<Ix2>() {
Some(m2d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert mask to 2D".to_string(),
));
}
}
None => None,
};
let result_2d = binary_erosion2d(
&input_2d,
structure_2d.as_ref(),
iterations,
mask_2d.as_ref(),
border_value,
origin,
brute_force,
)?;
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
}
_ => {
if let Ok(input_dyn) = input.clone().into_dimensionality::<IxDyn>() {
let structure_dyn = match structure {
Some(s) => {
if let Ok(sdyn) = s.clone().into_dimensionality::<IxDyn>() {
Some(sdyn)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert structure to dynamic dimension".to_string(),
));
}
}
None => None,
};
let mask_dyn = match mask {
Some(m) => {
if let Ok(mdyn) = m.clone().into_dimensionality::<IxDyn>() {
Some(mdyn)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert mask to dynamic dimension".to_string(),
));
}
}
None => None,
};
let result_dyn = binary_erosion_dyn(
&input_dyn,
structure_dyn.as_ref(),
iterations,
mask_dyn.as_ref(),
border_value,
origin,
brute_force,
)?;
return result_dyn.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
}
}
Err(NdimageError::DimensionError(
"Unsupported array dimensions for erosion".to_string(),
))
}
#[allow(dead_code)]
fn binary_erosion1d(
input: &Array1<bool>,
structure: Option<&Array1<bool>>,
iterations: Option<usize>,
mask: Option<&Array1<bool>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
brute_force: Option<bool>,
) -> NdimageResult<Array1<bool>> {
let iters = iterations.unwrap_or(1);
let border_val = border_value.unwrap_or(false);
let brute_force_algo = brute_force.unwrap_or(false);
let owned_structure;
let struct_elem = if let Some(s) = structure {
s
} else {
owned_structure = Array1::from_elem(3, true);
&owned_structure
};
let origin_vec: Vec<isize> = if let Some(o) = origin {
if o.len() != 1 {
return Err(NdimageError::DimensionError(format!(
"Origin must have same length as input dimensions (got {} expected {})",
o.len(),
1
)));
}
o.to_vec()
} else {
vec![(struct_elem.len() as isize) / 2]
};
let mut result = input.to_owned();
for _ in 0..iters {
let mut temp = Array1::from_elem(input.len(), false);
let prev = result.clone();
for (i, val) in temp.indexed_iter_mut() {
if let Some(m) = mask {
if !m[i] {
*val = prev[i];
continue;
}
}
let mut fits = true;
for (s_i, &s_val) in struct_elem.indexed_iter() {
if !s_val {
continue; }
let offset = s_i as isize - origin_vec[0];
let pos = i as isize + offset;
if pos < 0 || pos >= prev.len() as isize {
if !border_val {
fits = false;
break;
}
} else if !prev[pos as usize] {
fits = false;
break;
}
}
*val = fits;
}
result = temp;
if !brute_force_algo && result == prev {
break;
}
}
Ok(result)
}
#[allow(dead_code)]
fn binary_erosion2d(
input: &Array2<bool>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
mask: Option<&Array2<bool>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
brute_force: Option<bool>,
) -> NdimageResult<Array2<bool>> {
let iters = iterations.unwrap_or(1);
let border_val = border_value.unwrap_or(false);
let brute_force_algo = brute_force.unwrap_or(false);
let owned_structure;
let struct_elem = if let Some(s) = structure {
s
} else {
let size = [3, 3];
owned_structure = Array2::from_elem((size[0], size[1]), true);
&owned_structure
};
let origin_vec: Vec<isize> = if let Some(o) = origin {
if o.len() != 2 {
return Err(NdimageError::DimensionError(format!(
"Origin must have same length as input dimensions (got {} expected {})",
o.len(),
2
)));
}
o.to_vec()
} else {
struct_elem
.shape()
.iter()
.map(|&s| (s as isize) / 2)
.collect()
};
let shape = input.shape();
let mut result = input.to_owned();
for iter in 0..iters {
let prev = result.clone();
let mut temp = Array2::from_elem((shape[0], shape[1]), false);
let s_rows = struct_elem.shape()[0];
let s_cols = struct_elem.shape()[1];
let half_height = origin_vec[0];
let half_width = origin_vec[1];
for i in 0..shape[0] {
for j in 0..shape[1] {
if let Some(m) = mask {
if !m[[i, j]] {
temp[[i, j]] = prev[[i, j]];
continue;
}
}
let mut fits = true;
'outer: for si in 0..s_rows {
for sj in 0..s_cols {
if !struct_elem[[si, sj]] {
continue; }
let ni = i as isize + (si as isize - half_height);
let nj = j as isize + (sj as isize - half_width);
if ni < 0 || ni >= shape[0] as isize || nj < 0 || nj >= shape[1] as isize {
if !border_val {
fits = false;
break 'outer;
}
} else if !prev[[ni as usize, nj as usize]] {
fits = false;
break 'outer;
}
}
}
temp[[i, j]] = fits;
}
}
result = temp;
if !brute_force_algo && iter > 0 && result == prev {
break;
}
}
Ok(result)
}
#[allow(dead_code)]
fn binary_erosion_dyn(
input: &Array<bool, IxDyn>,
structure: Option<&Array<bool, IxDyn>>,
iterations: Option<usize>,
mask: Option<&Array<bool, IxDyn>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
_brute_force: Option<bool>,
) -> NdimageResult<Array<bool, IxDyn>> {
let iterations = iterations.unwrap_or(1);
let border = border_value.unwrap_or(false);
let default_structure = if let Some(s) = structure {
s.to_owned()
} else {
generate_binary_structure_dyn(input.ndim())?
};
if input.ndim() != default_structure.ndim() {
return Err(NdimageError::DimensionError(
"Input and structure must have the same number of dimensions".into(),
));
}
if let Some(m) = mask {
if m.ndim() != input.ndim() || m.shape() != input.shape() {
return Err(NdimageError::InvalidInput(
"Mask must have the same shape as input".into(),
));
}
}
let center = get_structure_center_dyn(&default_structure, origin)?;
let mut result = input.to_owned();
for _ in 0..iterations {
let temp = result.clone();
for idx in scirs2_core::ndarray::indices(input.shape()) {
let idx_vec: Vec<_> = idx.slice().to_vec();
if let Some(m) = mask {
if !m[idx_vec.as_slice()] {
continue;
}
}
let mut all_fit = true;
for str_idx in scirs2_core::ndarray::indices(default_structure.shape()) {
let str_idx_vec: Vec<_> = str_idx.slice().to_vec();
if !default_structure[str_idx_vec.as_slice()] {
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[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[input_idx.as_slice()]
} else {
border
};
if !val {
all_fit = false;
break;
}
}
result[idx_vec.as_slice()] = all_fit;
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn binary_dilation<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,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
match input.ndim() {
1 => {
if let Ok(input_1d) = input.clone().into_dimensionality::<Ix1>() {
let structure_1d = match structure {
Some(s) => {
if let Ok(s1d) = s.clone().into_dimensionality::<Ix1>() {
Some(s1d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert structure to 1D".to_string(),
));
}
}
None => None,
};
let mask_1d = match mask {
Some(m) => {
if let Ok(m1d) = m.clone().into_dimensionality::<Ix1>() {
Some(m1d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert mask to 1D".to_string(),
));
}
}
None => None,
};
let result_1d = binary_dilation1d(
&input_1d,
structure_1d.as_ref(),
iterations,
mask_1d.as_ref(),
border_value,
origin,
brute_force,
)?;
return result_1d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
}
2 => {
if let Ok(input_2d) = input.clone().into_dimensionality::<Ix2>() {
let structure_2d = match structure {
Some(s) => {
if let Ok(s2d) = s.clone().into_dimensionality::<Ix2>() {
Some(s2d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert structure to 2D".to_string(),
));
}
}
None => None,
};
let mask_2d = match mask {
Some(m) => {
if let Ok(m2d) = m.clone().into_dimensionality::<Ix2>() {
Some(m2d)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert mask to 2D".to_string(),
));
}
}
None => None,
};
let result_2d = binary_dilation2d(
&input_2d,
structure_2d.as_ref(),
iterations,
mask_2d.as_ref(),
border_value,
origin,
brute_force,
)?;
return result_2d.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
}
_ => {
if let Ok(input_dyn) = input.clone().into_dimensionality::<IxDyn>() {
let structure_dyn = match structure {
Some(s) => {
if let Ok(sdyn) = s.clone().into_dimensionality::<IxDyn>() {
Some(sdyn)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert structure to dynamic dimension".to_string(),
));
}
}
None => None,
};
let mask_dyn = match mask {
Some(m) => {
if let Ok(mdyn) = m.clone().into_dimensionality::<IxDyn>() {
Some(mdyn)
} else {
return Err(NdimageError::DimensionError(
"Failed to convert mask to dynamic dimension".to_string(),
));
}
}
None => None,
};
let result_dyn = binary_dilation_dyn(
&input_dyn,
structure_dyn.as_ref(),
iterations,
mask_dyn.as_ref(),
border_value,
origin,
brute_force,
)?;
return result_dyn.into_dimensionality().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensionality".to_string(),
)
});
}
}
}
Err(NdimageError::DimensionError(
"Unsupported array dimensions for dilation".to_string(),
))
}
#[allow(dead_code)]
fn binary_dilation1d(
input: &Array1<bool>,
structure: Option<&Array1<bool>>,
iterations: Option<usize>,
mask: Option<&Array1<bool>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
brute_force: Option<bool>,
) -> NdimageResult<Array1<bool>> {
let iters = iterations.unwrap_or(1);
let border_val = border_value.unwrap_or(false);
let brute_force_algo = brute_force.unwrap_or(false);
let owned_structure;
let struct_elem = if let Some(s) = structure {
s
} else {
owned_structure = Array1::from_elem(3, true);
&owned_structure
};
let origin_vec: Vec<isize> = if let Some(o) = origin {
if o.len() != 1 {
return Err(NdimageError::DimensionError(format!(
"Origin must have same length as input dimensions (got {} expected {})",
o.len(),
1
)));
}
o.to_vec()
} else {
vec![(struct_elem.len() as isize) / 2]
};
let mut result = input.to_owned();
for _ in 0..iters {
let mut temp = Array1::from_elem(input.len(), false);
let prev = result.clone();
for (i, val) in temp.indexed_iter_mut() {
if let Some(m) = mask {
if !m[i] {
*val = prev[i];
continue;
}
}
*val = prev[i];
if *val {
continue;
}
for (s_i, &s_val) in struct_elem.indexed_iter() {
if !s_val {
continue; }
let offset = origin_vec[0] - s_i as isize;
let pos = i as isize + offset;
if pos < 0 || pos >= prev.len() as isize {
if border_val {
*val = true;
break;
}
} else if prev[pos as usize] {
*val = true;
break;
}
}
}
result = temp;
if !brute_force_algo && result == prev {
break;
}
}
Ok(result)
}
#[allow(dead_code)]
fn binary_dilation2d(
input: &Array2<bool>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
mask: Option<&Array2<bool>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
brute_force: Option<bool>,
) -> NdimageResult<Array2<bool>> {
let iters = iterations.unwrap_or(1);
let border_val = border_value.unwrap_or(false);
let brute_force_algo = brute_force.unwrap_or(false);
let owned_structure;
let struct_elem = if let Some(s) = structure {
s
} else {
let size = [3, 3];
owned_structure = Array2::from_elem((size[0], size[1]), true);
&owned_structure
};
let origin_vec: Vec<isize> = if let Some(o) = origin {
if o.len() != 2 {
return Err(NdimageError::DimensionError(format!(
"Origin must have same length as input dimensions (got {} expected {})",
o.len(),
2
)));
}
o.to_vec()
} else {
struct_elem
.shape()
.iter()
.map(|&s| (s as isize) / 2)
.collect()
};
let shape = input.shape();
let mut result = input.to_owned();
for iter in 0..iters {
let prev = result.clone();
let mut temp = Array2::from_elem((shape[0], shape[1]), false);
let s_rows = struct_elem.shape()[0];
let s_cols = struct_elem.shape()[1];
let half_height = origin_vec[0];
let half_width = origin_vec[1];
for i in 0..shape[0] {
for j in 0..shape[1] {
if let Some(m) = mask {
if !m[[i, j]] {
temp[[i, j]] = prev[[i, j]];
continue;
}
}
temp[[i, j]] = prev[[i, j]];
if temp[[i, j]] {
continue;
}
let mut found_true = false;
'outer: for si in 0..s_rows {
for sj in 0..s_cols {
if !struct_elem[[si, sj]] {
continue; }
let ni = i as isize - (si as isize - half_height);
let nj = j as isize - (sj as isize - half_width);
if ni < 0 || ni >= shape[0] as isize || nj < 0 || nj >= shape[1] as isize {
if border_val {
found_true = true;
break 'outer;
}
} else if prev[[ni as usize, nj as usize]] {
found_true = true;
break 'outer;
}
}
}
if found_true {
temp[[i, j]] = true;
}
}
}
result = temp;
if !brute_force_algo && iter > 0 && result == prev {
break;
}
}
Ok(result)
}
#[allow(dead_code)]
fn binary_dilation_dyn(
input: &Array<bool, IxDyn>,
structure: Option<&Array<bool, IxDyn>>,
iterations: Option<usize>,
mask: Option<&Array<bool, IxDyn>>,
border_value: Option<bool>,
origin: Option<&[isize]>,
_brute_force: Option<bool>,
) -> NdimageResult<Array<bool, IxDyn>> {
let iterations = iterations.unwrap_or(1);
let border = border_value.unwrap_or(false);
let default_structure = if let Some(s) = structure {
s.to_owned()
} else {
generate_binary_structure_dyn(input.ndim())?
};
if input.ndim() != default_structure.ndim() {
return Err(NdimageError::DimensionError(
"Input and structure must have the same number of dimensions".into(),
));
}
if let Some(m) = mask {
if m.ndim() != input.ndim() || m.shape() != input.shape() {
return Err(NdimageError::InvalidInput(
"Mask must have the same shape as input".into(),
));
}
}
let center = get_structure_center_dyn(&default_structure, origin)?;
let mut result = input.to_owned();
for _ in 0..iterations {
let temp = result.clone();
for idx in scirs2_core::ndarray::indices(input.shape()) {
let idx_vec: Vec<_> = idx.slice().to_vec();
if let Some(m) = mask {
if !m[idx_vec.as_slice()] {
continue;
}
}
let mut any_fit = false;
for str_idx in scirs2_core::ndarray::indices(default_structure.shape()) {
let str_idx_vec: Vec<_> = str_idx.slice().to_vec();
if !default_structure[str_idx_vec.as_slice()] {
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[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[input_idx.as_slice()]
} else {
border
};
if val {
any_fit = true;
break;
}
}
result[idx_vec.as_slice()] = any_fit;
}
}
Ok(result)
}
#[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())
})?)
}
_ => {
Err(NdimageError::ImplementationError(
"Binary hit-or-miss transform for arrays with more than 2 dimensions is not yet implemented".into(),
))
}
}
}
#[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());
}
}