kornia_imgproc/
core.rs

1// reference: https://www.strchr.com/standard_deviation_in_one_pass
2use kornia_image::{Image, ImageError};
3use rayon::{
4    iter::{IndexedParallelIterator, ParallelIterator},
5    slice::{ParallelSlice, ParallelSliceMut},
6};
7
8/// Compute the mean and standard deviation of an image.
9///
10/// The mean and standard deviation are computed for each channel
11/// of the image in one pass.
12///
13/// # Arguments
14///
15/// * `image` - The input image to compute the mean and standard deviation.
16///
17/// # Returns
18///
19/// A tuple containing the mean and standard deviation of the image.
20/// The first element of the tuple is the standard deviation and the
21/// second element is the mean.
22///
23/// # Example
24///
25/// ```
26/// use kornia_image::{Image, ImageSize};
27/// use kornia_imgproc::core::std_mean;
28///
29/// let image = Image::<u8, 3>::new(
30///    ImageSize {
31///      width: 2,
32///      height: 2,
33///  },
34/// vec![0, 1, 2, 253, 254, 255, 128, 129, 130, 64, 65, 66],
35/// ).unwrap();
36///
37/// let (std, mean) = std_mean(&image);
38///
39/// assert_eq!(std, [93.5183805462862, 93.5183805462862, 93.5183805462862]);
40/// assert_eq!(mean, [111.25, 112.25, 113.25]);
41/// ```
42pub fn std_mean(image: &Image<u8, 3>) -> ([f64; 3], [f64; 3]) {
43    let (sum, sq_sum) = image.as_slice().chunks_exact(3).fold(
44        ([0f64; 3], [0f64; 3]),
45        |(mut sum, mut sq_sum), pixel| {
46            sum.iter_mut()
47                .zip(pixel.iter())
48                .for_each(|(s, &p)| *s += p as f64);
49            sq_sum
50                .iter_mut()
51                .zip(pixel.iter())
52                .for_each(|(s, &p)| *s += (p as f64).powi(2));
53            (sum, sq_sum)
54        },
55    );
56
57    let n = (image.width() * image.height()) as f64;
58    let mean = [sum[0] / n, sum[1] / n, sum[2] / n];
59
60    let variance = [
61        (sq_sum[0] / n - mean[0].powi(2)).sqrt(),
62        (sq_sum[1] / n - mean[1].powi(2)).sqrt(),
63        (sq_sum[2] / n - mean[2].powi(2)).sqrt(),
64    ];
65
66    (variance, mean)
67}
68
69/// Perform a bitwise AND operation between two images using a mask.
70///
71/// The mask is a binary image where the value 0 is considered as False
72/// and any other value is considered as True.
73///
74/// # Arguments
75///
76/// * `src1` - The first input image.
77/// * `src2` - The second input image.
78/// * `dst` - The output image.
79/// * `mask` - The binary mask to apply to the image.
80///
81/// # Returns
82///
83/// The output image after applying the mask.
84///
85/// # Example
86///
87/// ```
88/// use kornia_image::{Image, ImageSize};
89/// use kornia_imgproc::core::bitwise_and;
90///
91/// let image = Image::<u8, 3>::new(
92///    ImageSize {
93///        width: 2,
94///        height: 2,
95///    },
96///    vec![0, 1, 2, 253, 254, 255, 128, 129, 130, 64, 65, 66],
97/// ).unwrap();
98///
99/// let mask = Image::<u8, 1>::new(
100///    ImageSize {
101///        width: 2,
102///        height: 2,
103///    },
104///    vec![255, 0, 255, 0],
105/// ).unwrap();
106///
107/// let mut output = Image::<u8, 3>::from_size_val(image.size(), 0).unwrap();
108///
109/// bitwise_and(&image, &image, &mut output, &mask).unwrap();
110///
111/// assert_eq!(output.size().width, 2);
112/// assert_eq!(output.size().height, 2);
113///
114/// assert_eq!(output.as_slice(), &vec![0, 1, 2, 0, 0, 0, 128, 129, 130, 0, 0, 0]);
115/// ```
116pub fn bitwise_and<const C: usize>(
117    src1: &Image<u8, C>,
118    src2: &Image<u8, C>,
119    dst: &mut Image<u8, C>,
120    mask: &Image<u8, 1>,
121) -> Result<(), ImageError> {
122    if src1.size() != src2.size() {
123        return Err(ImageError::InvalidImageSize(
124            src1.cols(),
125            src1.rows(),
126            src2.cols(),
127            src2.rows(),
128        ));
129    }
130
131    if src1.size() != mask.size() {
132        return Err(ImageError::InvalidImageSize(
133            src1.cols(),
134            src1.rows(),
135            mask.cols(),
136            mask.rows(),
137        ));
138    }
139
140    if src1.size() != dst.size() {
141        return Err(ImageError::InvalidImageSize(
142            src1.cols(),
143            src1.rows(),
144            dst.cols(),
145            dst.rows(),
146        ));
147    }
148
149    // apply the mask to the image
150
151    dst.as_slice_mut()
152        .chunks_exact_mut(C)
153        .zip(src1.as_slice().chunks_exact(C))
154        .zip(src2.as_slice().chunks_exact(C))
155        .zip(mask.as_slice().iter())
156        .for_each(|(((dst_chunk, src1_chunk), src2_chunk), &mask_chunk)| {
157            dst_chunk
158                .iter_mut()
159                .zip(src1_chunk.iter().zip(src2_chunk.iter()))
160                .for_each(|(dst_pixel, (src1_pixel, src2_pixel))| {
161                    *dst_pixel = if mask_chunk != 0 {
162                        src1_pixel & src2_pixel
163                    } else {
164                        0
165                    };
166                });
167        });
168
169    Ok(())
170}
171
172/// Concatenate images horizontally from a vector of images into a destination image
173///
174/// # Arguments
175///
176/// * `src` - The vector of images to concatenate.
177/// * `dst` - The destination image.
178///
179/// Precondition: all images must have the same height
180/// Precondition: the output image must have enough space to store the concatenated images
181///
182/// # Example
183///
184/// ```
185/// use kornia_image::{Image, ImageSize};
186/// use kornia_imgproc::core::hconcat;
187///
188/// let image1 = Image::<u8, 3>::new(
189///     ImageSize {
190///         width: 2,
191///         height: 2,
192///     },
193///     vec![0, 1, 2, 253, 254, 255, 128, 129, 130, 64, 65, 66],
194/// ).unwrap();
195///
196/// let image2 = Image::<u8, 3>::new(
197///     ImageSize {
198///         width: 2,
199///         height: 2,
200///     },
201///     vec![128, 129, 130, 64, 65, 66, 253, 254, 255, 0, 1, 2],
202/// ).unwrap();
203///
204/// let mut output = Image::<u8, 3>::from_size_val(
205///     ImageSize {
206///         width: 4,
207///         height: 2,
208///     },
209///     0,
210/// ).unwrap();
211///
212/// hconcat(vec![&image1, &image2], &mut output).unwrap();
213/// ```
214pub fn hconcat<const C: usize>(
215    src: Vec<&Image<u8, C>>,
216    dst: &mut Image<u8, C>,
217) -> Result<(), ImageError> {
218    // check that all images have the same height
219    let mut count_cols = 0;
220    for img in src.iter() {
221        if img.rows() != dst.rows() {
222            return Err(ImageError::InvalidImageSize(
223                img.cols(),
224                img.rows(),
225                dst.cols(),
226                dst.rows(),
227            ));
228        }
229        count_cols += img.cols();
230    }
231
232    if count_cols > dst.cols() {
233        return Err(ImageError::InvalidImageSize(
234            count_cols,
235            dst.rows(),
236            dst.cols(),
237            dst.rows(),
238        ));
239    }
240
241    let dst_cols = dst.cols() * C;
242
243    // Copy each source image into the destination image
244    for (i, img) in src.iter().enumerate() {
245        let img_cols = img.cols() * C;
246        dst.as_slice_mut()
247            .par_chunks_exact_mut(dst_cols)
248            .zip_eq(img.as_slice().par_chunks_exact(img_cols))
249            .for_each(|(dst_row, src_row)| {
250                dst_row[i * img_cols..(i + 1) * img_cols].copy_from_slice(src_row);
251            });
252    }
253
254    Ok(())
255}
256
257#[cfg(test)]
258mod tests {
259    use kornia_image::{Image, ImageError, ImageSize};
260
261    #[test]
262    fn test_std_mean() -> Result<(), ImageError> {
263        let image = Image::<u8, 3>::new(
264            ImageSize {
265                width: 2,
266                height: 2,
267            },
268            vec![0, 1, 2, 253, 254, 255, 128, 129, 130, 64, 65, 66],
269        )?;
270
271        let std_expected = [93.5183805462862, 93.5183805462862, 93.5183805462862];
272        let mean_expected = [111.25, 112.25, 113.25];
273
274        let (std, mean) = super::std_mean(&image);
275        assert_eq!(std, std_expected);
276        assert_eq!(mean, mean_expected);
277        Ok(())
278    }
279
280    #[test]
281    fn test_bitwise_and() -> Result<(), ImageError> {
282        let image = Image::<u8, 3>::new(
283            ImageSize {
284                width: 2,
285                height: 2,
286            },
287            vec![0, 1, 2, 253, 254, 255, 128, 129, 130, 64, 65, 66],
288        )?;
289
290        let mask = Image::<u8, 1>::new(
291            ImageSize {
292                width: 2,
293                height: 2,
294            },
295            vec![255, 0, 255, 0],
296        )?;
297
298        let mut output = Image::<u8, 3>::from_size_val(image.size(), 0)?;
299
300        super::bitwise_and(&image, &image, &mut output, &mask)?;
301
302        assert_eq!(output.size().width, 2);
303        assert_eq!(output.size().height, 2);
304        assert_eq!(output.num_channels(), 3);
305
306        assert_eq!(
307            output.as_slice(),
308            vec![0, 1, 2, 0, 0, 0, 128, 129, 130, 0, 0, 0]
309        );
310        Ok(())
311    }
312
313    #[test]
314    fn test_hconcat() -> Result<(), ImageError> {
315        #[rustfmt::skip]
316        let image1 = Image::<u8, 3>::new(
317            ImageSize {
318                width: 2,
319                height: 2,
320            },
321            vec![
322                0, 1, 2,
323                253, 254, 255,
324                128, 129, 130,
325                64, 65, 66,
326            ],
327        )?;
328
329        #[rustfmt::skip]
330        let image2 = Image::<u8, 3>::new(
331            ImageSize {
332                width: 2,
333                height: 2,
334            },
335            vec![
336                128, 129, 130,
337                64, 65, 66,
338                253, 254, 255,
339                0, 1, 2,
340            ],
341        )?;
342
343        #[rustfmt::skip]
344        let expected = vec![
345            0, 1, 2, 253, 254, 255,
346            128, 129, 130, 64, 65, 66,
347            128, 129, 130, 64, 65, 66,
348            253, 254, 255, 0, 1, 2,
349        ];
350
351        let mut output = Image::<u8, 3>::from_size_val(
352            ImageSize {
353                width: 4,
354                height: 2,
355            },
356            0,
357        )?;
358
359        super::hconcat(vec![&image1, &image2], &mut output)?;
360
361        assert_eq!(output.size().width, 4);
362        assert_eq!(output.size().height, 2);
363        assert_eq!(output.num_channels(), 3);
364
365        assert_eq!(output.as_slice(), expected);
366        Ok(())
367    }
368}