use scirs2_core::ndarray::{Array, Array1, Array2, Dimension, Ix2, IxDyn};
use super::{pad_array, BorderMode};
use crate::error::{NdimageError, NdimageResult};
use scirs2_core::{parallel_ops, CoreError};
#[allow(dead_code)]
pub fn gaussian_filter<D>(
input: &Array<f64, D>,
sigma: f64,
mode: Option<BorderMode>,
truncate: Option<f64>,
) -> NdimageResult<Array<f64, D>>
where
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let trunc = truncate.unwrap_or(4.0);
if sigma <= 0.0 {
return Err(NdimageError::InvalidInput("Sigma must be positive".into()));
}
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if input.len() <= 1 {
return Ok(input.to_owned());
}
match input.ndim() {
1 => {
gaussian_filter1d_f64(input, sigma, mode, truncate)
}
2 => {
let temp = apply_gaussian_along_axis_f64(input, 0, sigma, &border_mode, trunc)?;
apply_gaussian_along_axis_f64(&temp, 1, sigma, &border_mode, trunc)
}
_ => {
let ndim = input.ndim();
let mut result = input.to_owned();
for axis in 0..ndim {
result =
apply_gaussian_along_axis_nd_f64(&result, axis, sigma, &border_mode, trunc)?;
}
Ok(result)
}
}
}
#[allow(dead_code)]
pub fn gaussian_filter1d_f64<D>(
input: &Array<f64, D>,
sigma: f64,
mode: Option<BorderMode>,
truncate: Option<f64>,
) -> NdimageResult<Array<f64, D>>
where
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let trunc = truncate.unwrap_or(4.0);
if sigma <= 0.0 {
return Err(NdimageError::InvalidInput("Sigma must be positive".into()));
}
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if input.len() <= 1 {
return Ok(input.to_owned());
}
let kernel = gaussian_kernel1d_f64(sigma, trunc)?;
if 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 result_1d = apply_kernel1d_1d_f64(&input_1d, &kernel, &border_mode)?;
return result_1d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back from 1D array".into())
});
}
Ok(input.to_owned())
}
#[allow(dead_code)]
pub fn gaussian_kernel1d_f64(sigma: f64, truncate: f64) -> NdimageResult<Array1<f64>> {
if sigma <= 0.0 {
return Err(NdimageError::InvalidInput("Sigma must be positive".into()));
}
let radius = (truncate * sigma).ceil();
let radius_int = radius as usize;
let size = 2 * radius_int + 1;
let mut kernel = Array1::zeros(size);
let mut x_values = Array1::zeros(size);
for i in 0..size {
x_values[i] = (i as f64) - (radius_int as f64);
}
let mut x_squared = Array1::zeros(size);
for i in 0..size {
x_squared[i] = x_values[i] * x_values[i];
}
let two_sigma_squared = 2.0 * sigma * sigma;
for i in 0..size {
kernel[i] = (-x_squared[i] / two_sigma_squared).exp();
}
let sum = kernel.sum();
if sum > 0.0 {
kernel.mapv_inplace(|x| x / sum);
}
Ok(kernel)
}
#[allow(dead_code)]
fn apply_kernel1d_1d_f64(
input: &Array1<f64>,
kernel: &Array1<f64>,
mode: &BorderMode,
) -> NdimageResult<Array1<f64>> {
let input_len = input.len();
let kernel_len = kernel.len();
let radius = kernel_len / 2;
let mut output = Array1::zeros(input_len);
let pad_width = vec![(radius, radius)];
let padded_input = pad_array(input, &pad_width, mode, None)?;
for i in 0..input_len {
let center = i + radius;
let mut sum = 0.0;
for k in 0..kernel_len {
sum += padded_input[center + k - radius] * kernel[k];
}
output[i] = sum;
}
Ok(output)
}
#[allow(dead_code)]
fn apply_gaussian_along_axis_f64<D>(
input: &Array<f64, D>,
axis: usize,
sigma: f64,
mode: &BorderMode,
truncate: f64,
) -> NdimageResult<Array<f64, D>>
where
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 kernel = gaussian_kernel1d_f64(sigma, truncate)?;
if input.ndim() == 2 {
let array2d = input
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| NdimageError::DimensionError("Failed to convert to 2D array".into()))?;
let mut output = array2d.clone();
match axis {
0 => {
for i in 0..array2d.shape()[0] {
let row_view = array2d.row(i).to_owned();
let row_1d = row_view.as_slice().ok_or_else(|| {
NdimageError::ComputationError(
"Failed to get contiguous slice from row".into(),
)
})?;
let row_array = Array1::from_vec(row_1d.to_vec());
let filtered_row = apply_kernel1d_1d_f64(&row_array, &kernel, mode)?;
for j in 0..array2d.shape()[1] {
output[[i, j]] = filtered_row[j];
}
}
}
1 => {
for j in 0..array2d.shape()[1] {
let col_view = array2d.column(j).to_owned();
let col_1d = col_view.as_slice().ok_or_else(|| {
NdimageError::ComputationError(
"Failed to get contiguous slice from column".into(),
)
})?;
let col_array = Array1::from_vec(col_1d.to_vec());
let filtered_col = apply_kernel1d_1d_f64(&col_array, &kernel, mode)?;
for i in 0..array2d.shape()[0] {
output[[i, j]] = filtered_col[i];
}
}
}
_ => unreachable!(),
}
output.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back from 2D array".into())
})
} else {
apply_gaussian_along_axis_nd_f64(input, axis, sigma, mode, truncate)
}
}
#[allow(dead_code)]
fn apply_gaussian_along_axis_nd_f64<D>(
input: &Array<f64, D>,
axis: usize,
sigma: f64,
mode: &BorderMode,
truncate: f64,
) -> NdimageResult<Array<f64, D>>
where
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 kernel = gaussian_kernel1d_f64(sigma, truncate)?;
let kernel_len = kernel.len();
let kernel_radius = kernel_len / 2;
let inputshape = input.shape();
let input_dyn = input.clone().into_dyn();
let mut output_dyn = Array::zeros(input_dyn.raw_dim());
let indices: Vec<IxDyn> = output_dyn
.indexed_iter()
.map(|(idx, _)| idx.clone())
.collect();
let convolve_position = |idx: &IxDyn| -> (IxDyn, f64) {
let idx_vec = idx.as_array_view().to_vec();
let mut sum = 0.0;
for k in 0..kernel.len() {
let mut padded_idx_vec = idx_vec.clone();
let kernel_pos = k as isize - kernel_radius as isize;
let src_pos = idx_vec[axis] as isize + kernel_pos;
let src_len = inputshape[axis] as isize;
let src_idx = match mode {
BorderMode::Reflect => {
if src_pos < 0 {
(-src_pos - 1) as usize % src_len as usize
} else if src_pos >= src_len {
(2 * src_len - src_pos - 1) as usize % src_len as usize
} else {
src_pos as usize
}
}
BorderMode::Mirror => {
if src_pos < 0 {
(-src_pos) as usize % (src_len as usize * 2 - 2).max(1)
} else if src_pos >= src_len {
(2 * src_len - src_pos - 2) as usize % (src_len as usize * 2 - 2).max(1)
} else {
src_pos as usize
}
}
BorderMode::Wrap => {
if src_len == 0 {
0
} else {
((src_pos % src_len + src_len) % src_len) as usize
}
}
BorderMode::Constant => {
if src_pos < 0 || src_pos >= src_len {
continue;
} else {
src_pos as usize
}
}
BorderMode::Nearest => {
if src_pos < 0 {
0
} else if src_pos >= src_len {
(src_len - 1) as usize
} else {
src_pos as usize
}
}
};
padded_idx_vec[axis] = src_idx;
let padded_idx = IxDyn(&padded_idx_vec);
sum += input_dyn[&padded_idx] * kernel[k];
}
(idx.clone(), sum)
};
let threshold = 1000;
if input.len() <= threshold {
for idx in indices {
let (pos, value) = convolve_position(&idx);
output_dyn[&pos] = value;
}
} else {
let inputshape_clone = inputshape.to_vec();
let axis_clone = axis;
let mode_clone = *mode;
let kernel_clone = kernel.clone();
let input_dyn_arc = std::sync::Arc::new(input_dyn.clone());
let parallel_convolve = move |idx: &IxDyn| -> std::result::Result<(IxDyn, f64), CoreError> {
let idx_vec = idx.as_array_view().to_vec();
let mut sum = 0.0;
for k in 0..kernel_clone.len() {
let mut padded_idx_vec = idx_vec.clone();
let kernel_pos = k as isize - (kernel_clone.len() / 2) as isize;
let src_pos = idx_vec[axis_clone] as isize + kernel_pos;
let src_len = inputshape_clone[axis_clone] as isize;
let src_idx = match mode_clone {
BorderMode::Reflect => {
if src_pos < 0 {
(-src_pos - 1) as usize % src_len as usize
} else if src_pos >= src_len {
(2 * src_len - src_pos - 1) as usize % src_len as usize
} else {
src_pos as usize
}
}
BorderMode::Mirror => {
if src_pos < 0 {
(-src_pos) as usize % (src_len as usize * 2 - 2).max(1)
} else if src_pos >= src_len {
(2 * src_len - src_pos - 2) as usize % (src_len as usize * 2 - 2).max(1)
} else {
src_pos as usize
}
}
BorderMode::Wrap => {
if src_len == 0 {
0
} else {
((src_pos % src_len + src_len) % src_len) as usize
}
}
BorderMode::Constant => {
if src_pos < 0 || src_pos >= src_len {
continue;
} else {
src_pos as usize
}
}
BorderMode::Nearest => {
if src_pos < 0 {
0
} else if src_pos >= src_len {
(src_len - 1) as usize
} else {
src_pos as usize
}
}
};
padded_idx_vec[axis_clone] = src_idx;
let padded_idx = IxDyn(&padded_idx_vec);
sum += input_dyn_arc[&padded_idx] * kernel_clone[k];
}
Ok((idx.clone(), sum))
};
let results = parallel_ops::parallel_map_result(&indices, parallel_convolve)?;
for (pos, value) in results {
output_dyn[&pos] = value;
}
}
output_dyn.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back to original dimensions".into())
})
}
#[allow(dead_code)]
pub fn gaussian_filter_f32<D>(
input: &Array<f32, D>,
sigma: f32,
mode: Option<BorderMode>,
truncate: Option<f32>,
) -> NdimageResult<Array<f32, D>>
where
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let trunc = truncate.unwrap_or(4.0);
if sigma <= 0.0 {
return Err(NdimageError::InvalidInput("Sigma must be positive".into()));
}
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if input.len() <= 1 {
return Ok(input.to_owned());
}
match input.ndim() {
1 => {
let array1d = input
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::Ix1>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to 1D array".into())
})?;
let radius = (trunc * sigma).ceil() as usize;
let size = 2 * radius + 1;
let mut kernel = Array1::zeros(size);
let two_sigma_sq = 2.0 * sigma * sigma;
let mut sum = 0.0;
for i in 0..size {
let x = (i as f32) - (radius as f32);
let val = (-x * x / two_sigma_sq).exp();
kernel[i] = val;
sum += val;
}
if sum > 0.0 {
kernel.mapv_inplace(|x| x / sum);
}
let mut output = Array1::zeros(array1d.raw_dim());
let padded = pad_array(&array1d, &[(radius, radius)], &border_mode, None)?;
for i in 0..array1d.len() {
let mut sum = 0.0;
for k in 0..kernel.len() {
sum += padded[i + k] * kernel[k];
}
output[i] = sum;
}
let result = output.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert from 1D array".into())
})?;
Ok(result)
}
2 => {
let array2d = input.to_owned().into_dimensionality::<Ix2>().map_err(|_| {
NdimageError::DimensionError("Failed to convert to 2D array".into())
})?;
let radius = (trunc * sigma).ceil() as usize;
let size = 2 * radius + 1;
let mut kernel = Array1::zeros(size);
let two_sigma_sq = 2.0 * sigma * sigma;
let mut sum = 0.0;
for i in 0..size {
let x = (i as f32) - (radius as f32);
let val = (-x * x / two_sigma_sq).exp();
kernel[i] = val;
sum += val;
}
if sum > 0.0 {
kernel.mapv_inplace(|x| x / sum);
}
let shape = array2d.shape();
let mut temp = Array2::zeros((shape[0], shape[1]));
let padded_rows = pad_array(&array2d, &[(0, 0), (radius, radius)], &border_mode, None)?;
for i in 0..shape[0] {
for j in 0..shape[1] {
let mut sum = 0.0;
for k in 0..kernel.len() {
sum += padded_rows[[i, j + k]] * kernel[k];
}
temp[[i, j]] = sum;
}
}
let mut output = Array2::zeros((shape[0], shape[1]));
let padded_cols = pad_array(&temp, &[(radius, radius), (0, 0)], &border_mode, None)?;
for i in 0..shape[0] {
for j in 0..shape[1] {
let mut sum = 0.0;
for k in 0..kernel.len() {
sum += padded_cols[[i + k, j]] * kernel[k];
}
output[[i, j]] = sum;
}
}
let result = output.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert from 2D array".into())
})?;
Ok(result)
}
_ => {
let input_dyn = input.to_owned().into_dyn();
let mut result = input_dyn.clone();
let radius = (trunc * sigma).ceil() as usize;
let size = 2 * radius + 1;
let mut kernel = Array1::zeros(size);
let two_sigma_sq = 2.0 * sigma * sigma;
let mut sum = 0.0;
for i in 0..size {
let x = (i as f32) - (radius as f32);
let val = (-x * x / two_sigma_sq).exp();
kernel[i] = val;
sum += val;
}
if sum > 0.0 {
kernel.mapv_inplace(|x| x / sum);
}
for axis in 0..input.ndim() {
let mut output = Array::zeros(result.raw_dim());
let inputshape = result.shape();
let mut pad_width = vec![(0, 0); input.ndim()];
pad_width[axis] = (radius, radius);
let padded = pad_array(&result, &pad_width, &border_mode, None)?;
for (idx, val) in output.indexed_iter_mut() {
let mut sum = 0.0;
for k in 0..kernel.len() {
let mut padded_idx = idx.as_array_view().to_vec();
let kernel_pos = k as isize - radius as isize;
let src_pos = idx[axis] as isize + kernel_pos;
let src_len = inputshape[axis] as isize;
let src_idx = match border_mode {
BorderMode::Reflect => {
if src_pos < 0 {
(-src_pos - 1) as usize % src_len as usize
} else if src_pos >= src_len {
(2 * src_len - src_pos - 1) as usize % src_len as usize
} else {
src_pos as usize
}
}
BorderMode::Mirror => {
if src_pos < 0 {
(-src_pos) as usize % (src_len as usize * 2 - 2).max(1)
} else if src_pos >= src_len {
(2 * src_len - src_pos - 2) as usize
% (src_len as usize * 2 - 2).max(1)
} else {
src_pos as usize
}
}
BorderMode::Wrap => {
if src_len == 0 {
0
} else {
((src_pos % src_len + src_len) % src_len) as usize
}
}
BorderMode::Constant => {
if src_pos < 0 || src_pos >= src_len {
continue;
} else {
src_pos as usize
}
}
BorderMode::Nearest => {
if src_pos < 0 {
0
} else if src_pos >= src_len {
(src_len - 1) as usize
} else {
src_pos as usize
}
}
};
padded_idx[axis] = src_idx;
let idx_dyn = IxDyn(&padded_idx);
sum += padded[&idx_dyn] * kernel[k];
}
*val = sum;
}
result = output;
}
let result = result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back to original dimensions".into())
})?;
Ok(result)
}
}
}
#[allow(dead_code)]
pub fn gaussian_kernel1d_f32(sigma: f32, truncate: f32) -> NdimageResult<Array1<f32>> {
if sigma <= 0.0 {
return Err(NdimageError::InvalidInput("Sigma must be positive".into()));
}
let radius = (truncate * sigma).ceil();
let radius_int = radius as usize;
let size = 2 * radius_int + 1;
let mut kernel = Array1::zeros(size);
let mut x_values = Array1::zeros(size);
for i in 0..size {
x_values[i] = (i as f32) - (radius_int as f32);
}
let mut x_squared = Array1::zeros(size);
for i in 0..size {
x_squared[i] = x_values[i] * x_values[i];
}
let two_sigma_squared = 2.0 * sigma * sigma;
for i in 0..size {
kernel[i] = (-x_squared[i] / two_sigma_squared).exp();
}
let sum = kernel.sum();
if sum > 0.0 {
kernel.mapv_inplace(|x| x / sum);
}
Ok(kernel)
}
#[allow(dead_code)]
pub fn gaussian_filter_f64<D>(
input: &Array<f64, D>,
sigma: f64,
mode: Option<BorderMode>,
truncate: Option<f64>,
) -> NdimageResult<Array<f64, D>>
where
D: Dimension + 'static,
{
gaussian_filter(input, sigma, mode, truncate)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::Array1;
#[test]
fn test_gaussian_kernel1d() {
let sigma = 1.0;
let truncate = 4.0;
let kernel = gaussian_kernel1d_f64(sigma, truncate)
.expect("gaussian_kernel1d_f64 should succeed for test");
assert_eq!(kernel.len(), 9);
let sum: f64 = kernel.sum();
assert!((sum - 1.0).abs() < 1e-10);
}
#[test]
fn test_gaussian_filter1d() {
let mut input = Array1::zeros(11);
input[5] = 1.0;
let sigma = 1.0;
let result = gaussian_filter1d_f64(&input, sigma, None, None)
.expect("gaussian_filter1d_f64 should succeed for test");
assert!(result[5] < 1.0); assert!(result[4] > 0.0); assert!(result[6] > 0.0);
let sum: f64 = result.sum();
assert_abs_diff_eq!(sum, 1.0, epsilon = 1e-10);
}
#[test]
fn test_gaussian_filter_2d() {
use scirs2_core::ndarray::Array2;
let mut input = Array2::zeros((7, 7));
input[[3, 3]] = 1.0;
let sigma = 1.0;
let result = gaussian_filter(&input, sigma, Some(BorderMode::Constant), None)
.expect("gaussian_filter should succeed for test");
assert!(result[[3, 3]] < 1.0); assert!(result[[2, 3]] > 0.0); assert!(result[[3, 2]] > 0.0);
assert!(result[[4, 3]] > 0.0);
assert!(result[[3, 4]] > 0.0);
let sum: f64 = result.sum();
assert!(sum > 0.95); assert!(sum < 1.02); }
#[test]
fn test_gaussian_filter_3d() {
use scirs2_core::ndarray::Array3;
let mut input = Array3::zeros((5, 5, 5));
input[[2, 2, 2]] = 1.0;
let sigma = 1.0;
let result = gaussian_filter(&input, sigma, Some(BorderMode::Reflect), None)
.expect("gaussian_filter should succeed for test");
assert!(result[[2, 2, 2]] > 0.0); assert!(result[[1, 2, 2]] > 0.0); assert!(result[[2, 1, 2]] > 0.0);
assert!(result[[2, 2, 1]] > 0.0);
assert!(result[[3, 2, 2]] > 0.0);
assert!(result[[2, 3, 2]] > 0.0);
assert!(result[[2, 2, 3]] > 0.0);
println!("Gaussian 3D filter center value: {}", result[[2, 2, 2]]);
let sum: f64 = result.sum();
println!("Gaussian 3D filter sum: {}", sum);
assert!(sum > 0.9);
assert!(sum < 1.1);
let small_sigma = 0.1;
let small_result = gaussian_filter(&input, small_sigma, Some(BorderMode::Reflect), None)
.expect("gaussian_filter should succeed for test");
println!(
"Gaussian 3D filter (small sigma) center value: {}",
small_result[[2, 2, 2]]
);
assert!(small_result[[2, 2, 2]] > 0.5); }
}