use scirs2_core::ndarray::{Array, Array1, Array2, ArrayView, Dimension};
use scirs2_core::numeric::{Float, FromPrimitive, Zero};
use scirs2_core::{parallel_ops, CoreError};
use std::fmt::Debug;
use super::{pad_array, BorderMode};
use crate::error::{NdimageError, NdimageResult};
#[allow(dead_code)]
pub fn maximum_filter<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
D: Dimension,
{
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()
)));
}
let mut total_size = 1;
for &s in size {
if s == 0 {
return Err(NdimageError::InvalidInput(
"Kernel size cannot be zero".into(),
));
}
total_size *= s;
}
rank_filter(input, total_size - 1, size, Some(border_mode))
}
#[allow(dead_code)]
pub fn minimum_filter<T, D>(
input: &Array<T, D>,
size: &[usize],
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
D: Dimension,
{
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(),
));
}
}
rank_filter(input, 0, size, Some(border_mode))
}
#[allow(dead_code)]
pub fn percentile_filter<T, D>(
input: &Array<T, D>,
percentile: f64,
size: &[usize],
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
D: Dimension,
{
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(),
));
}
}
if !(0.0..=100.0).contains(&percentile) {
return Err(NdimageError::InvalidInput(format!(
"Percentile must be between 0 and 100, got {}",
percentile
)));
}
let mut total_size = 1;
for &s in size {
total_size *= s;
}
let rank = ((percentile / 100.0) * (total_size as f64 - 1.0)).round() as usize;
if rank >= total_size {
return Err(NdimageError::InvalidInput(format!(
"Calculated rank {} is out of bounds for window of size {}",
rank, total_size
)));
}
rank_filter(input, rank, size, Some(border_mode))
}
#[allow(dead_code)]
pub fn percentile_filter_footprint<T, D>(
input: ArrayView<T, D>,
percentile: f64,
footprint: ArrayView<bool, D>,
mode: BorderMode,
origin: Vec<isize>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
D: Dimension,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if footprint.ndim() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Footprint must have same dimensionality as input (got {} expected {})",
footprint.ndim(),
input.ndim()
)));
}
if !(0.0..=100.0).contains(&percentile) {
return Err(NdimageError::InvalidInput(format!(
"Percentile must be between 0 and 100, got {}",
percentile
)));
}
if origin.len() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Origin must have same length as input dimensions (got {} expected {})",
origin.len(),
input.ndim()
)));
}
let total_size = footprint.iter().filter(|&&x| x).count();
if total_size == 0 {
return Err(NdimageError::InvalidInput(
"Footprint cannot be empty (must have at least one true value)".into(),
));
}
let rank = ((percentile / 100.0) * (total_size as f64 - 1.0)).round() as usize;
if rank >= total_size {
return Err(NdimageError::InvalidInput(format!(
"Calculated rank {} is out of bounds for footprint of size {}",
rank, total_size
)));
}
rank_filter_footprint(input, rank, footprint, mode, origin)
}
#[allow(dead_code)]
pub fn rank_filter_footprint<T, D>(
input: ArrayView<T, D>,
rank: usize,
footprint: ArrayView<bool, D>,
mode: BorderMode,
origin: Vec<isize>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
D: Dimension,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if footprint.ndim() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Footprint must have same dimensionality as input (got {} expected {})",
footprint.ndim(),
input.ndim()
)));
}
let total_size = footprint.iter().filter(|&&x| x).count();
if total_size == 0 {
return Err(NdimageError::InvalidInput(
"Footprint cannot be empty (must have at least one true value)".into(),
));
}
if rank >= total_size {
return Err(NdimageError::InvalidInput(format!(
"Rank {} is out of bounds for footprint of size {}",
rank, total_size
)));
}
if input.len() <= 1 {
return Ok(input.to_owned());
}
let mut output = Array::<T, D>::zeros(input.raw_dim());
let footprintshape = footprint.shape();
let footprint_center: Vec<isize> = footprintshape.iter().map(|&s| (s / 2) as isize).collect();
let mut pad_width = Vec::new();
for d in 0..input.ndim() {
let left_pad = (footprint_center[d] + origin[d]) as usize;
let right_pad = footprintshape[d] - 1 - left_pad;
pad_width.push((left_pad, right_pad));
}
let padded_input = pad_array(&input.to_owned(), &pad_width, &mode, None)?;
let input_dyn = input.view().into_dyn();
let footprint_dyn = footprint.view().into_dyn();
let mut output_dyn = output.view_mut().into_dyn();
for linear_idx in 0..input.len() {
let output_coords = {
let dims = input.shape();
let mut coords = Vec::new();
let mut remaining = linear_idx;
for d in (0..dims.len()).rev() {
coords.insert(0, remaining % dims[d]);
remaining /= dims[d];
}
coords
};
let mut values = Vec::new();
for footprint_linear_idx in 0..footprint.len() {
let footprint_coords = {
let dims = footprint.shape();
let mut coords = Vec::new();
let mut remaining = footprint_linear_idx;
for d in (0..dims.len()).rev() {
coords.insert(0, remaining % dims[d]);
remaining /= dims[d];
}
coords
};
let is_active = footprint_dyn[scirs2_core::ndarray::IxDyn(&footprint_coords)];
if is_active {
let mut input_coords = Vec::new();
for d in 0..input.ndim() {
let coord = output_coords[d] + footprint_coords[d];
input_coords.push(coord);
}
let padded_dyn = padded_input.view().into_dyn();
let value = padded_dyn[scirs2_core::ndarray::IxDyn(&input_coords)];
values.push(value);
}
}
if !values.is_empty() {
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let selected_value = values[rank.min(values.len() - 1)];
output_dyn[scirs2_core::ndarray::IxDyn(&output_coords)] = selected_value;
}
}
Ok(output)
}
#[allow(dead_code)]
pub fn rank_filter<T, D>(
input: &Array<T, D>,
rank: usize,
size: &[usize],
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
D: Dimension,
{
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()
)));
}
let mut total_size = 1;
for &s in size {
if s == 0 {
return Err(NdimageError::InvalidInput(
"Kernel size cannot be zero".into(),
));
}
total_size *= s;
}
if rank >= total_size {
return Err(NdimageError::InvalidInput(format!(
"Rank {} is out of bounds for window of size {}",
rank, total_size
)));
}
if input.len() <= 1 {
return Ok(input.to_owned());
}
let _radii: Vec<usize> = size.iter().map(|&s| s / 2).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())
})?;
let result_1d = rank_filter_1d(&input_1d, rank, size[0], &border_mode)?;
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())
})?;
let result_2d = rank_filter_2d(&input_2d, rank, size, &border_mode)?;
result_2d.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back from 2D array".into())
})
}
_ => {
let input_dyn = input
.to_owned()
.into_dimensionality::<scirs2_core::ndarray::IxDyn>()
.map_err(|_| {
NdimageError::DimensionError("Failed to convert to dynamic array".into())
})?;
let result_dyn = rank_filter_nd(&input_dyn, rank, size, &border_mode)?;
result_dyn.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError("Failed to convert back from dynamic array".into())
})
}
}
}
#[allow(dead_code)]
fn rank_filter_1d<T>(
input: &Array1<T>,
rank: usize,
size: usize,
mode: &BorderMode,
) -> NdimageResult<Array1<T>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + Zero + 'static,
{
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && input.len() > 100 {
if size == 3 || size == 5 {
if size == 3 {
return optimize_for_f32_size3(input, rank, mode);
} else if size == 5 {
return optimize_for_f32_size5(input, rank, mode);
}
unreachable!();
}
}
let radius = size / 2;
let mut output = Array1::zeros(input.len());
let pad_width = vec![(radius, radius)];
let padded_input = pad_array(input, &pad_width, mode, None)?;
if input.len() > 1000 {
let process_window = move |i: &usize| -> std::result::Result<T, CoreError> {
let mut window = vec![T::zero(); size];
let center = *i + radius;
for k in 0..size {
window[k] = padded_input[center - radius + k];
}
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(window[rank])
};
let indices: Vec<usize> = (0..input.len()).collect();
let parallel_results = parallel_ops::parallel_map_result(&indices, process_window)?;
for (i, val) in parallel_results.iter().enumerate() {
output[i] = *val;
}
return Ok(output);
}
let mut window = vec![T::zero(); size];
for i in 0..input.len() {
let center = i + radius;
for (k, window_val) in window.iter_mut().enumerate().take(size) {
*window_val = padded_input[center - radius + k];
}
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
output[i] = window[rank];
}
Ok(output)
}
#[inline]
#[allow(dead_code)]
fn sort3(a: f32, b: f32, c: f32) -> (f32, f32, f32) {
if a > b {
if b > c {
(c, b, a)
} else if a > c {
(b, c, a)
} else {
(b, a, c)
}
} else if a > c {
(c, a, b)
} else if b > c {
(a, c, b)
} else {
(a, b, c)
}
}
#[inline]
#[allow(dead_code)]
fn sort5(arr: &mut [f32; 5]) {
if arr[0] > arr[1] {
swap(arr, 0, 1);
}
if arr[3] > arr[4] {
swap(arr, 3, 4);
}
if arr[2] > arr[4] {
swap(arr, 2, 4);
}
if arr[2] > arr[3] {
swap(arr, 2, 3);
}
if arr[0] > arr[3] {
swap(arr, 0, 3);
}
if arr[0] > arr[2] {
swap(arr, 0, 2);
}
if arr[1] > arr[4] {
swap(arr, 1, 4);
}
if arr[1] > arr[3] {
swap(arr, 1, 3);
}
if arr[1] > arr[2] {
swap(arr, 1, 2);
}
}
#[inline]
#[allow(dead_code)]
fn swap<T: PartialOrd>(arr: &mut [T], i: usize, j: usize) {
if arr[i] > arr[j] {
arr.swap(i, j);
}
}
#[allow(dead_code)]
fn optimize_for_f32_size3<T>(
input: &Array1<T>,
rank: usize,
mode: &BorderMode,
) -> NdimageResult<Array1<T>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
{
let input_data: Vec<f32> = input
.iter()
.map(|x| {
x.to_f32().unwrap_or_else(|| {
f32::NAN
})
})
.collect();
let input_f32 = Array1::from_vec(input_data);
let size = 3;
let radius = size / 2;
let mut output_f32 = Array1::zeros(input.len());
let pad_width = vec![(radius, radius)];
let padded_input = pad_array(&input_f32, &pad_width, mode, None)?;
let mut results = Vec::with_capacity(input.len());
for i in 0..input.len() {
let center = i + radius;
let a = padded_input[center - 1];
let b = padded_input[center];
let c = padded_input[center + 1];
let (min, mid, max) = sort3(a, b, c);
let val = match rank {
0 => min,
1 => mid,
2 => max,
_ => unreachable!(), };
results.push((i, val));
}
for (idx, val) in results {
output_f32[idx] = val;
}
let output = output_f32.mapv(|x| {
T::from_f32(x).unwrap_or_else(|| {
T::zero()
})
});
Ok(output)
}
#[allow(dead_code)]
fn optimize_for_f32_size5<T>(
input: &Array1<T>,
rank: usize,
mode: &BorderMode,
) -> NdimageResult<Array1<T>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
{
let input_data: Vec<f32> = input
.iter()
.map(|x| {
x.to_f32().unwrap_or_else(|| {
f32::NAN
})
})
.collect();
let input_f32 = Array1::from_vec(input_data);
let size = 5;
let radius = size / 2;
let mut output_f32 = Array1::zeros(input.len());
let pad_width = vec![(radius, radius)];
let padded_input = pad_array(&input_f32, &pad_width, mode, None)?;
{
let process_window = move |i: &usize| -> std::result::Result<(usize, f32), CoreError> {
let center = *i + radius;
let mut window = [
padded_input[center - 2],
padded_input[center - 1],
padded_input[center],
padded_input[center + 1],
padded_input[center + 2],
];
sort5(&mut window);
Ok((*i, window[rank]))
};
let indices: Vec<usize> = (0..input.len()).collect();
let results = parallel_ops::parallel_map_result(&indices, process_window)?;
for (idx, val) in results {
output_f32[idx] = val;
}
let output = output_f32.mapv(|x| {
T::from_f32(x).unwrap_or_else(|| {
T::zero()
})
});
return Ok(output);
}
#[allow(unreachable_code)]
{
let output = output_f32.mapv(|x| {
T::from_f32(x).unwrap_or_else(|| {
T::zero()
})
});
Ok(output)
}
}
#[allow(dead_code)]
fn rank_filter_2d<T>(
input: &Array2<T>,
rank: usize,
size: &[usize],
mode: &BorderMode,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
{
let rows = input.shape()[0];
let cols = input.shape()[1];
let radius_y = size[0] / 2;
let radius_x = size[1] / 2;
let window_size = size[0] * size[1];
let mut output = Array2::zeros((rows, cols));
let pad_width = vec![(radius_y, radius_y), (radius_x, radius_x)];
let padded_input = pad_array(input, &pad_width, mode, None)?;
if rows * cols > 10000 {
let size_clone = size.to_vec();
let process_row = move |i: &usize| -> std::result::Result<Vec<T>, CoreError> {
let mut row_results = Vec::with_capacity(cols);
let mut window = vec![T::zero(); window_size];
for j in 0..cols {
let center_y = *i + radius_y;
let center_x = j + radius_x;
let mut window_idx = 0;
for ky in 0..size_clone[0] {
for kx in 0..size_clone[1] {
let y = center_y - radius_y + ky;
let x = center_x - radius_x + kx;
window[window_idx] = padded_input[[y, x]];
window_idx += 1;
}
}
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
row_results.push(window[rank]);
}
Ok(row_results)
};
let row_indices: Vec<usize> = (0..rows).collect();
let results = parallel_ops::parallel_map_result(&row_indices, process_row)?;
for (i, row) in results.iter().enumerate() {
for (j, val) in row.iter().enumerate() {
output[[i, j]] = *val;
}
}
return Ok(output);
}
let mut window = vec![T::zero(); window_size];
for i in 0..rows {
for j in 0..cols {
let center_y = i + radius_y;
let center_x = j + radius_x;
let mut window_idx = 0;
for ky in 0..size[0] {
for kx in 0..size[1] {
let y = center_y - radius_y + ky;
let x = center_x - radius_x + kx;
window[window_idx] = padded_input[[y, x]];
window_idx += 1;
}
}
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
output[[i, j]] = window[rank];
}
}
Ok(output)
}
#[allow(dead_code)]
fn rank_filter_nd<T>(
input: &Array<T, scirs2_core::ndarray::IxDyn>,
rank: usize,
size: &[usize],
mode: &BorderMode,
) -> NdimageResult<Array<T, scirs2_core::ndarray::IxDyn>>
where
T: Float + FromPrimitive + Debug + PartialOrd + Clone + Send + Sync + 'static,
{
let shape = input.shape();
let ndim = input.ndim();
let window_size = size.iter().product();
let radii: Vec<usize> = size.iter().map(|&s| s / 2).collect();
let mut output = Array::zeros(input.raw_dim());
let pad_width: Vec<(usize, usize)> = radii.iter().map(|&r| (r, r)).collect();
let padded_input = pad_array(input, &pad_width, mode, None)?;
let mut window = Vec::with_capacity(window_size);
for idx in scirs2_core::ndarray::indices(shape) {
let idx_vec: Vec<_> = idx.slice().to_vec();
window.clear();
let center_coords: Vec<usize> = idx_vec
.iter()
.zip(&radii)
.map(|(&idx, &radius)| idx + radius)
.collect();
let mut offset_stack = vec![Vec::new()];
for &window_dim_size in size {
let mut new_stack = Vec::new();
for existing_offset in offset_stack {
for offset in 0..window_dim_size {
let mut new_offset = existing_offset.clone();
new_offset.push(offset);
new_stack.push(new_offset);
}
}
offset_stack = new_stack;
}
for offsets in offset_stack {
let mut padded_coords = Vec::with_capacity(ndim);
for ((¢er, &radius), &offset) in center_coords.iter().zip(&radii).zip(&offsets) {
padded_coords.push(center - radius + offset);
}
let value = padded_input[padded_coords.as_slice()];
window.push(value);
}
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
output[idx_vec.as_slice()] = window[rank];
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::{Array1, Array2};
#[test]
fn test_maximum_filter_1d() {
let array = Array1::from_vec(vec![1.0, 5.0, 3.0, 4.0, 2.0]);
let result = maximum_filter(&array, &[3], None).expect("test function should succeed");
assert_eq!(result.len(), array.len());
assert_eq!(result[0], 5.0); assert_eq!(result[1], 5.0); assert_eq!(result[2], 5.0); assert_eq!(result[3], 4.0); assert_eq!(result[4], 4.0); }
#[test]
fn test_minimum_filter_1d() {
let array = Array1::from_vec(vec![5.0, 2.0, 3.0, 4.0, 1.0]);
let result = minimum_filter(&array, &[3], None).expect("test function should succeed");
let radius = 1;
let pad_width = vec![(radius, radius)];
let _padded_input = pad_array(&array, &pad_width, &BorderMode::Reflect, None)
.expect("test function should succeed");
assert_eq!(result.len(), array.len());
assert_eq!(result[0], 2.0);
assert_eq!(result[1], 2.0);
assert_eq!(result[2], 2.0);
assert_eq!(result[3], 1.0);
assert_eq!(result[4], 1.0);
}
#[test]
fn test_maximum_filter() {
let mut image = Array2::zeros((5, 5));
image[[1, 1]] = 1.0;
image[[1, 3]] = 0.8;
image[[3, 1]] = 0.9;
image[[3, 3]] = 0.7;
let result = maximum_filter(&image, &[3, 3], None).expect("test function should succeed");
assert_eq!(result.shape(), image.shape());
assert_eq!(result[[0, 0]], 1.0); assert_eq!(result[[2, 2]], 1.0); }
#[test]
fn test_minimum_filter() {
let mut image = Array2::ones((5, 5));
image[[2, 2]] = 0.0;
let result = minimum_filter(&image, &[3, 3], None).expect("test function should succeed");
assert_eq!(result.shape(), image.shape());
for i in 1..4 {
for j in 1..4 {
assert_eq!(result[[i, j]], 0.0);
}
}
assert_eq!(result[[0, 0]], 0.0); assert_eq!(result[[0, 4]], 0.0); assert_eq!(result[[4, 0]], 0.0); assert_eq!(result[[4, 4]], 0.0); }
#[test]
fn test_percentile_filter() {
let array = Array1::from_vec(vec![1.0, 2.0, 3.0, 10.0, 5.0]);
let result =
percentile_filter(&array, 50.0, &[3], None).expect("test function should succeed");
let radius = 1;
let pad_width = vec![(radius, radius)];
let _padded_input = pad_array(&array, &pad_width, &BorderMode::Reflect, None)
.expect("test function should succeed");
assert_eq!(result.len(), array.len());
assert_eq!(result[0], 2.0);
assert_eq!(result[1], 2.0);
assert_eq!(result[2], 3.0);
assert_eq!(result[3], 5.0);
assert_eq!(result[4], 10.0);
}
#[test]
fn test_rank_filter() {
let array = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = rank_filter(&array, 1, &[3], None).expect("test function should succeed");
let radius = 1;
let pad_width = vec![(radius, radius)];
let _padded_input = pad_array(&array, &pad_width, &BorderMode::Reflect, None)
.expect("test function should succeed");
assert_eq!(result.len(), array.len());
assert_eq!(result[0], 2.0);
assert_eq!(result[1], 2.0);
assert_eq!(result[2], 3.0);
assert_eq!(result[3], 4.0);
assert_eq!(result[4], 4.0);
}
#[test]
fn test_rank_filter_invalid_rank() {
let array = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = rank_filter(&array, 3, &[3], None);
assert!(result.is_err());
}
#[test]
fn test_rank_filter_3d() {
use scirs2_core::ndarray::Array3;
let mut array = Array3::zeros((4, 4, 4));
array[[1, 1, 1]] = 10.0;
array[[1, 2, 1]] = 8.0;
array[[2, 1, 1]] = 6.0;
array[[2, 2, 1]] = 4.0;
array[[1, 1, 2]] = 2.0;
let window_size = 3 * 3 * 3;
let result = rank_filter(&array, window_size - 1, &[3, 3, 3], None)
.expect("test function should succeed");
assert_eq!(result.shape(), array.shape());
assert_eq!(result[[1, 1, 1]], 10.0);
assert_eq!(result.ndim(), 3);
assert_eq!(result.len(), array.len());
}
#[test]
fn test_minimum_filter_3d() {
use scirs2_core::ndarray::Array3;
let mut array = Array3::ones((4, 4, 4));
array[[2, 2, 2]] = 0.0;
let result =
minimum_filter(&array, &[3, 3, 3], None).expect("test function should succeed");
assert_eq!(result.shape(), array.shape());
assert_eq!(result[[2, 2, 2]], 0.0);
assert_eq!(result[[1, 2, 2]], 0.0);
assert_eq!(result[[3, 2, 2]], 0.0);
assert_eq!(result[[2, 1, 2]], 0.0);
assert_eq!(result[[2, 3, 2]], 0.0);
assert_eq!(result[[2, 2, 1]], 0.0);
assert_eq!(result[[2, 2, 3]], 0.0);
}
#[test]
fn test_percentile_filter_3d() {
use scirs2_core::ndarray::Array3;
let array = Array3::from_shape_fn((3, 3, 3), |(i, j, k)| {
(i * 9 + j * 3 + k) as f64 });
let result = percentile_filter(&array, 50.0, &[3, 3, 3], None)
.expect("test function should succeed");
assert_eq!(result.shape(), array.shape());
assert_eq!(result.ndim(), 3);
assert_eq!(result.len(), array.len());
for &value in result.iter() {
assert!(value.is_finite());
}
}
}