Skip to main content

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