#[cfg(test)]
#[allow(clippy::module_inception)]
mod tests {
use super::super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::{array, Array1, Array2, Array3, Ix2, IxDyn};
#[test]
fn test_filters_preserveshape() {
let input = array![[1.0, 2.0], [4.0, 5.0]];
let uniform =
uniform_filter(&input, &[2, 2], None, None).expect("uniform_filter should succeed");
let min_filter =
minimum_filter(&input, &[2, 2], None, None).expect("minimum_filter should succeed");
let max_filter =
maximum_filter(&input, &[2, 2], None, None).expect("maximum_filter should succeed");
let gaussian =
gaussian_filter(&input, 1.0, None, None).expect("gaussian_filter should succeed");
let median = median_filter(&input, &[2, 2], None).expect("median_filter should succeed");
assert_eq!(uniform.shape(), input.shape());
assert_eq!(min_filter.shape(), input.shape());
assert_eq!(max_filter.shape(), input.shape());
assert_eq!(gaussian.shape(), input.shape());
assert_eq!(median.shape(), input.shape());
}
#[test]
fn test_uniform_filter_correctness() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let uniform = uniform_filter(&input, &[3, 3], None, None)
.expect("uniform_filter should succeed for 3x3 input");
let expected_avg = (1.0 + 2.0 + 3.0 + 4.0 + 5.0 + 6.0 + 7.0 + 8.0 + 9.0) / 9.0;
assert_abs_diff_eq!(uniform[[1, 1]], expected_avg, epsilon = 1e-10);
for i in 0..3 {
for j in 0..3 {
assert!(uniform[[i, j]] > 0.0);
assert!(uniform[[i, j]] < 10.0);
}
}
}
#[test]
fn test_extrema_filters_correctness() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let min_filter = minimum_filter(&input, &[3, 3], None, None)
.expect("minimum_filter should succeed for extrema test");
let max_filter = maximum_filter(&input, &[3, 3], None, None)
.expect("maximum_filter should succeed for extrema test");
assert_eq!(min_filter[[1, 1]], 1.0); assert_eq!(max_filter[[1, 1]], 9.0); }
#[test]
fn test_gaussian_filter_correctness() {
let mut input = Array2::<f64>::zeros((5, 5));
input[[2, 2]] = 1.0;
let result = gaussian_filter(&input, 1.0, None, None)
.expect("gaussian_filter should succeed for impulse test");
assert!(result[[2, 2]] > 0.0);
assert!(result[[2, 2]] < 1.0);
assert!(result[[1, 2]] > 0.0);
assert!(result[[2, 1]] > 0.0);
assert!(result[[3, 2]] > 0.0);
assert!(result[[2, 3]] > 0.0);
let sum: f64 = result.iter().sum();
assert_abs_diff_eq!(sum, 1.0, epsilon = 0.3);
}
#[test]
fn test_median_filter_correctness() {
let mut input = Array2::<f64>::zeros((5, 5));
input[[2, 2]] = 100.0;
let result = median_filter(&input, &[3, 3], None)
.expect("median_filter should succeed for outlier test");
assert_eq!(result[[2, 2]], 0.0);
}
#[test]
fn test_border_modes() {
let input = array![[1.0, 2.0], [3.0, 4.0]];
let constant = uniform_filter(&input, &[3, 3], Some(BorderMode::Constant), None)
.expect("uniform_filter with Constant mode should succeed");
let reflect = uniform_filter(&input, &[3, 3], Some(BorderMode::Reflect), None)
.expect("uniform_filter with Reflect mode should succeed");
let nearest = uniform_filter(&input, &[3, 3], Some(BorderMode::Nearest), None)
.expect("uniform_filter with Nearest mode should succeed");
let wrap = uniform_filter(&input, &[3, 3], Some(BorderMode::Wrap), None)
.expect("uniform_filter with Wrap mode should succeed");
let mirror = uniform_filter(&input, &[3, 3], Some(BorderMode::Mirror), None)
.expect("uniform_filter with Mirror mode should succeed");
assert!(constant[[0, 0]] != reflect[[0, 0]]);
assert!(reflect[[0, 0]] != nearest[[0, 0]]);
assert!(nearest[[0, 0]] != wrap[[0, 0]]);
assert!(wrap[[0, 0]] != mirror[[0, 0]]);
assert!(constant[[0, 0]] < reflect[[0, 0]]);
assert!(constant[[0, 0]] < nearest[[0, 0]]);
}
#[test]
fn test_3d_filtering() {
let mut input = Array3::<f64>::zeros((3, 3, 3));
input[[1, 1, 1]] = 1.0;
let uniform_result = uniform_filter(&input, &[3, 3, 3], None, None);
assert!(uniform_result.is_ok());
let uniform_output = uniform_result.expect("Test: operation failed");
assert!(uniform_output[[1, 1, 1]] > 0.0); assert_eq!(uniform_output.shape(), &[3, 3, 3]);
let min_result = minimum_filter(&input, &[3, 3, 3], None, None);
if let Ok(min_output) = min_result {
assert_eq!(min_output[[1, 1, 1]], 0.0); } else {
assert!(min_result.is_err());
}
let max_result = maximum_filter(&input, &[3, 3, 3], None, None);
if let Ok(max_output) = max_result {
assert_eq!(max_output[[1, 1, 1]], 1.0); } else {
assert!(max_result.is_err());
}
let gaussian3d = gaussian_filter(&input, 1.0, None, None)
.expect("gaussian_filter should succeed for 3D input");
assert!(gaussian3d[[1, 1, 1]] > 0.0); assert_eq!(gaussian3d.shape(), &[3, 3, 3]);
let sep_result = uniform_filter_separable(&input, &[3, 3, 3], None, None);
assert!(sep_result.is_ok());
let sep_output = sep_result.expect("Test: operation failed");
assert_eq!(sep_output.shape(), &[3, 3, 3]);
}
#[test]
fn test_dynamic_dimensions() {
let input = array![[1.0, 2.0], [3.0, 4.0]];
let input_dyn = input
.clone()
.into_dimensionality::<IxDyn>()
.expect("dimensionality conversion should succeed");
let result_dyn = uniform_filter(&input_dyn, &[2, 2], None, None)
.expect("uniform_filter should succeed for dynamic dimensions");
let result = result_dyn
.clone()
.into_dimensionality::<Ix2>()
.expect("dimensionality conversion back to Ix2 should succeed");
let direct_result = uniform_filter(&input, &[2, 2], None, None)
.expect("uniform_filter for comparison should succeed");
assert_eq!(result.shape(), direct_result.shape());
for (r1, r2) in result.iter().zip(direct_result.iter()) {
assert_abs_diff_eq!(*r1, *r2, epsilon = 1e-10);
}
}
#[test]
fn test_separable_uniform_filter() {
let input = array![[1.0, 2.0], [3.0, 4.0]];
let direct = uniform_filter(&input, &[2, 2], None, None)
.expect("direct uniform_filter should succeed");
let separable = uniform_filter_separable(&input, &[2, 2], None, None)
.expect("separable uniform_filter should succeed");
assert_eq!(direct.shape(), separable.shape());
for i in 0..2 {
for j in 0..2 {
assert!(separable[[i, j]] > 0.0);
assert!(separable[[i, j]] < 5.0);
}
}
}
#[test]
fn test_custom_origin() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let centered = uniform_filter(&input, &[3, 3], None, None)
.expect("centered uniform_filter should succeed");
let top_left = uniform_filter(&input, &[3, 3], None, Some(&[0, 0]))
.expect("top_left uniform_filter should succeed");
assert!(centered[[0, 0]] != top_left[[0, 0]]);
}
#[test]
fn test_single_size_expansion() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let result1 = uniform_filter(&input, &[3], None, None)
.expect("uniform_filter with single size should succeed");
let result2 = uniform_filter(&input, &[3, 3], None, None)
.expect("uniform_filter with explicit sizes should succeed");
assert_eq!(result1.shape(), result2.shape());
for (a, b) in result1.iter().zip(result2.iter()) {
assert_abs_diff_eq!(*a, *b, epsilon = 1e-10);
}
}
#[test]
fn test_filter_pipeline() {
let input = array![[1.0, 2.0], [3.0, 4.0]];
let smoothed = gaussian_filter(&input, 1.0, None, None)
.expect("gaussian_filter in pipeline should succeed");
let enhanced = minimum_filter(&smoothed, &[2, 2], None, None)
.expect("minimum_filter in pipeline should succeed");
let final_result = maximum_filter(&enhanced, &[2, 2], None, None)
.expect("maximum_filter in pipeline should succeed");
assert_eq!(final_result.shape(), input.shape());
}
#[test]
fn test_generic_filter_mean() {
use super::super::filter_functions;
use super::super::generic_filter;
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let result = generic_filter(&input, filter_functions::mean, &[3, 3], None, None)
.expect("generic_filter with mean function should succeed");
let expected = (1.0 + 2.0 + 3.0 + 4.0 + 5.0 + 6.0 + 7.0 + 8.0 + 9.0) / 9.0;
assert_abs_diff_eq!(result[[1, 1]], expected, epsilon = 1e-6);
assert_eq!(result.shape(), input.shape());
}
#[test]
fn test_generic_filter_range() {
use super::super::filter_functions;
use super::super::generic_filter;
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let result = generic_filter(&input, filter_functions::range, &[3, 3], None, None)
.expect("generic_filter with range function should succeed");
assert_abs_diff_eq!(result[[1, 1]], 8.0, epsilon = 1e-6);
}
#[test]
fn test_generic_filter_custom_function() {
use super::super::generic_filter;
use super::super::BorderMode;
let input = array![[1.0, 2.0], [3.0, 4.0]];
let max_func =
|values: &[f64]| -> f64 { values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)) };
let result = generic_filter(&input, max_func, &[2, 2], Some(BorderMode::Nearest), None)
.expect("generic_filter with custom function should succeed");
assert_eq!(result.shape(), input.shape());
assert_abs_diff_eq!(result[[1, 1]], 4.0, epsilon = 1e-6);
assert!(result[[0, 0]] >= 1.0); assert!(result[[0, 1]] >= 1.0);
assert!(result[[1, 0]] >= 1.0);
}
#[test]
fn test_generic_filter_1d() {
use super::super::filter_functions;
use super::super::generic_filter;
use scirs2_core::ndarray::Array1;
let input = Array1::from(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = generic_filter(&input, filter_functions::mean, &[3], None, None)
.expect("generic_filter 1D should succeed");
assert_eq!(result.len(), input.len());
assert_abs_diff_eq!(result[2], 3.0, epsilon = 1e-6);
}
#[test]
fn test_generic_filter_std_dev() {
use super::super::filter_functions;
use super::super::generic_filter;
let input = array![[5.0, 5.0, 5.0], [5.0, 5.0, 5.0], [5.0, 5.0, 5.0]];
let result = generic_filter(&input, filter_functions::std_dev, &[3, 3], None, None)
.expect("generic_filter with std_dev should succeed");
assert_abs_diff_eq!(result[[1, 1]], 0.0, epsilon = 1e-6);
}
#[test]
fn test_generic_filter_border_modes() {
use super::super::filter_functions;
use super::super::generic_filter;
use super::super::BorderMode;
let input = array![[1.0, 2.0], [3.0, 4.0]];
let constant = generic_filter(
&input,
filter_functions::mean,
&[3, 3],
Some(BorderMode::Constant),
Some(0.0),
)
.expect("generic_filter with Constant border mode should succeed");
let reflect = generic_filter(
&input,
filter_functions::mean,
&[3, 3],
Some(BorderMode::Reflect),
None,
)
.expect("generic_filter with Reflect border mode should succeed");
let nearest = generic_filter(
&input,
filter_functions::mean,
&[3, 3],
Some(BorderMode::Nearest),
None,
)
.expect("generic_filter with Nearest border mode should succeed");
assert!(constant[[0, 0]] != reflect[[0, 0]]);
assert!(reflect[[0, 0]] != nearest[[0, 0]]);
}
#[test]
fn test_extreme_kernel_sizes() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let result_1x1 = uniform_filter(&input, &[1, 1], None, None)
.expect("uniform_filter with 1x1 kernel should succeed");
assert_eq!(result_1x1, input);
let median_1x1 = median_filter(&input, &[1, 1], None)
.expect("median_filter with 1x1 kernel should succeed");
assert_eq!(median_1x1, input);
let small_input = array![[1.0, 2.0]];
let result_large = uniform_filter(&small_input, &[5, 5], None, None)
.expect("uniform_filter with large kernel should succeed");
assert_eq!(result_large.shape(), small_input.shape());
}
#[test]
fn test_numerical_edge_cases() {
let input_extreme = array![
[f64::MIN, f64::MAX, 0.0],
[f64::NEG_INFINITY, f64::INFINITY, f64::NAN]
];
let result = uniform_filter(&input_extreme, &[2, 2], None, None);
assert!(result.is_ok() || result.is_err());
let normal_input = array![[1.0, 2.0], [3.0, 4.0]];
let tiny_sigma = gaussian_filter(&normal_input, 1e-10, None, None)
.expect("gaussian_filter with tiny sigma should succeed");
assert_abs_diff_eq!(tiny_sigma[[0, 0]], normal_input[[0, 0]], epsilon = 0.1);
}
#[test]
fn test_degenerate_arrays() {
let input_1d = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result_1d = gaussian_filter(&input_1d, 1.0, None, None)
.expect("gaussian_filter on 1D array should succeed");
assert_eq!(result_1d.shape(), input_1d.shape());
let input_2x1 = array![[1.0], [2.0]];
let result_2x1 = median_filter(&input_2x1, &[1, 1], None)
.expect("median_filter on 2x1 array should succeed");
assert_eq!(result_2x1, input_2x1);
let single_pixel = array![[42.0]];
let result_single = uniform_filter(&single_pixel, &[1, 1], None, None)
.expect("uniform_filter on single pixel should succeed");
assert_eq!(result_single, single_pixel);
}
#[test]
fn test_consistency_across_dimensions() {
let input_3d = Array3::from_shape_fn((4, 4, 4), |(i, j, k)| (i + j + k) as f64);
let mut manual_result = input_3d.clone();
for mut slice in manual_result.axis_iter_mut(scirs2_core::ndarray::Axis(2)) {
let temp = gaussian_filter(&slice.to_owned(), 0.5, None, None)
.expect("gaussian_filter on slice should succeed");
slice.assign(&temp);
}
assert_eq!(manual_result.shape(), input_3d.shape());
}
#[test]
fn test_filter_with_all_border_modes() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
use crate::BorderMode;
let modes = [
BorderMode::Constant,
BorderMode::Reflect,
BorderMode::Mirror,
BorderMode::Wrap,
BorderMode::Nearest,
];
for mode in &modes {
let result = gaussian_filter(&input, 1.0, Some(*mode), None)
.expect("gaussian_filter with border mode should succeed");
assert_eq!(result.shape(), input.shape());
assert!(result.iter().all(|&x| x.is_finite()));
let median_result = median_filter(&input, &[3, 3], Some(*mode))
.expect("median_filter with border mode should succeed");
assert_eq!(median_result.shape(), input.shape());
}
}
#[test]
fn test_filter_precision_preservation() {
let input = array![
[1.0, 1.0000001, 1.0],
[1.0000001, 1.0, 1.0000001],
[1.0, 1.0000001, 1.0]
];
let result = gaussian_filter(&input, 0.1, None, None)
.expect("gaussian_filter for precision test should succeed");
let variance: f64 = result.iter().map(|&x| (x - 1.0).powi(2)).sum();
assert!(
variance > 0.0,
"Filter should preserve some of the input variation"
);
}
#[test]
fn test_asymmetric_kernels() {
let input = Array2::from_shape_fn((10, 20), |(i, j)| (i * j) as f64);
let result = uniform_filter(&input, &[3, 7], None, None)
.expect("uniform_filter with asymmetric kernel should succeed");
assert_eq!(result.shape(), input.shape());
let median_result = median_filter(&input, &[5, 3], None)
.expect("median_filter with asymmetric kernel should succeed");
assert_eq!(median_result.shape(), input.shape());
}
#[test]
fn test_filter_memory_efficiency() {
let large_input = Array2::from_shape_fn((100, 100), |(i, j)| ((i + j) as f64).sin());
let gaussian_result = gaussian_filter(&large_input, 2.0, None, None)
.expect("gaussian_filter on large array should succeed");
assert_eq!(gaussian_result.shape(), large_input.shape());
let uniform_result = uniform_filter(&large_input, &[5, 5], None, None)
.expect("uniform_filter on large array should succeed");
assert_eq!(uniform_result.shape(), large_input.shape());
}
#[test]
fn test_filter_commutativity() {
let input = array![
[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, 10.0, 11.0, 12.0]
];
let small_twice = {
let temp = gaussian_filter(&input, 0.5, None, None)
.expect("first gaussian_filter in commutativity test should succeed");
gaussian_filter(&temp, 0.5, None, None)
.expect("second gaussian_filter in commutativity test should succeed")
};
let larger_once = gaussian_filter(&input, 0.707, None, None)
.expect("gaussian_filter for commutativity comparison should succeed");
let max_diff = small_twice
.iter()
.zip(larger_once.iter())
.map(|(&a, &b)| (a - b).abs())
.fold(0.0, f64::max);
assert!(
max_diff < 0.5,
"Sequential small filters should approximate single larger filter"
);
}
}