use scirs2_core::ndarray::{Array2, Axis};
use scirs2_core::numeric::{Float, FromPrimitive};
use scirs2_core::parallel_ops::{self};
use scirs2_core::simd_ops::SimdUnifiedOps;
use std::fmt::Debug;
use std::sync::Arc;
use crate::error::NdimageResult;
#[allow(dead_code)]
pub fn grey_erosion_2d_optimized<T>(
input: &Array2<T>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
border_value: Option<T>,
origin: Option<&[isize; 2]>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
T: SimdUnifiedOps,
{
let iters = iterations.unwrap_or(1);
let _border_val = border_value.unwrap_or_else(|| T::from_f64(0.0).expect("Operation failed"));
let default_structure = Array2::from_elem((3, 3), true);
let struct_elem = structure.unwrap_or(&default_structure);
let default_origin = [
(struct_elem.shape()[0] / 2) as isize,
(struct_elem.shape()[1] / 2) as isize,
];
let struct_origin = origin.unwrap_or(&default_origin);
let (height, width) = input.dim();
let (s_height, s_width) = struct_elem.dim();
let mut offsets = Vec::new();
for si in 0..s_height {
for sj in 0..s_width {
if struct_elem[[si, sj]] {
offsets.push((
si as isize - struct_origin[0],
sj as isize - struct_origin[1],
));
}
}
}
let offsets = Arc::new(offsets);
let use_parallel = height * width > 10_000;
let mut buffer1 = input.to_owned();
let mut buffer2 = Array2::from_elem(input.dim(), T::zero());
for iter in 0..iters {
let (src, dst) = if iter % 2 == 0 {
(&buffer1, &mut buffer2)
} else {
(&buffer2, &mut buffer1)
};
if use_parallel {
erosion_iteration_parallel(src, dst, &offsets, height, width);
} else {
erosion_iteration_simd(src, dst, &offsets, height, width);
}
}
Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
}
#[allow(dead_code)]
fn erosion_iteration_simd<T>(
src: &Array2<T>,
dst: &mut Array2<T>,
offsets: &[(isize, isize)],
height: usize,
width: usize,
) where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
T: SimdUnifiedOps,
{
for i in 0..height {
let mut row_slice = dst.row_mut(i);
for j in 0..width {
let mut min_val = T::infinity();
for &(di, dj) in offsets.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
src[[ni as usize, nj as usize]]
} else {
let ri = ni.clamp(0, (height as isize) - 1) as usize;
let rj = nj.clamp(0, (width as isize) - 1) as usize;
src[[ri, rj]]
};
min_val = min_val.min(val);
}
row_slice[j] = min_val;
}
}
}
#[allow(dead_code)]
fn erosion_iteration_parallel<T>(
src: &Array2<T>,
dst: &mut Array2<T>,
offsets: &Arc<Vec<(isize, isize)>>,
height: usize,
width: usize,
) where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
{
use parallel_ops::*;
let offsets_clone = offsets.clone();
dst.axis_iter_mut(Axis(0))
.into_par_iter()
.enumerate()
.for_each(|(i, mut row)| {
let src_ref = src;
for j in 0..width {
let mut min_val = T::infinity();
for &(di, dj) in offsets_clone.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
src_ref[[ni as usize, nj as usize]]
} else {
let ri = ni.clamp(0, (height as isize) - 1) as usize;
let rj = nj.clamp(0, (width as isize) - 1) as usize;
src_ref[[ri, rj]]
};
min_val = min_val.min(val);
}
row[j] = min_val;
}
});
}
#[allow(dead_code)]
pub fn grey_dilation_2d_optimized<T>(
input: &Array2<T>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
border_value: Option<T>,
origin: Option<&[isize; 2]>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
T: SimdUnifiedOps,
{
let iters = iterations.unwrap_or(1);
let _border_val = border_value.unwrap_or_else(|| T::from_f64(0.0).expect("Operation failed"));
let default_structure = Array2::from_elem((3, 3), true);
let struct_elem = structure.unwrap_or(&default_structure);
let default_origin = [
(struct_elem.shape()[0] / 2) as isize,
(struct_elem.shape()[1] / 2) as isize,
];
let struct_origin = origin.unwrap_or(&default_origin);
let (height, width) = input.dim();
let (s_height, s_width) = struct_elem.dim();
let mut offsets = Vec::new();
for si in 0..s_height {
for sj in 0..s_width {
if struct_elem[[si, sj]] {
offsets.push((
-(si as isize - struct_origin[0]),
-(sj as isize - struct_origin[1]),
));
}
}
}
let offsets = Arc::new(offsets);
let use_parallel = height * width > 10_000;
let mut buffer1 = input.to_owned();
let mut buffer2 = Array2::from_elem(input.dim(), T::zero());
for iter in 0..iters {
let (src, dst) = if iter % 2 == 0 {
(&buffer1, &mut buffer2)
} else {
(&buffer2, &mut buffer1)
};
if use_parallel {
dilation_iteration_parallel(src, dst, &offsets, height, width);
} else {
dilation_iteration_simd(src, dst, &offsets, height, width);
}
}
Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
}
#[allow(dead_code)]
fn dilation_iteration_simd<T>(
src: &Array2<T>,
dst: &mut Array2<T>,
offsets: &[(isize, isize)],
height: usize,
width: usize,
) where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
T: SimdUnifiedOps,
{
for i in 0..height {
let mut row_slice = dst.row_mut(i);
for j in 0..width {
let mut max_val = T::neg_infinity();
for &(di, dj) in offsets.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
src[[ni as usize, nj as usize]]
} else {
let ri = ni.clamp(0, (height as isize) - 1) as usize;
let rj = nj.clamp(0, (width as isize) - 1) as usize;
src[[ri, rj]]
};
max_val = max_val.max(val);
}
row_slice[j] = max_val;
}
}
}
#[allow(dead_code)]
fn dilation_iteration_parallel<T>(
src: &Array2<T>,
dst: &mut Array2<T>,
offsets: &Arc<Vec<(isize, isize)>>,
height: usize,
width: usize,
) where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
{
use parallel_ops::*;
let offsets_clone = offsets.clone();
dst.axis_iter_mut(Axis(0))
.into_par_iter()
.enumerate()
.for_each(|(i, mut row)| {
let src_ref = src;
for j in 0..width {
let mut max_val = T::neg_infinity();
for &(di, dj) in offsets_clone.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
let val = if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
src_ref[[ni as usize, nj as usize]]
} else {
let ri = ni.clamp(0, (height as isize) - 1) as usize;
let rj = nj.clamp(0, (width as isize) - 1) as usize;
src_ref[[ri, rj]]
};
max_val = max_val.max(val);
}
row[j] = max_val;
}
});
}
#[allow(dead_code)]
pub fn binary_erosion_2d_optimized(
input: &Array2<bool>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
mask: Option<&Array2<bool>>,
origin: Option<&[isize; 2]>,
) -> NdimageResult<Array2<bool>> {
let iters = iterations.unwrap_or(1);
let default_structure = Array2::from_elem((3, 3), true);
let struct_elem = structure.unwrap_or(&default_structure);
let default_origin = [
(struct_elem.shape()[0] / 2) as isize,
(struct_elem.shape()[1] / 2) as isize,
];
let struct_origin = origin.unwrap_or(&default_origin);
let (height, width) = input.dim();
let (s_height, s_width) = struct_elem.dim();
let mut offsets = Vec::new();
for si in 0..s_height {
for sj in 0..s_width {
if struct_elem[[si, sj]] {
offsets.push((
si as isize - struct_origin[0],
sj as isize - struct_origin[1],
));
}
}
}
let offsets = Arc::new(offsets);
let use_parallel = height * width > 10_000;
let mut buffer1 = input.to_owned();
let mut buffer2 = Array2::from_elem(input.dim(), false);
for iter in 0..iters {
let (src, dst) = if iter % 2 == 0 {
(&buffer1, &mut buffer2)
} else {
(&buffer2, &mut buffer1)
};
if use_parallel {
binary_erosion_iteration_parallel(src, dst, &offsets, height, width, mask);
} else {
binary_erosion_iteration_sequential(src, dst, &offsets, height, width, mask);
}
}
Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
}
#[allow(dead_code)]
fn binary_erosion_iteration_sequential(
src: &Array2<bool>,
dst: &mut Array2<bool>,
offsets: &[(isize, isize)],
height: usize,
width: usize,
mask: Option<&Array2<bool>>,
) {
for i in 0..height {
for j in 0..width {
if let Some(m) = mask {
if !m[[i, j]] {
dst[[i, j]] = src[[i, j]];
continue;
}
}
let mut eroded = true;
for &(di, dj) in offsets.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
if !src[[ni as usize, nj as usize]] {
eroded = false;
break;
}
} else {
eroded = false;
break;
}
}
dst[[i, j]] = eroded;
}
}
}
#[allow(dead_code)]
fn binary_erosion_iteration_parallel(
src: &Array2<bool>,
dst: &mut Array2<bool>,
offsets: &Arc<Vec<(isize, isize)>>,
height: usize,
width: usize,
mask: Option<&Array2<bool>>,
) {
use parallel_ops::*;
let offsets_clone = offsets.clone();
dst.axis_iter_mut(Axis(0))
.into_par_iter()
.enumerate()
.for_each(|(i, mut row)| {
let src_ref = src;
let mask_ref = mask;
for j in 0..width {
if let Some(m) = mask_ref {
if !m[[i, j]] {
row[j] = src_ref[[i, j]];
continue;
}
}
let mut eroded = true;
for &(di, dj) in offsets_clone.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
if !src_ref[[ni as usize, nj as usize]] {
eroded = false;
break;
}
} else {
eroded = false;
break;
}
}
row[j] = eroded;
}
});
}
#[allow(dead_code)]
pub fn binary_dilation_2d_optimized(
input: &Array2<bool>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
mask: Option<&Array2<bool>>,
origin: Option<&[isize; 2]>,
) -> NdimageResult<Array2<bool>> {
let iters = iterations.unwrap_or(1);
let default_structure = Array2::from_elem((3, 3), true);
let struct_elem = structure.unwrap_or(&default_structure);
let default_origin = [
(struct_elem.shape()[0] / 2) as isize,
(struct_elem.shape()[1] / 2) as isize,
];
let struct_origin = origin.unwrap_or(&default_origin);
let (height, width) = input.dim();
let (s_height, s_width) = struct_elem.dim();
let mut offsets = Vec::new();
for si in 0..s_height {
for sj in 0..s_width {
if struct_elem[[si, sj]] {
offsets.push((
-(si as isize - struct_origin[0]),
-(sj as isize - struct_origin[1]),
));
}
}
}
let offsets = Arc::new(offsets);
let use_parallel = height * width > 10_000;
let mut buffer1 = input.to_owned();
let mut buffer2 = Array2::from_elem(input.dim(), false);
for iter in 0..iters {
let (src, dst) = if iter % 2 == 0 {
(&buffer1, &mut buffer2)
} else {
(&buffer2, &mut buffer1)
};
if use_parallel {
binary_dilation_iteration_parallel(src, dst, &offsets, height, width, mask);
} else {
binary_dilation_iteration_sequential(src, dst, &offsets, height, width, mask);
}
}
Ok(if iters % 2 == 0 { buffer1 } else { buffer2 })
}
#[allow(dead_code)]
fn binary_dilation_iteration_sequential(
src: &Array2<bool>,
dst: &mut Array2<bool>,
offsets: &[(isize, isize)],
height: usize,
width: usize,
mask: Option<&Array2<bool>>,
) {
for i in 0..height {
for j in 0..width {
if let Some(m) = mask {
if !m[[i, j]] {
dst[[i, j]] = src[[i, j]];
continue;
}
}
let mut dilated = false;
for &(di, dj) in offsets.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
if src[[ni as usize, nj as usize]] {
dilated = true;
break;
}
}
}
dst[[i, j]] = dilated;
}
}
}
#[allow(dead_code)]
fn binary_dilation_iteration_parallel(
src: &Array2<bool>,
dst: &mut Array2<bool>,
offsets: &Arc<Vec<(isize, isize)>>,
height: usize,
width: usize,
mask: Option<&Array2<bool>>,
) {
use parallel_ops::*;
let offsets_clone = offsets.clone();
dst.axis_iter_mut(Axis(0))
.into_par_iter()
.enumerate()
.for_each(|(i, mut row)| {
let src_ref = src;
let mask_ref = mask;
for j in 0..width {
if let Some(m) = mask_ref {
if !m[[i, j]] {
row[j] = src_ref[[i, j]];
continue;
}
}
let mut dilated = false;
for &(di, dj) in offsets_clone.iter() {
let ni = i as isize + di;
let nj = j as isize + dj;
if ni >= 0 && ni < height as isize && nj >= 0 && nj < width as isize {
if src_ref[[ni as usize, nj as usize]] {
dilated = true;
break;
}
}
}
row[j] = dilated;
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_grey_erosion_optimized() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let result =
grey_erosion_2d_optimized(&input, None, None, None, None).expect("Operation failed");
assert_eq!(result[[1, 1]], 1.0);
}
#[test]
fn test_grey_dilation_optimized() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let result =
grey_dilation_2d_optimized(&input, None, None, None, None).expect("Operation failed");
assert_eq!(result[[1, 1]], 9.0);
}
#[test]
fn test_binary_erosion_optimized() {
let input = array![
[false, true, true],
[false, true, true],
[false, false, false]
];
let result =
binary_erosion_2d_optimized(&input, None, None, None, None).expect("Operation failed");
assert!(!result[[1, 1]]);
}
#[test]
fn test_binary_dilation_optimized() {
let input = array![
[false, true, false],
[false, true, false],
[false, false, false]
];
let result =
binary_dilation_2d_optimized(&input, None, None, None, None).expect("Operation failed");
assert!(result[[0, 0]]);
assert!(result[[1, 0]]);
}
}
#[derive(Debug, Clone)]
pub struct MultiScaleMorphConfig {
pub scales: Vec<usize>,
pub operation: MorphOperation,
pub structure_type: StructureType,
pub normalize: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MorphOperation {
Erosion,
Dilation,
Opening,
Closing,
Gradient,
TopHat,
BlackHat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructureType {
Box,
Disk,
Cross,
Diamond,
}
impl Default for MultiScaleMorphConfig {
fn default() -> Self {
Self {
scales: vec![1, 3, 5, 7],
operation: MorphOperation::Opening,
structure_type: StructureType::Disk,
normalize: true,
}
}
}
#[allow(dead_code)]
pub fn geodesic_erosion_2d<T>(
marker: &Array2<T>,
mask: &Array2<T>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ 'static
+ PartialOrd
+ std::ops::AddAssign
+ std::ops::DivAssign,
T: SimdUnifiedOps,
{
if marker.shape() != mask.shape() {
return Err(crate::error::NdimageError::DimensionError(
"Marker and mask must have the same shape".into(),
));
}
let max_iters = iterations.unwrap_or(1000);
let mut current = marker.clone();
let mut previous = Array2::zeros(marker.dim());
let default_structure = Array2::from_elem((3, 3), true);
let struct_elem = structure.unwrap_or(&default_structure);
for iter in 0..max_iters {
previous.assign(¤t);
current = grey_erosion_2d_optimized(¤t, Some(struct_elem), Some(1), None, None)?;
for ((c, m), p) in current.iter_mut().zip(mask.iter()).zip(previous.iter()) {
*c = (*c).max(*m);
}
if iter > 0 {
let mut converged = true;
for (c, p) in current.iter().zip(previous.iter()) {
if (*c - *p).abs() > T::from_f64(1e-10).unwrap_or(T::epsilon()) {
converged = false;
break;
}
}
if converged {
break;
}
}
}
Ok(current)
}
#[allow(dead_code)]
pub fn geodesic_dilation_2d<T>(
marker: &Array2<T>,
mask: &Array2<T>,
structure: Option<&Array2<bool>>,
iterations: Option<usize>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ 'static
+ PartialOrd
+ std::ops::AddAssign
+ std::ops::DivAssign,
T: SimdUnifiedOps,
{
if marker.shape() != mask.shape() {
return Err(crate::error::NdimageError::DimensionError(
"Marker and mask must have the same shape".into(),
));
}
let max_iters = iterations.unwrap_or(1000);
let mut current = marker.clone();
let mut previous = Array2::zeros(marker.dim());
let default_structure = Array2::from_elem((3, 3), true);
let struct_elem = structure.unwrap_or(&default_structure);
for iter in 0..max_iters {
previous.assign(¤t);
current = grey_dilation_2d_optimized(¤t, Some(struct_elem), Some(1), None, None)?;
for ((c, m), p) in current.iter_mut().zip(mask.iter()).zip(previous.iter()) {
*c = (*c).min(*m);
}
if iter > 0 {
let mut converged = true;
for (c, p) in current.iter().zip(previous.iter()) {
if (*c - *p).abs() > T::from_f64(1e-10).unwrap_or(T::epsilon()) {
converged = false;
break;
}
}
if converged {
break;
}
}
}
Ok(current)
}
#[allow(dead_code)]
pub fn morphological_reconstruction_2d<T>(
marker: &Array2<T>,
mask: &Array2<T>,
method: MorphOperation,
structure: Option<&Array2<bool>>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ 'static
+ PartialOrd
+ std::ops::AddAssign
+ std::ops::DivAssign,
T: SimdUnifiedOps,
{
match method {
MorphOperation::Dilation => geodesic_dilation_2d(marker, mask, structure, None),
MorphOperation::Erosion => geodesic_erosion_2d(marker, mask, structure, None),
_ => Err(crate::error::NdimageError::InvalidInput(
"Only dilation and erosion methods are supported for reconstruction".into(),
)),
}
}
#[allow(dead_code)]
pub fn multi_scale_morphology_2d<T>(
input: &Array2<T>,
config: &MultiScaleMorphConfig,
) -> NdimageResult<Vec<Array2<T>>>
where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ 'static
+ PartialOrd
+ std::ops::AddAssign
+ std::ops::DivAssign,
T: SimdUnifiedOps,
{
let mut results = Vec::with_capacity(config.scales.len());
for &scale in &config.scales {
let structure = create_structuring_element(config.structure_type, scale)?;
let result = match config.operation {
MorphOperation::Erosion => {
grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?
}
MorphOperation::Dilation => {
grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?
}
MorphOperation::Opening => {
let eroded =
grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
grey_dilation_2d_optimized(&eroded, Some(&structure), Some(1), None, None)?
}
MorphOperation::Closing => {
let dilated =
grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?;
grey_erosion_2d_optimized(&dilated, Some(&structure), Some(1), None, None)?
}
MorphOperation::Gradient => {
let dilated =
grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?;
let eroded =
grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
let mut gradient = Array2::zeros(input.dim());
for ((d, e), g) in dilated.iter().zip(eroded.iter()).zip(gradient.iter_mut()) {
*g = *d - *e;
}
gradient
}
MorphOperation::TopHat => {
let opened = {
let eroded =
grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
grey_dilation_2d_optimized(&eroded, Some(&structure), Some(1), None, None)?
};
let mut tophat = Array2::zeros(input.dim());
for ((i, o), t) in input.iter().zip(opened.iter()).zip(tophat.iter_mut()) {
*t = *i - *o;
}
tophat
}
MorphOperation::BlackHat => {
let closed = {
let dilated =
grey_dilation_2d_optimized(input, Some(&structure), Some(1), None, None)?;
grey_erosion_2d_optimized(&dilated, Some(&structure), Some(1), None, None)?
};
let mut blackhat = Array2::zeros(input.dim());
for ((c, i), b) in closed.iter().zip(input.iter()).zip(blackhat.iter_mut()) {
*b = *c - *i;
}
blackhat
}
};
results.push(result);
}
if config.normalize {
for result in &mut results {
normalize_array(result)?;
}
}
Ok(results)
}
#[allow(dead_code)]
pub fn granulometry_2d<T>(
input: &Array2<T>,
scales: &[usize],
structure_type: StructureType,
) -> NdimageResult<Vec<f64>>
where
T: Float
+ FromPrimitive
+ Debug
+ Send
+ Sync
+ 'static
+ PartialOrd
+ std::ops::AddAssign
+ std::ops::DivAssign,
T: SimdUnifiedOps,
{
let mut curve = Vec::with_capacity(scales.len());
let original_sum: f64 = input.iter().map(|&x| x.to_f64().unwrap_or(0.0)).sum();
for &scale in scales {
let structure = create_structuring_element(structure_type, scale)?;
let eroded = grey_erosion_2d_optimized(input, Some(&structure), Some(1), None, None)?;
let opened = grey_dilation_2d_optimized(&eroded, Some(&structure), Some(1), None, None)?;
let opened_sum: f64 = opened.iter().map(|&x| x.to_f64().unwrap_or(0.0)).sum();
let granulo_value = if original_sum > 0.0 {
opened_sum / original_sum
} else {
0.0
};
curve.push(granulo_value);
}
Ok(curve)
}
#[allow(dead_code)]
pub fn area_opening_2d<T>(
input: &Array2<T>,
area_threshold: usize,
connectivity: usize,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Send + Sync + 'static + PartialOrd,
{
if connectivity != 4 && connectivity != 8 {
return Err(crate::error::NdimageError::InvalidInput(
"Connectivity must be 4 or 8".into(),
));
}
let mut result = input.clone();
let (height, width) = input.dim();
let _threshold = compute_threshold_for_area(input, area_threshold)?;
for i in 0..height {
for j in 0..width {
if input[[i, j]] < _threshold {
result[[i, j]] = T::zero();
}
}
}
Ok(result)
}
#[allow(dead_code)]
fn create_structuring_element(
structure_type: StructureType,
size: usize,
) -> NdimageResult<Array2<bool>> {
let radius = size / 2;
let dim = 2 * radius + 1;
let mut structure = Array2::from_elem((dim, dim), false);
match structure_type {
StructureType::Box => {
structure.fill(true);
}
StructureType::Cross => {
for i in 0..dim {
structure[[i, radius]] = true; structure[[radius, i]] = true; }
}
StructureType::Diamond => {
let center = radius as isize;
for i in 0..dim {
for j in 0..dim {
let di = i as isize - center;
let dj = j as isize - center;
if (di.abs() + dj.abs()) <= radius as isize {
structure[[i, j]] = true;
}
}
}
}
StructureType::Disk => {
let center = radius as f64;
for i in 0..dim {
for j in 0..dim {
let di = i as f64 - center;
let dj = j as f64 - center;
if (di * di + dj * dj).sqrt() <= radius as f64 {
structure[[i, j]] = true;
}
}
}
}
}
Ok(structure)
}
#[allow(dead_code)]
fn normalize_array<T>(array: &mut Array2<T>) -> NdimageResult<()>
where
T: Float + FromPrimitive + Debug + 'static,
{
let min_val = array.iter().fold(T::infinity(), |acc, &x| acc.min(x));
let max_val = array.iter().fold(T::neg_infinity(), |acc, &x| acc.max(x));
let range = max_val - min_val;
if range > T::zero() {
for value in array.iter_mut() {
*value = (*value - min_val) / range;
}
}
Ok(())
}
#[allow(dead_code)]
fn compute_threshold_for_area<T>(_input: &Array2<T>, _areathreshold: usize) -> NdimageResult<T>
where
T: Float + FromPrimitive + Debug + 'static,
{
let mut values: Vec<T> = _input.iter().copied().collect();
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let median_idx = values.len() / 2;
Ok(values[median_idx])
}