use scirs2_core::ndarray::{array, Array, Array2, Dimension, Ix2};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
use super::{convolve, BorderMode};
use crate::error::{NdimageError, NdimageResult};
#[allow(dead_code)]
fn safe_i32_to_float<T: Float + FromPrimitive>(value: i32) -> NdimageResult<T> {
T::from_i32(value).ok_or_else(|| {
NdimageError::ComputationError(format!("Failed to convert i32 {} to float type", value))
})
}
#[allow(dead_code)]
pub fn sobel<T, D>(
input: &Array<T, D>,
axis: usize,
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
if input.ndim() <= 1 {
return Err(NdimageError::InvalidInput(
"Input array must have at least 2 dimensions for sobel filter".into(),
));
}
if axis >= input.ndim() {
return Err(NdimageError::InvalidInput(format!(
"Axis {} is out of bounds for array of dimension {}",
axis,
input.ndim()
)));
}
if input.ndim() == 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 = match axis {
0 => sobel_2d_x(&input_2d, &border_mode)?, 1 => sobel_2d_y(&input_2d, &border_mode)?, _ => {
return Err(NdimageError::InvalidInput(format!(
"Invalid axis {} for 2D array, must be 0 or 1",
axis
)));
}
};
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
} else {
sobel_nd(input, axis, &border_mode)
}
}
#[allow(dead_code)]
pub fn laplace<T, D>(
input: &Array<T, D>,
mode: Option<BorderMode>,
diagonal: Option<bool>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let use_diagonal = diagonal.unwrap_or(false);
if input.ndim() <= 1 {
return Err(NdimageError::InvalidInput(
"Input array must have at least 2 dimensions for laplace filter".into(),
));
}
if input.ndim() == 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 = if use_diagonal {
laplace_2d_8connected(&input_2d, &border_mode)?
} else {
laplace_2d_4connected(&input_2d, &border_mode)?
};
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
} else {
laplace_nd(input, &border_mode)
}
}
#[allow(dead_code)]
fn laplace_nd<T, D>(input: &Array<T, D>, mode: &BorderMode) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
use super::convolve::correlate1d;
use scirs2_core::ndarray::Array1;
let neg_two = T::from_f64(-2.0)
.ok_or_else(|| NdimageError::ComputationError("Failed to convert -2 to float".into()))?;
let kernel = Array1::from(vec![T::one(), neg_two, T::one()]);
let mut acc: Array<T, D> = Array::zeros(input.raw_dim());
for ax in 0..input.ndim() {
let d2 = correlate1d(input, &kernel, ax, Some(*mode), None)?;
for (a, b) in acc.iter_mut().zip(d2.iter()) {
*a = *a + *b;
}
}
Ok(acc)
}
#[allow(dead_code)]
pub fn prewitt<T, D>(
input: &Array<T, D>,
axis: usize,
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
if input.ndim() <= 1 {
return Err(NdimageError::InvalidInput(
"Input array must have at least 2 dimensions for prewitt filter".into(),
));
}
if axis >= input.ndim() {
return Err(NdimageError::InvalidInput(format!(
"Axis {} is out of bounds for array of dimension {}",
axis,
input.ndim()
)));
}
if input.ndim() == 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 = match axis {
0 => prewitt_2d_x(&input_2d, &border_mode)?, 1 => prewitt_2d_y(&input_2d, &border_mode)?, _ => {
return Err(NdimageError::InvalidInput(format!(
"Invalid axis {} for 2D array, must be 0 or 1",
axis
)));
}
};
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
} else {
prewitt_nd(input, axis, &border_mode)
}
}
#[allow(dead_code)]
fn prewitt_nd<T, D>(
input: &Array<T, D>,
axis: usize,
mode: &BorderMode,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
use super::convolve::correlate1d;
use scirs2_core::ndarray::Array1;
let deriv_kernel = Array1::from(vec![-T::one(), T::zero(), T::one()]);
let smooth_kernel = Array1::from(vec![T::one(), T::one(), T::one()]);
let mut result = correlate1d(input, &deriv_kernel, axis, Some(*mode), None)?;
for ax in 0..input.ndim() {
if ax != axis {
result = correlate1d(&result, &smooth_kernel, ax, Some(*mode), None)?;
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn roberts<T, D>(
input: &Array<T, D>,
mode: Option<BorderMode>,
axis: Option<usize>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
if input.ndim() <= 1 {
return Err(NdimageError::InvalidInput(
"Input array must have at least 2 dimensions for Roberts filter".into(),
));
}
if input.ndim() == 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 = match axis {
Some(0) => roberts_2d_x(&input_2d, &border_mode)?, Some(1) => roberts_2d_y(&input_2d, &border_mode)?, Some(_) => {
return Err(NdimageError::InvalidInput(
"Invalid axis for 2D array, must be 0 or 1".to_string(),
));
}
None => {
let gradient_x = roberts_2d_x(&input_2d, &border_mode)?;
let gradient_y = roberts_2d_y(&input_2d, &border_mode)?;
let mut magnitude = gradient_x.to_owned();
for i in 0..magnitude.nrows() {
for j in 0..magnitude.ncols() {
let gx = gradient_x[[i, j]];
let gy = gradient_y[[i, j]];
magnitude[[i, j]] = (gx * gx + gy * gy).sqrt();
}
}
magnitude
}
};
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
} else {
roberts_nd(input, axis, &border_mode)
}
}
#[allow(dead_code)]
fn roberts_nd<T, D>(
input: &Array<T, D>,
axis: Option<usize>,
mode: &BorderMode,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
use super::convolve::correlate1d;
use scirs2_core::ndarray::Array1;
let fwd_kernel = Array1::from(vec![-T::one(), T::one()]);
if let Some(ax) = axis {
if ax >= input.ndim() {
return Err(NdimageError::InvalidInput(format!(
"Axis {} is out of bounds for array of dimension {}",
ax,
input.ndim()
)));
}
correlate1d(input, &fwd_kernel, ax, Some(*mode), None)
} else {
let ndim = input.ndim();
let mut acc: Array<T, D> = Array::zeros(input.raw_dim());
for ax in 0..ndim {
let d = correlate1d(input, &fwd_kernel, ax, Some(*mode), None)?;
for (a, b) in acc.iter_mut().zip(d.iter()) {
*a = *a + *b * *b;
}
}
let result = acc.mapv(|v| v.sqrt());
Ok(result)
}
}
#[allow(dead_code)]
pub fn gradient_magnitude<T, D>(
input: &Array<T, D>,
mode: Option<BorderMode>,
method: Option<&str>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let method_str = method.unwrap_or("sobel");
if input.ndim() <= 1 {
return Err(NdimageError::InvalidInput(
"Input array must have at least 2 dimensions for gradient magnitude".into(),
));
}
if input.ndim() == 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 (gradient_x, gradient_y) = match method_str.to_lowercase().as_str() {
"sobel" => {
let gx = sobel_2d_y(&input_2d, &border_mode)?;
let gy = sobel_2d_x(&input_2d, &border_mode)?;
(gx, gy)
}
"prewitt" => {
let gx = prewitt_2d_y(&input_2d, &border_mode)?;
let gy = prewitt_2d_x(&input_2d, &border_mode)?;
(gx, gy)
}
"roberts" => {
let gx = roberts_2d_x(&input_2d, &border_mode)?;
let gy = roberts_2d_y(&input_2d, &border_mode)?;
(gx, gy)
}
"scharr" => {
let gx = scharr_2d_y(&input_2d, &border_mode)?;
let gy = scharr_2d_x(&input_2d, &border_mode)?;
(gx, gy)
}
_ => {
return Err(NdimageError::InvalidInput(format!(
"Invalid method: {}, must be one of: sobel, prewitt, roberts, scharr",
method_str
)));
}
};
let mut result = input_2d.to_owned();
for i in 0..result.nrows() {
for j in 0..result.ncols() {
let gx = gradient_x[[i, j]];
let gy = gradient_y[[i, j]];
let magnitude = (gx * gx + gy * gy).sqrt();
result[[i, j]] = magnitude;
}
}
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
} else {
gradient_magnitude_nd(input, method_str, &border_mode)
}
}
#[allow(dead_code)]
fn gradient_magnitude_nd<T, D>(
input: &Array<T, D>,
method: &str,
mode: &BorderMode,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
let ndim = input.ndim();
let mut acc: Array<T, D> = Array::zeros(input.raw_dim());
for ax in 0..ndim {
let grad = match method.to_lowercase().as_str() {
"sobel" => sobel_nd(input, ax, mode)?,
"prewitt" => prewitt_nd(input, ax, mode)?,
"scharr" => scharr_nd(input, ax, mode)?,
"roberts" => roberts_nd(input, Some(ax), mode)?,
_ => {
return Err(NdimageError::InvalidInput(format!(
"Invalid method: {}, must be one of: sobel, prewitt, roberts, scharr",
method
)));
}
};
for (a, b) in acc.iter_mut().zip(grad.iter()) {
*a = *a + *b * *b;
}
}
let result = acc.mapv(|v| v.sqrt());
Ok(result)
}
#[allow(dead_code)]
fn prewitt_2d_x<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 0]] = T::one();
kernel[[0, 1]] = T::one();
kernel[[0, 2]] = T::one();
kernel[[2, 0]] = -T::one();
kernel[[2, 1]] = -T::one();
kernel[[2, 2]] = -T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn prewitt_2d_y<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 0]] = -T::one();
kernel[[1, 0]] = -T::one();
kernel[[2, 0]] = -T::one();
kernel[[0, 2]] = T::one();
kernel[[1, 2]] = T::one();
kernel[[2, 2]] = T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn sobel_2d_x<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 0]] = T::one();
kernel[[0, 1]] = safe_i32_to_float(2)?;
kernel[[0, 2]] = T::one();
kernel[[2, 0]] = -T::one();
kernel[[2, 1]] = -safe_i32_to_float(2)?;
kernel[[2, 2]] = -T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn sobel_2d_y<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 0]] = -T::one();
kernel[[1, 0]] = -safe_i32_to_float(2)?;
kernel[[2, 0]] = -T::one();
kernel[[0, 2]] = T::one();
kernel[[1, 2]] = safe_i32_to_float(2)?;
kernel[[2, 2]] = T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn roberts_2d_x<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((2, 2));
kernel[[0, 0]] = T::one();
kernel[[1, 1]] = -T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn roberts_2d_y<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((2, 2));
kernel[[0, 1]] = T::one();
kernel[[1, 0]] = -T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn laplace_2d_4connected<T>(
input: &Array<T, Ix2>,
mode: &BorderMode,
) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 1]] = T::one();
kernel[[1, 0]] = T::one();
kernel[[1, 1]] = -safe_i32_to_float(4)?;
kernel[[1, 2]] = T::one();
kernel[[2, 1]] = T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn laplace_2d_8connected<T>(
input: &Array<T, Ix2>,
mode: &BorderMode,
) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 0]] = -T::one();
kernel[[0, 1]] = -T::one();
kernel[[0, 2]] = -T::one();
kernel[[1, 0]] = -T::one();
kernel[[1, 1]] = safe_i32_to_float(8)?;
kernel[[1, 2]] = -T::one();
kernel[[2, 0]] = -T::one();
kernel[[2, 1]] = -T::one();
kernel[[2, 2]] = -T::one();
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
pub fn scharr<T, D>(
input: &Array<T, D>,
axis: usize,
mode: Option<BorderMode>,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
if input.ndim() <= 1 {
return Err(NdimageError::InvalidInput(
"Input array must have at least 2 dimensions for scharr filter".into(),
));
}
if axis >= input.ndim() {
return Err(NdimageError::InvalidInput(format!(
"Axis {} is out of bounds for array of dimension {}",
axis,
input.ndim()
)));
}
if input.ndim() == 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 = match axis {
0 => scharr_2d_x(&input_2d, &border_mode)?, 1 => scharr_2d_y(&input_2d, &border_mode)?, _ => {
return Err(NdimageError::InvalidInput(format!(
"Invalid axis {} for 2D array, must be 0 or 1",
axis
)));
}
};
result.into_dimensionality::<D>().map_err(|_| {
NdimageError::DimensionError(
"Failed to convert result back to original dimensions".into(),
)
})
} else {
scharr_nd(input, axis, &border_mode)
}
}
#[allow(dead_code)]
fn scharr_nd<T, D>(
input: &Array<T, D>,
axis: usize,
mode: &BorderMode,
) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
use super::convolve::correlate1d;
use scirs2_core::ndarray::Array1;
let three = T::from_f64(3.0)
.ok_or_else(|| NdimageError::ComputationError("Failed to convert 3 to float".into()))?;
let ten = T::from_f64(10.0)
.ok_or_else(|| NdimageError::ComputationError("Failed to convert 10 to float".into()))?;
let deriv_kernel = Array1::from(vec![-T::one(), T::zero(), T::one()]);
let smooth_kernel = Array1::from(vec![three, ten, three]);
let mut result = correlate1d(input, &deriv_kernel, axis, Some(*mode), None)?;
for ax in 0..input.ndim() {
if ax != axis {
result = correlate1d(&result, &smooth_kernel, ax, Some(*mode), None)?;
}
}
Ok(result)
}
#[allow(dead_code)]
fn scharr_2d_x<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 0]] = safe_i32_to_float(3)?;
kernel[[0, 1]] = safe_i32_to_float(10)?;
kernel[[0, 2]] = safe_i32_to_float(3)?;
kernel[[2, 0]] = safe_i32_to_float(-3)?;
kernel[[2, 1]] = safe_i32_to_float(-10)?;
kernel[[2, 2]] = safe_i32_to_float(-3)?;
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn scharr_2d_y<T>(input: &Array<T, Ix2>, mode: &BorderMode) -> NdimageResult<Array<T, Ix2>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
{
let mut kernel = Array2::<T>::zeros((3, 3));
kernel[[0, 0]] = safe_i32_to_float(-3)?;
kernel[[1, 0]] = safe_i32_to_float(-10)?;
kernel[[2, 0]] = safe_i32_to_float(-3)?;
kernel[[0, 2]] = safe_i32_to_float(3)?;
kernel[[1, 2]] = safe_i32_to_float(10)?;
kernel[[2, 2]] = safe_i32_to_float(3)?;
convolve(input, &kernel, Some(*mode))
}
#[allow(dead_code)]
fn sobel_nd<T, D>(input: &Array<T, D>, axis: usize, mode: &BorderMode) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + Debug + std::ops::AddAssign + std::ops::DivAssign + Clone + 'static,
D: Dimension + 'static,
{
use super::convolve::correlate1d;
let deriv_kernel = array![-T::one(), T::zero(), T::one()];
let mut result = correlate1d(input, &deriv_kernel, axis, Some(*mode), None)?;
let smooth_kernel = array![T::one(), safe_i32_to_float(2)?, T::one()];
for ax in 0..input.ndim() {
if ax != axis {
result = correlate1d(&result, &smooth_kernel, ax, Some(*mode), None)?;
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::{array, Array2};
#[test]
fn test_sobel() {
let mut image = Array2::<f64>::zeros((5, 5));
image[[2, 2]] = 1.0;
let sobel_x = sobel(&image, 0, None).expect("sobel filter should succeed"); let sobel_y = sobel(&image, 1, None).expect("sobel filter should succeed");
assert_eq!(sobel_x.shape(), image.shape());
assert_eq!(sobel_y.shape(), image.shape());
assert!(sobel_x[[1, 2]] > 0.0); assert!(sobel_x[[3, 2]] < 0.0);
assert!(sobel_y[[2, 3]] > 0.0); assert!(sobel_y[[2, 1]] < 0.0); }
#[test]
fn test_scharr() {
let mut image = Array2::<f64>::zeros((5, 5));
image[[2, 2]] = 1.0;
let scharr_x = scharr(&image, 0, None).expect("scharr filter should succeed"); let scharr_y = scharr(&image, 1, None).expect("scharr filter should succeed");
assert_eq!(scharr_x.shape(), image.shape());
assert_eq!(scharr_y.shape(), image.shape());
assert!(scharr_x[[1, 2]] > 0.0); assert!(scharr_x[[3, 2]] < 0.0);
assert!(scharr_y[[2, 3]] > 0.0); assert!(scharr_y[[2, 1]] < 0.0);
let sobel_x = sobel(&image, 0, None).expect("sobel filter for comparison should succeed");
let sobel_y = sobel(&image, 1, None).expect("sobel filter for comparison should succeed");
assert!(scharr_x[[1, 1]].abs() > sobel_x[[1, 1]].abs());
assert!(scharr_y[[1, 1]].abs() > sobel_y[[1, 1]].abs());
}
#[test]
fn test_laplace() {
let mut image = Array2::<f64>::zeros((5, 5));
image[[2, 2]] = 1.0;
let result = laplace(&image, None, None).expect("laplace filter should succeed");
assert_eq!(result.shape(), image.shape());
assert!(result[[2, 2]] < 0.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);
assert_eq!(result[[1, 1]], 0.0);
assert_eq!(result[[1, 3]], 0.0);
assert_eq!(result[[3, 1]], 0.0);
assert_eq!(result[[3, 3]], 0.0);
}
#[test]
fn test_laplace_8connected() {
let mut image = Array2::<f64>::zeros((5, 5));
image[[2, 2]] = 1.0;
let result =
laplace(&image, None, Some(true)).expect("laplace 8-connected filter should succeed");
assert_eq!(result.shape(), image.shape());
assert!(result[[2, 2]] > 0.0);
assert!(result[[1, 1]] < 0.0); assert!(result[[1, 2]] < 0.0);
assert!(result[[1, 3]] < 0.0);
assert!(result[[2, 1]] < 0.0);
assert!(result[[2, 3]] < 0.0);
assert!(result[[3, 1]] < 0.0);
assert!(result[[3, 2]] < 0.0);
assert!(result[[3, 3]] < 0.0);
let center_val = result[[2, 2]];
let diagonal_val = -result[[1, 1]];
let ratio = (center_val / diagonal_val).abs();
assert!((ratio - 8.0).abs() < 1e-10);
}
#[test]
fn test_prewitt() {
let mut image = Array2::<f64>::zeros((5, 5));
image[[2, 2]] = 1.0;
let prewitt_x = prewitt(&image, 0, None).expect("prewitt filter should succeed"); let prewitt_y = prewitt(&image, 1, None).expect("prewitt filter should succeed");
assert_eq!(prewitt_x.shape(), image.shape());
assert_eq!(prewitt_y.shape(), image.shape());
assert!(prewitt_x[[1, 2]] > 0.0); assert!(prewitt_x[[3, 2]] < 0.0);
assert!(prewitt_y[[2, 3]] > 0.0); assert!(prewitt_y[[2, 1]] < 0.0); }
#[test]
fn test_roberts() {
let mut image = Array2::<f64>::zeros((5, 5));
image[[1, 1]] = 0.0;
image[[1, 2]] = 1.0;
image[[2, 1]] = 1.0;
image[[2, 2]] = 0.0;
let roberts_x =
roberts(&image, None, Some(0)).expect("roberts filter x-component should succeed"); let roberts_y =
roberts(&image, None, Some(1)).expect("roberts filter y-component should succeed");
assert_eq!(roberts_x.shape(), image.shape());
assert_eq!(roberts_y.shape(), image.shape());
let roberts_mag =
roberts(&image, None, None).expect("roberts magnitude filter should succeed");
let center_region = roberts_mag.slice(scirs2_core::ndarray::s![1..3, 1..3]);
let mut has_significant_response = false;
for i in 0..2 {
for j in 0..2 {
if center_region[[i, j]] > 0.5 {
has_significant_response = true;
break;
}
}
if has_significant_response {
break;
}
}
assert!(
has_significant_response,
"Roberts filter should have a significant response to the diagonal pattern"
);
for i in 1..3 {
for j in 1..3 {
let calculated = (roberts_x[[i, j]].powi(2) + roberts_y[[i, j]].powi(2)).sqrt();
assert!(
(roberts_mag[[i, j]] - calculated).abs() < 1e-10,
"Magnitude should be sqrt(x² + y²) at position [{}, {}]",
i,
j
);
}
}
}
#[test]
fn test_gradient_magnitude() {
let image = array![
[0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0, 1.0],
];
let result_sobel = gradient_magnitude(&image, None, None)
.expect("gradient_magnitude with sobel should succeed");
let result_prewitt = gradient_magnitude(&image, None, Some("prewitt"))
.expect("gradient_magnitude with prewitt should succeed");
let result_roberts = gradient_magnitude(&image, None, Some("roberts"))
.expect("gradient_magnitude with roberts should succeed");
let result_scharr = gradient_magnitude(&image, None, Some("scharr"))
.expect("gradient_magnitude with scharr should succeed");
assert_eq!(result_sobel.shape(), image.shape());
assert_eq!(result_prewitt.shape(), image.shape());
assert_eq!(result_roberts.shape(), image.shape());
assert_eq!(result_scharr.shape(), image.shape());
for i in 0..5 {
let mut max_sobel_idx = 0;
let mut max_prewitt_idx = 0;
let mut max_roberts_idx = 0;
let mut max_scharr_idx = 0;
for j in 0..5 {
if result_sobel[[i, j]] > result_sobel[[i, max_sobel_idx]] {
max_sobel_idx = j;
}
if result_prewitt[[i, j]] > result_prewitt[[i, max_prewitt_idx]] {
max_prewitt_idx = j;
}
if result_roberts[[i, j]] > result_roberts[[i, max_roberts_idx]] {
max_roberts_idx = j;
}
if result_scharr[[i, j]] > result_scharr[[i, max_scharr_idx]] {
max_scharr_idx = j;
}
}
assert!(
max_sobel_idx == 1 || max_sobel_idx == 2,
"Row {}: max Sobel gradient not at expected position, found at {}",
i,
max_sobel_idx
);
assert!(
max_prewitt_idx == 1 || max_prewitt_idx == 2,
"Row {}: max Prewitt gradient not at expected position, found at {}",
i,
max_prewitt_idx
);
assert!(
max_scharr_idx == 1 || max_scharr_idx == 2,
"Row {}: max Scharr gradient not at expected position, found at {}",
i,
max_scharr_idx
);
assert!(
max_roberts_idx >= 1 && max_roberts_idx <= 3,
"Row {}: max Roberts gradient not near edge, found at {}",
i,
max_roberts_idx
);
}
let invalid_result = gradient_magnitude(&image, None, Some("invalid_method"));
assert!(invalid_result.is_err());
}
#[test]
fn test_sobel_3d() {
use scirs2_core::ndarray::Array3;
let mut volume = Array3::<f64>::zeros((3, 3, 3));
for i in 0..3 {
for j in 0..3 {
volume[[i, j, 1]] = 1.0;
}
}
let result_x = sobel(&volume, 0, None).expect("sobel 3D x-axis should succeed");
assert_eq!(result_x.shape(), volume.shape());
let result_y = sobel(&volume, 1, None).expect("sobel 3D y-axis should succeed");
assert_eq!(result_y.shape(), volume.shape());
let result_z = sobel(&volume, 2, None).expect("sobel 3D z-axis should succeed");
assert_eq!(result_z.shape(), volume.shape());
let max_x = result_x.iter().map(|&x| x.abs()).fold(0.0, f64::max);
let max_y = result_y.iter().map(|&x| x.abs()).fold(0.0, f64::max);
let max_z = result_z.iter().map(|&x| x.abs()).fold(0.0, f64::max);
assert!(max_z > max_x, "Z gradient should be strongest");
assert!(max_z > max_y, "Z gradient should be strongest");
}
#[test]
fn test_laplace_3d() {
use scirs2_core::ndarray::Array3;
let mut volume = Array3::<f64>::zeros((5, 5, 5));
volume[[2, 2, 2]] = 1.0;
let result = laplace(&volume, None, None).expect("laplace 3D should succeed");
assert_eq!(result.shape(), volume.shape());
assert!(
result[[2, 2, 2]] < 0.0,
"Centre of Laplacian should be negative"
);
assert!(result[[1, 2, 2]] > 0.0);
assert!(result[[3, 2, 2]] > 0.0);
assert!(result[[2, 1, 2]] > 0.0);
assert!(result[[2, 3, 2]] > 0.0);
assert!(result[[2, 2, 1]] > 0.0);
assert!(result[[2, 2, 3]] > 0.0);
}
#[test]
fn test_prewitt_3d() {
use scirs2_core::ndarray::Array3;
let mut volume = Array3::<f64>::zeros((5, 5, 5));
for k in 0..5usize {
for i in 0..5usize {
for j in 0..5usize {
volume[[i, j, k]] = k as f64;
}
}
}
let result = prewitt(&volume, 2, None).expect("prewitt 3D axis-2 should succeed");
assert_eq!(result.shape(), volume.shape());
for i in 1..4 {
for j in 1..4 {
for k in 1..4 {
assert!(
result[[i, j, k]] > 0.0,
"Ramp gradient should be positive interior"
);
}
}
}
let result_ax0 = prewitt(&volume, 0, None).expect("prewitt 3D axis-0 should succeed");
for i in 1..4 {
for j in 1..4 {
for k in 1..4 {
assert!(
result_ax0[[i, j, k]].abs() < 1e-12,
"Constant-axis gradient should be zero"
);
}
}
}
}
#[test]
fn test_scharr_3d() {
use scirs2_core::ndarray::Array3;
let mut volume = Array3::<f64>::zeros((5, 5, 5));
volume[[2, 2, 2]] = 1.0;
let result = scharr(&volume, 0, None).expect("scharr 3D axis-0 should succeed");
assert_eq!(result.shape(), volume.shape());
assert!(result[[1, 2, 2]] > 0.0, "Scharr: axis-0 positive lobe");
assert!(result[[3, 2, 2]] < 0.0, "Scharr: axis-0 negative lobe");
}
#[test]
fn test_gradient_magnitude_3d() {
use scirs2_core::ndarray::Array3;
let mut volume = Array3::<f64>::zeros((5, 5, 5));
for k in 0..5usize {
for i in 0..5usize {
for j in 0..5usize {
volume[[i, j, k]] = k as f64;
}
}
}
for method in &["sobel", "prewitt", "scharr", "roberts"] {
let result = gradient_magnitude(&volume, None, Some(method))
.unwrap_or_else(|e| panic!("gradient_magnitude 3D {method} should succeed: {e}"));
assert_eq!(result.shape(), volume.shape());
for i in 1..4 {
for j in 1..4 {
for k in 1..4 {
assert!(
result[[i, j, k]] > 0.0,
"{method}: interior gradient should be > 0"
);
}
}
}
}
}
#[test]
fn test_roberts_3d() {
use scirs2_core::ndarray::Array3;
let mut volume = Array3::<f64>::zeros((5, 5, 5));
volume[[2, 2, 2]] = 1.0;
let result = roberts(&volume, None, Some(2)).expect("roberts 3D axis-2 should succeed");
assert_eq!(result.shape(), volume.shape());
assert!(
result[[2, 2, 2]].abs() > 0.0,
"Roberts axis-2 should respond at impulse"
);
let response_sum: f64 = result.iter().sum();
assert!(
response_sum.abs() < 1e-10,
"Forward-difference response should sum to 0"
);
let mag = roberts(&volume, None, None).expect("roberts 3D magnitude should succeed");
assert_eq!(mag.shape(), volume.shape());
assert!(
mag[[2, 2, 2]] > 0.0,
"Roberts magnitude at impulse should be positive"
);
}
}