use scirs2_core::ndarray::{Array, Array1, Dimension};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
use super::{convolve_optimized as convolve_opt, BorderMode};
use crate::error::{NdimageError, NdimageResult};
#[allow(dead_code)]
pub fn uniform_filter<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
) -> 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 size.len() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Size must have same length as input dimensions (got {} expected {})",
size.len(),
input.ndim()
)));
}
for &s in size {
if s == 0 {
return Err(NdimageError::InvalidInput(
"Kernel size cannot be zero".into(),
));
}
}
use super::uniform::uniform_filter as uniform_filter_impl;
uniform_filter_impl(input, size, Some(_border_mode), None)
}
#[allow(dead_code)]
pub fn convolve<T, D, E>(
input: &Array<T, D>,
weights: &Array<T, E>,
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
E: 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 weights.ndim() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Weights must have same rank as input (got {} expected {})",
weights.ndim(),
input.ndim()
)));
}
if input.ndim() == 2 && weights.ndim() == 2 {
match (input.ndim(), weights.ndim()) {
(2, 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 weights_2d = weights
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert weights to 2D array".into())
})?;
let result = convolve_2d(&input_2d, &weights_2d, &border_mode)?;
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
}
_ => unreachable!(), }
} else {
match (input.ndim(), weights.ndim()) {
(1, 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 weights_1d = weights
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert weights to 1D array".into())
})?;
let result = convolve_1d(&input_1d, &weights_1d, &border_mode)?;
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
}
(3, 3) => {
let input_3d = input
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix3>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 3D array".into())
})?;
let weights_3d = weights
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix3>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert weights to 3D array".into())
})?;
let result = convolve_3d(&input_3d, &weights_3d, &border_mode)?;
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
}
_n_ => {
convolve_nd(input, weights, &border_mode)
}
}
}
}
#[allow(dead_code)]
fn convolve_2d<T>(
input: &Array<T, scirs2_core::ndarray::Ix2>,
weights: &Array<T, scirs2_core::ndarray::Ix2>,
mode: &BorderMode,
) -> NdimageResult<Array<T, scirs2_core::ndarray::Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let (input_rows, input_cols) = input.dim();
let (weights_rows, weights_cols) = weights.dim();
let pad_rows_before = weights_rows / 2;
let pad_rows_after = weights_rows - pad_rows_before - 1;
let pad_cols_before = weights_cols / 2;
let pad_cols_after = weights_cols - pad_cols_before - 1;
let mut output = Array::<T, scirs2_core::ndarray::Ix2>::zeros((input_rows, input_cols));
let pad_width = vec![
(pad_rows_before, pad_rows_after),
(pad_cols_before, pad_cols_after),
];
use super::pad_array;
let padded = pad_array(input, &pad_width, mode, None)?;
for i in 0..input_rows {
for j in 0..input_cols {
let mut sum = T::zero();
for ki in 0..weights_rows {
for kj in 0..weights_cols {
let input_val = padded[[i + ki, j + kj]];
let weight = weights[[weights_rows - ki - 1, weights_cols - kj - 1]]; sum += input_val * weight;
}
}
output[[i, j]] = sum;
}
}
Ok(output)
}
#[allow(dead_code)]
pub fn convolve_fast<T, D, E>(
input: &Array<T, D>,
weights: &Array<T, E>,
mode: Option<BorderMode>,
use_optimization: bool,
) -> NdimageResult<Array<T, D>>
where
T: Float
+ FromPrimitive
+ Debug
+ std::ops::AddAssign
+ std::ops::DivAssign
+ Clone
+ Send
+ Sync
+ 'static,
D: Dimension + 'static,
E: Dimension + 'static,
{
if use_optimization {
convolve_opt(input, weights, mode.unwrap_or(BorderMode::Reflect), None)
} else {
convolve(input, weights, mode)
}
}
#[allow(dead_code)]
pub fn correlate1d<T, D>(
input: &Array<T, D>,
weights: &Array1<T>,
axis: usize,
mode: Option<BorderMode>,
cval: Option<T>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + Clone + std::ops::AddAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
use scirs2_core::ndarray::IxDyn;
let mode = mode.unwrap_or(BorderMode::Reflect);
let _cval = cval.unwrap_or(T::zero());
if axis >= input.ndim() {
return Err(NdimageError::InvalidInput(format!(
"Axis {} is out of bounds for array of dimension {}",
axis,
input.ndim()
)));
}
let input_dyn = input
.to_owned()
.into_dimensionality::<IxDyn>()
.expect("Operation failed");
let mut pad_width = vec![(0, 0); input.ndim()];
let kernel_size = weights.len();
let pad_before = kernel_size / 2;
let pad_after = kernel_size - pad_before - 1;
pad_width[axis] = (pad_before, pad_after);
use super::pad_array;
let padded = pad_array(&input_dyn, &pad_width, &mode, cval)?;
let mut output = Array::zeros(input_dyn.raw_dim());
for out_idx in scirs2_core::ndarray::indices(output.shape()) {
let mut sum = T::zero();
let out_coords: Vec<_> = out_idx.slice().to_vec();
for k in 0..kernel_size {
let mut in_coords = out_coords.clone();
in_coords[axis] = out_coords[axis] + k;
sum = sum + padded[IxDyn(&in_coords)] * weights[k];
}
output[IxDyn(&out_coords)] = sum;
}
output.into_dimensionality().map_err(|_| {
NdimageError::DimensionError("Failed to convert output back to original dimension".into())
})
}
#[allow(dead_code)]
fn convolve_1d<T>(
input: &Array<T, scirs2_core::ndarray::Ix1>,
weights: &Array<T, scirs2_core::ndarray::Ix1>,
mode: &BorderMode,
) -> NdimageResult<Array<T, scirs2_core::ndarray::Ix1>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let input_len = input.len();
let weights_len = weights.len();
if weights_len == 0 {
return Err(NdimageError::InvalidInput("Kernel cannot be empty".into()));
}
let half_kernel = weights_len / 2;
let mut output = Array::zeros(input_len);
for i in 0..input_len {
let mut sum = T::zero();
for k in 0..weights_len {
let input_idx = (i as isize) + (k as isize) - (half_kernel as isize);
let value = if input_idx < 0 || input_idx >= input_len as isize {
match mode {
BorderMode::Constant => T::zero(),
BorderMode::Nearest => {
if input_idx < 0 {
input[0]
} else {
input[input_len - 1]
}
}
BorderMode::Reflect => {
let reflected_idx = if input_idx < 0 {
(-input_idx - 1) as usize
} else {
2 * input_len - 1 - (input_idx as usize)
};
input[reflected_idx.min(input_len - 1)]
}
BorderMode::Wrap => {
let wrapped_idx = ((input_idx % (input_len as isize) + input_len as isize)
% (input_len as isize))
as usize;
input[wrapped_idx]
}
BorderMode::Mirror => {
let mirrored_idx = if input_idx < 0 {
(-input_idx) as usize
} else {
2 * input_len - 2 - (input_idx as usize)
};
input[mirrored_idx.min(input_len - 1)]
}
}
} else {
input[input_idx as usize]
};
sum += value * weights[k];
}
output[i] = sum;
}
Ok(output)
}
#[allow(dead_code)]
fn convolve_3d<T>(
input: &Array<T, scirs2_core::ndarray::Ix3>,
weights: &Array<T, scirs2_core::ndarray::Ix3>,
mode: &BorderMode,
) -> NdimageResult<Array<T, scirs2_core::ndarray::Ix3>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let (input_d, input_h, input_w) = input.dim();
let (weights_d, weights_h, weights_w) = weights.dim();
if weights_d == 0 || weights_h == 0 || weights_w == 0 {
return Err(NdimageError::InvalidInput(
"Kernel cannot have zero dimensions".into(),
));
}
let half_d = weights_d / 2;
let half_h = weights_h / 2;
let half_w = weights_w / 2;
let mut output = Array::zeros((input_d, input_h, input_w));
for z in 0..input_d {
for y in 0..input_h {
for x in 0..input_w {
let mut sum = T::zero();
for kz in 0..weights_d {
for ky in 0..weights_h {
for kx in 0..weights_w {
let input_z = (z as isize) + (kz as isize) - (half_d as isize);
let input_y = (y as isize) + (ky as isize) - (half_h as isize);
let input_x = (x as isize) + (kx as isize) - (half_w as isize);
let value = get_padded_value_3d(input, input_z, input_y, input_x, mode);
sum += value * weights[[kz, ky, kx]];
}
}
}
output[[z, y, x]] = sum;
}
}
}
Ok(output)
}
#[allow(dead_code)]
fn get_padded_value_3d<T>(
input: &Array<T, scirs2_core::ndarray::Ix3>,
z: isize,
y: isize,
x: isize,
mode: &BorderMode,
) -> T
where
T: Float + Clone + 'static,
{
let (depth, height, width) = input.dim();
if z >= 0 && z < depth as isize && y >= 0 && y < height as isize && x >= 0 && x < width as isize
{
return input[[z as usize, y as usize, x as usize]];
}
match mode {
BorderMode::Constant => T::zero(),
BorderMode::Nearest => {
let clamped_z = z.max(0).min(depth as isize - 1) as usize;
let clamped_y = y.max(0).min(height as isize - 1) as usize;
let clamped_x = x.max(0).min(width as isize - 1) as usize;
input[[clamped_z, clamped_y, clamped_x]]
}
BorderMode::Reflect => {
let reflect_coord = |coord: isize, size: usize| -> usize {
if coord < 0 {
(-coord - 1) as usize % size
} else if coord >= size as isize {
(2 * size - 1 - coord as usize) % size
} else {
coord as usize
}
};
let ref_z = reflect_coord(z, depth);
let ref_y = reflect_coord(y, height);
let ref_x = reflect_coord(x, width);
input[[ref_z, ref_y, ref_x]]
}
BorderMode::Wrap => {
let wrap_coord = |coord: isize, size: usize| -> usize {
((coord % size as isize + size as isize) % size as isize) as usize
};
let wrap_z = wrap_coord(z, depth);
let wrap_y = wrap_coord(y, height);
let wrap_x = wrap_coord(x, width);
input[[wrap_z, wrap_y, wrap_x]]
}
BorderMode::Mirror => {
let mirror_coord = |coord: isize, size: usize| -> usize {
if coord < 0 {
(-coord) as usize % size
} else if coord >= size as isize {
(2 * size - 2 - coord as usize) % size
} else {
coord as usize
}
};
let mir_z = mirror_coord(z, depth);
let mir_y = mirror_coord(y, height);
let mir_x = mirror_coord(x, width);
input[[mir_z, mir_y, mir_x]]
}
}
}
#[allow(dead_code)]
fn convolve_nd<T, D, E>(
input: &Array<T, D>,
weights: &Array<T, E>,
mode: &BorderMode,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
E: Dimension + 'static,
{
let inputshape = input.shape();
let weightsshape = weights.shape();
let ndim = input.ndim();
if ndim != weights.ndim() {
return Err(NdimageError::DimensionError(
"Input and weights must have the same number of dimensions".into(),
));
}
if weightsshape.iter().any(|&s| s == 0) {
return Err(NdimageError::InvalidInput(
"Kernel cannot have zero dimensions".into(),
));
}
let half_sizes: Vec<usize> = weightsshape.iter().map(|&s| s / 2).collect();
let mut output = Array::zeros(input.raw_dim());
for out_indices in scirs2_core::ndarray::indices(inputshape) {
let out_coords: Vec<usize> = out_indices.slice().to_vec();
let mut sum = T::zero();
for weight_indices in scirs2_core::ndarray::indices(weightsshape) {
let weight_coords: Vec<usize> = weight_indices.slice().to_vec();
let mut input_coords = vec![0isize; ndim];
for d in 0..ndim {
input_coords[d] = (out_coords[d] as isize) + (weight_coords[d] as isize)
- (half_sizes[d] as isize);
}
let value = get_padded_value_nd(input, &input_coords, mode);
let weights_dyn = weights.view().into_dyn();
let weight = weights_dyn[weight_indices];
sum += value * weight;
}
let mut output_dyn = output.view_mut().into_dyn();
output_dyn[out_indices] = sum;
}
Ok(output)
}
#[allow(dead_code)]
fn get_padded_value_nd<T, D>(input: &Array<T, D>, coords: &[isize], mode: &BorderMode) -> T
where
T: Float + Clone + 'static,
D: Dimension + 'static,
{
let shape = input.shape();
let ndim = input.ndim();
let mut in_bounds = true;
let mut clamped_coords = vec![0usize; ndim];
for d in 0..ndim {
if coords[d] < 0 || coords[d] >= shape[d] as isize {
in_bounds = false;
}
clamped_coords[d] = coords[d].max(0).min(shape[d] as isize - 1) as usize;
}
if in_bounds {
let input_dyn = input.view().into_dyn();
return input_dyn[scirs2_core::ndarray::IxDyn(&clamped_coords)];
}
match mode {
BorderMode::Constant => T::zero(),
BorderMode::Nearest => {
let input_dyn = input.view().into_dyn();
input_dyn[scirs2_core::ndarray::IxDyn(&clamped_coords)]
}
BorderMode::Reflect => {
let mut reflected_coords = vec![0usize; ndim];
for d in 0..ndim {
reflected_coords[d] = if coords[d] < 0 {
(-coords[d] - 1) as usize % shape[d]
} else if coords[d] >= shape[d] as isize {
(2 * shape[d] - 1 - coords[d] as usize) % shape[d]
} else {
coords[d] as usize
};
}
let input_dyn = input.view().into_dyn();
input_dyn[scirs2_core::ndarray::IxDyn(&reflected_coords)]
}
BorderMode::Wrap => {
let mut wrapped_coords = vec![0usize; ndim];
for d in 0..ndim {
wrapped_coords[d] = ((coords[d] % shape[d] as isize + shape[d] as isize)
% shape[d] as isize) as usize;
}
let input_dyn = input.view().into_dyn();
input_dyn[scirs2_core::ndarray::IxDyn(&wrapped_coords)]
}
BorderMode::Mirror => {
let mut mirrored_coords = vec![0usize; ndim];
for d in 0..ndim {
mirrored_coords[d] = if coords[d] < 0 {
(-coords[d]) as usize % shape[d]
} else if coords[d] >= shape[d] as isize {
(2 * shape[d] - 2 - coords[d] as usize) % shape[d]
} else {
coords[d] as usize
};
}
let input_dyn = input.view().into_dyn();
input_dyn[scirs2_core::ndarray::IxDyn(&mirrored_coords)]
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
#[test]
fn test_uniform_filter() {
let image: Array2<f64> = Array2::eye(5);
let result = uniform_filter(&image, &[3, 3], None).expect("Operation failed");
assert_eq!(result.shape(), image.shape());
}
#[test]
fn test_convolve() {
let image: Array2<f64> = Array2::eye(5);
let kernel: Array2<f64> = Array2::from_elem((3, 3), 1.0 / 9.0);
let result = convolve(&image, &kernel, None).expect("Operation failed");
assert_eq!(result.shape(), image.shape());
}
}