use scirs2_core::ndarray::{Array, Array1, Array2, Dimension};
use scirs2_core::numeric::{Float, FromPrimitive};
use scirs2_core::validation::{check_1d, check_2d, check_positive};
use std::fmt::Debug;
use super::{pad_array, BorderMode};
use crate::error::{NdimageError, NdimageResult};
#[allow(dead_code)]
pub fn minimum_filter<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ PartialOrd
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
D: Dimension + 'static,
{
extrema_filter(input, size, mode, origin, FilterType::Min)
}
#[allow(dead_code)]
pub fn maximum_filter<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ PartialOrd
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
D: Dimension + 'static,
{
extrema_filter(input, size, mode, origin, FilterType::Max)
}
#[derive(Clone, Copy, Debug)]
enum FilterType {
Min,
Max,
}
#[allow(dead_code)]
fn extrema_filter<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
origin: Option<&[isize]>,
filter_type: FilterType,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ PartialOrd
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if input.len() <= 1 {
return Ok(input.to_owned());
}
if size.len() == 1 {
let uniform_size = size[0];
check_positive(uniform_size, "size").map_err(NdimageError::from)?;
let size_array: Vec<usize> = vec![uniform_size; input.ndim()];
return extrema_filter(input, &size_array, Some(border_mode), origin, filter_type);
} else if size.len() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Size must have same length as input dimensions (got {} expected {})",
size.len(),
input.ndim()
)));
}
for (i, &s) in size.iter().enumerate() {
check_positive(s, format!("Kernel size in dimension {}", i)).map_err(NdimageError::from)?;
}
let origin: Vec<isize> = if let Some(orig) = origin {
if orig.len() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Origin must have same length as input dimensions (got {} expected {})",
orig.len(),
input.ndim()
)));
}
orig.to_vec()
} else {
size.iter().map(|&s| (s / 2) as isize).collect()
};
match input.ndim() {
1 => {
let input_1d = input
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 1D array".into())
})?;
check_1d(&input_1d, "input").map_err(NdimageError::from)?;
let result_1d =
extrema_filter_1d(&input_1d, size[0], &border_mode, origin[0], filter_type)?;
result_1d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back from 1D array".into())
})
}
2 => {
let input_2d = input
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 2D array".into())
})?;
check_2d(&input_2d, "input").map_err(NdimageError::from)?;
let result_2d = extrema_filter_2d(
&input_2d,
size,
&border_mode,
&[origin[0], origin[1]],
filter_type,
)?;
result_2d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back from 2D array".into())
})
}
_ => {
extrema_filter_nd(input, size, &border_mode, &origin, filter_type)
}
}
}
#[allow(dead_code)]
fn extrema_filter_1d<T>(
input: &Array1<T>,
size: usize,
mode: &BorderMode,
origin: isize,
filter_type: FilterType,
) -> NdimageResult<Array1<T>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone,
{
let left_pad = origin as usize;
let right_pad = size - left_pad - 1;
let mut output = Array1::zeros(input.raw_dim());
let pad_width = vec![(left_pad, right_pad)];
let padded_input = pad_array(input, &pad_width, mode, None)?;
for i in 0..input.len() {
let mut extrema = padded_input[i];
for k in 1..size {
let val = padded_input[i + k];
match filter_type {
FilterType::Min => {
if val < extrema {
extrema = val;
}
}
FilterType::Max => {
if val > extrema {
extrema = val;
}
}
}
}
output[i] = extrema;
}
Ok(output)
}
#[allow(dead_code)]
fn extrema_filter_2d<T>(
input: &Array2<T>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
filter_type: FilterType,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone,
{
let rows = input.shape()[0];
let cols = input.shape()[1];
let top_pad = origin[0] as usize;
let bottom_pad = size[0] - top_pad - 1;
let left_pad = origin[1] as usize;
let right_pad = size[1] - left_pad - 1;
let mut output = Array2::zeros((rows, cols));
let pad_width = vec![(top_pad, bottom_pad), (left_pad, right_pad)];
let padded_input = pad_array(input, &pad_width, mode, None)?;
for i in 0..rows {
for j in 0..cols {
let mut extrema = padded_input[[i, j]];
for ki in 0..size[0] {
for kj in 0..size[1] {
let val = padded_input[[i + ki, j + kj]];
match filter_type {
FilterType::Min => {
if val < extrema {
extrema = val;
}
}
FilterType::Max => {
if val > extrema {
extrema = val;
}
}
}
}
}
output[[i, j]] = extrema;
}
}
Ok(output)
}
#[allow(dead_code)]
fn extrema_filter_nd<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
filter_type: FilterType,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ PartialOrd
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
D: Dimension + 'static,
{
let pad_width: Vec<(usize, usize)> = size
.iter()
.zip(origin)
.map(|(&s, &o)| {
let left = o as usize;
let right = s - left - 1;
(left, right)
})
.collect();
let mut output = Array::<T, D>::zeros(input.raw_dim());
match input.ndim() {
1 => {
let input_1d = input
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 1D array".into())
})?;
let padded_input = pad_array(&input_1d, &pad_width, mode, None)?;
let mut output_1d = output
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 1D array".into())
})?;
for i in 0..input_1d.len() {
let mut extrema = padded_input[i];
for k in 1..size[0] {
let val = padded_input[i + k];
match filter_type {
FilterType::Min => {
if val < extrema {
extrema = val;
}
}
FilterType::Max => {
if val > extrema {
extrema = val;
}
}
}
}
output_1d[i] = extrema;
}
output = output_1d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back to original dimensions".into())
})?;
}
2 => {
let input_2d = input
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 2D array".into())
})?;
let padded_input = pad_array(&input_2d, &pad_width, mode, None)?;
let mut output_2d = output
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 2D array".into())
})?;
let (rows, cols) = input_2d.dim();
for i in 0..rows {
for j in 0..cols {
let mut extrema = padded_input[[i, j]];
for ki in 0..size[0] {
for kj in 0..size[1] {
if ki == 0 && kj == 0 {
continue; }
let val = padded_input[[i + ki, j + kj]];
match filter_type {
FilterType::Min => {
if val < extrema {
extrema = val;
}
}
FilterType::Max => {
if val > extrema {
extrema = val;
}
}
}
}
}
output_2d[[i, j]] = extrema;
}
}
output = output_2d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back to original dimensions".into())
})?;
}
_ => {
output = extrema_filter_nd_general(input, size, mode, origin, filter_type, &pad_width)?;
}
}
Ok(output)
}
#[allow(dead_code)]
fn extrema_filter_nd_general<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
filter_type: FilterType,
pad_width: &[(usize, usize)],
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ PartialOrd
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
D: Dimension + 'static,
{
let padded_input = pad_array(input, pad_width, mode, None)?;
let mut output = Array::<T, D>::zeros(input.raw_dim());
let inputshape = input.shape();
let total_elements = input.len();
#[cfg(feature = "parallel")]
{
use scirs2_core::parallel_ops::*;
if total_elements > 10000 {
return extrema_filter_nd_parallel(input, &padded_input, size, filter_type, inputshape);
}
}
extrema_filter_nd_sequential(
input,
&padded_input,
size,
filter_type,
inputshape,
&mut output,
)
}
#[allow(dead_code)]
fn extrema_filter_nd_sequential<T, D>(
input: &Array<T, D>,
padded_input: &Array<T, D>,
size: &[usize],
filter_type: FilterType,
inputshape: &[usize],
output: &mut Array<T, D>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone,
D: Dimension + 'static,
{
let ndim = input.ndim();
fn index_to_coords(mut index: usize, shape: &[usize]) -> Vec<usize> {
let mut coords = vec![0; shape.len()];
for i in (0..shape.len()).rev() {
coords[i] = index % shape[i];
index /= shape[i];
}
coords
}
for linear_idx in 0..input.len() {
let coords = index_to_coords(linear_idx, inputshape);
let extrema_coords = coords.clone();
let padded_dyn = padded_input.view().into_dyn();
let mut extrema = padded_dyn[scirs2_core::ndarray::IxDyn(&extrema_coords)];
let mut window_coords = vec![0; ndim];
let mut finished = false;
while !finished {
let mut actual_coords = Vec::with_capacity(ndim);
for d in 0..ndim {
actual_coords.push(coords[d] + window_coords[d]);
}
let val = padded_dyn[scirs2_core::ndarray::IxDyn(&actual_coords)];
match filter_type {
FilterType::Min => {
if val < extrema {
extrema = val;
}
}
FilterType::Max => {
if val > extrema {
extrema = val;
}
}
}
let mut carry = 1;
for d in (0..ndim).rev() {
window_coords[d] += carry;
if window_coords[d] < size[d] {
carry = 0;
break;
} else {
window_coords[d] = 0;
}
}
finished = carry == 1; }
let mut output_dyn = output.view_mut().into_dyn();
output_dyn[scirs2_core::ndarray::IxDyn(&coords)] = extrema;
}
Ok(output.clone())
}
#[cfg(feature = "parallel")]
#[allow(dead_code)]
fn extrema_filter_nd_parallel<T, D>(
input: &Array<T, D>,
padded_input: &Array<T, D>,
size: &[usize],
filter_type: FilterType,
inputshape: &[usize],
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ PartialOrd
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
D: Dimension + 'static,
{
use scirs2_core::parallel_ops::*;
let ndim = input.ndim();
let total_elements = input.len();
fn index_to_coords(mut index: usize, shape: &[usize]) -> Vec<usize> {
let mut coords = vec![0; shape.len()];
for i in (0..shape.len()).rev() {
coords[i] = index % shape[i];
index /= shape[i];
}
coords
}
let results: Result<Vec<T>, NdimageError> = (0..total_elements)
.into_par_iter()
.map(|linear_idx| -> Result<T, NdimageError> {
let coords = index_to_coords(linear_idx, inputshape);
let mut extrema_coords = coords.clone();
let mut linear_idx = 0;
let mut stride = 1;
let shape = padded_input.shape();
for i in (0..extrema_coords.len()).rev() {
if extrema_coords[i] >= shape[i] {
return Err(NdimageError::InvalidInput(
"Invalid coordinates for extrema access".to_string(),
));
}
linear_idx += extrema_coords[i] * stride;
stride *= shape[i];
}
let flat_view = padded_input.as_slice().ok_or_else(|| {
NdimageError::InvalidInput("Cannot access array as flat slice".to_string())
})?;
let mut extrema = flat_view[linear_idx];
let mut window_coords = vec![0; ndim];
let mut finished = false;
while !finished {
let mut actual_coords = Vec::with_capacity(ndim);
for d in 0..ndim {
actual_coords.push(coords[d] + window_coords[d]);
}
let mut actual_linear_idx = 0;
let mut actual_stride = 1;
for i in (0..actual_coords.len()).rev() {
if actual_coords[i] >= shape[i] {
continue; }
actual_linear_idx += actual_coords[i] * actual_stride;
actual_stride *= shape[i];
}
if actual_linear_idx >= flat_view.len() {
continue; }
let val = flat_view[actual_linear_idx];
match filter_type {
FilterType::Min => {
if val < extrema {
extrema = val;
}
}
FilterType::Max => {
if val > extrema {
extrema = val;
}
}
}
let mut carry = 1;
for d in (0..ndim).rev() {
window_coords[d] += carry;
if window_coords[d] < size[d] {
carry = 0;
break;
} else {
window_coords[d] = 0;
}
}
finished = carry == 1; }
Ok(extrema)
})
.collect();
let results = results?;
let output = Array::from_shape_vec(input.raw_dim(), results)
.map_err(|_| NdimageError::DimensionError("Failed to create output array".into()))?;
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::{array, Array3};
#[test]
fn test_minimum_filter_2d_simple() {
let input = array![[5.0, 2.0], [1.0, 8.0]];
let result = minimum_filter(&input, &[2, 2], None, None).expect("Operation failed");
assert_eq!(result.shape(), input.shape());
assert!(result[[0, 0]] <= 5.0);
}
#[test]
fn test_maximum_filter_2d_simple() {
let input = array![[5.0, 2.0], [1.0, 8.0]];
let result = maximum_filter(&input, &[2, 2], None, None).expect("Operation failed");
assert_eq!(result.shape(), input.shape());
assert!(result[[0, 0]] >= 1.0);
}
#[test]
fn test_extrema_filter_3d() {
let mut input = Array3::<f64>::zeros((3, 3, 3));
input[[1, 1, 1]] = 5.0;
input[[0, 0, 0]] = 1.0;
input[[2, 2, 2]] = 9.0;
let min_result = minimum_filter(&input, &[2, 2, 2], None, None).expect("Operation failed");
let max_result = maximum_filter(&input, &[2, 2, 2], None, None).expect("Operation failed");
assert_eq!(min_result.shape(), input.shape());
assert_eq!(max_result.shape(), input.shape());
for elem in min_result.iter() {
assert!(*elem <= 9.0);
}
for elem in max_result.iter() {
assert!(*elem >= 0.0);
}
}
#[test]
fn test_extrema_filter_4d() {
let input = scirs2_core::ndarray::Array4::<f64>::from_elem((2, 2, 2, 2), 3.0);
let min_result =
minimum_filter(&input, &[2, 2, 2, 2], None, None).expect("Operation failed");
let max_result =
maximum_filter(&input, &[2, 2, 2, 2], None, None).expect("Operation failed");
for elem in min_result.iter() {
assert_eq!(*elem, 3.0);
}
for elem in max_result.iter() {
assert_eq!(*elem, 3.0);
}
}
}