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}