ndarray_vision/core/
padding.rs

1use crate::core::{ColourModel, Image, ImageBase};
2use ndarray::{prelude::*, s, Data, OwnedRepr};
3use num_traits::identities::Zero;
4use std::marker::PhantomData;
5
6/// Defines a method for padding the data of an image applied directly to the
7/// ndarray type internally. Padding is symmetric
8pub trait PaddingStrategy<T>
9where
10    T: Copy,
11{
12    /// Taking in the image data and the margin to apply to rows and columns
13    /// returns a padded image
14    fn pad(
15        &self,
16        image: ArrayView<T, Ix3>,
17        padding: (usize, usize),
18    ) -> ArrayBase<OwnedRepr<T>, Ix3>;
19
20    /// Taking in the image data and row and column return the pixel value
21    /// if the coordinates are within the image bounds this should probably not
22    /// be used in the name of performance
23    fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>>;
24
25    /// Gets a value for a channel rows and columns can exceed bounds but the channel index must be
26    /// present
27    fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T>;
28
29    /// Returns true if the padder will return a value for (row, col) or if None if it can pad
30    /// an image at all. `NoPadding` is a special instance which will always be false
31    fn will_pad(&self, _coord: Option<(isize, isize)>) -> bool {
32        true
33    }
34}
35
36/// Doesn't apply any padding to the image returning it unaltered regardless
37/// of padding value
38#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
39pub struct NoPadding;
40
41/// Pad the image with a constant value
42#[derive(Clone, Eq, PartialEq, Hash, Debug)]
43pub struct ConstantPadding<T>(T)
44where
45    T: Copy;
46
47/// Pad the image with zeros. Uses ConstantPadding internally
48#[derive(Clone, Eq, PartialEq, Hash, Debug)]
49pub struct ZeroPadding;
50
51#[inline]
52fn is_out_of_bounds(dim: (usize, usize, usize), index: (isize, isize, usize)) -> bool {
53    index.0 < 0
54        || index.1 < 0
55        || index.0 >= dim.0 as isize
56        || index.1 >= dim.1 as isize
57        || index.2 >= dim.2
58}
59
60impl<T> PaddingStrategy<T> for NoPadding
61where
62    T: Copy,
63{
64    fn pad(
65        &self,
66        image: ArrayView<T, Ix3>,
67        _padding: (usize, usize),
68    ) -> ArrayBase<OwnedRepr<T>, Ix3> {
69        image.to_owned()
70    }
71
72    fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>> {
73        let index = (index.0, index.1, 0);
74        if is_out_of_bounds(image.dim(), index) {
75            None
76        } else {
77            Some(image.slice(s![index.0, index.1, ..]).to_owned())
78        }
79    }
80
81    fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T> {
82        if is_out_of_bounds(image.dim(), index) {
83            None
84        } else {
85            image
86                .get((index.0 as usize, index.1 as usize, index.2))
87                .copied()
88        }
89    }
90
91    fn will_pad(&self, _coord: Option<(isize, isize)>) -> bool {
92        false
93    }
94}
95
96impl<T> PaddingStrategy<T> for ConstantPadding<T>
97where
98    T: Copy,
99{
100    fn pad(
101        &self,
102        image: ArrayView<T, Ix3>,
103        padding: (usize, usize),
104    ) -> ArrayBase<OwnedRepr<T>, Ix3> {
105        let shape = (
106            image.shape()[0] + padding.0 * 2,
107            image.shape()[1] + padding.1 * 2,
108            image.shape()[2],
109        );
110
111        let mut result = Array::from_elem(shape, self.0);
112        result
113            .slice_mut(s![
114                padding.0..shape.0 - padding.0,
115                padding.1..shape.1 - padding.1,
116                ..
117            ])
118            .assign(&image);
119
120        result
121    }
122
123    fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>> {
124        let index = (index.0, index.1, 0);
125        if is_out_of_bounds(image.dim(), index) {
126            let v = vec![self.0; image.dim().2];
127            Some(Array1::from(v))
128        } else {
129            Some(image.slice(s![index.0, index.1, ..]).to_owned())
130        }
131    }
132
133    fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T> {
134        if is_out_of_bounds(image.dim(), index) {
135            Some(self.0)
136        } else {
137            image
138                .get((index.0 as usize, index.1 as usize, index.2))
139                .copied()
140        }
141    }
142}
143
144impl<T> PaddingStrategy<T> for ZeroPadding
145where
146    T: Copy + Zero,
147{
148    fn pad(
149        &self,
150        image: ArrayView<T, Ix3>,
151        padding: (usize, usize),
152    ) -> ArrayBase<OwnedRepr<T>, Ix3> {
153        let padder = ConstantPadding(T::zero());
154        padder.pad(image, padding)
155    }
156
157    fn get_pixel(&self, image: ArrayView<T, Ix3>, index: (isize, isize)) -> Option<Array1<T>> {
158        let padder = ConstantPadding(T::zero());
159        padder.get_pixel(image, index)
160    }
161
162    fn get_value(&self, image: ArrayView<T, Ix3>, index: (isize, isize, usize)) -> Option<T> {
163        let padder = ConstantPadding(T::zero());
164        padder.get_value(image, index)
165    }
166}
167
168/// Padding extension for images
169pub trait PaddingExt<T> {
170    /// Type of the output image
171    type Output;
172    /// Pad the object with the given padding and strategy
173    fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy<T>) -> Self::Output;
174}
175
176impl<T, U> PaddingExt<T> for ArrayBase<U, Ix3>
177where
178    U: Data<Elem = T>,
179    T: Copy,
180{
181    type Output = ArrayBase<OwnedRepr<T>, Ix3>;
182
183    fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy<T>) -> Self::Output {
184        strategy.pad(self.view(), padding)
185    }
186}
187
188impl<T, U, C> PaddingExt<T> for ImageBase<U, C>
189where
190    U: Data<Elem = T>,
191    T: Copy,
192    C: ColourModel,
193{
194    type Output = Image<T, C>;
195
196    fn pad(&self, padding: (usize, usize), strategy: &dyn PaddingStrategy<T>) -> Self::Output {
197        Self::Output {
198            data: strategy.pad(self.data.view(), padding),
199            model: PhantomData,
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use crate::core::colour_models::{Gray, RGB};
208
209    #[test]
210    fn constant_padding() {
211        let i = Image::<u8, Gray>::from_shape_data(3, 3, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
212
213        let p = i.pad((1, 1), &ConstantPadding(0));
214
215        let exp = Image::<u8, Gray>::from_shape_data(
216            5,
217            5,
218            vec![
219                0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0,
220            ],
221        );
222        assert_eq!(p, exp);
223
224        let p = i.pad((1, 1), &ConstantPadding(2));
225
226        let exp = Image::<u8, Gray>::from_shape_data(
227            5,
228            5,
229            vec![
230                2, 2, 2, 2, 2, 2, 1, 2, 3, 2, 2, 4, 5, 6, 2, 2, 7, 8, 9, 2, 2, 2, 2, 2, 2,
231            ],
232        );
233        assert_eq!(p, exp);
234
235        let p = i.pad((2, 0), &ConstantPadding(0));
236        let z = i.pad((2, 0), &ZeroPadding {});
237        assert_eq!(p, z);
238
239        let exp = Image::<u8, Gray>::from_shape_data(
240            7,
241            3,
242            vec![
243                0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
244            ],
245        );
246        assert_eq!(p, exp);
247    }
248
249    #[test]
250    fn no_padding() {
251        let i = Image::<u8, RGB>::new(5, 5);
252        let p = i.pad((10, 10), &NoPadding {});
253
254        assert_eq!(i, p);
255
256        let p = i.pad((0, 0), &NoPadding {});
257        assert_eq!(i, p);
258    }
259}