diff --git a/skimage/filter/edges.py b/skimage/filter/edges.py index cd91effb..134aa796 100644 --- a/skimage/filter/edges.py +++ b/skimage/filter/edges.py @@ -13,6 +13,26 @@ from skimage import img_as_float from scipy.ndimage import convolve, binary_erosion, generate_binary_structure +EROSION_SELEM = generate_binary_structure(2, 2) + + +def _mask_filter_result(result, mask): + """Return result after masking. + + Input masks are eroded so that mask areas in the original image don't + affect values in the result. + """ + if mask is None: + result[0, :] = 0 + result[-1, :] = 0 + result[:, 0] = 0 + result[:, -1] = 0 + return result + else: + mask = binary_erosion(mask, EROSION_SELEM, border_value=0) + return result * mask + + def sobel(image, mask=None): """Calculate the absolute magnitude Sobel to find edges. @@ -22,6 +42,8 @@ def sobel(image, mask=None): Image to process. mask : array_like, dtype=bool, optional An optional mask to limit the application to a certain area. + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. Returns ------- @@ -49,6 +71,8 @@ def hsobel(image, mask=None): Image to process. mask : array_like, dtype=bool, optional An optional mask to limit the application to a certain area. + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. Returns ------- @@ -66,17 +90,11 @@ def hsobel(image, mask=None): """ image = img_as_float(image) - if mask is None: - mask = np.ones(image.shape, bool) - big_mask = binary_erosion(mask, - generate_binary_structure(2, 2), - border_value=0) result = np.abs(convolve(image, np.array([[ 1, 2, 1], [ 0, 0, 0], [-1,-2,-1]]).astype(float) / 4.0)) - result[big_mask == False] = 0 - return result + return _mask_filter_result(result, mask) def vsobel(image, mask=None): @@ -88,6 +106,8 @@ def vsobel(image, mask=None): Image to process mask : array_like, dtype=bool, optional An optional mask to limit the application to a certain area + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. Returns ------- @@ -105,17 +125,11 @@ def vsobel(image, mask=None): """ image = img_as_float(image) - if mask is None: - mask = np.ones(image.shape, bool) - big_mask = binary_erosion(mask, - generate_binary_structure(2, 2), - border_value=0) result = np.abs(convolve(image, np.array([[1, 0, -1], [2, 0, -2], [1, 0, -1]]).astype(float) / 4.0)) - result[big_mask == False] = 0 - return result + return _mask_filter_result(result, mask) def prewitt(image, mask=None): @@ -127,6 +141,8 @@ def prewitt(image, mask=None): Image to process. mask : array_like, dtype=bool, optional An optional mask to limit the application to a certain area. + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. Returns ------- @@ -150,6 +166,8 @@ def hprewitt(image, mask=None): Image to process. mask : array_like, dtype=bool, optional An optional mask to limit the application to a certain area. + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. Returns ------- @@ -167,17 +185,11 @@ def hprewitt(image, mask=None): """ image = img_as_float(image) - if mask is None: - mask = np.ones(image.shape, bool) - big_mask = binary_erosion(mask, - generate_binary_structure(2, 2), - border_value=0) result = np.abs(convolve(image, np.array([[ 1, 1, 1], [ 0, 0, 0], [-1,-1,-1]]).astype(float) / 3.0)) - result[big_mask == False] = 0 - return result + return _mask_filter_result(result, mask) def vprewitt(image, mask=None): @@ -189,6 +201,8 @@ def vprewitt(image, mask=None): Image to process. mask : array_like, dtype=bool, optional An optional mask to limit the application to a certain area. + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. Returns ------- @@ -206,14 +220,8 @@ def vprewitt(image, mask=None): """ image = img_as_float(image) - if mask is None: - mask = np.ones(image.shape, bool) - big_mask = binary_erosion(mask, - generate_binary_structure(2, 2), - border_value=0) result = np.abs(convolve(image, np.array([[1, 0, -1], [1, 0, -1], [1, 0, -1]]).astype(float) / 3.0)) - result[big_mask == False] = 0 - return result + return _mask_filter_result(result, mask) diff --git a/skimage/filter/tests/test_edges.py b/skimage/filter/tests/test_edges.py index bd9a2702..a5d52aa5 100644 --- a/skimage/filter/tests/test_edges.py +++ b/skimage/filter/tests/test_edges.py @@ -1,211 +1,236 @@ -import os - -from numpy.testing import * import numpy as np -from scipy.ndimage import binary_dilation, binary_erosion +from numpy.testing import assert_array_almost_equal as assert_close import skimage.filter as F -from skimage import data_dir, img_as_float -class TestSobel(): - def test_00_00_zeros(self): - """Sobel on an array of all zeros""" - result = F.sobel(np.zeros((10, 10)), np.ones((10, 10), bool)) - assert (np.all(result == 0)) +def test_sobel_zeros(): + """Sobel on an array of all zeros""" + result = F.sobel(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) - def test_00_01_mask(self): - """Sobel on a masked array should be zero""" - np.random.seed(0) - result = F.sobel(np.random.uniform(size=(10, 10)), - np.zeros((10, 10), bool)) - assert (np.all(result == 0)) +def test_sobel_mask(): + """Sobel on a masked array should be zero""" + np.random.seed(0) + result = F.sobel(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) - def test_01_01_horizontal(self): - """Sobel on an edge should be a horizontal line""" - i, j = np.mgrid[-5:6, -5:6] - image = (i >= 0).astype(float) - result = F.sobel(image) - # Fudge the eroded points - i[np.abs(j) == 5] = 10000 - assert (np.all(result[i == 0] == 1)) - assert (np.all(result[np.abs(i) > 1] == 0)) +def test_sobel_horizontal(): + """Sobel on an edge should be a horizontal line""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.sobel(image) + # Fudge the eroded points + i[np.abs(j) == 5] = 10000 + assert (np.all(result[i == 0] == 1)) + assert (np.all(result[np.abs(i) > 1] == 0)) - def test_01_02_vertical(self): - """Sobel on a vertical edge should be a vertical line""" - i, j = np.mgrid[-5:6, -5:6] - image = (j >= 0).astype(float) - result = F.sobel(image) - j[np.abs(i) == 5] = 10000 - assert (np.all(result[j == 0] == 1)) - assert (np.all(result[np.abs(j) > 1] == 0)) +def test_sobel_vertical(): + """Sobel on a vertical edge should be a vertical line""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.sobel(image) + j[np.abs(i) == 5] = 10000 + assert (np.all(result[j == 0] == 1)) + assert (np.all(result[np.abs(j) > 1] == 0)) -class TestHSobel(): - def test_00_00_zeros(self): - """Horizontal sobel on an array of all zeros""" - result = F.hsobel(np.zeros((10, 10)), np.ones((10, 10), bool)) - assert (np.all(result == 0)) +def test_hsobel_zeros(): + """Horizontal sobel on an array of all zeros""" + result = F.hsobel(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) - def test_00_01_mask(self): - """Horizontal Sobel on a masked array should be zero""" - np.random.seed(0) - result = F.hsobel(np.random.uniform(size=(10, 10)), - np.zeros((10, 10), bool)) - assert (np.all(result == 0)) +def test_hsobel_mask(): + """Horizontal Sobel on a masked array should be zero""" + np.random.seed(0) + result = F.hsobel(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) - def test_01_01_horizontal(self): - """Horizontal Sobel on an edge should be a horizontal line""" - i, j = np.mgrid[-5:6, -5:6] - image = (i >= 0).astype(float) - result = F.hsobel(image) - # Fudge the eroded points - i[np.abs(j) == 5] = 10000 - assert (np.all(result[i == 0] == 1)) - assert (np.all(result[np.abs(i) > 1] == 0)) +def test_hsobel_horizontal(): + """Horizontal Sobel on an edge should be a horizontal line""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.hsobel(image) + # Fudge the eroded points + i[np.abs(j) == 5] = 10000 + assert (np.all(result[i == 0] == 1)) + assert (np.all(result[np.abs(i) > 1] == 0)) - def test_01_02_vertical(self): - """Horizontal Sobel on a vertical edge should be zero""" - i, j = np.mgrid[-5:6, -5:6] - image = (j >= 0).astype(float) - result = F.hsobel(image) - assert (np.all(result == 0)) +def test_hsobel_vertical(): + """Horizontal Sobel on a vertical edge should be zero""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.hsobel(image) + assert (np.all(result == 0)) -class TestVSobel(): - def test_00_00_zeros(self): - """Vertical sobel on an array of all zeros""" - result = F.vsobel(np.zeros((10, 10)), np.ones((10, 10), bool)) - assert (np.all(result == 0)) +def test_vsobel_zeros(): + """Vertical sobel on an array of all zeros""" + result = F.vsobel(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) - def test_00_01_mask(self): - """Vertical Sobel on a masked array should be zero""" - np.random.seed(0) - result = F.vsobel(np.random.uniform(size=(10, 10)), - np.zeros((10, 10), bool)) - assert (np.all(result == 0)) +def test_vsobel_mask(): + """Vertical Sobel on a masked array should be zero""" + np.random.seed(0) + result = F.vsobel(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) - def test_01_01_vertical(self): - """Vertical Sobel on an edge should be a vertical line""" - i, j = np.mgrid[-5:6, -5:6] - image = (j >= 0).astype(float) - result = F.vsobel(image) - # Fudge the eroded points - j[np.abs(i) == 5] = 10000 - assert (np.all(result[j == 0] == 1)) - assert (np.all(result[np.abs(j) > 1] == 0)) +def test_vsobel_vertical(): + """Vertical Sobel on an edge should be a vertical line""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.vsobel(image) + # Fudge the eroded points + j[np.abs(i) == 5] = 10000 + assert (np.all(result[j == 0] == 1)) + assert (np.all(result[np.abs(j) > 1] == 0)) - def test_01_02_horizontal(self): - """vertical Sobel on a horizontal edge should be zero""" - i, j = np.mgrid[-5:6, -5:6] - image = (i >= 0).astype(float) - result = F.vsobel(image) - eps = .000001 - assert (np.all(np.abs(result) < eps)) +def test_vsobel_horizontal(): + """vertical Sobel on a horizontal edge should be zero""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.vsobel(image) + eps = .000001 + assert (np.all(np.abs(result) < eps)) -class TestPrewitt(): - def test_00_00_zeros(self): - """Prewitt on an array of all zeros""" - result = F.prewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) - assert (np.all(result == 0)) +def test_prewitt_zeros(): + """Prewitt on an array of all zeros""" + result = F.prewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) - def test_00_01_mask(self): - """Prewitt on a masked array should be zero""" - np.random.seed(0) - result = F.prewitt(np.random.uniform(size=(10, 10)), - np.zeros((10, 10), bool)) - eps = .000001 - assert (np.all(np.abs(result) < eps)) +def test_prewitt_mask(): + """Prewitt on a masked array should be zero""" + np.random.seed(0) + result = F.prewitt(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + eps = .000001 + assert (np.all(np.abs(result) < eps)) - def test_01_01_horizontal(self): - """Prewitt on an edge should be a horizontal line""" - i, j = np.mgrid[-5:6, -5:6] - image = (i >= 0).astype(float) - result = F.prewitt(image) - # Fudge the eroded points - i[np.abs(j) == 5] = 10000 - eps = .000001 - assert (np.all(result[i == 0] == 1)) - assert (np.all(np.abs(result[np.abs(i) > 1]) < eps)) +def test_prewitt_horizontal(): + """Prewitt on an edge should be a horizontal line""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.prewitt(image) + # Fudge the eroded points + i[np.abs(j) == 5] = 10000 + eps = .000001 + assert (np.all(result[i == 0] == 1)) + assert (np.all(np.abs(result[np.abs(i) > 1]) < eps)) - def test_01_02_vertical(self): - """Prewitt on a vertical edge should be a vertical line""" - i, j = np.mgrid[-5:6, -5:6] - image = (j >= 0).astype(float) - result = F.prewitt(image) - eps = .000001 - j[np.abs(i) == 5] = 10000 - assert (np.all(result[j == 0] == 1)) - assert (np.all(np.abs(result[np.abs(j) > 1]) < eps)) +def test_prewitt_vertical(): + """Prewitt on a vertical edge should be a vertical line""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.prewitt(image) + eps = .000001 + j[np.abs(i) == 5] = 10000 + assert (np.all(result[j == 0] == 1)) + assert (np.all(np.abs(result[np.abs(j) > 1]) < eps)) -class TestHPrewitt(): - def test_00_00_zeros(self): - """Horizontal sobel on an array of all zeros""" - result = F.hprewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) - assert (np.all(result == 0)) +def test_hprewitt_zeros(): + """Horizontal prewitt on an array of all zeros""" + result = F.hprewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) - def test_00_01_mask(self): - """Horizontal prewitt on a masked array should be zero""" - np.random.seed(0) - result = F.hprewitt(np.random.uniform(size=(10, 10)), - np.zeros((10, 10), bool)) - eps = .000001 - assert (np.all(np.abs(result) < eps)) +def test_hprewitt_mask(): + """Horizontal prewitt on a masked array should be zero""" + np.random.seed(0) + result = F.hprewitt(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + eps = .000001 + assert (np.all(np.abs(result) < eps)) - def test_01_01_horizontal(self): - """Horizontal prewitt on an edge should be a horizontal line""" - i, j = np.mgrid[-5:6, -5:6] - image = (i >= 0).astype(float) - result = F.hprewitt(image) - # Fudge the eroded points - i[np.abs(j) == 5] = 10000 - eps = .000001 - assert (np.all(result[i == 0] == 1)) - assert (np.all(np.abs(result[np.abs(i) > 1]) < eps)) +def test_hprewitt_horizontal(): + """Horizontal prewitt on an edge should be a horizontal line""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.hprewitt(image) + # Fudge the eroded points + i[np.abs(j) == 5] = 10000 + eps = .000001 + assert (np.all(result[i == 0] == 1)) + assert (np.all(np.abs(result[np.abs(i) > 1]) < eps)) - def test_01_02_vertical(self): - """Horizontal prewitt on a vertical edge should be zero""" - i, j = np.mgrid[-5:6, -5:6] - image = (j >= 0).astype(float) - result = F.hprewitt(image) - eps = .000001 - assert (np.all(np.abs(result) < eps)) +def test_hprewitt_vertical(): + """Horizontal prewitt on a vertical edge should be zero""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.hprewitt(image) + eps = .000001 + assert (np.all(np.abs(result) < eps)) -class TestVPrewitt(): - def test_00_00_zeros(self): - """Vertical prewitt on an array of all zeros""" - result = F.vprewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) - assert (np.all(result == 0)) +def test_vprewitt_zeros(): + """Vertical prewitt on an array of all zeros""" + result = F.vprewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) - def test_00_01_mask(self): - """Vertical prewitt on a masked array should be zero""" - np.random.seed(0) - result = F.vprewitt(np.random.uniform(size=(10, 10)), - np.zeros((10, 10), bool)) - assert (np.all(result == 0)) +def test_vprewitt_mask(): + """Vertical prewitt on a masked array should be zero""" + np.random.seed(0) + result = F.vprewitt(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) - def test_01_01_vertical(self): - """Vertical prewitt on an edge should be a vertical line""" - i, j = np.mgrid[-5:6, -5:6] - image = (j >= 0).astype(float) - result = F.vprewitt(image) - # Fudge the eroded points - j[np.abs(i) == 5] = 10000 - assert (np.all(result[j == 0] == 1)) - eps = .000001 - assert (np.all(np.abs(result[np.abs(j) > 1]) < eps)) +def test_vprewitt_vertical(): + """Vertical prewitt on an edge should be a vertical line""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.vprewitt(image) + # Fudge the eroded points + j[np.abs(i) == 5] = 10000 + assert (np.all(result[j == 0] == 1)) + eps = .000001 + assert (np.all(np.abs(result[np.abs(j) > 1]) < eps)) - def test_01_02_horizontal(self): - """Vertical prewitt on a horizontal edge should be zero""" - i, j = np.mgrid[-5:6, -5:6] - image = (i >= 0).astype(float) - result = F.vprewitt(image) - eps = .000001 - assert (np.all(np.abs(result) < eps)) +def test_vprewitt_horizontal(): + """Vertical prewitt on a horizontal edge should be zero""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.vprewitt(image) + eps = .000001 + assert (np.all(np.abs(result) < eps)) + + +def test_horizontal_mask_line(): + """Horizontal edge filters mask pixels surrounding input mask.""" + vgrad, _ = np.mgrid[:1:11j, :1:11j] # vertical gradient with spacing 0.1 + vgrad[5, :] = 1 # bad horizontal line + + mask = np.ones_like(vgrad) + mask[5, :] = 0 # mask bad line + + expected = np.zeros_like(vgrad) + expected[1:-1, 1:-1] = 0.2 # constant gradient for most of image, + expected[4:7, 1:-1] = 0 # but line and neighbors masked + + for grad_func in (F.hprewitt, F.hsobel): + result = grad_func(vgrad, mask) + yield assert_close, result, expected + + +def test_vertical_mask_line(): + """Vertical edge filters mask pixels surrounding input mask.""" + _, hgrad = np.mgrid[:1:11j, :1:11j] # horizontal gradient with spacing 0.1 + hgrad[:, 5] = 1 # bad vertical line + + mask = np.ones_like(hgrad) + mask[:, 5] = 0 # mask bad line + + expected = np.zeros_like(hgrad) + expected[1:-1, 1:-1] = 0.2 # constant gradient for most of image, + expected[1:-1, 4:7] = 0 # but line and neighbors masked + + for grad_func in (F.vprewitt, F.vsobel): + result = grad_func(hgrad, mask) + yield assert_close, result, expected if __name__ == "__main__": - run_module_suite() + from numpy import testing + testing.run_module_suite()