From 068293962da3d5dc271534f21d98855dcfc8b6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 9 Oct 2012 11:00:39 +0200 Subject: [PATCH 01/11] Add hough transform peak detection --- skimage/transform/hough_transform.py | 106 +++++++++++++++++- .../transform/tests/test_hough_transform.py | 29 +++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 4e3acd6e..71454a21 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -1,4 +1,4 @@ -__all__ = ['hough', 'probabilistic_hough'] +__all__ = ['hough', 'hough_peaks', 'probabilistic_hough'] from itertools import izip as zip @@ -135,3 +135,107 @@ def hough(img, theta=None): """ return _hough(img, theta) + + +def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, + threshold_abs=0, threshold_rel=0.5, num_peaks=np.inf): + """Return peaks in hough transform. + + Identifies most prominent lines separated by a certain angle and distance in + a hough transform. Non-maximum suppresion with different sizes is applied + separately in the first (distances) and second (angles) dimension of the + hough space to identify peaks. + + Parameters + ---------- + hspace : (N, M) array + Hough space returned by the `hough` function. + angles : (M,) array + Angles returned by the `hough` function. + dists : (N, ) array + Distances returned by the `hough` function. + min_distance : int + Minimum distance separating lines (maximum filter size for first + dimension of hough space). + min_angle : int + Minimum angle separating lines (maximum filter size for second + dimension of hough space). + threshold_abs : float + Minimum intensity of peaks in hough space. + threshold_rel : float + Minimum intensity of peaks calculated as `max(hspace) * threshold_rel`. + num_peaks : int + Maximum number of peaks. When the number of peaks exceeds `num_peaks`, + return `num_peaks` coordinates based on peak intensity. + + Returns + ------- + hspace, angles, dists : tuple of array + Peak values in hough space, angles and distances. + + Examples + -------- + >>> import numpy as np + >>> from skimage.transform import hough, hough_peaks + >>> from skimage.draw import line + >>> img = np.zeros((15, 15), dtype=np.bool_) + >>> rr, cc = line(0, 0, 14, 14) + >>> img[rr, cc] = 1 + >>> rr, cc = line(0, 14, 14, 0) + >>> img[cc, rr] = 1 + >>> hspace, angles, dists = hough(img) + >>> hspace, angles, dists = hough_peaks(hspace, angles, dists) + >>> angles + array([ 0.74590887, -0.79856126]) + >>> dists + array([ 10.74418605, 0.51162791]) + + """ + + hspace = hspace.copy() + rows, cols = hspace.shape + + threshold = max(threshold_abs, threshold_rel * np.max(hspace)) + + # sort accumulators from large to small + hspace_max = np.argsort(hspace.flat)[::-1] + hspace_max = np.column_stack(np.unravel_index(hspace_max, hspace.shape)) + + hspace_peaks = [] + dist_peaks = [] + angle_peaks = [] + + # relative coordinate grid for local neighbourhood suppresion + dist_ext, angle_ext = np.mgrid[- min_distance:min_distance + 1, + - min_angle:min_angle + 1] + + for dist_idx, angle_idx in hspace_max: + accum = hspace[dist_idx, angle_idx] + if accum > threshold: + # absolute coordinate grid for local neighbourhood suppresion + dist_nh = dist_idx + dist_ext + angle_nh = angle_idx + angle_ext + + # no reflection for distance neighbourhood + dist_in = np.logical_and(dist_nh > 0, dist_nh < rows) + dist_nh = dist_nh[dist_in] + angle_nh = angle_nh[dist_in] + + # reflect angles and assume angles are continuous, e.g. + # (..., 88, 89, -90, -89, ..., 89, -90, -89, ...) + angle_low = angle_nh < 0 + dist_nh[angle_low] = rows - dist_nh[angle_low] + angle_nh[angle_low] += cols + angle_high = angle_nh >= cols + dist_nh[angle_high] = rows - dist_nh[angle_high] + angle_nh[angle_high] -= cols + + # suppress neighbourhood + hspace[dist_nh, angle_nh] = 0 + + # add current line to peaks + hspace_peaks.append(accum) + dist_peaks.append(dists[dist_idx]) + angle_peaks.append(angles[angle_idx]) + + return np.array(hspace_peaks), np.array(dist_peaks), np.array(angle_peaks) diff --git a/skimage/transform/tests/test_hough_transform.py b/skimage/transform/tests/test_hough_transform.py index 03b63d07..3fb0a6f3 100644 --- a/skimage/transform/tests/test_hough_transform.py +++ b/skimage/transform/tests/test_hough_transform.py @@ -72,5 +72,34 @@ def test_probabilistic_hough(): assert([(25, 25), (74, 74)] in sorted_lines) +def test_hough_peaks_dist(): + img = np.zeros((100, 100), dtype=np.bool_) + img[:, 30] = True + img[:, 40] = True + hspace, angles, dists = tf.hough(img) + assert len(tf.hough_peaks(hspace, angles, dists, min_distance=5)[0]) == 2 + assert len(tf.hough_peaks(hspace, angles, dists, min_distance=15)[0]) == 1 + + +def test_hough_peaks_angle(): + img = np.zeros((100, 100), dtype=np.bool_) + img[:, 0] = True + img[0, :] = True + + hspace, angles, dists = tf.hough(img) + assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 + assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 + + theta = np.linspace(0, np.pi, 100) + hspace, angles, dists = tf.hough(img, theta) + assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 + assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 + + theta = np.linspace(np.pi / 3, 4. / 3 * np.pi, 100) + hspace, angles, dists = tf.hough(img, theta) + assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 + assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 + + if __name__ == "__main__": run_module_suite() From f4bdd94452f9dbf204a85c9d01e0bfcf5dc43e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 9 Oct 2012 11:07:35 +0200 Subject: [PATCH 02/11] Add note about range of angle values --- skimage/transform/hough_transform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 71454a21..d4ba37b9 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -151,7 +151,8 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, hspace : (N, M) array Hough space returned by the `hough` function. angles : (M,) array - Angles returned by the `hough` function. + Angles returned by the `hough` function. Assumed to be continuous + (`angles[-1] - angles[0] == PI`). dists : (N, ) array Distances returned by the `hough` function. min_distance : int From 5fbdd917126bcd6ad86ab0e8b7ae8521450751bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 10 Oct 2012 09:15:22 +0200 Subject: [PATCH 03/11] Fix typo --- skimage/transform/hough_transform.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index d4ba37b9..36693f14 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -142,7 +142,7 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, """Return peaks in hough transform. Identifies most prominent lines separated by a certain angle and distance in - a hough transform. Non-maximum suppresion with different sizes is applied + a hough transform. Non-maximum suppression with different sizes is applied separately in the first (distances) and second (angles) dimension of the hough space to identify peaks. @@ -206,14 +206,14 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, dist_peaks = [] angle_peaks = [] - # relative coordinate grid for local neighbourhood suppresion + # relative coordinate grid for local neighbourhood suppression dist_ext, angle_ext = np.mgrid[- min_distance:min_distance + 1, - min_angle:min_angle + 1] for dist_idx, angle_idx in hspace_max: accum = hspace[dist_idx, angle_idx] if accum > threshold: - # absolute coordinate grid for local neighbourhood suppresion + # absolute coordinate grid for local neighbourhood suppression dist_nh = dist_idx + dist_ext angle_nh = angle_idx + angle_ext From d9a7bac999f475916b32eee93f165251f10c6962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 10 Oct 2012 09:21:37 +0200 Subject: [PATCH 04/11] Refactor non maximum suppression for faster computation --- skimage/transform/hough_transform.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 36693f14..0d47a376 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -3,6 +3,7 @@ __all__ = ['hough', 'hough_peaks', 'probabilistic_hough'] from itertools import izip as zip import numpy as np +from scipy import ndimage from ._hough_transform import _probabilistic_hough @@ -198,9 +199,17 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, threshold = max(threshold_abs, threshold_rel * np.max(hspace)) - # sort accumulators from large to small - hspace_max = np.argsort(hspace.flat)[::-1] - hspace_max = np.column_stack(np.unravel_index(hspace_max, hspace.shape)) + distance_size = 2 * min_distance + 1 + angle_size = 2 * min_angle + 1 + hspace_max = ndimage.maximum_filter1d(hspace, size=distance_size, axis=0, + mode='constant') + hspace_max = ndimage.maximum_filter1d(hspace_max, size=angle_size, axis=1, + mode='constant') + mask = (hspace == hspace_max) + hspace *= mask + hspace_t = hspace > threshold + + coords = np.transpose(hspace_t.nonzero()) hspace_peaks = [] dist_peaks = [] @@ -210,7 +219,7 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, dist_ext, angle_ext = np.mgrid[- min_distance:min_distance + 1, - min_angle:min_angle + 1] - for dist_idx, angle_idx in hspace_max: + for dist_idx, angle_idx in coords: accum = hspace[dist_idx, angle_idx] if accum > threshold: # absolute coordinate grid for local neighbourhood suppression From ede5df6c7030cc5acca7f09c3030efc08ef4db38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 10 Oct 2012 09:28:47 +0200 Subject: [PATCH 05/11] Add missing support for num_peaks parameter --- skimage/transform/hough_transform.py | 12 +++++++++++- skimage/transform/tests/test_hough_transform.py | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 0d47a376..9a37cf2e 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -248,4 +248,14 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, dist_peaks.append(dists[dist_idx]) angle_peaks.append(angles[angle_idx]) - return np.array(hspace_peaks), np.array(dist_peaks), np.array(angle_peaks) + hspace_peaks = np.array(hspace_peaks) + dist_peaks = np.array(dist_peaks) + angle_peaks = np.array(angle_peaks) + + if num_peaks < len(hspace_peaks): + idx_maxsort = np.argsort(hspace_peaks)[::-1][:num_peaks] + hspace_peaks = hspace_peaks[idx_maxsort] + dist_peaks = dist_peaks[idx_maxsort] + angle_peaks = angle_peaks[idx_maxsort] + + return hspace_peaks, dist_peaks, angle_peaks diff --git a/skimage/transform/tests/test_hough_transform.py b/skimage/transform/tests/test_hough_transform.py index 3fb0a6f3..a38e6e76 100644 --- a/skimage/transform/tests/test_hough_transform.py +++ b/skimage/transform/tests/test_hough_transform.py @@ -101,5 +101,14 @@ def test_hough_peaks_angle(): assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 +def test_hough_peaks_num(): + img = np.zeros((100, 100), dtype=np.bool_) + img[:, 30] = True + img[:, 40] = True + hspace, angles, dists = tf.hough(img) + assert len(tf.hough_peaks(hspace, angles, dists, min_distance=0, + min_angle=0, num_peaks=1)[0]) == 1 + + if __name__ == "__main__": run_module_suite() From ee098224ebcb7b83e6384d2b73b6f6b2180e4b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 10 Oct 2012 13:05:24 +0200 Subject: [PATCH 06/11] Set constant value for maximum filter explicitly --- skimage/transform/hough_transform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 9a37cf2e..9b5c1050 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -202,9 +202,9 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, distance_size = 2 * min_distance + 1 angle_size = 2 * min_angle + 1 hspace_max = ndimage.maximum_filter1d(hspace, size=distance_size, axis=0, - mode='constant') + mode='constant', cval=0) hspace_max = ndimage.maximum_filter1d(hspace_max, size=angle_size, axis=1, - mode='constant') + mode='constant', cval=0) mask = (hspace == hspace_max) hspace *= mask hspace_t = hspace > threshold From 1221a98a4252bdc09146d6b301803610519c6194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 31 Oct 2012 09:24:09 +0100 Subject: [PATCH 07/11] Fix ordering of return values --- skimage/transform/hough_transform.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 9b5c1050..a5987d5d 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -216,8 +216,8 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, angle_peaks = [] # relative coordinate grid for local neighbourhood suppression - dist_ext, angle_ext = np.mgrid[- min_distance:min_distance + 1, - - min_angle:min_angle + 1] + dist_ext, angle_ext = np.mgrid[-min_distance:min_distance + 1, + -min_angle:min_angle + 1] for dist_idx, angle_idx in coords: accum = hspace[dist_idx, angle_idx] @@ -258,4 +258,4 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, dist_peaks = dist_peaks[idx_maxsort] angle_peaks = angle_peaks[idx_maxsort] - return hspace_peaks, dist_peaks, angle_peaks + return hspace_peaks, angle_peaks, dist_peaks From 5be7f628138602e2692f526730402321316a6227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 31 Oct 2012 09:26:47 +0100 Subject: [PATCH 08/11] Fix hough transform return value ordering in doc string --- skimage/transform/hough_transform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index a5987d5d..5e77954d 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -111,10 +111,10 @@ def hough(img, theta=None): ------- H : 2-D ndarray of uint64 Hough transform accumulator. - distances : ndarray - Distance values. theta : ndarray Angles at which the transform was computed. + distances : ndarray + Distance values. Examples -------- From 531060ec2054f06958f66c90eb37e4c2fdffe95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Thu, 1 Nov 2012 15:01:14 +0100 Subject: [PATCH 09/11] Reduce to one threshold paramater --- skimage/transform/hough_transform.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 5e77954d..3c27e346 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -139,7 +139,7 @@ def hough(img, theta=None): def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, - threshold_abs=0, threshold_rel=0.5, num_peaks=np.inf): + threshold=None, num_peaks=np.inf): """Return peaks in hough transform. Identifies most prominent lines separated by a certain angle and distance in @@ -162,10 +162,8 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, min_angle : int Minimum angle separating lines (maximum filter size for second dimension of hough space). - threshold_abs : float - Minimum intensity of peaks in hough space. - threshold_rel : float - Minimum intensity of peaks calculated as `max(hspace) * threshold_rel`. + threshold : float + Minimum intensity of peaks calculated as `0.5 * max(hspace)`. num_peaks : int Maximum number of peaks. When the number of peaks exceeds `num_peaks`, return `num_peaks` coordinates based on peak intensity. @@ -197,7 +195,8 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, hspace = hspace.copy() rows, cols = hspace.shape - threshold = max(threshold_abs, threshold_rel * np.max(hspace)) + if threshold is None: + threshold = 0.5 * np.max(hspace) distance_size = 2 * min_distance + 1 angle_size = 2 * min_angle + 1 From 692cfe7e8f88f95520e3486a01d4e09d9fe0b97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Thu, 8 Nov 2012 16:56:24 +0100 Subject: [PATCH 10/11] Use center of mass for peaks with equal accumulator values --- skimage/transform/hough_transform.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 3c27e346..79facb62 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -5,6 +5,7 @@ from itertools import izip as zip import numpy as np from scipy import ndimage from ._hough_transform import _probabilistic_hough +from skimage import measure, morphology def _hough(img, theta=None): @@ -208,7 +209,9 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, hspace *= mask hspace_t = hspace > threshold - coords = np.transpose(hspace_t.nonzero()) + label_hspace = morphology.label(hspace_t) + props = measure.regionprops(label_hspace, ['Centroid']) + coords = np.array([np.round(p['Centroid']) for p in props], dtype=int) hspace_peaks = [] dist_peaks = [] From 6df06b46dd793159f90b7003ef711f6add00e348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Thu, 8 Nov 2012 17:14:30 +0100 Subject: [PATCH 11/11] Fix threshold default value description --- skimage/transform/hough_transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 79facb62..05d5cfbe 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -164,7 +164,7 @@ def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, Minimum angle separating lines (maximum filter size for second dimension of hough space). threshold : float - Minimum intensity of peaks calculated as `0.5 * max(hspace)`. + Minimum intensity of peaks. Default is `0.5 * max(hspace)`. num_peaks : int Maximum number of peaks. When the number of peaks exceeds `num_peaks`, return `num_peaks` coordinates based on peak intensity.