Skip to main content

akaze/
derivatives.rs

1use crate::image::{fill_border, GrayFloatImage};
2use ndarray::{s, Array2, ArrayView2, ArrayViewMut2};
3
4/// Compute the Scharr derivative horizontally
5///
6/// The implementation of this function is using a separable kernel, for speed.
7///
8/// # Arguments
9/// * `image` - the input image.
10/// * `sigma_size` - the scale of the derivative.
11///
12/// # Return value
13/// Output image derivative (an image.)
14pub fn scharr_horizontal(image: &GrayFloatImage, sigma_size: u32) -> GrayFloatImage {
15    let img_horizontal = scharr_axis(
16        &image,
17        sigma_size,
18        FilterDirection::Horizontal,
19        FilterOrder::Main,
20    );
21    scharr_axis(
22        &img_horizontal,
23        sigma_size,
24        FilterDirection::Vertical,
25        FilterOrder::Off,
26    )
27}
28
29/// Compute the Scharr derivative vertically
30///
31/// The implementation of this function is using a separable kernel, for speed.
32///
33/// # Arguments
34/// * `image` - the input image.
35/// * `sigma_size` - the scale of the derivative.
36///
37/// # Return value
38/// Output image derivative (an image.)
39pub fn scharr_vertical(image: &GrayFloatImage, sigma_size: u32) -> GrayFloatImage {
40    let img_horizontal = scharr_axis(
41        &image,
42        sigma_size,
43        FilterDirection::Horizontal,
44        FilterOrder::Off,
45    );
46    scharr_axis(
47        &img_horizontal,
48        sigma_size,
49        FilterDirection::Vertical,
50        FilterOrder::Main,
51    )
52}
53
54/// Multiplies and accumulates
55fn accumulate_mul_offset(
56    mut accumulator: ArrayViewMut2<f32>,
57    source: ArrayView2<f32>,
58    val: f32,
59    border: usize,
60    xoff: usize,
61    yoff: usize,
62) {
63    assert_eq!(source.dim(), accumulator.dim());
64    let dims = source.dim();
65    let mut accumulator =
66        accumulator.slice_mut(s![border..dims.0 - border, border..dims.1 - border]);
67    accumulator.scaled_add(
68        val,
69        &source.slice(s![
70            yoff..dims.0 + yoff - 2 * border,
71            xoff..dims.1 + xoff - 2 * border
72        ]),
73    );
74}
75
76#[derive(Copy, Clone, Debug, PartialEq)]
77enum FilterDirection {
78    Horizontal,
79    Vertical,
80}
81
82#[derive(Copy, Clone, Debug, PartialEq)]
83enum FilterOrder {
84    Main,
85    Off,
86}
87
88fn scharr_axis(
89    image: &GrayFloatImage,
90    sigma_size: u32,
91    dir: FilterDirection,
92    order: FilterOrder,
93) -> GrayFloatImage {
94    let mut output = Array2::<f32>::zeros([image.height(), image.width()]);
95    // Get the border size (we wont fill in this border width of the output).
96    let border = sigma_size as usize;
97    // Difference between middle and sides of main axis filter.
98    let w = 10.0 / 3.0;
99    // Side intensity of filter.
100    let norm = (1.0 / (2.0 * f64::from(sigma_size) * (w + 2.0))) as f32;
101    // Middle intensity of filter.
102    let middle = norm * w as f32;
103
104    let mut offsets = match order {
105        FilterOrder::Main => vec![
106            (norm, [border, 0]),
107            (middle, [border, border]),
108            (norm, [border, 2 * border]),
109        ],
110        FilterOrder::Off => vec![(-1.0, [border, 0]), (1.0, [border, 2 * border])],
111    };
112
113    if dir == FilterDirection::Horizontal {
114        // Swap the offsets if the filter is a horizontal filter.
115        for (_, [x, y]) in &mut offsets {
116            std::mem::swap(x, y);
117        }
118    }
119
120    // Accumulate the three components.
121    for (val, [x, y]) in offsets {
122        accumulate_mul_offset(output.view_mut(), image.ref_array2(), val, border, x, y);
123    }
124    let mut output = GrayFloatImage::from_array2(output);
125    fill_border(&mut output, border);
126    output
127}