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..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) @@ -175,6 +176,7 @@ def hsobel(image, mask=None): Parameters ---------- + image : 2-D array Image to process. mask : 2-D array, optional @@ -762,3 +764,35 @@ def roberts_negative_diagonal(image, mask=None): """ return np.abs(roberts_neg_diag(image, mask)) + +def laplace(image, ksize=3, mask=None): + """Find the edges of an image using the Laplace operator. + + Parameters + ---------- + image : ndarray + Image to process. + 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 : ndarray + The Laplace edge map. + + Notes + ----- + The Laplacian operator is generated using the function + skimage.restoration.uft.laplacian(). + + """ + image = img_as_float(image) + # 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 d2c0f469..0fbaa4ac 100644 --- a/skimage/filters/tests/test_edges.py +++ b/skimage/filters/tests/test_edges.py @@ -335,6 +335,34 @@ def test_vprewitt_horizontal(): assert_allclose(result, 0) +def test_laplace_zeros(): + """Laplace on a square image.""" + # Create a synthetic 2D image + image = np.zeros((9, 9)) + image[3:-3, 3:-3] = 1 + result = filters.laplace(image) + 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) + + +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, 3:-3] = 1 + # Define the mask + result = filters.laplace(image, ksize=3, mask=np.zeros((9, 9), 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 +379,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