1use crate::definitions::Image;
4use crate::math::cast;
5use conv::ValueInto;
6use image::{GenericImageView, GrayImage, Pixel, Primitive};
7use num::Bounded;
8
9pub struct ChannelHistogram {
11 pub channels: Vec<[u32; 256]>,
13}
14
15pub fn histogram<P>(image: &Image<P>) -> ChannelHistogram
17where
18 P: Pixel<Subpixel = u8>,
19{
20 let mut hist = vec![[0u32; 256]; P::CHANNEL_COUNT as usize];
21
22 for pix in image.pixels() {
23 for (i, c) in pix.channels().iter().enumerate() {
24 hist[i][*c as usize] += 1;
25 }
26 }
27
28 ChannelHistogram { channels: hist }
29}
30
31pub struct CumulativeChannelHistogram {
33 pub channels: Vec<[u32; 256]>,
35}
36
37pub fn cumulative_histogram<P>(image: &Image<P>) -> CumulativeChannelHistogram
39where
40 P: Pixel<Subpixel = u8>,
41{
42 let mut hist = histogram(image);
43
44 for c in 0..hist.channels.len() {
45 for i in 1..hist.channels[c].len() {
46 hist.channels[c][i] += hist.channels[c][i - 1];
47 }
48 }
49
50 CumulativeChannelHistogram {
51 channels: hist.channels,
52 }
53}
54
55pub fn percentile(image: &GrayImage, p: u8) -> u8 {
90 assert!(p <= 100, "requested percentile must be <= 100");
91
92 let cum_hist = cumulative_histogram(image).channels[0];
93 let total = cum_hist[255] as u64;
94
95 for i in 0..256 {
96 if 100 * cum_hist[i] as u64 / total >= p as u64 {
97 return i as u8;
98 }
99 }
100
101 unreachable!();
102}
103
104pub fn root_mean_squared_error<I, J, P>(left: &I, right: &J) -> f64
109where
110 I: GenericImageView<Pixel = P>,
111 J: GenericImageView<Pixel = P>,
112 P: Pixel,
113 P::Subpixel: ValueInto<f64>,
114{
115 mean_squared_error(left, right).sqrt()
116}
117
118pub fn peak_signal_to_noise_ratio<I, J, P>(original: &I, noisy: &J) -> f64
123where
124 I: GenericImageView<Pixel = P>,
125 J: GenericImageView<Pixel = P>,
126 P: Pixel,
127 P::Subpixel: ValueInto<f64> + Primitive,
128{
129 let max: f64 = cast(<P::Subpixel as Bounded>::max_value());
130 let mse = mean_squared_error(original, noisy);
131 20f64 * max.log(10f64) - 10f64 * mse.log(10f64)
132}
133
134fn mean_squared_error<I, J, P>(left: &I, right: &J) -> f64
135where
136 I: GenericImageView<Pixel = P>,
137 J: GenericImageView<Pixel = P>,
138 P: Pixel,
139 P::Subpixel: ValueInto<f64>,
140{
141 assert_dimensions_match!(left, right);
142 let mut sum_squared_diffs = 0f64;
143 for (p, q) in left.pixels().zip(right.pixels()) {
144 for (c, d) in p.2.channels().iter().zip(q.2.channels().iter()) {
145 let fc: f64 = cast(*c);
146 let fd: f64 = cast(*d);
147 let diff = fc - fd;
148 sum_squared_diffs += diff * diff;
149 }
150 }
151 let count = (left.width() * left.height() * P::CHANNEL_COUNT as u32) as f64;
152 sum_squared_diffs / count
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use image::{GrayImage, Luma, Rgb, RgbImage};
159 use test::Bencher;
160
161 #[test]
162 fn test_cumulative_histogram() {
163 let image = gray_image!(1u8, 2u8, 3u8, 2u8, 1u8);
164 let hist = cumulative_histogram(&image).channels[0];
165
166 assert_eq!(hist[0..4], [0, 2, 4, 5]);
167 assert!(hist.iter().skip(4).all(|x| *x == 5));
168 }
169
170 #[test]
171 fn test_cumulative_histogram_rgb() {
172 let image = rgb_image!(
173 [1u8, 10u8, 1u8],
174 [2u8, 20u8, 2u8],
175 [3u8, 30u8, 3u8],
176 [2u8, 20u8, 2u8],
177 [1u8, 10u8, 1u8]
178 );
179
180 let hist = cumulative_histogram(&image);
181 let r = hist.channels[0];
182 let g = hist.channels[1];
183 let b = hist.channels[2];
184
185 assert_eq!(r[0..4], [0, 2, 4, 5]);
186 assert!(r.iter().skip(4).all(|x| *x == 5));
187
188 assert_eq!(g[0..10], [0; 10]);
189 assert_eq!(g[10..20], [2; 10]);
190 assert_eq!(g[20..30], [4; 10]);
191 assert_eq!(g[30], 5);
192 assert!(b.iter().skip(30).all(|x| *x == 5));
193
194 assert_eq!(b[0..4], [0, 2, 4, 5]);
195 assert!(b.iter().skip(4).all(|x| *x == 5));
196 }
197
198 #[test]
199 fn test_histogram() {
200 let image = gray_image!(1u8, 2u8, 3u8, 2u8, 1u8);
201 let hist = histogram(&image).channels[0];
202
203 assert_eq!(hist[0..4], [0, 2, 2, 1]);
204 }
205
206 #[test]
207 fn test_histogram_rgb() {
208 let image = rgb_image!(
209 [1u8, 10u8, 1u8],
210 [2u8, 20u8, 2u8],
211 [3u8, 30u8, 3u8],
212 [2u8, 20u8, 2u8],
213 [1u8, 10u8, 1u8]
214 );
215
216 let hist = histogram(&image);
217 let r = hist.channels[0];
218 let g = hist.channels[1];
219 let b = hist.channels[2];
220
221 assert_eq!(r[0..4], [0, 2, 2, 1]);
222 assert!(r.iter().skip(4).all(|x| *x == 0));
223
224 assert_eq!(g[0..10], [0; 10]);
225 assert_eq!(g[10], 2);
226 assert_eq!(g[11..20], [0; 9]);
227 assert_eq!(g[20], 2);
228 assert_eq!(g[21..30], [0; 9]);
229 assert_eq!(g[30], 1);
230 assert!(b.iter().skip(30).all(|x| *x == 0));
231
232 assert_eq!(b[0..4], [0, 2, 2, 1]);
233 assert!(b.iter().skip(4).all(|x| *x == 0));
234 }
235
236 #[test]
237 fn test_root_mean_squared_error_grayscale() {
238 let left = gray_image!(
239 1, 2, 3;
240 4, 5, 6);
241
242 let right = gray_image!(
243 8, 4, 7;
244 6, 9, 1);
245
246 let rms = root_mean_squared_error(&left, &right);
247 let expected = (114f64 / 6f64).sqrt();
248 assert_eq!(rms, expected);
249 }
250
251 #[test]
252 fn test_root_mean_squared_error_rgb() {
253 let left = rgb_image!([1, 2, 3], [4, 5, 6]);
254 let right = rgb_image!([8, 4, 7], [6, 9, 1]);
255 let rms = root_mean_squared_error(&left, &right);
256 let expected = (114f64 / 6f64).sqrt();
257 assert_eq!(rms, expected);
258 }
259
260 #[test]
261 #[should_panic]
262 fn test_root_mean_squares_rejects_mismatched_dimensions() {
263 let left = gray_image!(1, 2);
264 let right = gray_image!(8; 4);
265 let _ = root_mean_squared_error(&left, &right);
266 }
267
268 fn left_image_rgb(width: u32, height: u32) -> RgbImage {
269 RgbImage::from_fn(width, height, |x, y| Rgb([x as u8, y as u8, (x + y) as u8]))
270 }
271
272 fn right_image_rgb(width: u32, height: u32) -> RgbImage {
273 RgbImage::from_fn(width, height, |x, y| Rgb([(x + y) as u8, x as u8, y as u8]))
274 }
275
276 #[bench]
277 fn bench_root_mean_squared_error_rgb(b: &mut Bencher) {
278 let left = left_image_rgb(50, 50);
279 let right = right_image_rgb(50, 50);
280
281 b.iter(|| {
282 let error = root_mean_squared_error(&left, &right);
283 test::black_box(error);
284 });
285 }
286
287 fn left_image_gray(width: u32, height: u32) -> GrayImage {
288 GrayImage::from_fn(width, height, |x, _| Luma([x as u8]))
289 }
290
291 fn right_image_gray(width: u32, height: u32) -> GrayImage {
292 GrayImage::from_fn(width, height, |_, y| Luma([y as u8]))
293 }
294
295 #[bench]
296 fn bench_root_mean_squared_error_gray(b: &mut Bencher) {
297 let left = left_image_gray(50, 50);
298 let right = right_image_gray(50, 50);
299
300 b.iter(|| {
301 let error = root_mean_squared_error(&left, &right);
302 test::black_box(error);
303 });
304 }
305}