From 89dbb4dccb93af5ebbfb669474b06d5bc14731be Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 5 Aug 2013 09:26:36 +1000 Subject: [PATCH 01/11] Add unique_rows function --- skimage/util/unique.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 skimage/util/unique.py diff --git a/skimage/util/unique.py b/skimage/util/unique.py new file mode 100644 index 00000000..a09fadd9 --- /dev/null +++ b/skimage/util/unique.py @@ -0,0 +1,36 @@ +import numpy as np + + +def unique_rows(ar): + """Remove repeated rows from a 2D array. + + Parameters + ---------- + ar : 2D np.ndarray + The input array. + + Returns + ------- + ar_out : 2D np.ndarray + A copy of the input array with repeated rows removed. + + Raises + ------ + ValueError : if `ar` is not two-dimensional. + + Examples + -------- + >>> ar = np.array([[1, 0, 1], + [0, 1, 0], + [1, 0, 1]], np.uint8) + >>> aru = unique_rows(ar) + array([[0, 1, 0], + [1, 0, 1]], dtype=uint8) + """ + if ar.ndim != 2: + raise ValueError("unique_rows() only makes sense for 2D arrays, " + "got %dd" % ar.ndim) + ar_row_view = ar.view('|S%d' % (ar.itemsize * ar.shape[1])) + _, unique_row_indices = np.unique(ar_row_view, return_index=True) + ar_out = ar[unique_row_indices] + return ar_out From 11f9a5bdbd601b600c08816806de3813b1a10aca Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 5 Aug 2013 09:35:50 +1000 Subject: [PATCH 02/11] Add test for unique_rows --- skimage/util/tests/test_unique_rows.py | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 skimage/util/tests/test_unique_rows.py diff --git a/skimage/util/tests/test_unique_rows.py b/skimage/util/tests/test_unique_rows.py new file mode 100644 index 00000000..cfbaaf05 --- /dev/null +++ b/skimage/util/tests/test_unique_rows.py @@ -0,0 +1,32 @@ +import numpy as np +from numpy.testing import assert_equal, assert_raises +from skimage.util import unique_rows + + +def test_uint8_array(): + ar = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], np.uint8) + ar_out = unique_rows(ar) + desired_ar_out = np.array([[0, 1, 0], [1, 0, 1]], np.uint8) + assert_equal(ar_out, desired_ar_out) + + +def test_float_array(): + ar = np.array([[1.1, 0.0, 1.1], [0.0, 1.1, 0.0], [1.1, 0.0, 1.1]], + np.float) + ar_out = unique_rows(ar) + desired_ar_out = np.array([[0.0, 1.1, 0.0], [1.1, 0.0, 1.1]], np.float) + assert_equal(ar_out, desired_ar_out) + + +def test_1d_array(): + ar = np.array([1, 0, 1, 1], np.uint8) + assert_raises(ValueError, unique_rows, ar) + + +def test_3d_array(): + ar = np.arange(8).reshape((2, 2, 2)) + assert_raises(ValueError, unique_rows, ar) + + +if __name__ == '__main__': + np.testing.run_module_suite() From 640c49608def953f377c5f25f810056de360c3cc Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 5 Aug 2013 09:40:10 +1000 Subject: [PATCH 03/11] Add unique_rows to util/__init__.py --- skimage/util/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/skimage/util/__init__.py b/skimage/util/__init__.py index 7afcf54a..9cd2bc50 100644 --- a/skimage/util/__init__.py +++ b/skimage/util/__init__.py @@ -12,6 +12,7 @@ else: from numpy import pad del numpy, ver, chk from ._regular_grid import regular_grid +from .unique import unique_rows __all__ = ['img_as_float', @@ -24,4 +25,5 @@ __all__ = ['img_as_float', 'view_as_windows', 'pad', 'random_noise', - 'regular_grid'] + 'regular_grid', + 'unique_rows'] From a6ec1741dcbe3119e0ab8d45dd24ddc1e409cfa1 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 5 Aug 2013 09:42:23 +1000 Subject: [PATCH 04/11] Remove repeated coordinates before computing convex hull --- skimage/morphology/convex_hull.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py index 28ff204b..b045d7f3 100644 --- a/skimage/morphology/convex_hull.py +++ b/skimage/morphology/convex_hull.py @@ -4,6 +4,7 @@ import numpy as np from ._pnpoly import grid_points_inside_poly from ._convex_hull import possible_hull from skimage.morphology import label +from skimage.util import unique_rows def convex_hull_image(image): @@ -35,6 +36,9 @@ def convex_hull_image(image): # limits the number of coordinates to examine for the virtual # hull. coords = possible_hull(image.astype(np.uint8)) + # repeated coordinates can *sometimes* cause problems in + # scipy.spatial.Delaunay, so we remove them. + coords = unique_rows(coords) N = len(coords) # Add a vertex for the middle of each pixel edge From b03fa9247859c678d451429d5a42dd9181536573 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 5 Aug 2013 10:09:37 +1000 Subject: [PATCH 05/11] Unique rows after getting the pixel corners --- skimage/morphology/convex_hull.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py index b045d7f3..565b10ab 100644 --- a/skimage/morphology/convex_hull.py +++ b/skimage/morphology/convex_hull.py @@ -38,7 +38,6 @@ def convex_hull_image(image): coords = possible_hull(image.astype(np.uint8)) # repeated coordinates can *sometimes* cause problems in # scipy.spatial.Delaunay, so we remove them. - coords = unique_rows(coords) N = len(coords) # Add a vertex for the middle of each pixel edge @@ -47,7 +46,7 @@ def convex_hull_image(image): (-0.5, 0.5, 0, 0))): coords_corners[i * N:(i + 1) * N] = coords + [x_offset, y_offset] - coords = coords_corners + coords = unique_rows(coords_corners) try: from scipy.spatial import Delaunay From e9c9c2ca072533a474d5ab9e5fee3a8046317560 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 5 Aug 2013 10:20:48 +1000 Subject: [PATCH 06/11] Move code comment to appropriate line --- skimage/morphology/convex_hull.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py index 565b10ab..3d592dca 100644 --- a/skimage/morphology/convex_hull.py +++ b/skimage/morphology/convex_hull.py @@ -36,8 +36,6 @@ def convex_hull_image(image): # limits the number of coordinates to examine for the virtual # hull. coords = possible_hull(image.astype(np.uint8)) - # repeated coordinates can *sometimes* cause problems in - # scipy.spatial.Delaunay, so we remove them. N = len(coords) # Add a vertex for the middle of each pixel edge @@ -46,6 +44,8 @@ def convex_hull_image(image): (-0.5, 0.5, 0, 0))): coords_corners[i * N:(i + 1) * N] = coords + [x_offset, y_offset] + # repeated coordinates can *sometimes* cause problems in + # scipy.spatial.Delaunay, so we remove them. coords = unique_rows(coords_corners) try: From c3582f21f8fafb1677910c73f6fd3cc7ef647d29 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 5 Aug 2013 10:39:42 +1000 Subject: [PATCH 07/11] Add (previously) pathological qhull test case --- skimage/morphology/tests/test_convex_hull.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/skimage/morphology/tests/test_convex_hull.py b/skimage/morphology/tests/test_convex_hull.py index 075762ed..ee3b6bfa 100644 --- a/skimage/morphology/tests/test_convex_hull.py +++ b/skimage/morphology/tests/test_convex_hull.py @@ -32,6 +32,19 @@ def test_basic(): assert_array_equal(convex_hull_image(image), expected) +@skipif(not scipy_spatial) +def test_pathological_qhull_example(): + image = np.array( + [[0, 0, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 0]], dtype=bool) + expected = np.array( + [[0, 0, 0, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 0, 0]], dtype=bool) + assert_array_equal(convex_hull_image(image), expected) + + @skipif(not scipy_spatial) def test_possible_hull(): image = np.array( From 909d4cb5f6ac2d051091c43c849475f1d1ae4aa1 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Tue, 6 Aug 2013 22:45:21 +1000 Subject: [PATCH 08/11] Add test to unique_rows for discontiguous arrays --- skimage/util/tests/test_unique_rows.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/skimage/util/tests/test_unique_rows.py b/skimage/util/tests/test_unique_rows.py index cfbaaf05..ad033b69 100644 --- a/skimage/util/tests/test_unique_rows.py +++ b/skimage/util/tests/test_unique_rows.py @@ -3,6 +3,14 @@ from numpy.testing import assert_equal, assert_raises from skimage.util import unique_rows +def test_discontiguous_array(): + ar = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], np.uint8) + ar = ar[::2] + ar_out = unique_rows(ar) + desired_ar_out = np.array([[1, 0, 1]], np.uint8) + assert_equal(ar_out, desired_ar_out) + + def test_uint8_array(): ar = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], np.uint8) ar_out = unique_rows(ar) From b40ef3ad7d461a9728522191f35ab3a08c608f0b Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Tue, 6 Aug 2013 22:45:52 +1000 Subject: [PATCH 09/11] Ensure array input to unique_rows is contiguous --- skimage/util/unique.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/skimage/util/unique.py b/skimage/util/unique.py index a09fadd9..6034d9a4 100644 --- a/skimage/util/unique.py +++ b/skimage/util/unique.py @@ -30,6 +30,11 @@ def unique_rows(ar): if ar.ndim != 2: raise ValueError("unique_rows() only makes sense for 2D arrays, " "got %dd" % ar.ndim) + # the view in the next line only works if the array is C-contiguous + ar = np.ascontiguousarray(ar) + # np.unique() finds identical items in a raveled array. To make it + # see each row as a single item, we create a view of each row as a + # byte string of length itemsize times number of columns in `ar` ar_row_view = ar.view('|S%d' % (ar.itemsize * ar.shape[1])) _, unique_row_indices = np.unique(ar_row_view, return_index=True) ar_out = ar[unique_row_indices] From cb28bba6ee642828df473383ea469a6aa46ca59c Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 25 Aug 2013 11:04:50 +0200 Subject: [PATCH 10/11] Add note describing array copy if discontiguous --- skimage/util/unique.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/skimage/util/unique.py b/skimage/util/unique.py index 6034d9a4..ecd6e8cc 100644 --- a/skimage/util/unique.py +++ b/skimage/util/unique.py @@ -18,6 +18,12 @@ def unique_rows(ar): ------ ValueError : if `ar` is not two-dimensional. + Notes + ----- + The function will generate a copy of `ar` if it is not + C-contiguous, which will negatively affect performance for large + input arrays. + Examples -------- >>> ar = np.array([[1, 0, 1], From 072eb7640a42b249633f3f495156a3d8a203300e Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 25 Aug 2013 11:06:31 +0200 Subject: [PATCH 11/11] Add docstring note explaining coord use case --- skimage/util/unique.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/skimage/util/unique.py b/skimage/util/unique.py index ecd6e8cc..e65c05ce 100644 --- a/skimage/util/unique.py +++ b/skimage/util/unique.py @@ -4,6 +4,9 @@ import numpy as np def unique_rows(ar): """Remove repeated rows from a 2D array. + In particular, if given an array of coordinates of shape + (Npoints, Ndim), it will remove repeated points. + Parameters ---------- ar : 2D np.ndarray