use crate::image::ImageView;
use crate::image::sequential::private;
use crate::image::sequential::{ContiguousImage, ImageArray};
pub trait Kernel {
type Weight: Copy;
type Weights: ImageView<Pixel = Self::Weight>;
fn weights(&self) -> &Self::Weights;
fn anchor(&self) -> (usize, usize);
fn flipped(&self) -> Self
where
Self: Sized;
}
pub struct Neighborhood<W: Copy, const KW: usize, const KH: usize>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
{
weights: ImageArray<W, KW, KH>,
anchor: (usize, usize),
}
impl<W: Copy, const KW: usize, const KH: usize> Neighborhood<W, KW, KH>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
{
pub fn new(data: <private::Dim<W, KW, KH> as private::_Array2D>::Array) -> Self {
Self {
weights: ImageArray::new(data),
anchor: (KW / 2, KH / 2),
}
}
pub fn with_anchor(
data: <private::Dim<W, KW, KH> as private::_Array2D>::Array,
anchor: (usize, usize),
) -> Self {
assert!(
anchor.0 < KW && anchor.1 < KH,
"anchor ({}, {}) is out of bounds for {}x{} kernel",
anchor.0,
anchor.1,
KW,
KH,
);
Self {
weights: ImageArray::new(data),
anchor,
}
}
pub fn weights(&self) -> &ImageArray<W, KW, KH> {
&self.weights
}
pub fn anchor(&self) -> (usize, usize) {
self.anchor
}
pub fn kernel_width(&self) -> usize {
KW
}
pub fn kernel_height(&self) -> usize {
KH
}
pub fn as_slice(&self) -> &[W] {
self.weights.as_slice()
}
pub fn positions(&self) -> PositionsIter<'_, W, KW, KH> {
PositionsIter {
weights: &self.weights,
anchor: self.anchor,
x: 0,
y: 0,
}
}
}
impl<W: Copy, const KW: usize, const KH: usize> Kernel for Neighborhood<W, KW, KH>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
{
type Weight = W;
type Weights = ImageArray<W, KW, KH>;
fn weights(&self) -> &ImageArray<W, KW, KH> {
&self.weights
}
fn anchor(&self) -> (usize, usize) {
self.anchor
}
fn flipped(&self) -> Self {
let flipped_weights =
ImageArray::generate(|x, y| self.weights.pixel_at(KW - 1 - x, KH - 1 - y));
let flipped_anchor = (KW - 1 - self.anchor.0, KH - 1 - self.anchor.1);
Self {
weights: flipped_weights,
anchor: flipped_anchor,
}
}
}
impl<W: Copy, const KW: usize, const KH: usize> Clone for Neighborhood<W, KW, KH>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
<private::Dim<W, KW, KH> as private::_Array2D>::Array: Clone,
{
fn clone(&self) -> Self {
Self {
weights: ImageArray::new(
{
let slice = self.weights.as_slice();
unsafe {
std::ptr::read(slice.as_ptr()
as *const <private::Dim<W, KW, KH> as private::_Array2D>::Array)
}
},
),
anchor: self.anchor,
}
}
}
impl<W: Copy + std::fmt::Debug, const KW: usize, const KH: usize> std::fmt::Debug
for Neighborhood<W, KW, KH>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Neighborhood")
.field("weights", &self.as_slice())
.field("anchor", &self.anchor)
.field("size", &(KW, KH))
.finish()
}
}
pub struct PositionsIter<'a, W, const KW: usize, const KH: usize>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
{
weights: &'a ImageArray<W, KW, KH>,
anchor: (usize, usize),
x: usize,
y: usize,
}
impl<'a, W: Copy, const KW: usize, const KH: usize> Iterator for PositionsIter<'a, W, KW, KH>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
{
type Item = (isize, isize, W);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.y >= KH {
return None;
}
let dx = self.x as isize - self.anchor.0 as isize;
let dy = self.y as isize - self.anchor.1 as isize;
let weight = self.weights.pixel_at(self.x, self.y);
self.x += 1;
if self.x >= KW {
self.x = 0;
self.y += 1;
}
Some((dx, dy, weight))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = if self.y >= KH {
0
} else {
(KH - self.y - 1) * KW + (KW - self.x)
};
(remaining, Some(remaining))
}
}
impl<'a, W: Copy, const KW: usize, const KH: usize> ExactSizeIterator
for PositionsIter<'a, W, KW, KH>
where
private::Dim<W, KW, KH>: private::_Array2D<Pixel = W>,
{
}
pub type Kernel3x3 = Neighborhood<f32, 3, 3>;
pub type Kernel5x5 = Neighborhood<f32, 5, 5>;
pub type Kernel7x7 = Neighborhood<f32, 7, 7>;
pub type Kernel3x3i = Neighborhood<i32, 3, 3>;
pub type Kernel5x5i = Neighborhood<i32, 5, 5>;
pub type Mask<const KW: usize, const KH: usize> = Neighborhood<bool, KW, KH>;
pub type Mask3x3 = Mask<3, 3>;
pub type Mask5x5 = Mask<5, 5>;
impl Neighborhood<f32, 3, 3> {
pub fn box_blur_3x3() -> Self {
Self::new([1.0 / 9.0; 9])
}
pub fn gaussian_3x3() -> Self {
Self::new([1.0, 2.0, 1.0, 2.0, 4.0, 2.0, 1.0, 2.0, 1.0])
}
pub fn sobel_x() -> Self {
Self::new([-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0])
}
pub fn sobel_y() -> Self {
Self::new([-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0])
}
pub fn scharr_x() -> Self {
Self::new([-3.0, -10.0, -3.0, 0.0, 0.0, 0.0, 3.0, 10.0, 3.0])
}
pub fn scharr_y() -> Self {
Self::new([-3.0, 0.0, 3.0, -10.0, 0.0, 10.0, -3.0, 0.0, 3.0])
}
pub fn prewitt_x() -> Self {
Self::new([-1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0])
}
pub fn prewitt_y() -> Self {
Self::new([-1.0, 0.0, 1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0])
}
pub fn laplacian() -> Self {
Self::new([0.0, -1.0, 0.0, -1.0, 4.0, -1.0, 0.0, -1.0, 0.0])
}
pub fn laplacian_8() -> Self {
Self::new([-1.0, -1.0, -1.0, -1.0, 8.0, -1.0, -1.0, -1.0, -1.0])
}
pub fn sharpen() -> Self {
Self::new([0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0])
}
pub fn identity_3x3() -> Self {
Self::new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])
}
pub fn emboss() -> Self {
Self::new([-2.0, -1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 1.0, 2.0])
}
}
impl Neighborhood<f32, 5, 5> {
pub fn gaussian_5x5() -> Self {
#[rustfmt::skip]
let data = [
1.0, 4.0, 6.0, 4.0, 1.0,
4.0, 16.0, 24.0, 16.0, 4.0,
6.0, 24.0, 36.0, 24.0, 6.0,
4.0, 16.0, 24.0, 16.0, 4.0,
1.0, 4.0, 6.0, 4.0, 1.0,
];
Self::new(data)
}
pub fn box_blur_5x5() -> Self {
Self::new([1.0 / 25.0; 25])
}
pub fn identity_5x5() -> Self {
#[rustfmt::skip]
let data = [
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.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0,
];
Self::new(data)
}
}
impl Neighborhood<f32, 3, 1> {
pub fn gaussian_1d_3_h() -> Self {
Self::new([1.0, 2.0, 1.0])
}
pub fn box_1d_3_h() -> Self {
Self::new([1.0 / 3.0; 3])
}
}
impl Neighborhood<f32, 1, 3> {
pub fn gaussian_1d_3_v() -> Self {
Self::new([1.0, 2.0, 1.0])
}
pub fn box_1d_3_v() -> Self {
Self::new([1.0 / 3.0; 3])
}
}
impl Neighborhood<f32, 5, 1> {
pub fn gaussian_1d_5_h() -> Self {
Self::new([1.0, 4.0, 6.0, 4.0, 1.0])
}
pub fn box_1d_5_h() -> Self {
Self::new([1.0 / 5.0; 5])
}
}
impl Neighborhood<f32, 1, 5> {
pub fn gaussian_1d_5_v() -> Self {
Self::new([1.0, 4.0, 6.0, 4.0, 1.0])
}
pub fn box_1d_5_v() -> Self {
Self::new([1.0 / 5.0; 5])
}
}
impl Neighborhood<bool, 3, 3> {
pub fn full_rect_3x3() -> Self {
Self::new([true; 9])
}
pub fn cross_3x3() -> Self {
#[rustfmt::skip]
let data = [
false, true, false,
true, true, true,
false, true, false,
];
Self::new(data)
}
pub fn diamond_3x3() -> Self {
Self::cross_3x3()
}
}
impl Neighborhood<bool, 5, 5> {
pub fn full_rect_5x5() -> Self {
Self::new([true; 25])
}
pub fn cross_5x5() -> Self {
#[rustfmt::skip]
let data = [
false, false, true, false, false,
false, false, true, false, false,
true, true, true, true, true,
false, false, true, false, false,
false, false, true, false, false,
];
Self::new(data)
}
pub fn diamond_5x5() -> Self {
#[rustfmt::skip]
let data = [
false, false, true, false, false,
false, true, true, true, false,
true, true, true, true, true,
false, true, true, true, false,
false, false, true, false, false,
];
Self::new(data)
}
pub fn circle_5x5() -> Self {
#[rustfmt::skip]
let data = [
false, true, true, true, false,
true, true, true, true, true,
true, true, true, true, true,
true, true, true, true, true,
false, true, true, true, false,
];
Self::new(data)
}
}
impl Neighborhood<i32, 3, 3> {
pub fn sobel_x_i32() -> Self {
Self::new([-1, -2, -1, 0, 0, 0, 1, 2, 1])
}
pub fn sobel_y_i32() -> Self {
Self::new([-1, 0, 1, -2, 0, 2, -1, 0, 1])
}
pub fn laplacian_i32() -> Self {
Self::new([0, -1, 0, -1, 4, -1, 0, -1, 0])
}
pub fn scharr_x_i32() -> Self {
Self::new([-3, -10, -3, 0, 0, 0, 3, 10, 3])
}
pub fn scharr_y_i32() -> Self {
Self::new([-3, 0, 3, -10, 0, 10, -3, 0, 3])
}
pub fn prewitt_x_i32() -> Self {
Self::new([-1, -1, -1, 0, 0, 0, 1, 1, 1])
}
pub fn prewitt_y_i32() -> Self {
Self::new([-1, 0, 1, -1, 0, 1, -1, 0, 1])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Size;
use crate::image::ImageView;
#[test]
fn test_new_default_anchor_3x3() {
let k = Neighborhood::<f32, 3, 3>::new([0.0; 9]);
assert_eq!(k.anchor(), (1, 1));
assert_eq!(k.kernel_width(), 3);
assert_eq!(k.kernel_height(), 3);
}
#[test]
fn test_new_default_anchor_5x5() {
let k = Neighborhood::<f32, 5, 5>::new([0.0; 25]);
assert_eq!(k.anchor(), (2, 2));
}
#[test]
fn test_new_default_anchor_1x1() {
let k = Neighborhood::<f32, 1, 1>::new([1.0]);
assert_eq!(k.anchor(), (0, 0));
}
#[test]
fn test_new_default_anchor_even_size() {
let k = Neighborhood::<f32, 4, 4>::new([0.0; 16]);
assert_eq!(k.anchor(), (2, 2));
}
#[test]
fn test_new_default_anchor_non_square() {
let k = Neighborhood::<f32, 5, 3>::new([0.0; 15]);
assert_eq!(k.anchor(), (2, 1));
}
#[test]
fn test_with_anchor_custom() {
let k = Neighborhood::<f32, 3, 3>::with_anchor([0.0; 9], (0, 0));
assert_eq!(k.anchor(), (0, 0));
}
#[test]
fn test_with_anchor_bottom_right() {
let k = Neighborhood::<f32, 3, 3>::with_anchor([0.0; 9], (2, 2));
assert_eq!(k.anchor(), (2, 2));
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_with_anchor_out_of_bounds_x() {
Neighborhood::<f32, 3, 3>::with_anchor([0.0; 9], (3, 1));
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_with_anchor_out_of_bounds_y() {
Neighborhood::<f32, 3, 3>::with_anchor([0.0; 9], (1, 3));
}
#[test]
fn test_weights_accessor() {
let k = Neighborhood::<f32, 3, 3>::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
let w = k.weights();
assert_eq!(w.size(), Size::new(3, 3));
assert_eq!(w.pixel_at(0, 0), 1.0);
assert_eq!(w.pixel_at(1, 1), 5.0);
assert_eq!(w.pixel_at(2, 2), 9.0);
}
#[test]
fn test_as_slice() {
let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
let k = Neighborhood::<f32, 3, 3>::new(data);
assert_eq!(k.as_slice(), &data);
}
#[test]
fn test_kernel_width_height() {
let k = Neighborhood::<f32, 7, 5>::new([0.0; 35]);
assert_eq!(k.kernel_width(), 7);
assert_eq!(k.kernel_height(), 5);
}
#[test]
fn test_positions_3x3_centered() {
let k = Neighborhood::<f32, 3, 3>::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
let positions: Vec<_> = k.positions().collect();
assert_eq!(positions.len(), 9);
assert_eq!(positions[0], (-1, -1, 1.0));
assert_eq!(positions[1], (0, -1, 2.0));
assert_eq!(positions[2], (1, -1, 3.0));
assert_eq!(positions[3], (-1, 0, 4.0));
assert_eq!(positions[4], (0, 0, 5.0));
assert_eq!(positions[5], (1, 0, 6.0));
assert_eq!(positions[6], (-1, 1, 7.0));
assert_eq!(positions[7], (0, 1, 8.0));
assert_eq!(positions[8], (1, 1, 9.0));
}
#[test]
fn test_positions_1d_horizontal() {
let k = Neighborhood::<i32, 3, 1>::new([1, 2, 1]);
let positions: Vec<_> = k.positions().collect();
assert_eq!(positions.len(), 3);
assert_eq!(positions[0], (-1, 0, 1));
assert_eq!(positions[1], (0, 0, 2));
assert_eq!(positions[2], (1, 0, 1));
}
#[test]
fn test_positions_1d_vertical() {
let k = Neighborhood::<i32, 1, 3>::new([1, 2, 1]);
let positions: Vec<_> = k.positions().collect();
assert_eq!(positions.len(), 3);
assert_eq!(positions[0], (0, -1, 1));
assert_eq!(positions[1], (0, 0, 2));
assert_eq!(positions[2], (0, 1, 1));
}
#[test]
fn test_positions_custom_anchor() {
let k = Neighborhood::<f32, 3, 3>::with_anchor(
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
(0, 0),
);
let positions: Vec<_> = k.positions().collect();
assert_eq!(positions[0], (0, 0, 1.0));
assert_eq!(positions[1], (1, 0, 2.0));
assert_eq!(positions[2], (2, 0, 3.0));
assert_eq!(positions[3], (0, 1, 4.0));
assert_eq!(positions[8], (2, 2, 9.0));
}
#[test]
fn test_positions_1x1() {
let k = Neighborhood::<f32, 1, 1>::new([42.0]);
let positions: Vec<_> = k.positions().collect();
assert_eq!(positions.len(), 1);
assert_eq!(positions[0], (0, 0, 42.0));
}
#[test]
fn test_positions_5x5_center() {
let k = Neighborhood::<f32, 5, 5>::new([0.0; 25]);
let positions: Vec<_> = k.positions().collect();
assert_eq!(positions.len(), 25);
assert_eq!(positions[0].0, -2); assert_eq!(positions[0].1, -2); assert_eq!(positions[12].0, 0);
assert_eq!(positions[12].1, 0);
assert_eq!(positions[24].0, 2);
assert_eq!(positions[24].1, 2);
}
#[test]
fn test_positions_exact_size() {
let k = Neighborhood::<f32, 3, 3>::new([0.0; 9]);
let mut iter = k.positions();
assert_eq!(iter.len(), 9);
iter.next();
assert_eq!(iter.len(), 8);
for _ in &mut iter {}
assert_eq!(iter.len(), 0);
}
#[test]
fn test_positions_size_hint() {
let k = Neighborhood::<f32, 5, 5>::new([0.0; 25]);
let iter = k.positions();
let (lo, hi) = iter.size_hint();
assert_eq!(lo, 25);
assert_eq!(hi, Some(25));
}
#[test]
fn test_positions_size_hint_after_partial_consume() {
let k = Neighborhood::<f32, 3, 3>::new([0.0; 9]);
let mut iter = k.positions();
iter.next(); iter.next(); iter.next(); let (lo, hi) = iter.size_hint();
assert_eq!(lo, 6);
assert_eq!(hi, Some(6));
}
#[test]
fn test_positions_exhausted() {
let k = Neighborhood::<f32, 1, 1>::new([1.0]);
let mut iter = k.positions();
assert!(iter.next().is_some());
assert!(iter.next().is_none());
assert!(iter.next().is_none()); assert_eq!(iter.len(), 0);
}
#[test]
fn test_box_blur_3x3() {
let k = Neighborhood::box_blur_3x3();
assert_eq!(k.anchor(), (1, 1));
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_gaussian_3x3() {
let k = Neighborhood::gaussian_3x3();
assert_eq!(k.anchor(), (1, 1));
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 16.0);
}
#[test]
fn test_gaussian_5x5() {
let k = Neighborhood::gaussian_5x5();
assert_eq!(k.anchor(), (2, 2));
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 256.0);
}
#[test]
fn test_sobel_x_antisymmetric() {
let k = Neighborhood::sobel_x();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_sobel_y_antisymmetric() {
let k = Neighborhood::sobel_y();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_scharr_x_antisymmetric() {
let k = Neighborhood::scharr_x();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_scharr_y_antisymmetric() {
let k = Neighborhood::scharr_y();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_prewitt_x_antisymmetric() {
let k = Neighborhood::prewitt_x();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_prewitt_y_antisymmetric() {
let k = Neighborhood::prewitt_y();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_laplacian_sum_zero() {
let k = Neighborhood::laplacian();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_laplacian_8_sum_zero() {
let k = Neighborhood::laplacian_8();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0.0);
}
#[test]
fn test_sharpen_center_weight() {
let k = Neighborhood::sharpen();
assert_eq!(k.weights().pixel_at(1, 1), 5.0);
}
#[test]
fn test_identity_3x3() {
let k = Neighborhood::identity_3x3();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 1.0);
assert_eq!(k.weights().pixel_at(1, 1), 1.0);
for (dx, dy, w) in k.positions() {
if dx == 0 && dy == 0 {
assert_eq!(w, 1.0);
} else {
assert_eq!(w, 0.0);
}
}
}
#[test]
fn test_identity_5x5() {
let k = Neighborhood::identity_5x5();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 1.0);
assert_eq!(k.weights().pixel_at(2, 2), 1.0);
}
#[test]
fn test_box_blur_5x5() {
let k = Neighborhood::box_blur_5x5();
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_emboss() {
let k = Neighborhood::emboss();
assert_eq!(k.anchor(), (1, 1));
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 1.0);
}
#[test]
fn test_gaussian_1d_3_h() {
let k = Neighborhood::gaussian_1d_3_h();
assert_eq!(k.anchor(), (1, 0));
assert_eq!(k.kernel_width(), 3);
assert_eq!(k.kernel_height(), 1);
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 4.0);
}
#[test]
fn test_gaussian_1d_3_v() {
let k = Neighborhood::gaussian_1d_3_v();
assert_eq!(k.anchor(), (0, 1));
assert_eq!(k.kernel_width(), 1);
assert_eq!(k.kernel_height(), 3);
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 4.0);
}
#[test]
fn test_gaussian_1d_5_h() {
let k = Neighborhood::gaussian_1d_5_h();
assert_eq!(k.anchor(), (2, 0));
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 16.0);
}
#[test]
fn test_gaussian_1d_5_v() {
let k = Neighborhood::gaussian_1d_5_v();
assert_eq!(k.anchor(), (0, 2));
let sum: f32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 16.0);
}
#[test]
fn test_box_1d_3_h() {
let k = Neighborhood::box_1d_3_h();
let sum: f32 = k.as_slice().iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_box_1d_3_v() {
let k = Neighborhood::box_1d_3_v();
let sum: f32 = k.as_slice().iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_box_1d_5_h() {
let k = Neighborhood::box_1d_5_h();
let sum: f32 = k.as_slice().iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_box_1d_5_v() {
let k = Neighborhood::box_1d_5_v();
let sum: f32 = k.as_slice().iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_full_rect_3x3() {
let se = Neighborhood::full_rect_3x3();
assert_eq!(se.anchor(), (1, 1));
assert!(se.as_slice().iter().all(|&v| v));
}
#[test]
fn test_cross_3x3() {
let se = Neighborhood::cross_3x3();
let active: Vec<_> = se.positions().filter(|&(_, _, w)| w).collect();
assert_eq!(active.len(), 5); assert!(active.iter().any(|&(dx, dy, _)| dx == 0 && dy == 0));
let corners: Vec<_> = se
.positions()
.filter(|&(dx, dy, _)| dx.abs() == 1 && dy.abs() == 1)
.collect();
assert!(corners.iter().all(|&(_, _, w)| !w));
}
#[test]
fn test_diamond_3x3_same_as_cross() {
let d = Neighborhood::diamond_3x3();
let c = Neighborhood::cross_3x3();
assert_eq!(d.as_slice(), c.as_slice());
}
#[test]
fn test_full_rect_5x5() {
let se = Neighborhood::full_rect_5x5();
assert_eq!(se.anchor(), (2, 2));
assert_eq!(se.as_slice().len(), 25);
assert!(se.as_slice().iter().all(|&v| v));
}
#[test]
fn test_cross_5x5() {
let se = Neighborhood::cross_5x5();
let active: Vec<_> = se.positions().filter(|&(_, _, w)| w).collect();
assert_eq!(active.len(), 9);
}
#[test]
fn test_diamond_5x5() {
let se = Neighborhood::diamond_5x5();
let active: Vec<_> = se.positions().filter(|&(_, _, w)| w).collect();
assert_eq!(active.len(), 13);
}
#[test]
fn test_circle_5x5() {
let se = Neighborhood::circle_5x5();
let active: Vec<_> = se.positions().filter(|&(_, _, w)| w).collect();
assert_eq!(active.len(), 21);
}
#[test]
fn test_sobel_x_i32() {
let k = Neighborhood::sobel_x_i32();
let sum: i32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0);
}
#[test]
fn test_sobel_y_i32() {
let k = Neighborhood::sobel_y_i32();
let sum: i32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0);
}
#[test]
fn test_laplacian_i32() {
let k = Neighborhood::laplacian_i32();
let sum: i32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0);
}
#[test]
fn test_scharr_x_i32() {
let k = Neighborhood::scharr_x_i32();
let sum: i32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0);
}
#[test]
fn test_scharr_y_i32() {
let k = Neighborhood::scharr_y_i32();
let sum: i32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0);
}
#[test]
fn test_prewitt_x_i32() {
let k = Neighborhood::prewitt_x_i32();
let sum: i32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0);
}
#[test]
fn test_prewitt_y_i32() {
let k = Neighborhood::prewitt_y_i32();
let sum: i32 = k.positions().map(|(_, _, w)| w).sum();
assert_eq!(sum, 0);
}
#[test]
fn test_kernel3x3_alias() {
let k: Kernel3x3 = Neighborhood::identity_3x3();
assert_eq!(k.weights().size(), Size::new(3, 3));
}
#[test]
fn test_kernel5x5_alias() {
let k: Kernel5x5 = Neighborhood::identity_5x5();
assert_eq!(k.weights().size(), Size::new(5, 5));
}
#[test]
fn test_kernel3x3i_alias() {
let k: Kernel3x3i = Neighborhood::sobel_x_i32();
assert_eq!(k.weights().size(), Size::new(3, 3));
}
#[test]
fn test_mask_3x3_alias() {
let m: Mask3x3 = Neighborhood::full_rect_3x3();
assert_eq!(m.weights().size(), Size::new(3, 3));
}
#[test]
fn test_mask_5x5_alias() {
let m: Mask5x5 = Neighborhood::full_rect_5x5();
assert_eq!(m.weights().size(), Size::new(5, 5));
}
#[test]
fn test_mask_general_alias() {
let m: Mask<7, 7> = Neighborhood::new([true; 49]);
assert_eq!(m.weights().size(), Size::new(7, 7));
}
#[test]
fn test_clone() {
let k = Neighborhood::<f32, 3, 3>::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
let k2 = k.clone();
assert_eq!(k.as_slice(), k2.as_slice());
assert_eq!(k.anchor(), k2.anchor());
}
#[test]
fn test_clone_with_custom_anchor() {
let k = Neighborhood::<f32, 3, 3>::with_anchor([1.0; 9], (0, 2));
let k2 = k.clone();
assert_eq!(k2.anchor(), (0, 2));
assert_eq!(k2.as_slice(), k.as_slice());
}
#[test]
fn test_clone_bool() {
let se = Neighborhood::cross_3x3();
let se2 = se.clone();
assert_eq!(se.as_slice(), se2.as_slice());
}
#[test]
fn test_debug_output() {
let k = Neighborhood::<f32, 3, 3>::new([0.0; 9]);
let dbg = format!("{:?}", k);
assert!(dbg.contains("Neighborhood"));
assert!(dbg.contains("anchor"));
assert!(dbg.contains("size"));
}
#[test]
fn test_weights_imageview() {
let k = Neighborhood::<f32, 3, 3>::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
let w = k.weights();
assert_eq!(w.width(), 3);
assert_eq!(w.height(), 3);
assert_eq!(w.pixel_at(0, 0), 1.0);
assert_eq!(w.pixel_at(2, 2), 9.0);
assert_eq!(w.get(3, 0), None);
}
#[test]
fn test_weights_composable_with_zip_pixels() {
use crate::image::zip_pixels;
let k1 = Neighborhood::<f32, 3, 3>::new([1.0, 0.0, -1.0, 2.0, 0.0, -2.0, 1.0, 0.0, -1.0]);
let k2 = Neighborhood::<f32, 3, 3>::new([1.0; 9]);
let dot: f32 = zip_pixels(k1.weights(), k2.weights())
.unwrap()
.map(|(a, b)| a * b)
.sum();
assert_eq!(dot, 0.0);
}
#[test]
fn test_non_square_5x3() {
let k = Neighborhood::<f32, 5, 3>::new([1.0; 15]);
assert_eq!(k.anchor(), (2, 1));
assert_eq!(k.kernel_width(), 5);
assert_eq!(k.kernel_height(), 3);
assert_eq!(k.positions().len(), 15);
}
#[test]
fn test_non_square_3x5() {
let k = Neighborhood::<f32, 3, 5>::new([1.0; 15]);
assert_eq!(k.anchor(), (1, 2));
assert_eq!(k.kernel_width(), 3);
assert_eq!(k.kernel_height(), 5);
assert_eq!(k.positions().len(), 15);
}
#[test]
fn test_u8_weight_neighborhood() {
let k = Neighborhood::<u8, 3, 3>::new([0, 1, 0, 1, 1, 1, 0, 1, 0]);
let active: Vec<_> = k.positions().filter(|&(_, _, w)| w > 0).collect();
assert_eq!(active.len(), 5);
}
#[test]
fn test_separable_gaussian_product() {
let h = Neighborhood::gaussian_1d_3_h();
let v = Neighborhood::gaussian_1d_3_v();
let full = Neighborhood::gaussian_3x3();
for ky in 0..3usize {
for kx in 0..3usize {
let expected = v.weights().pixel_at(0, ky) * h.weights().pixel_at(kx, 0);
assert_eq!(
full.weights().pixel_at(kx, ky),
expected,
"mismatch at ({}, {})",
kx,
ky,
);
}
}
}
#[test]
fn test_sobel_x_weights() {
let k = Neighborhood::sobel_x();
let expected = [-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0];
assert_eq!(k.as_slice(), &expected);
}
#[test]
fn test_sobel_y_weights() {
let k = Neighborhood::sobel_y();
let expected = [-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0];
assert_eq!(k.as_slice(), &expected);
}
#[test]
fn test_sobel_i32_matches_f32() {
let fi = Neighborhood::sobel_x();
let ii = Neighborhood::sobel_x_i32();
for (f, i) in fi.as_slice().iter().zip(ii.as_slice().iter()) {
assert_eq!(*f as i32, *i);
}
}
#[test]
fn test_scharr_i32_matches_f32() {
let fi = Neighborhood::scharr_x();
let ii = Neighborhood::scharr_x_i32();
for (f, i) in fi.as_slice().iter().zip(ii.as_slice().iter()) {
assert_eq!(*f as i32, *i);
}
}
#[test]
fn test_flipped_identity_3x3_is_identity() {
let kernel = Neighborhood::<f32, 3, 3>::identity_3x3();
let flipped = kernel.flipped();
assert_eq!(kernel.as_slice(), flipped.as_slice());
assert_eq!(kernel.anchor(), flipped.anchor());
}
#[test]
fn test_flipped_involution_f32() {
let kernel = Neighborhood::<f32, 3, 3>::sobel_x();
let double_flipped = kernel.flipped().flipped();
assert_eq!(kernel.as_slice(), double_flipped.as_slice());
assert_eq!(kernel.anchor(), double_flipped.anchor());
}
#[test]
fn test_flipped_involution_bool() {
let kernel = Neighborhood::<bool, 3, 3>::cross_3x3();
let double_flipped = kernel.flipped().flipped();
assert_eq!(kernel.as_slice(), double_flipped.as_slice());
assert_eq!(kernel.anchor(), double_flipped.anchor());
}
#[test]
fn test_flipped_involution_i32() {
let kernel = Neighborhood::<i32, 3, 3>::sobel_x_i32();
let double_flipped = kernel.flipped().flipped();
assert_eq!(kernel.as_slice(), double_flipped.as_slice());
assert_eq!(kernel.anchor(), double_flipped.anchor());
}
#[test]
fn test_flipped_sobel_x_content() {
let kernel = Neighborhood::<f32, 3, 3>::sobel_x();
let flipped = kernel.flipped();
let expected = [1.0, 2.0, 1.0, 0.0, 0.0, 0.0, -1.0, -2.0, -1.0f32];
for (a, b) in flipped.as_slice().iter().zip(expected.iter()) {
assert!((a - b).abs() < 1e-6, "got {a}, expected {b}");
}
}
#[test]
fn test_flipped_non_centered_anchor() {
let kernel = Neighborhood::<f32, 3, 3>::with_anchor([1.0; 9], (0, 0));
let flipped = kernel.flipped();
assert_eq!(flipped.anchor(), (2, 2));
let kernel2 = Neighborhood::<f32, 5, 3>::with_anchor([1.0; 15], (1, 0));
let flipped2 = kernel2.flipped();
assert_eq!(flipped2.anchor(), (3, 2));
}
#[test]
fn test_flipped_centered_anchor_stays_centered() {
let kernel = Neighborhood::<f32, 3, 3>::box_blur_3x3();
let flipped = kernel.flipped();
assert_eq!(flipped.anchor(), (1, 1));
let kernel5 = Neighborhood::<f32, 5, 5>::box_blur_5x5();
let flipped5 = kernel5.flipped();
assert_eq!(flipped5.anchor(), (2, 2));
}
#[test]
fn test_flipped_symmetric_kernel_unchanged() {
let kernel = Neighborhood::<f32, 3, 3>::box_blur_3x3();
let flipped = kernel.flipped();
for (a, b) in kernel.as_slice().iter().zip(flipped.as_slice().iter()) {
assert!((a - b).abs() < 1e-6);
}
}
#[test]
fn test_flipped_1d_horizontal() {
let kernel = Neighborhood::<f32, 3, 1>::new([1.0, 2.0, 3.0]);
let flipped = kernel.flipped();
assert_eq!(flipped.as_slice(), &[3.0, 2.0, 1.0]);
assert_eq!(flipped.anchor(), (1, 0)); }
#[test]
fn test_flipped_1d_vertical() {
let kernel = Neighborhood::<f32, 1, 3>::new([1.0, 2.0, 3.0]);
let flipped = kernel.flipped();
assert_eq!(flipped.as_slice(), &[3.0, 2.0, 1.0]);
assert_eq!(flipped.anchor(), (0, 1));
}
#[test]
fn test_flipped_bool_full_rect() {
let kernel = Neighborhood::<bool, 3, 3>::full_rect_3x3();
let flipped = kernel.flipped();
assert_eq!(kernel.as_slice(), flipped.as_slice());
}
#[test]
fn test_kernel_trait_weights_matches_method() {
use crate::image::Kernel;
let kernel = Neighborhood::<f32, 3, 3>::gaussian_3x3();
let trait_weights: &ImageArray<f32, 3, 3> = Kernel::weights(&kernel);
let method_weights = Neighborhood::weights(&kernel);
assert_eq!(trait_weights.as_slice(), method_weights.as_slice());
}
#[test]
fn test_kernel_trait_anchor_matches_method() {
use crate::image::Kernel;
let kernel = Neighborhood::<f32, 3, 3>::with_anchor([1.0; 9], (2, 0));
assert_eq!(Kernel::anchor(&kernel), Neighborhood::anchor(&kernel));
}
}