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 {
Err(NdimageError::NotImplementedError(
"Laplace filter not yet implemented for arrays with more than 2 dimensions".into(),
))
}
}
#[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 {
Err(NdimageError::NotImplementedError(
"Prewitt filter not yet implemented for arrays with more than 2 dimensions".into(),
))
}
}
#[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 {
Err(NdimageError::NotImplementedError(
"Roberts filter not yet implemented for arrays with more than 2 dimensions".into(),
))
}
}
#[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 {
Err(NdimageError::NotImplementedError(
"Gradient magnitude not yet implemented for arrays with more than 2 dimensions".into(),
))
}
}
#[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 {
Err(NdimageError::NotImplementedError(
"Scharr filter not yet implemented for arrays with more than 2 dimensions".into(),
))
}
}
#[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");
}
}