From 5d209a68a957d5bd17b177c8da73064985baa8b1 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 27 Oct 2015 01:23:29 +0100 Subject: [PATCH 1/3] Added the Laplacian operator --- skimage/filters/__init__.py | 3 ++- skimage/filters/edges.py | 34 +++++++++++++++++++++++++++++ skimage/filters/tests/test_edges.py | 15 ++++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/skimage/filters/__init__.py b/skimage/filters/__init__.py index 9dd0b4df..708f5d53 100644 --- a/skimage/filters/__init__.py +++ b/skimage/filters/__init__.py @@ -5,7 +5,7 @@ from .edges import (sobel, hsobel, vsobel, sobel_h, sobel_v, prewitt, hprewitt, vprewitt, prewitt_h, prewitt_v, roberts, roberts_positive_diagonal, roberts_negative_diagonal, roberts_pos_diag, - roberts_neg_diag) + roberts_neg_diag, laplace) from ._rank_order import rank_order from ._gabor import gabor_kernel, gabor from .thresholding import (threshold_adaptive, threshold_otsu, threshold_yen, @@ -62,6 +62,7 @@ __all__ = ['inverse', 'roberts_negative_diagonal', 'roberts_pos_diag', 'roberts_neg_diag', + 'laplace', 'denoise_tv_chambolle', 'denoise_bilateral', 'denoise_tv_bregman', diff --git a/skimage/filters/edges.py b/skimage/filters/edges.py index 1eeed4de..501dd8ea 100644 --- a/skimage/filters/edges.py +++ b/skimage/filters/edges.py @@ -37,6 +37,9 @@ ROBERTS_PD_WEIGHTS = np.array([[1, 0], ROBERTS_ND_WEIGHTS = np.array([[0, 1], [-1, 0]], dtype=np.double) +LAPLACE_WEIGHTS = np.array([[1, 1, 1], + [1, -8, 1], + [1, 1, 1]]) / 16.0 def _mask_filter_result(result, mask): """Return result after masking. @@ -762,3 +765,34 @@ def roberts_negative_diagonal(image, mask=None): """ return np.abs(roberts_neg_diag(image, mask)) + +def laplace(image, mask=None): + """Find the edges of an image using the Laplace operator. + + Parameters + ---------- + image : 2-D array + Image to process. + mask : 2-D array, 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 + ------- + output : 2-D array + The Laplace edge map. + + Notes + ----- + We use the following kernel:: + + 1 1 1 + 1 -8 1 + 1 1 1 + + """ + assert_nD(image, 2) + image = img_as_float(image) + result = convolve(image, LAPLACE_WEIGHTS) + return _mask_filter_result(result, mask) diff --git a/skimage/filters/tests/test_edges.py b/skimage/filters/tests/test_edges.py index d2c0f469..2754cbe8 100644 --- a/skimage/filters/tests/test_edges.py +++ b/skimage/filters/tests/test_edges.py @@ -335,6 +335,20 @@ def test_vprewitt_horizontal(): assert_allclose(result, 0) +def test_laplace_zeros(): + """Laplace on an array of all zeros.""" + result = filters.laplace(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) + + +def test_laplace_mask(): + """Laplace on a masked array should be zero.""" + np.random.seed(0) + result = filters.laplace(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) + + 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 @@ -351,7 +365,6 @@ def test_horizontal_mask_line(): 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 From 41875cf59ab2c638ab73a093aa269711b1bf165d Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 27 Oct 2015 15:17:07 +0100 Subject: [PATCH 2/3] Generalize Laplce operator + testing Reuse the function skimage.restoration.uft.laplacian() to create the kernel Improve the testing for a specific case --- skimage/filters/edges.py | 28 ++++++++++++++-------------- skimage/filters/tests/test_edges.py | 24 +++++++++++++++++++----- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/skimage/filters/edges.py b/skimage/filters/edges.py index 501dd8ea..4cf083a3 100644 --- a/skimage/filters/edges.py +++ b/skimage/filters/edges.py @@ -14,6 +14,7 @@ from .. import img_as_float from .._shared.utils import assert_nD, deprecated from scipy.ndimage import convolve, binary_erosion, generate_binary_structure +from ..restoration.uft import laplacian EROSION_SELEM = generate_binary_structure(2, 2) @@ -37,9 +38,6 @@ ROBERTS_PD_WEIGHTS = np.array([[1, 0], ROBERTS_ND_WEIGHTS = np.array([[0, 1], [-1, 0]], dtype=np.double) -LAPLACE_WEIGHTS = np.array([[1, 1, 1], - [1, -8, 1], - [1, 1, 1]]) / 16.0 def _mask_filter_result(result, mask): """Return result after masking. @@ -178,6 +176,7 @@ def hsobel(image, mask=None): Parameters ---------- + image : 2-D array Image to process. mask : 2-D array, optional @@ -766,33 +765,34 @@ def roberts_negative_diagonal(image, mask=None): """ return np.abs(roberts_neg_diag(image, mask)) -def laplace(image, mask=None): +def laplace(image, ksize=3, mask=None): """Find the edges of an image using the Laplace operator. Parameters ---------- - image : 2-D array + image : ndarray Image to process. - mask : 2-D array, optional + ksize : int, optional + Define the size of the discrete Laplacian operator such that it + will have a size of (ksize,) * image.ndim. + mask : ndarray, 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 ------- - output : 2-D array + output : ndarray The Laplace edge map. Notes ----- - We use the following kernel:: - - 1 1 1 - 1 -8 1 - 1 1 1 + The Laplacian operator is generated using the function + skimage.restoration.uft.laplacian(). """ - assert_nD(image, 2) image = img_as_float(image) - result = convolve(image, LAPLACE_WEIGHTS) + # Create the discrete Laplacian operator - We keep only the real part of the filter + _, laplace_op = laplacian(image.ndim, (ksize, ) * image.ndim) + result = convolve(image, laplace_op) return _mask_filter_result(result, mask) diff --git a/skimage/filters/tests/test_edges.py b/skimage/filters/tests/test_edges.py index 2754cbe8..1fd5e32f 100644 --- a/skimage/filters/tests/test_edges.py +++ b/skimage/filters/tests/test_edges.py @@ -337,15 +337,29 @@ def test_vprewitt_horizontal(): def test_laplace_zeros(): """Laplace on an array of all zeros.""" - result = filters.laplace(np.zeros((10, 10)), np.ones((10, 10), bool)) - assert (np.all(result == 0)) + # Create a synthetic 2D image + image = np.zeros((9,9)) + image[3:-3] = 1 + result = filters.laplace(image) + res_chk = array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., -1., -1., -1., 0., 0., 0.], + [ 0., 0., -1., 2., 1., 2., -1., 0., 0.], + [ 0., 0., -1., 1., 0., 1., -1., 0., 0.], + [ 0., 0., -1., 2., 1., 2., -1., 0., 0.], + [ 0., 0., 0., -1., -1., -1., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + assert_allclose(result, res_chk) def test_laplace_mask(): """Laplace on a masked array should be zero.""" - np.random.seed(0) - result = filters.laplace(np.random.uniform(size=(10, 10)), - np.zeros((10, 10), bool)) + # Create a synthetic 2D image + image = np.zeros((9, 9)) + image[3:-3] = 1 + # Define the mask + result = filters.laplace(image, np.zeros((10, 10), bool)) assert (np.all(result == 0)) From ed55226dfb5153884ae89abe0c7bad6009d69ce6 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 27 Oct 2015 16:35:15 +0100 Subject: [PATCH 3/3] Correct bug inside the test --- skimage/filters/tests/test_edges.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/skimage/filters/tests/test_edges.py b/skimage/filters/tests/test_edges.py index 1fd5e32f..0fbaa4ac 100644 --- a/skimage/filters/tests/test_edges.py +++ b/skimage/filters/tests/test_edges.py @@ -336,20 +336,20 @@ def test_vprewitt_horizontal(): def test_laplace_zeros(): - """Laplace on an array of all zeros.""" + """Laplace on a square image.""" # Create a synthetic 2D image - image = np.zeros((9,9)) - image[3:-3] = 1 + image = np.zeros((9, 9)) + image[3:-3, 3:-3] = 1 result = filters.laplace(image) - res_chk = array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., -1., -1., -1., 0., 0., 0.], - [ 0., 0., -1., 2., 1., 2., -1., 0., 0.], - [ 0., 0., -1., 1., 0., 1., -1., 0., 0.], - [ 0., 0., -1., 2., 1., 2., -1., 0., 0.], - [ 0., 0., 0., -1., -1., -1., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + res_chk = np.array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., -1., -1., -1., 0., 0., 0.], + [ 0., 0., -1., 2., 1., 2., -1., 0., 0.], + [ 0., 0., -1., 1., 0., 1., -1., 0., 0.], + [ 0., 0., -1., 2., 1., 2., -1., 0., 0.], + [ 0., 0., 0., -1., -1., -1., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) assert_allclose(result, res_chk) @@ -357,9 +357,9 @@ def test_laplace_mask(): """Laplace on a masked array should be zero.""" # Create a synthetic 2D image image = np.zeros((9, 9)) - image[3:-3] = 1 + image[3:-3, 3:-3] = 1 # Define the mask - result = filters.laplace(image, np.zeros((10, 10), bool)) + result = filters.laplace(image, ksize=3, mask=np.zeros((9, 9), bool)) assert (np.all(result == 0))