use crate::core::*;
use crate::processing::*;
use core::mem::MaybeUninit;
use core::ops::Neg;
use ndarray::{prelude::*, s, DataMut, OwnedRepr, Zip};
use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps};
use std::marker::Sized;
pub trait SobelExt
where
Self: Sized,
{
type Output;
fn apply_sobel(&self) -> Result<Self::Output, Error>;
fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error>;
}
fn get_edge_images<T, U>(mat: &ArrayBase<U, Ix3>) -> Result<(Array3<T>, Array3<T>), Error>
where
U: DataMut<Elem = T>,
T: Copy + Clone + Num + NumAssignOps + Neg<Output = T> + FromPrimitive + Real,
{
let v_temp: Array3<T> = SobelFilter::build_with_params(Orientation::Vertical).unwrap();
let h_temp: Array3<T> = SobelFilter::build_with_params(Orientation::Horizontal).unwrap();
let shape = (v_temp.shape()[0], v_temp.shape()[1], mat.shape()[2]);
let mut h_kernel = Array3::<T>::uninit(shape);
let mut v_kernel = Array3::<T>::uninit(shape);
for i in 0..mat.dim().2 {
h_temp
.slice(s![.., .., 0])
.assign_to(h_kernel.slice_mut(s![.., .., i]));
v_temp
.slice(s![.., .., 0])
.assign_to(v_kernel.slice_mut(s![.., .., i]));
}
let h_kernel = unsafe { h_kernel.assume_init() };
let v_kernel = unsafe { v_kernel.assume_init() };
let h_deriv = mat.conv2d(h_kernel.view())?;
let v_deriv = mat.conv2d(v_kernel.view())?;
Ok((h_deriv, v_deriv))
}
impl<T, U> SobelExt for ArrayBase<U, Ix3>
where
U: DataMut<Elem = T>,
T: Copy + Clone + Num + NumAssignOps + Neg<Output = T> + FromPrimitive + Real,
{
type Output = ArrayBase<OwnedRepr<T>, Ix3>;
fn apply_sobel(&self) -> Result<Self::Output, Error> {
let (h_deriv, v_deriv) = get_edge_images(self)?;
let res_shape = h_deriv.dim();
let mut result = Self::Output::uninit(res_shape);
for r in 0..res_shape.0 {
for c in 0..res_shape.1 {
for channel in 0..res_shape.2 {
let temp = (h_deriv[[r, c, channel]].powi(2)
+ v_deriv[[r, c, channel]].powi(2))
.sqrt();
unsafe {
*result.uget_mut([r, c, channel]) = MaybeUninit::new(temp);
}
}
}
}
Ok(unsafe { result.assume_init() })
}
fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error> {
let (h_deriv, v_deriv) = get_edge_images(self)?;
let mut magnitude = h_deriv.mapv(|x| x.powi(2)) + v_deriv.mapv(|x| x.powi(2));
magnitude.mapv_inplace(|x| x.sqrt());
let dim = h_deriv.dim();
let mut rotation = Array3::uninit((dim.0, dim.1, dim.2));
Zip::from(&mut rotation)
.and(&h_deriv)
.and(&v_deriv)
.for_each(|r, &h, &v| *r = MaybeUninit::new(h.atan2(v)));
let rotation = unsafe { rotation.assume_init() };
Ok((magnitude, rotation))
}
}
impl<T, U, C> SobelExt for ImageBase<U, C>
where
U: DataMut<Elem = T>,
T: Copy + Clone + Num + NumAssignOps + Neg<Output = T> + FromPrimitive + Real,
C: ColourModel,
{
type Output = Image<T, C>;
fn apply_sobel(&self) -> Result<Self::Output, Error> {
let data = self.data.apply_sobel()?;
Ok(Image::from_data(data))
}
fn full_sobel(&self) -> Result<(Self::Output, Self::Output), Error> {
self.data
.full_sobel()
.map(|(m, r)| (Image::from_data(m), Image::from_data(r)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::*;
#[test]
fn simple() {
let mut image: Image<f64, Gray> = ImageBase::new(11, 11);
image.data.slice_mut(s![4..7, 4..7, ..]).fill(1.0);
image.data.slice_mut(s![3..8, 5, ..]).fill(1.0);
image.data.slice_mut(s![5, 3..8, ..]).fill(1.0);
let sobel = image.full_sobel().unwrap();
#[rustfmt::skip]
let mag = vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.41421356237301, 2.0, 1.41421356237301, 0.0, 0.0, 0.0,
0.0, 0.0, 1.41421356237301, 4.24264068711929, 4.0, 4.24264068711929, 1.4142135623731, 0.0, 0.0,
0.0, 1.4142135623731, 4.24264068711929, 4.24264068711929, 2.0, 4.24264068711929, 4.24264068711929, 1.4142135623731, 0.0,
0.0, 2.0, 4.0, 2.0, 0.0, 2.0, 4.0, 2.0, 0.0,
0.0, 1.4142135623731, 4.24264068711929, 4.24264068711929, 2.0, 4.24264068711929, 4.24264068711929, 1.4142135623731, 0.0,
0.0, 0.0, 1.4142135623731, 4.24264068711929, 4.0, 4.24264068711929, 1.4142135623731, 0.0,
0.0, 0.0, 0.0, 0.0, 1.4142135623731, 2.0, 1.4142135623731, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
];
let mag = Array::from_shape_vec((9, 9), mag).unwrap();
assert_abs_diff_eq!(sobel.0.data.slice(s![1..10, 1..10, 0]), mag, epsilon = 1e-5);
let only_mag = image.apply_sobel().unwrap();
assert_abs_diff_eq!(sobel.0.data, only_mag.data);
#[rustfmt::skip]
let rot = vec![0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,
0.00000000000000,0.00000000000000,0.00000000000000,-2.35619449019234,3.14159265358979,2.35619449019234,0.00000000000000,0.00000000000000,0.00000000000000,
0.00000000000000,0.00000000000000,-2.35619449019234,-2.35619449019234,3.14159265358979,2.35619449019234,2.35619449019234,0.00000000000000,0.00000000000000,
0.00000000000000,-2.35619449019234,-2.35619449019234,-2.35619449019234,3.14159265358979,2.35619449019234,2.35619449019234,2.35619449019234,0.00000000000000,
0.00000000000000,-1.57079632679490,-1.57079632679490,-1.57079632679490,0.00000000000000,1.57079632679490,1.57079632679490,1.57079632679490,0.00000000000000,
0.00000000000000,-0.78539816339745,-0.78539816339745,-0.78539816339745,0.00000000000000,0.78539816339745,0.78539816339745,0.78539816339745,0.00000000000000,
0.00000000000000,0.00000000000000,-0.78539816339745,-0.78539816339745,0.00000000000000,0.78539816339745,0.78539816339745,0.00000000000000,0.00000000000000,
0.00000000000000,0.00000000000000,0.00000000000000,-0.78539816339745,0.00000000000000,0.78539816339745,0.00000000000000,0.00000000000000,0.00000000000000,
0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000];
let rot = Array::from_shape_vec((9, 9), rot).unwrap();
assert_abs_diff_eq!(sobel.1.data.slice(s![1..10, 1..10, 0]), rot, epsilon = 1e-5);
}
}