diff --git a/skimage/morphology/__init__.py b/skimage/morphology/__init__.py index c8fafe6f..f01fc3ed 100644 --- a/skimage/morphology/__init__.py +++ b/skimage/morphology/__init__.py @@ -4,7 +4,8 @@ from .grey import (erosion, dilation, opening, closing, white_tophat, black_tophat, greyscale_erode, greyscale_dilate, greyscale_open, greyscale_close, greyscale_white_top_hat, greyscale_black_top_hat) -from .selem import square, rectangle, diamond, disk, cube, octahedron, ball +from .selem import (square, rectangle, diamond, disk, cube, octahedron, ball, + octagon, star) from .ccomp import label from .watershed import watershed, is_local_maximum from ._skeletonize import skeletonize, medial_axis @@ -36,6 +37,7 @@ __all__ = ['binary_erosion', 'cube', 'octahedron', 'ball', + 'octagon', 'label', 'watershed', 'is_local_maximum', diff --git a/skimage/morphology/selem.py b/skimage/morphology/selem.py index 2cccae22..f85b3a95 100644 --- a/skimage/morphology/selem.py +++ b/skimage/morphology/selem.py @@ -15,18 +15,18 @@ def square(width, dtype=np.uint8): Parameters ---------- width : int - The width and height of the square + The width and height of the square Other Parameters ---------------- dtype : data-type - The data type of the structuring element. + The data type of the structuring element. Returns ------- selem : ndarray - A structuring element consisting only of ones, i.e. every - pixel belongs to the neighborhood. + A structuring element consisting only of ones, i.e. every + pixel belongs to the neighborhood. """ return np.ones((width, width), dtype=dtype) @@ -41,21 +41,20 @@ def rectangle(width, height, dtype=np.uint8): Parameters ---------- width : int - The width of the rectangle - + The width of the rectangle height : int - The height of the rectangle + The height of the rectangle Other Parameters ---------------- dtype : data-type - The data type of the structuring element. + The data type of the structuring element. Returns ------- selem : ndarray - A structuring element consisting only of ones, i.e. every - pixel belongs to the neighborhood. + A structuring element consisting only of ones, i.e. every + pixel belongs to the neighborhood. """ return np.ones((width, height), dtype=dtype) @@ -71,17 +70,19 @@ def diamond(radius, dtype=np.uint8): Parameters ---------- radius : int - The radius of the diamond-shaped structuring element. + The radius of the diamond-shaped structuring element. + Other Parameters + ---------------- dtype : data-type - The data type of the structuring element. + The data type of the structuring element. Returns ------- selem : ndarray - The structuring element where elements of the neighborhood - are 1 and 0 otherwise. + The structuring element where elements of the neighborhood + are 1 and 0 otherwise. """ half = radius (I, J) = np.meshgrid(range(0, radius * 2 + 1), range(0, radius * 2 + 1)) @@ -98,16 +99,18 @@ def disk(radius, dtype=np.uint8): Parameters ---------- radius : int - The radius of the disk-shaped structuring element. + The radius of the disk-shaped structuring element. + Other Parameters + ---------------- dtype : data-type - The data type of the structuring element. + The data type of the structuring element. Returns ------- selem : ndarray - The structuring element where elements of the neighborhood - are 1 and 0 otherwise. + The structuring element where elements of the neighborhood + are 1 and 0 otherwise. """ L = np.linspace(-radius, radius, 2 * radius + 1) (X, Y) = np.meshgrid(L, L) @@ -125,18 +128,18 @@ def cube(width, dtype=np.uint8): Parameters ---------- width : int - The width, height and depth of the cube + The width, height and depth of the cube Other Parameters ---------------- dtype : data-type - The data type of the structuring element. + The data type of the structuring element. Returns ------- selem : ndarray - A structuring element consisting only of ones, i.e. every - pixel belongs to the neighborhood. + A structuring element consisting only of ones, i.e. every + pixel belongs to the neighborhood. """ return np.ones((width, width, width), dtype=dtype) @@ -153,17 +156,19 @@ def octahedron(radius, dtype=np.uint8): Parameters ---------- radius : int - The radius of the octahedron-shaped structuring element. + The radius of the octahedron-shaped structuring element. + Other Parameters + ---------------- dtype : data-type - The data type of the structuring element. + The data type of the structuring element. Returns ------- selem : ndarray - The structuring element where elements of the neighborhood - are 1 and 0 otherwise. + The structuring element where elements of the neighborhood + are 1 and 0 otherwise. """ # note that in contrast to diamond(), this method allows non-integer radii n = 2 * radius + 1 @@ -184,16 +189,18 @@ def ball(radius, dtype=np.uint8): Parameters ---------- radius : int - The radius of the ball-shaped structuring element. + The radius of the ball-shaped structuring element. + Other Parameters + ---------------- dtype : data-type - The data type of the structuring element. + The data type of the structuring element. Returns ------- selem : ndarray - The structuring element where elements of the neighborhood - are 1 and 0 otherwise. + The structuring element where elements of the neighborhood + are 1 and 0 otherwise. """ n = 2 * radius + 1 Z, Y, X = np.mgrid[ -radius:radius:n*1j, @@ -201,3 +208,85 @@ def ball(radius, dtype=np.uint8): -radius:radius:n*1j] s = X**2 + Y**2 + Z**2 return np.array(s <= radius * radius, dtype=dtype) + + +def octagon(m, n, dtype=np.uint8): + """ + Generates an octagon shaped structuring element with a given size of + horizontal and vertical sides and a given height or width of slanted + sides. The slanted sides are 45 or 135 degrees to the horizontal axis + and hence the widths and heights are equal. + + Parameters + ---------- + m : int + The size of the horizontal and vertical sides. + n : int + The height or width of the slanted sides. + + Other Parameters + ---------------- + dtype : data-type + The data type of the structuring element. + + Returns + ------- + selem : ndarray + The structuring element where elements of the neighborhood + are 1 and 0 otherwise. + + """ + from . import convex_hull_image + selem = np.zeros((m + 2*n, m + 2*n)) + selem[0, n] = 1 + selem[n, 0] = 1 + selem[0, m + n -1] = 1 + selem[m + n - 1, 0] = 1 + selem[-1, n] = 1 + selem[n, -1] = 1 + selem[-1, m + n - 1] = 1 + selem[m + n - 1, -1] = 1 + selem = convex_hull_image(selem).astype(dtype) + return selem + + +def star(a, dtype=np.uint8): + """ + Generates a star shaped structuring element that has 8 vertices and is an + overlap of square of size `2*a + 1` with its 45 degree rotated version. + The slanted sides are 45 or 135 degrees to the horizontal axis. + + Parameters + ---------- + a : int + Parameter deciding the size of the star structural element. The side + of the square array returned is `2*a + 1 + 2*floor(a / 2)`. + + Other Parameters + ---------------- + dtype : data-type + The data type of the structuring element. + + Returns + ------- + selem : ndarray + The structuring element where elements of the neighborhood + are 1 and 0 otherwise. + + """ + from . import convex_hull_image + if a == 1: + bfilter = np.zeros((3, 3), dtype) + bfilter[:] = 1 + return bfilter + m = 2 * a + 1 + n = a / 2 + selem_square = np.zeros((m + 2 * n, m + 2 * n)) + selem_square[n: m + n, n: m + n] = 1 + c = (m + 2 * n - 1) / 2 + selem_rotated = np.zeros((m + 2 * n, m + 2 * n)) + selem_rotated[0, c] = selem_rotated[-1, c] = selem_rotated[c, 0] = selem_rotated[c, -1] = 1 + selem_rotated = convex_hull_image(selem_rotated).astype(int) + selem = selem_square + selem_rotated + selem[selem > 0] = 1 + return selem.astype(dtype) diff --git a/skimage/morphology/tests/test_selem.py b/skimage/morphology/tests/test_selem.py index 9a8ca895..7895fa58 100644 --- a/skimage/morphology/tests/test_selem.py +++ b/skimage/morphology/tests/test_selem.py @@ -66,6 +66,48 @@ class TestSElem(): def test_selem_octahedron(self): self.strel_worker_3d("diamond-matlab-output.npz", selem.octahedron) + def test_selem_octagon(self): + expected_mask1 = np.array([[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0]], dtype=np.uint8) + actual_mask1 = selem.octagon(5, 3) + expected_mask2 = np.array([[0, 1, 0], + [1, 1, 1], + [0, 1, 0]], dtype=np.uint8) + actual_mask2 = selem.octagon(1, 1) + assert_equal(expected_mask1, actual_mask1) + assert_equal(expected_mask2, actual_mask2) + + def test_selem_star(self): + expected_mask1 = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]], dtype=np.uint8) + actual_mask1 = selem.star(4) + expected_mask2 = np.array([[1, 1, 1], + [1, 1, 1], + [1, 1, 1]], dtype=np.uint8) + actual_mask2 = selem.star(1) + assert_equal(expected_mask1, actual_mask1) + assert_equal(expected_mask2, actual_mask2) + if __name__ == '__main__': np.testing.run_module_suite()