use scirs2_core::ndarray::{s, Array, Array1, Array2, Dim, Dimension, IxDyn, IxDynImpl, NdIndex};
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};
#[cfg(feature = "simd")]
use scirs2_core::simd_ops::SimdUnifiedOps;
#[cfg(feature = "parallel")]
use scirs2_core::parallel_ops;
#[allow(dead_code)]
pub fn uniform_filter<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Send
+ Sync
+ '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 uniform_filter(input, &size_array, Some(border_mode), origin);
} 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 = uniform_filter_1d(&input_1d, size[0], &border_mode, origin[0])?;
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 =
uniform_filter_2d(&input_2d, size, &border_mode, &[origin[0], origin[1]])?;
result_2d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back from 2D array".into())
})
}
_ => {
uniform_filter_nd(input, size, &border_mode, &origin)
}
}
}
#[allow(dead_code)]
fn uniform_filter_1d<T>(
input: &Array1<T>,
size: usize,
mode: &BorderMode,
origin: isize,
) -> NdimageResult<Array1<T>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
{
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && input.len() > 64 && size >= 3
{
return uniform_filter_1d_simd_f32(input, size, mode, origin).map(|result| {
result.mapv(|x| T::from_f32(x).unwrap_or_else(T::zero))
});
}
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)?;
let norm_factor = T::one() / T::from_usize(size).expect("Operation failed");
for i in 0..input.len() {
let mut sum = T::zero();
for k in 0..size {
sum += padded_input[i + k];
}
output[i] = sum * norm_factor;
}
Ok(output)
}
#[cfg(feature = "simd")]
#[allow(dead_code)]
fn uniform_filter_1d_simd_f32<T>(
input: &Array1<T>,
size: usize,
mode: &BorderMode,
origin: isize,
) -> NdimageResult<Array1<f32>>
where
T: Float + FromPrimitive + Debug,
{
let input_f32: Vec<f32> = input.iter().map(|&x| x.to_f32().unwrap_or(0.0)).collect();
let input_f32_array = Array1::from_vec(input_f32);
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_f32_array, &pad_width, mode, None)?;
let norm_factor = 1.0f32 / (size as f32);
let padded_data = padded_input.as_slice().ok_or_else(|| {
NdimageError::ComputationError("Failed to get contiguous slice".to_string())
})?;
for i in 0..input.len() {
let window_start = i;
let window_end = i + size;
if window_end <= padded_data.len() {
let window_slice = &padded_data[window_start..window_end];
let window_array = Array1::from_vec(window_slice.to_vec());
let sum = f32::simd_sum(&window_array.view());
output[i] = sum * norm_factor;
} else {
let mut sum = 0.0f32;
for k in 0..size {
if window_start + k < padded_data.len() {
sum += padded_data[window_start + k];
}
}
output[i] = sum * norm_factor;
}
}
Ok(output)
}
#[cfg(not(feature = "simd"))]
#[allow(dead_code)]
fn uniform_filter_1d_simd_f32<T>(
input: &Array1<T>,
size: usize,
mode: &BorderMode,
origin: isize,
) -> NdimageResult<Array1<f32>>
where
T: Float + FromPrimitive + Debug,
{
let input_f32: Vec<f32> = input.iter().map(|&x| x.to_f32().unwrap_or(0.0)).collect();
let input_f32_array = Array1::from_vec(input_f32);
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_f32_array, &pad_width, mode, None)?;
let norm_factor = 1.0f32 / (size as f32);
for i in 0..input.len() {
let mut sum = 0.0f32;
for k in 0..size {
sum += padded_input[i + k];
}
output[i] = sum * norm_factor;
}
Ok(output)
}
#[allow(dead_code)]
fn uniform_filter_2d<T>(
input: &Array2<T>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + 'static,
{
let rows = input.shape()[0];
let cols = input.shape()[1];
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>()
&& rows * cols > 1024
&& size[0] >= 3
&& size[1] >= 3
{
return uniform_filter_2d_simd_f32(input, size, mode, origin).map(|result| {
result.mapv(|x| T::from_f32(x).unwrap_or_else(T::zero))
});
}
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)?;
let total_size = size[0] * size[1];
let norm_factor = T::one() / T::from_usize(total_size).expect("Operation failed");
for i in 0..rows {
for j in 0..cols {
let mut sum = T::zero();
for ki in 0..size[0] {
for kj in 0..size[1] {
sum += padded_input[[i + ki, j + kj]];
}
}
output[[i, j]] = sum * norm_factor;
}
}
Ok(output)
}
#[cfg(feature = "simd")]
#[allow(dead_code)]
fn uniform_filter_2d_simd_f32<T>(
input: &Array2<T>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
) -> NdimageResult<Array2<f32>>
where
T: Float + FromPrimitive + Debug,
{
let rows = input.shape()[0];
let cols = input.shape()[1];
let input_f32 = input.mapv(|x| x.to_f32().unwrap_or(0.0));
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_f32, &pad_width, mode, None)?;
let total_size = size[0] * size[1];
let norm_factor = 1.0f32 / (total_size as f32);
for i in 0..rows {
for j in 0..cols {
let mut sum = 0.0f32;
for ki in 0..size[0] {
let row_start = j;
let row_end = j + size[1];
let padded_row = padded_input.row(i + ki);
let window_slice =
&padded_row.as_slice().expect("Operation failed")[row_start..row_end];
let window_array = Array1::from_vec(window_slice.to_vec());
sum += f32::simd_sum(&window_array.view());
}
output[[i, j]] = sum * norm_factor;
}
}
Ok(output)
}
#[cfg(not(feature = "simd"))]
#[allow(dead_code)]
fn uniform_filter_2d_simd_f32<T>(
input: &Array2<T>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
) -> NdimageResult<Array2<f32>>
where
T: Float + FromPrimitive + Debug,
{
let rows = input.shape()[0];
let cols = input.shape()[1];
let input_f32 = input.mapv(|x| x.to_f32().unwrap_or(0.0));
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_f32, &pad_width, mode, None)?;
let total_size = size[0] * size[1];
let norm_factor = 1.0f32 / (total_size as f32);
for i in 0..rows {
for j in 0..cols {
let mut sum = 0.0f32;
for ki in 0..size[0] {
for kj in 0..size[1] {
sum += padded_input[[i + ki, j + kj]];
}
}
output[[i, j]] = sum * norm_factor;
}
}
Ok(output)
}
#[allow(dead_code)]
fn uniform_filter_nd<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Send
+ Sync
+ '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 total_size: usize = size.iter().product();
let norm_factor = T::one() / T::from_usize(total_size).expect("Operation failed");
let 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 sum = T::zero();
for k in 0..size[0] {
sum += padded_input[i + k];
}
output_1d[i] = sum * norm_factor;
}
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 sum = T::zero();
for ki in 0..size[0] {
for kj in 0..size[1] {
sum += padded_input[[i + ki, j + kj]];
}
}
output_2d[[i, j]] = sum * norm_factor;
}
}
output_2d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back to original dimensions".into())
})
}
_ => {
uniform_filter_nd_general(input, size, mode, origin, &pad_width, norm_factor)
}
}
}
#[allow(dead_code)]
fn uniform_filter_nd_general<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: &BorderMode,
origin: &[isize],
pad_width: &[(usize, usize)],
norm_factor: T,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Send
+ Sync
+ '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();
uniform_filter_nd_sequential(
input,
&padded_input,
size,
norm_factor,
inputshape,
&mut output,
)
}
#[allow(dead_code)]
fn uniform_filter_nd_sequential<T, D>(
input: &Array<T, D>,
padded_input: &Array<T, D>,
size: &[usize],
norm_factor: T,
inputshape: &[usize],
output: &mut Array<T, D>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign,
D: Dimension,
{
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 mut sum = T::zero();
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 padded_dyn = padded_input.view().into_dyn();
let val = padded_dyn[IxDyn(&actual_coords)];
sum += 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[IxDyn(&coords)] = sum * norm_factor;
}
Ok(output.clone())
}
#[cfg(feature = "parallel")]
#[allow(dead_code)]
fn uniform_filter_nd_parallel<T, D>(
input: &Array<T, D>,
padded_input: &Array<T, D>,
size: &[usize],
norm_factor: T,
inputshape: &[usize],
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + Send + Sync,
D: Dimension + 'static,
Dim<IxDynImpl>: NdIndex<D>,
{
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: Vec<T> = (0..total_elements)
.into_par_iter()
.map(|linear_idx| {
let coords = index_to_coords(linear_idx, inputshape);
let mut sum = T::zero();
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_input[IxDyn(&actual_coords)];
sum += 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; }
sum * norm_factor
})
.collect();
let output = Array::from_shape_vec(input.raw_dim(), results)
.map_err(|_| NdimageError::DimensionError("Failed to create output array".into()))?;
Ok(output)
}
#[allow(dead_code)]
pub fn uniform_filter_separable<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
origin: Option<&[isize]>,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Send
+ Sync
+ 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
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()
};
let mut result = input.to_owned();
for axis in 0..input.ndim() {
let axis_size = size[axis];
let axis_origin = origin[axis];
if axis_size <= 1 {
continue;
}
result = uniform_filter_along_axis(&result, axis, axis_size, &border_mode, axis_origin)?;
}
Ok(result)
}
#[allow(dead_code)]
fn uniform_filter_along_axis<T, D>(
input: &Array<T, D>,
axis: usize,
size: usize,
mode: &BorderMode,
origin: isize,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Send
+ Sync
+ 'static,
D: Dimension + 'static,
{
if axis >= input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Axis {} is out of bounds for array of dimension {}",
axis,
input.ndim()
)));
}
let left_pad = origin as usize;
let right_pad = size - left_pad - 1;
let mut pad_width = vec![(0, 0); input.ndim()];
pad_width[axis] = (left_pad, right_pad);
let mut output = Array::<T, D>::zeros(input.raw_dim());
let padded_input = pad_array(input, &pad_width, mode, None)?;
let norm_factor = T::one() / T::from_usize(size).expect("Operation failed");
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_1d = padded_input
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert padded input to 1D".into())
})?;
let mut output_1d = output
.view_mut()
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert output to 1D".into())
})?;
for i in 0..input_1d.len() {
let mut sum = T::zero();
for k in 0..size {
sum += padded_input_1d[i + k];
}
output_1d[i] = sum * norm_factor;
}
}
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_2d = padded_input
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert padded input to 2D".into())
})?;
let mut output_2d = output
.view_mut()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert output to 2D".into())
})?;
let (rows, cols) = input_2d.dim();
if axis == 0 {
for j in 0..cols {
for i in 0..rows {
let mut sum = T::zero();
for k in 0..size {
sum += padded_input_2d[[i + k, j]];
}
output_2d[[i, j]] = sum * norm_factor;
}
}
} else if axis == 1 {
for i in 0..rows {
for j in 0..cols {
let mut sum = T::zero();
for k in 0..size {
sum += padded_input_2d[[i, j + k]];
}
output_2d[[i, j]] = sum * norm_factor;
}
}
}
}
_ => {
let inputshape = input.shape();
let padded_input_dyn = padded_input.view().into_dyn();
let mut output_dyn = output.view_mut().into_dyn();
#[allow(clippy::too_many_arguments)]
fn process_indices<T: Float + FromPrimitive + std::ops::AddAssign>(
input_dyn: &scirs2_core::ndarray::ArrayViewD<T>,
output_dyn: &mut scirs2_core::ndarray::ArrayViewMutD<T>,
size: usize,
axis: usize,
idx: &mut Vec<usize>,
inputshape: &[usize],
dimension: usize,
norm_factor: T,
) {
if dimension == inputshape.len() {
let mut sum = T::zero();
let mut temp_idx = idx.clone();
for k in 0..size {
temp_idx[axis] = idx[axis] + k;
if let Some(val) = input_dyn.get(temp_idx.as_slice()) {
sum += *val;
}
}
output_dyn[idx.as_slice()] = sum * norm_factor;
return;
}
if dimension == axis {
process_indices(
input_dyn,
output_dyn,
size,
axis,
idx,
inputshape,
dimension + 1,
norm_factor,
);
} else {
for i in 0..inputshape[dimension] {
idx[dimension] = i;
process_indices(
input_dyn,
output_dyn,
size,
axis,
idx,
inputshape,
dimension + 1,
norm_factor,
);
}
}
}
let mut idx = vec![0; input.ndim()];
process_indices(
&padded_input_dyn,
&mut output_dyn,
size,
axis,
&mut idx,
inputshape,
0,
norm_factor,
);
}
}
Ok(output)
}
#[cfg(feature = "parallel")]
#[allow(dead_code)]
pub fn uniform_filter_chunked<T>(
input: &Array2<T>,
size: &[usize],
_chunk_size: usize,
mode: Option<BorderMode>,
origin: Option<&[isize]>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Send
+ Sync
+ Clone
+ 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let (rows, cols) = input.dim();
let origin: Vec<isize> = if let Some(orig) = origin {
if orig.len() != 2 {
return Err(NdimageError::DimensionError(format!(
"Origin must have length 2 for 2D arrays (got {})",
orig.len()
)));
}
orig.to_vec()
} else {
vec![(size[0] / 2) as isize, (size[1] / 2) as isize]
};
let pad_rows = size[0];
let pad_cols = size[1];
let mut output = Array2::zeros((rows, cols));
let num_row_chunks = (rows + _chunk_size - 1) / _chunk_size;
let num_col_chunks = (cols + _chunk_size - 1) / _chunk_size;
let chunk_indices: Vec<(usize, usize)> = (0..num_row_chunks)
.flat_map(|i| (0..num_col_chunks).map(move |j| (i, j)))
.collect();
let process_chunk = |&(chunk_row, chunk_col): &(usize, usize)| -> Result<((usize, usize), Array2<T>), crate::error::NdimageError> {
let row_start = chunk_row * _chunk_size;
let row_end = std::cmp::min(row_start + _chunk_size, rows);
let col_start = chunk_col * _chunk_size;
let col_end = std::cmp::min(col_start + _chunk_size, cols);
let padded_row_start = row_start.saturating_sub(pad_rows);
let padded_row_end = std::cmp::min(row_end + pad_rows, rows);
let padded_col_start = col_start.saturating_sub(pad_cols);
let padded_col_end = std::cmp::min(col_end + pad_cols, cols);
let chunk_slice = input.slice(s![
padded_row_start..padded_row_end,
padded_col_start..padded_col_end
]);
let chunk = chunk_slice.to_owned();
let filtered_chunk = uniform_filter_2d(&chunk, size, &border_mode, &origin)?;
let output_row_offset = row_start.saturating_sub(padded_row_start);
let output_col_offset = col_start.saturating_sub(padded_col_start);
let output_row_count = row_end - row_start;
let output_col_count = col_end - col_start;
let result_chunk = filtered_chunk.slice(s![
output_row_offset..output_row_offset + output_row_count,
output_col_offset..output_col_offset + output_col_count
]).to_owned();
Ok(((chunk_row, chunk_col), result_chunk))
};
let chunk_results = parallel_ops::parallel_map_result(&chunk_indices, process_chunk)?;
for ((chunk_row, chunk_col), chunk_result) in chunk_results {
let row_start = chunk_row * _chunk_size;
let row_end = std::cmp::min(row_start + _chunk_size, rows);
let col_start = chunk_col * _chunk_size;
let col_end = std::cmp::min(col_start + _chunk_size, cols);
let mut output_slice = output.slice_mut(s![row_start..row_end, col_start..col_end]);
output_slice.assign(&chunk_result);
}
Ok(output)
}
#[cfg(not(feature = "parallel"))]
#[allow(dead_code)]
pub fn uniform_filter_chunked<T>(
input: &Array2<T>,
size: &[usize],
_chunk_size: usize,
mode: Option<BorderMode>,
origin: Option<&[isize]>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Send
+ Sync
+ Clone
+ 'static,
{
uniform_filter_2d(input, size, &mode.unwrap_or(BorderMode::Reflect), &{
if let Some(orig) = origin {
orig.to_vec()
} else {
vec![(size[0] / 2) as isize, (size[1] / 2) as isize]
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::{array, Array3};
#[test]
fn test_uniform_filter_2d() {
let input = array![[1.0, 2.0], [4.0, 5.0]];
let result = uniform_filter(&input, &[2, 2], None, None).expect("Operation failed");
assert_eq!(result.shape(), input.shape());
assert!(result[[0, 0]] > 1.0);
assert!(result[[0, 0]] < 5.0);
}
#[test]
fn test_invalid_inputs() {
let input = array![[1.0, 2.0], [3.0, 4.0]];
let result = uniform_filter(&input, &[0, 0], None, None);
assert!(result.is_err());
let result = uniform_filter(&input, &[2, 2], None, Some(&[0, 0, 0]));
assert!(result.is_err());
}
#[test]
fn test_uniform_filter_3d() {
let mut input = Array3::<f64>::zeros((3, 3, 3));
input[[1, 1, 1]] = 8.0;
input[[0, 0, 0]] = 2.0;
input[[2, 2, 2]] = 6.0;
let result = uniform_filter(&input, &[2, 2, 2], None, None).expect("Operation failed");
assert_eq!(result.shape(), input.shape());
for elem in result.iter() {
assert!(*elem >= 0.0);
assert!(*elem <= 8.0);
}
let result_sep =
uniform_filter_separable(&input, &[2, 2, 2], None, None).expect("Operation failed");
assert_eq!(result_sep.shape(), input.shape());
}
#[test]
fn test_uniform_filter_4d() {
let input = scirs2_core::ndarray::Array4::<f64>::from_elem((2, 2, 2, 2), 5.0);
let result = uniform_filter(&input, &[2, 2, 2, 2], None, None).expect("Operation failed");
for elem in result.iter() {
assert!((elem - 5.0).abs() < 1e-10);
}
}
}