1use crate::definitions::{Position, Score};
4use image::{GenericImage, ImageBuffer, Luma, Primitive};
5use std::cmp;
6
7pub fn suppress_non_maximum<I, C>(image: &I, radius: u32) -> ImageBuffer<Luma<C>, Vec<C>>
11where
12 I: GenericImage<Pixel = Luma<C>>,
13 C: Primitive + Ord,
14{
15 let (width, height) = image.dimensions();
16 let mut out: ImageBuffer<Luma<C>, Vec<C>> = ImageBuffer::new(width, height);
17 if width == 0 || height == 0 {
18 return out;
19 }
20
21 for y in (0..height).step_by(radius as usize + 1) {
29 for x in (0..width).step_by(radius as usize + 1) {
30 let mut best_x = x;
31 let mut best_y = y;
32 let mut mi = image.get_pixel(x, y)[0];
33
34 for cy in y..cmp::min(height, y + radius + 1) {
36 for cx in x..cmp::min(width, x + radius + 1) {
37 let ci = unsafe { image.unsafe_get_pixel(cx, cy)[0] };
38 if ci < mi {
39 continue;
40 }
41 if ci > mi || (cx, cy) < (best_x, best_y) {
42 best_x = cx;
43 best_y = cy;
44 mi = ci;
45 }
46 }
47 }
48
49 let x0 = if radius >= best_x { 0 } else { best_x - radius };
50 let x1 = x;
51 let x2 = cmp::min(width, x + radius + 1);
52 let x3 = cmp::min(width, best_x + radius + 1);
53
54 let y0 = if radius >= best_y { 0 } else { best_y - radius };
55 let y1 = y;
56 let y2 = cmp::min(height, y + radius + 1);
57 let y3 = cmp::min(height, best_y + radius + 1);
58
59 let mut failed = contains_greater_value(image, best_x, best_y, mi, y0, y1, x0, x3);
61 failed |= contains_greater_value(image, best_x, best_y, mi, y1, y2, x0, x1);
63 failed |= contains_greater_value(image, best_x, best_y, mi, y1, y2, x2, x3);
65 failed |= contains_greater_value(image, best_x, best_y, mi, y2, y3, x0, x3);
67
68 if !failed {
69 unsafe { out.unsafe_put_pixel(best_x, best_y, Luma([mi])) };
70 }
71 }
72 }
73
74 out
75}
76
77fn contains_greater_value<I, C>(
81 image: &I,
82 x: u32,
83 y: u32,
84 v: C,
85 y_lower: u32,
86 y_upper: u32,
87 x_lower: u32,
88 x_upper: u32,
89) -> bool
90where
91 I: GenericImage<Pixel = Luma<C>>,
92 C: Primitive + Ord,
93{
94 for cy in y_lower..y_upper {
95 for cx in x_lower..x_upper {
96 let ci = unsafe { image.unsafe_get_pixel(cx, cy)[0] };
97 if ci < v {
98 continue;
99 }
100 if ci > v || (cx, cy) < (x, y) {
101 return true;
102 }
103 }
104 }
105 false
106}
107
108pub fn local_maxima<T>(ts: &[T], radius: u32) -> Vec<T>
111where
112 T: Position + Score + Copy,
113{
114 let mut ordered_ts = ts.to_vec();
115 ordered_ts.sort_by_key(|&c| (c.y(), c.x()));
116 let height = match ordered_ts.last() {
117 Some(t) => t.y(),
118 None => 0,
119 };
120
121 let mut ts_by_row = vec![vec![]; (height + 1) as usize];
122 for t in &ordered_ts {
123 ts_by_row[t.y() as usize].push(t);
124 }
125
126 let mut max_ts = vec![];
127 for t in &ordered_ts {
128 let cx = t.x();
129 let cy = t.y();
130 let cs = t.score();
131
132 let mut is_max = true;
133 let row_lower = if radius > cy { 0 } else { cy - radius };
134 let row_upper = if cy + radius + 1 > height {
135 height
136 } else {
137 cy + radius + 1
138 };
139 for y in row_lower..row_upper {
140 for c in &ts_by_row[y as usize] {
141 if c.x() + radius < cx {
142 continue;
143 }
144 if c.x() > cx + radius {
145 break;
146 }
147 if c.score() > cs {
148 is_max = false;
149 break;
150 }
151 if c.score() < cs {
152 continue;
153 }
154 if (c.y(), c.x()) < (cy, cx) {
156 is_max = false;
157 break;
158 }
159 }
160 if !is_max {
161 break;
162 }
163 }
164
165 if is_max {
166 max_ts.push(*t);
167 }
168 }
169
170 max_ts
171}
172
173#[cfg(test)]
174mod tests {
175 use super::{local_maxima, suppress_non_maximum};
176 use crate::definitions::{Position, Score};
177 use crate::noise::gaussian_noise_mut;
178 use crate::property_testing::GrayTestImage;
179 use crate::utils::pixel_diff_summary;
180 use image::{GenericImage, GrayImage, ImageBuffer, Luma, Primitive};
181 use quickcheck::{quickcheck, TestResult};
182 use std::cmp;
183 use test::Bencher;
184
185 #[derive(PartialEq, Debug, Copy, Clone)]
186 struct T {
187 x: u32,
188 y: u32,
189 score: f32,
190 }
191
192 impl T {
193 fn new(x: u32, y: u32, score: f32) -> T {
194 T { x, y, score }
195 }
196 }
197
198 impl Position for T {
199 fn x(&self) -> u32 {
200 self.x
201 }
202 fn y(&self) -> u32 {
203 self.y
204 }
205 }
206
207 impl Score for T {
208 fn score(&self) -> f32 {
209 self.score
210 }
211 }
212
213 #[test]
214 fn test_local_maxima() {
215 let ts = vec![
216 T::new(0, 0, 8f32),
218 T::new(0, 3, 10f32),
219 T::new(0, 6, 9f32),
220 T::new(5, 5, 10f32),
222 T::new(7, 5, 15f32),
223 T::new(12, 20, 10f32),
225 T::new(13, 20, 10f32),
226 T::new(13, 21, 10f32),
227 ];
228
229 let expected = vec![
230 T::new(0, 3, 10f32),
231 T::new(7, 5, 15f32),
232 T::new(12, 20, 10f32),
233 ];
234
235 let max = local_maxima(&ts, 3);
236 assert_eq!(max, expected);
237 }
238
239 #[bench]
240 fn bench_local_maxima_dense(b: &mut Bencher) {
241 let mut ts = vec![];
242 for x in 0..20 {
243 for y in 0..20 {
244 let score = (x * y) % 15;
245 ts.push(T::new(x, y, score as f32));
246 }
247 }
248 b.iter(|| local_maxima(&ts, 15));
249 }
250
251 #[bench]
252 fn bench_local_maxima_sparse(b: &mut Bencher) {
253 let mut ts = vec![];
254 for x in 0..20 {
255 for y in 0..20 {
256 ts.push(T::new(50 * x, 50 * y, 50f32));
257 }
258 }
259 b.iter(|| local_maxima(&ts, 15));
260 }
261
262 #[test]
263 fn test_suppress_non_maximum() {
264 let mut image = GrayImage::new(25, 25);
265 image.put_pixel(0, 0, Luma([8u8]));
267 image.put_pixel(0, 3, Luma([10u8]));
268 image.put_pixel(0, 6, Luma([9u8]));
269 image.put_pixel(5, 5, Luma([10u8]));
271 image.put_pixel(7, 5, Luma([15u8]));
272 image.put_pixel(12, 20, Luma([10u8]));
274 image.put_pixel(13, 20, Luma([10u8]));
275 image.put_pixel(13, 21, Luma([10u8]));
276
277 let mut expected = GrayImage::new(25, 25);
278 expected.put_pixel(0, 3, Luma([10u8]));
279 expected.put_pixel(7, 5, Luma([15u8]));
280 expected.put_pixel(12, 20, Luma([10u8]));
281
282 let actual = suppress_non_maximum(&image, 3);
283 assert_pixels_eq!(actual, expected);
284 }
285
286 #[test]
287 fn test_suppress_non_maximum_handles_radius_greater_than_image_side() {
288 let image = GrayImage::new(7, 3);
291 let r = suppress_non_maximum(&image, 5);
292 let image = GrayImage::new(3, 7);
293 let s = suppress_non_maximum(&image, 5);
294 assert!(r.width() == 7);
296 assert!(s.width() == 3);
297 }
298
299 #[bench]
300 fn bench_suppress_non_maximum_increasing_gradient(b: &mut Bencher) {
301 let img = ImageBuffer::from_fn(40, 20, |x, y| Luma([(x + y) as u8]));
304 b.iter(|| suppress_non_maximum(&img, 7));
305 }
306
307 #[bench]
308 fn bench_suppress_non_maximum_decreasing_gradient(b: &mut Bencher) {
309 let width = 40u32;
310 let height = 20u32;
311 let img = ImageBuffer::from_fn(width, height, |x, y| {
312 Luma([((width - x) + (height - y)) as u8])
313 });
314 b.iter(|| suppress_non_maximum(&img, 7));
315 }
316
317 #[bench]
318 fn bench_suppress_non_maximum_noise_7(b: &mut Bencher) {
319 let mut img: GrayImage = ImageBuffer::new(40, 20);
320 gaussian_noise_mut(&mut img, 128f64, 30f64, 1);
321 b.iter(|| suppress_non_maximum(&img, 7));
322 }
323
324 #[bench]
325 fn bench_suppress_non_maximum_noise_3(b: &mut Bencher) {
326 let mut img: GrayImage = ImageBuffer::new(40, 20);
327 gaussian_noise_mut(&mut img, 128f64, 30f64, 1);
328 b.iter(|| suppress_non_maximum(&img, 3));
329 }
330
331 #[bench]
332 fn bench_suppress_non_maximum_noise_1(b: &mut Bencher) {
333 let mut img: GrayImage = ImageBuffer::new(40, 20);
334 gaussian_noise_mut(&mut img, 128f64, 30f64, 1);
335 b.iter(|| suppress_non_maximum(&img, 1));
336 }
337
338 fn suppress_non_maximum_reference<I, C>(image: &I, radius: u32) -> ImageBuffer<Luma<C>, Vec<C>>
341 where
342 I: GenericImage<Pixel = Luma<C>>,
343 C: Primitive + Ord,
344 {
345 let (width, height) = image.dimensions();
346 let mut out = ImageBuffer::new(width, height);
347 out.copy_from(image, 0, 0).unwrap();
348
349 let iradius = radius as i32;
350 let iheight = height as i32;
351 let iwidth = width as i32;
352
353 for y in 0..height {
356 for x in 0..width {
357 let intensity = image.get_pixel(x, y)[0];
358 let mut is_max = true;
359
360 let y_lower = cmp::max(0, y as i32 - iradius);
361 let y_upper = cmp::min(y as i32 + iradius + 1, iheight);
362 let x_lower = cmp::max(0, x as i32 - iradius);
363 let x_upper = cmp::min(x as i32 + iradius + 1, iwidth);
364
365 for py in y_lower..y_upper {
366 for px in x_lower..x_upper {
367 let v = image.get_pixel(px as u32, py as u32)[0];
368 let candidate_is_lexically_earlier = (px as u32, py as u32) < (x, y);
370 if v > intensity || (v == intensity && candidate_is_lexically_earlier) {
371 is_max = false;
372 break;
373 }
374 }
375 }
376
377 if !is_max {
378 out.put_pixel(x, y, Luma([C::zero()]));
379 }
380 }
381 }
382
383 out
384 }
385
386 #[test]
387 fn test_suppress_non_maximum_matches_reference_implementation() {
388 fn prop(image: GrayTestImage) -> TestResult {
389 let expected = suppress_non_maximum_reference(&image.0, 3);
390 let actual = suppress_non_maximum(&image.0, 3);
391 match pixel_diff_summary(&actual, &expected) {
392 None => TestResult::passed(),
393 Some(err) => TestResult::error(err),
394 }
395 }
396 quickcheck(prop as fn(GrayTestImage) -> TestResult);
397 }
398
399 #[test]
400 fn test_step() {
401 assert_eq!((0u32..5).step_by(4).collect::<Vec<u32>>(), vec![0, 4]);
402 assert_eq!((0u32..4).step_by(4).collect::<Vec<u32>>(), vec![0]);
403 assert_eq!((4u32..4).step_by(4).collect::<Vec<u32>>(), vec![]);
404 }
405}