imageproc/
distance_transform.rs

1//! Functions for computing distance transforms - the distance of each pixel in an
2//! image from the nearest pixel of interest.
3
4use crate::definitions::Image;
5use image::{GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma};
6use std::cmp::min;
7use std::{f64, u8};
8
9/// How to measure distance between coordinates.
10/// See the [`distance_transform`](fn.distance_transform.html) documentation for examples.
11///
12/// Note that this enum doesn't currently include the `L2` norm. As `Norm`
13/// is used by the [`morphology`](../morphology/index.html) functions, this means that we
14/// don't support using the `L2` norm for any of those functions.
15///
16/// This module does support calculating the `L2` distance function, via the
17/// [`euclidean_squared_distance_transform`](fn.euclidean_squared_distance_transform.html)
18/// function, but the signature of this function is not currently compatible with those for
19/// computing `L1` and `LInf` distance transforms. It would be nice to unify these functions
20/// in future.
21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22pub enum Norm {
23    /// Defines d((x1, y1), (x2, y2)) to be abs(x1 - x2) + abs(y1 - y2).
24    /// Also known as the Manhattan or city block norm.
25    L1,
26    /// Defines d((x1, y1), (x2, y2)) to be max(abs(x1 - x2), abs(y1 - y2)).
27    /// Also known as the chessboard norm.
28    LInf,
29}
30
31/// Returns an image showing the distance of each pixel from a foreground pixel in the original image.
32///
33/// A pixel belongs to the foreground if it has non-zero intensity. As the image
34/// has a bit-depth of 8, distances saturate at 255.
35///
36/// # Examples
37/// ```
38/// # extern crate image;
39/// # #[macro_use]
40/// # extern crate imageproc;
41/// # fn main() {
42/// use image::GrayImage;
43/// use imageproc::distance_transform::{distance_transform, Norm};
44///
45/// let image = gray_image!(
46///     0,   0,   0,   0,   0;
47///     0,   0,   0,   0,   0;
48///     0,   0,   1,   0,   0;
49///     0,   0,   0,   0,   0;
50///     0,   0,   0,   0,   0
51/// );
52///
53/// // L1 norm
54/// let l1_distances = gray_image!(
55///     4,   3,   2,   3,   4;
56///     3,   2,   1,   2,   3;
57///     2,   1,   0,   1,   2;
58///     3,   2,   1,   2,   3;
59///     4,   3,   2,   3,   4
60/// );
61///
62/// assert_pixels_eq!(distance_transform(&image, Norm::L1), l1_distances);
63///
64/// // LInf norm
65/// let linf_distances = gray_image!(
66///     2,   2,   2,   2,   2;
67///     2,   1,   1,   1,   2;
68///     2,   1,   0,   1,   2;
69///     2,   1,   1,   1,   2;
70///     2,   2,   2,   2,   2
71/// );
72///
73/// assert_pixels_eq!(distance_transform(&image, Norm::LInf), linf_distances);
74/// # }
75/// ```
76pub fn distance_transform(image: &GrayImage, norm: Norm) -> GrayImage {
77    let mut out = image.clone();
78    distance_transform_mut(&mut out, norm);
79    out
80}
81
82/// Updates an image in place so that each pixel contains its distance from a foreground pixel in the original image.
83///
84/// A pixel belongs to the foreground if it has non-zero intensity. As the image has a bit-depth of 8,
85/// distances saturate at 255.
86///
87/// See the [`distance_transform`](fn.distance_transform.html) documentation for examples.
88pub fn distance_transform_mut(image: &mut GrayImage, norm: Norm) {
89    distance_transform_impl(image, norm, DistanceFrom::Foreground);
90}
91
92#[derive(PartialEq, Eq, Copy, Clone)]
93pub(crate) enum DistanceFrom {
94    Foreground,
95    Background,
96}
97
98pub(crate) fn distance_transform_impl(image: &mut GrayImage, norm: Norm, from: DistanceFrom) {
99    let max_distance = Luma([min(image.width() + image.height(), 255u32) as u8]);
100
101    unsafe {
102        // Top-left to bottom-right
103        for y in 0..image.height() {
104            for x in 0..image.width() {
105                if from == DistanceFrom::Foreground {
106                    if image.unsafe_get_pixel(x, y)[0] > 0u8 {
107                        image.unsafe_put_pixel(x, y, Luma([0u8]));
108                        continue;
109                    }
110                } else if image.unsafe_get_pixel(x, y)[0] == 0u8 {
111                    image.unsafe_put_pixel(x, y, Luma([0u8]));
112                    continue;
113                }
114
115                image.unsafe_put_pixel(x, y, max_distance);
116
117                if x > 0 {
118                    check(image, x, y, x - 1, y);
119                }
120
121                if y > 0 {
122                    check(image, x, y, x, y - 1);
123
124                    if norm == Norm::LInf {
125                        if x > 0 {
126                            check(image, x, y, x - 1, y - 1);
127                        }
128                        if x < image.width() - 1 {
129                            check(image, x, y, x + 1, y - 1);
130                        }
131                    }
132                }
133            }
134        }
135
136        // Bottom-right to top-left
137        for y in (0..image.height()).rev() {
138            for x in (0..image.width()).rev() {
139                if x < image.width() - 1 {
140                    check(image, x, y, x + 1, y);
141                }
142
143                if y < image.height() - 1 {
144                    check(image, x, y, x, y + 1);
145
146                    if norm == Norm::LInf {
147                        if x < image.width() - 1 {
148                            check(image, x, y, x + 1, y + 1);
149                        }
150                        if x > 0 {
151                            check(image, x, y, x - 1, y + 1);
152                        }
153                    }
154                }
155            }
156        }
157    }
158}
159
160// Sets image[current_x, current_y] to min(image[current_x, current_y], image[candidate_x, candidate_y] + 1).
161// We avoid overflow by performing the arithmetic at type u16. We could use u8::saturating_add instead, but
162// (based on the benchmarks tests) this appears to be considerably slower.
163unsafe fn check(
164    image: &mut GrayImage,
165    current_x: u32,
166    current_y: u32,
167    candidate_x: u32,
168    candidate_y: u32,
169) {
170    let current = image.unsafe_get_pixel(current_x, current_y)[0] as u16;
171    let candidate_incr = image.unsafe_get_pixel(candidate_x, candidate_y)[0] as u16 + 1;
172    if candidate_incr < current {
173        image.unsafe_put_pixel(current_x, current_y, Luma([candidate_incr as u8]));
174    }
175}
176
177/// Computes the square of the `L2` (Euclidean) distance transform of `image`. Distances are to the
178/// nearest foreground pixel, where a pixel is counted as foreground if it has non-zero value.
179///
180/// Uses the algorithm from [Distance Transforms of Sampled Functions] to achieve time linear
181/// in the size of the image.
182///
183/// [Distance Transforms of Sampled Functions]: https://www.cs.cornell.edu/~dph/papers/dt.pdf
184pub fn euclidean_squared_distance_transform(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
185    let (width, height) = image.dimensions();
186    let mut result = ImageBuffer::new(width, height);
187    let mut column_envelope = LowerEnvelope::new(height as usize);
188
189    // Compute 1d transforms of each column
190    for x in 0..width {
191        let source = Column { image, column: x };
192        let mut sink = ColumnMut {
193            image: &mut result,
194            column: x,
195        };
196        distance_transform_1d_mut(&source, &mut sink, &mut column_envelope);
197    }
198
199    let mut row_buffer = vec![0f64; width as usize];
200    let mut row_envelope = LowerEnvelope::new(width as usize);
201
202    // Compute 1d transforms of each row
203    for y in 0..height {
204        for x in 0..width {
205            row_buffer[x as usize] = result.get_pixel(x, y)[0];
206        }
207        let mut sink = Row {
208            image: &mut result,
209            row: y,
210        };
211        distance_transform_1d_mut(&row_buffer, &mut sink, &mut row_envelope);
212    }
213
214    result
215}
216
217struct LowerEnvelope {
218    // Indices of the parabolas in the lower envelope.
219    locations: Vec<usize>,
220    // Points at which the parabola in the lower envelope
221    // changes. The parabola centred at locations[i] has the least
222    // values of all parabolas in the lower envelope for all
223    // coordinates in [ boundaries[i], boundaries[i + 1] ).
224    boundaries: Vec<f64>,
225}
226
227impl LowerEnvelope {
228    fn new(image_side: usize) -> LowerEnvelope {
229        LowerEnvelope {
230            locations: vec![0; image_side],
231            boundaries: vec![f64::NAN; image_side + 1],
232        }
233    }
234}
235
236trait Sink {
237    fn put(&mut self, idx: usize, value: f64);
238    fn len(&self) -> usize;
239}
240
241trait Source {
242    fn get(&self, idx: usize) -> f64;
243    fn len(&self) -> usize;
244}
245
246struct Row<'a> {
247    image: &'a mut Image<Luma<f64>>,
248    row: u32,
249}
250
251impl<'a> Sink for Row<'a> {
252    fn put(&mut self, idx: usize, value: f64) {
253        unsafe {
254            self.image
255                .unsafe_put_pixel(idx as u32, self.row, Luma([value]));
256        }
257    }
258    fn len(&self) -> usize {
259        self.image.width() as usize
260    }
261}
262
263struct ColumnMut<'a> {
264    image: &'a mut Image<Luma<f64>>,
265    column: u32,
266}
267
268impl<'a> Sink for ColumnMut<'a> {
269    fn put(&mut self, idx: usize, value: f64) {
270        unsafe {
271            self.image
272                .unsafe_put_pixel(self.column, idx as u32, Luma([value]));
273        }
274    }
275    fn len(&self) -> usize {
276        self.image.height() as usize
277    }
278}
279
280impl Source for Vec<f64> {
281    fn get(&self, idx: usize) -> f64 {
282        self[idx]
283    }
284    fn len(&self) -> usize {
285        self.len()
286    }
287}
288
289impl Source for [f64] {
290    fn get(&self, idx: usize) -> f64 {
291        self[idx]
292    }
293    fn len(&self) -> usize {
294        self.len()
295    }
296}
297
298struct Column<'a> {
299    image: &'a Image<Luma<u8>>,
300    column: u32,
301}
302
303impl<'a> Source for Column<'a> {
304    fn get(&self, idx: usize) -> f64 {
305        let pixel = unsafe { self.image.unsafe_get_pixel(self.column, idx as u32)[0] as f64 };
306        if pixel > 0f64 {
307            0f64
308        } else {
309            f64::INFINITY
310        }
311    }
312    fn len(&self) -> usize {
313        self.image.height() as usize
314    }
315}
316
317fn distance_transform_1d_mut<S, T>(f: &S, result: &mut T, envelope: &mut LowerEnvelope)
318where
319    S: Source,
320    T: Sink,
321{
322    assert!(result.len() == f.len());
323    assert!(envelope.boundaries.len() == f.len() + 1);
324    assert!(envelope.locations.len() == f.len());
325
326    if f.len() == 0 {
327        return;
328    }
329
330    // Index of rightmost parabola in the lower envelope
331    let mut k = 0;
332
333    // First parabola is the best current value as we've not looked
334    // at any other yet
335    envelope.locations[0] = 0;
336
337    // First parabola has the lowest value for all x coordinates
338    envelope.boundaries[0] = f64::NEG_INFINITY;
339    envelope.boundaries[1] = f64::INFINITY;
340
341    for q in 1..f.len() {
342        if f.get(q) == f64::INFINITY {
343            continue;
344        }
345
346        if k == 0 && f.get(envelope.locations[k]) == f64::INFINITY {
347            envelope.locations[k] = q;
348            envelope.boundaries[k] = f64::NEG_INFINITY;
349            envelope.boundaries[k + 1] = f64::INFINITY;
350            continue;
351        }
352
353        // Let p = locations[k], i.e. the centre of the rightmost
354        // parabola in the current approximation to the lower envelope.
355        //
356        // We find the intersection of this parabola with
357        // the parabola centred at q to determine if the latter
358        // is part of the lower envelope (and if the former should
359        // be removed from our current approximation to it).
360        let mut s = intersection(f, envelope.locations[k], q);
361
362        while s <= envelope.boundaries[k] {
363            // The parabola centred at q is the best we've seen for an
364            // intervals that extends past the lower bound of the region
365            // where we believed that the parabola centred at p gave the
366            // least value
367            k -= 1;
368            s = intersection(f, envelope.locations[k], q);
369        }
370
371        k += 1;
372        envelope.locations[k] = q;
373        envelope.boundaries[k] = s;
374        envelope.boundaries[k + 1] = f64::INFINITY;
375    }
376
377    let mut k = 0;
378    for q in 0..f.len() {
379        while envelope.boundaries[k + 1] < q as f64 {
380            k += 1;
381        }
382        let dist = q as f64 - envelope.locations[k] as f64;
383        result.put(q, dist * dist + f.get(envelope.locations[k]));
384    }
385}
386
387/// Returns the intersection of the parabolas f(p) + (x - p) ^ 2 and f(q) + (x - q) ^ 2.
388fn intersection<S: Source + ?Sized>(f: &S, p: usize, q: usize) -> f64 {
389    // The intersection s of the two parabolas satisfies:
390    //
391    // f[q] + (q - s) ^ 2 = f[p] + (s - q) ^ 2
392    //
393    // Rearranging gives:
394    //
395    // s = [( f[q] + q ^ 2 ) - ( f[p] + p ^ 2 )] / (2q - 2p)
396    let fq = f.get(q);
397    let fp = f.get(p);
398    let p = p as f64;
399    let q = q as f64;
400
401    ((fq + q * q) - (fp + p * p)) / (2.0 * q - 2.0 * p)
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407    use crate::definitions::Image;
408    use crate::property_testing::GrayTestImage;
409    use crate::utils::{gray_bench_image, pixel_diff_summary};
410    use image::{GrayImage, Luma};
411    use quickcheck::{quickcheck, TestResult};
412    use std::cmp::max;
413    use std::f64;
414    use test::{black_box, Bencher};
415
416    #[test]
417    fn test_distance_transform_saturation() {
418        // A single foreground pixel in the top-left
419        let image = GrayImage::from_fn(300, 300, |x, y| match (x, y) {
420            (0, 0) => Luma([255u8]),
421            _ => Luma([0u8]),
422        });
423
424        // Distances should not overflow
425        let expected = GrayImage::from_fn(300, 300, |x, y| Luma([min(255, max(x, y)) as u8]));
426
427        let distances = distance_transform(&image, Norm::LInf);
428        assert_pixels_eq!(distances, expected);
429    }
430
431    impl<'a> Sink for Vec<f64> {
432        fn put(&mut self, idx: usize, value: f64) {
433            self[idx] = value;
434        }
435        fn len(&self) -> usize {
436            self.len()
437        }
438    }
439
440    fn distance_transform_1d(f: &Vec<f64>) -> Vec<f64> {
441        let mut r = vec![0.0; f.len()];
442        let mut e = LowerEnvelope::new(f.len());
443        distance_transform_1d_mut(f, &mut r, &mut e);
444        r
445    }
446
447    #[test]
448    fn test_distance_transform_1d_constant() {
449        let f = vec![0.0, 0.0, 0.0];
450        let dists = distance_transform_1d(&f);
451        assert_eq!(dists, &[0.0, 0.0, 0.0]);
452    }
453
454    #[test]
455    fn test_distance_transform_1d_descending_gradient() {
456        let f = vec![7.0, 5.0, 3.0, 1.0];
457        let dists = distance_transform_1d(&f);
458        assert_eq!(dists, &[6.0, 4.0, 2.0, 1.0]);
459    }
460
461    #[test]
462    fn test_distance_transform_1d_ascending_gradient() {
463        let f = vec![1.0, 3.0, 5.0, 7.0];
464        let dists = distance_transform_1d(&f);
465        assert_eq!(dists, &[1.0, 2.0, 4.0, 6.0]);
466    }
467
468    #[test]
469    fn test_distance_transform_1d_with_infinities() {
470        let f = vec![f64::INFINITY, f64::INFINITY, 5.0, f64::INFINITY];
471        let dists = distance_transform_1d(&f);
472        assert_eq!(dists, &[9.0, 6.0, 5.0, 6.0]);
473    }
474
475    // Simple implementation of 1d distance transform which performs an
476    // exhaustive search. Used to valid the more complicated lower-envelope
477    // implementation against.
478    fn distance_transform_1d_reference(f: &[f64]) -> Vec<f64> {
479        let mut ret = vec![0.0; f.len()];
480        for q in 0..f.len() {
481            ret[q] = (0..f.len())
482                .map(|p| {
483                    let dist = p as f64 - q as f64;
484                    dist * dist + f[p]
485                })
486                .fold(0.0 / 0.0, f64::min);
487        }
488        ret
489    }
490
491    #[test]
492    fn test_distance_transform_1d_matches_reference_implementation() {
493        fn prop(f: Vec<f64>) -> bool {
494            let expected = distance_transform_1d_reference(&f);
495            let actual = distance_transform_1d(&f);
496            expected == actual
497        }
498        quickcheck(prop as fn(Vec<f64>) -> bool);
499    }
500
501    fn euclidean_squared_distance_transform_reference(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
502        let (width, height) = image.dimensions();
503
504        let mut dists = Image::new(width, height);
505
506        for y in 0..height {
507            for x in 0..width {
508                let mut min = f64::INFINITY;
509                for yc in 0..height {
510                    for xc in 0..width {
511                        let pc = image.get_pixel(xc, yc)[0];
512                        if pc > 0 {
513                            let dx = xc as f64 - x as f64;
514                            let dy = yc as f64 - y as f64;
515
516                            min = f64::min(min, dx * dx + dy * dy);
517                        }
518                    }
519                }
520
521                dists.put_pixel(x, y, Luma([min]));
522            }
523        }
524
525        dists
526    }
527
528    #[test]
529    fn test_euclidean_squared_distance_transform_matches_reference_implementation() {
530        fn prop(image: GrayTestImage) -> TestResult {
531            let expected = euclidean_squared_distance_transform_reference(&image.0);
532            let actual = euclidean_squared_distance_transform(&image.0);
533            match pixel_diff_summary(&actual, &expected) {
534                None => TestResult::passed(),
535                Some(err) => TestResult::error(err),
536            }
537        }
538        quickcheck(prop as fn(GrayTestImage) -> TestResult);
539    }
540
541    #[test]
542    fn test_euclidean_squared_distance_transform_example() {
543        let image = gray_image!(
544            1, 0, 0, 0, 0;
545            0, 1, 0, 0, 0;
546            1, 1, 1, 0, 0;
547            0, 0, 0, 0, 0;
548            0, 0, 1, 0, 0
549        );
550
551        let expected = gray_image!(type: f64,
552            0.0, 1.0, 2.0, 5.0, 8.0;
553            1.0, 0.0, 1.0, 2.0, 5.0;
554            0.0, 0.0, 0.0, 1.0, 4.0;
555            1.0, 1.0, 1.0, 2.0, 5.0;
556            4.0, 1.0, 0.0, 1.0, 4.0
557        );
558
559        let dist = euclidean_squared_distance_transform(&image);
560        assert_pixels_eq_within!(dist, expected, 1e-6);
561    }
562
563    macro_rules! bench_euclidean_squared_distance_transform {
564        ($name:ident, side: $s:expr) => {
565            #[bench]
566            fn $name(b: &mut Bencher) {
567                let image = gray_bench_image($s, $s);
568                b.iter(|| {
569                    let distance = euclidean_squared_distance_transform(&image);
570                    black_box(distance);
571                })
572            }
573        };
574    }
575
576    bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_10, side: 10);
577    bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_100, side: 100);
578    bench_euclidean_squared_distance_transform!(bench_euclidean_squared_distance_transform_200, side: 200);
579
580    macro_rules! bench_distance_transform {
581        ($name:ident, $norm:expr, side: $s:expr) => {
582            #[bench]
583            fn $name(b: &mut Bencher) {
584                let image = gray_bench_image($s, $s);
585                b.iter(|| {
586                    let distance = distance_transform(&image, $norm);
587                    black_box(distance);
588                })
589            }
590        };
591    }
592
593    bench_distance_transform!(bench_distance_transform_l1_10, Norm::L1, side: 10);
594    bench_distance_transform!(bench_distance_transform_l1_100, Norm::L1, side: 100);
595    bench_distance_transform!(bench_distance_transform_l1_200, Norm::L1, side: 200);
596    bench_distance_transform!(bench_distance_transform_linf_10, Norm::LInf, side: 10);
597    bench_distance_transform!(bench_distance_transform_linf_100, Norm::LInf, side: 100);
598    bench_distance_transform!(bench_distance_transform_linf_200, Norm::LInf, side: 200);
599}