Merge pull request #62 from tonysyu/fix_morph_symmetry

Fix morph symmetry
This commit is contained in:
emmanuelle
2011-10-16 05:46:47 -07:00
5 changed files with 257 additions and 56 deletions
+15 -2
View File
@@ -8,6 +8,7 @@ import numpy as np
cimport numpy as np
cimport cython
from cpython cimport bool
STREL_DTYPE = np.uint8
ctypedef np.uint8_t STREL_DTYPE_t
@@ -21,9 +22,15 @@ cdef inline int int_min(int a, int b): return a if a <= b else b
@cython.boundscheck(False)
def dilate(np.ndarray[IMAGE_DTYPE_t, ndim=2] image not None,
np.ndarray[IMAGE_DTYPE_t, ndim=2] selem not None,
np.ndarray[IMAGE_DTYPE_t, ndim=2] out):
np.ndarray[IMAGE_DTYPE_t, ndim=2] out,
bool shift_x, bool shift_y):
cdef int hw = selem.shape[0] // 2
cdef int hh = selem.shape[1] // 2
if shift_x:
hh -= 1
if shift_y:
hw -= 1
cdef int width = image.shape[0], height = image.shape[1]
if out is None:
out = np.zeros([width, height], dtype=IMAGE_DTYPE)
@@ -64,9 +71,15 @@ def dilate(np.ndarray[IMAGE_DTYPE_t, ndim=2] image not None,
@cython.boundscheck(False)
def erode(np.ndarray[IMAGE_DTYPE_t, ndim=2] image not None,
np.ndarray[IMAGE_DTYPE_t, ndim=2] selem not None,
np.ndarray[IMAGE_DTYPE_t, ndim=2] out):
np.ndarray[IMAGE_DTYPE_t, ndim=2] out,
bool shift_x, bool shift_y):
cdef int hw = selem.shape[0] // 2
cdef int hh = selem.shape[1] // 2
if shift_x:
hh -= 1
if shift_y:
hw -= 1
cdef int width = image.shape[0], height = image.shape[1]
if out is None:
out = np.zeros([width, height], dtype=IMAGE_DTYPE)
+171 -38
View File
@@ -9,16 +9,17 @@ import numpy as np
eps = np.finfo(float).eps
def greyscale_erode(image, selem, out=None):
"""
Performs a greyscale morphological erosion on an image given a flat
structuring element. The eroded pixel at (i,j) is the minimum over all
pixels in the neighborhood centered at (i,j).
def greyscale_erode(image, selem, out=None, shift_x=False, shift_y=False):
"""Return greyscale morphological erosion of an image.
Morphological erosion sets a pixel at (i,j) to the minimum over all pixels
in the neighborhood centered at (i,j). Erosion shrinks bright regions and
enlarges dark regions.
Parameters
----------
image : ndarray
The image as an ndarray.
The image as a uint8 ndarray.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
@@ -27,31 +28,54 @@ def greyscale_erode(image, selem, out=None):
The array to store the result of the morphology. If None is
passed, a new array will be allocated.
shift_x, shift_y : bool
shift structuring element about center point. This only affects
eccentric structuring elements (i.e. selem with even numbered sides).
Returns
-------
eroded : ndarray
The result of the morphological erosion.
Examples
--------
>>> # Erosion shrinks bright regions
>>> from scikits.image.morphology import square
>>> bright_square = np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> greyscale_erode(bright_square, square(3))
array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]], dtype='uint8')
"""
if image is out:
raise NotImplementedError("In-place erosion not supported!")
try:
import scikits.image.morphology.cmorph as cmorph
out = cmorph.erode(image, selem, out=out)
out = cmorph.erode(image, selem, out=out,
shift_x=shift_x, shift_y=shift_y)
return out;
except ImportError:
raise ImportError("cmorph extension not available.")
def greyscale_dilate(image, selem, out=None):
"""
Performs a greyscale morphological dilation on an image given a flat
structuring element. The dilated pixel at (i,j) is the maximum over all
pixels in the neighborhood centered at (i,j).
def greyscale_dilate(image, selem, out=None, shift_x=False, shift_y=False):
"""Return greyscale morphological dilation of an image.
Morphological dilation sets a pixel at (i,j) to the maximum over all pixels
in the neighborhood centered at (i,j). Dilation enlarges bright regions
and shrinks dark regions.
Parameters
----------
image : ndarray
The image as an ndarray.
The image as a uint8 ndarray.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
@@ -60,29 +84,54 @@ def greyscale_dilate(image, selem, out=None):
The array to store the result of the morphology. If None, is
passed, a new array will be allocated.
shift_x, shift_y : bool
shift structuring element about center point. This only affects
eccentric structuring elements (i.e. selem with even numbered sides).
Returns
-------
dilated : ndarray
The result of the morphological dilation.
Examples
--------
>>> # Dilation enlarges bright regions
>>> from scikits.image.morphology import square
>>> bright_pixel = np.array([[0, 0, 0, 0, 0],
... [0, 0, 0, 0, 0],
... [0, 0, 1, 0, 0],
... [0, 0, 0, 0, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> greyscale_dilate(bright_pixel, square(3))
array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype='uint8')
"""
if image is out:
raise NotImplementedError("In-place dilation not supported!")
try:
from . import cmorph
out = cmorph.dilate(image, selem, out=out)
out = cmorph.dilate(image, selem, out=out,
shift_x=shift_x, shift_y=shift_y)
return out;
except ImportError:
raise ImportError("cmorph extension not available.")
def greyscale_open(image, selem, out=None):
"""
Performs a greyscale morphological opening on an image given a flat
structuring element defined as a erosion followed by a dilation.
"""Return greyscale morphological opening of an image.
The morphological opening on an image is defined as an erosion followed by
a dilation. Opening can remove small bright spots (i.e. "salt") and connect
small dark cracks. This tends to "open" up (dark) gaps between (bright)
features.
Parameters
----------
image : ndarray
The image as an ndarray.
The image as a uint8 ndarray.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
@@ -95,20 +144,45 @@ def greyscale_open(image, selem, out=None):
-------
opening : ndarray
The result of the morphological opening.
Examples
--------
>>> # Open up gap between two bright regions (but also shrink regions)
>>> from scikits.image.morphology import square
>>> bad_connection = np.array([[1, 0, 0, 0, 1],
... [1, 1, 0, 1, 1],
... [1, 1, 1, 1, 1],
... [1, 1, 0, 1, 1],
... [1, 0, 0, 0, 1]], dtype=np.uint8)
>>> greyscale_open(bad_connection, square(3))
array([[0, 0, 0, 0, 0],
[1, 1, 0, 1, 1],
[1, 1, 0, 1, 1],
[1, 1, 0, 1, 1],
[0, 0, 0, 0, 0]], dtype='uint8')
"""
h, w = selem.shape
shift_x = True if (w % 2) == 0 else False
shift_y = True if (h % 2) == 0 else False
eroded = greyscale_erode(image, selem)
out = greyscale_dilate(eroded, selem, out=out)
out = greyscale_dilate(eroded, selem, out=out,
shift_x=shift_x, shift_y=shift_y)
return out
def greyscale_close(image, selem, out=None):
"""
Performs a greyscale morphological closing on an image given a flat
structuring element defined as a dilation followed by an erosion.
"""Return greyscale morphological closing of an image.
The morphological closing on an image is defined as a dilation followed by
an erosion. Closing can remove small dark spots (i.e. "pepper") and connect
small bright cracks. This tends to "close" up (dark) gaps between (bright)
features.
Parameters
----------
image : ndarray
The image as an ndarray.
The image as a uint8 ndarray.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
@@ -121,19 +195,44 @@ def greyscale_close(image, selem, out=None):
-------
opening : ndarray
The result of the morphological opening.
Examples
--------
>>> # Close a gap between two bright lines
>>> from scikits.image.morphology import square
>>> broken_line = np.array([[0, 0, 0, 0, 0],
... [0, 0, 0, 0, 0],
... [1, 1, 0, 1, 1],
... [0, 0, 0, 0, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> greyscale_close(broken_line, square(3))
array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]], dtype='uint8')
"""
h, w = selem.shape
shift_x = True if (w % 2) == 0 else False
shift_y = True if (h % 2) == 0 else False
dilated = greyscale_dilate(image, selem)
out = greyscale_erode(dilated, selem, out=out)
out = greyscale_erode(dilated, selem, out=out,
shift_x=shift_x, shift_y=shift_y)
return out
def greyscale_white_top_hat(image, selem, out=None):
"""
Applies a white top hat on an image given a flat structuring element.
"""Return white top hat of an image.
The white top hat of an image is defined as the image minus its
morphological opening. This operation returns the bright spots of the image
that are smaller than the structuring element.
Parameters
----------
image : ndarray
The image as an ndarray.
The image as a uint8 ndarray.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
@@ -146,23 +245,43 @@ def greyscale_white_top_hat(image, selem, out=None):
-------
opening : ndarray
The result of the morphological white top hat.
"""
Examples
--------
>>> # Subtract grey background from bright peak
>>> from scikits.image.morphology import square
>>> bright_on_grey = np.array([[2, 3, 3, 3, 2],
... [3, 4, 5, 4, 3],
... [3, 5, 9, 5, 3],
... [3, 4, 5, 4, 3],
... [2, 3, 3, 3, 2]], dtype=np.uint8)
>>> greyscale_white_top_hat(bright_on_grey, square(3))
array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 5, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype='uint8')
"""
if image is out:
raise NotImplementedError("Cannot perform white top hat in place.")
eroded = greyscale_erode(image, selem)
out = greyscale_dilate(eroded, selem, out=out)
out = greyscale_open(image, selem, out=out)
out = image - out
return out
def greyscale_black_top_hat(image, selem, out=None):
"""
Applies a black top hat on an image given a flat structuring element.
"""Return black top hat of an image.
The black top hat of an image is defined as its morphological closing minus
the original image. This operation returns the dark spots of the image that
are smaller than the structuring element. Note that dark spots in the
original image are bright spots after the black top hat.
Parameters
----------
image : ndarray
The image as an ndarray.
The image as a uint8 ndarray.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
@@ -175,13 +294,27 @@ def greyscale_black_top_hat(image, selem, out=None):
-------
opening : ndarray
The result of the black top filter.
Examples
--------
>>> # Change dark peak to bright peak and subtract background
>>> from scikits.image.morphology import square
>>> dark_on_grey = np.array([[7, 6, 6, 6, 7],
... [6, 5, 4, 5, 6],
... [6, 4, 0, 4, 6],
... [6, 5, 4, 5, 6],
... [7, 6, 6, 6, 7]], dtype=np.uint8)
>>> greyscale_black_top_hat(dark_on_grey, square(3))
array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 5, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype='uint8')
"""
if image is out:
raise NotImplementedError("Cannot perform white top hat in place.")
dilated = greyscale_dilate(image, selem)
out = greyscale_erode(dilated, selem, out=out)
out = greyscale_close(image, selem, out=out)
out = out - image
if image is out:
raise NotImplementedError("Cannot perform black top hat in place.")
return out
+1 -1
View File
@@ -69,7 +69,7 @@ def diamond(radius, dtype=np.uint8):
Parameters
----------
radius : string
The radius of the disk-shaped structuring element.
The radius of the diamond-shaped structuring element.
dtype : data-type
The data type of the structuring element.
@@ -61,3 +61,58 @@ class TestMorphology():
def test_close_disk(self):
self.morph_worker(lena, "disk-close-matlab-output.npz",
greyscale_close, disk)
class TestEccentricStructuringElements():
def setUp(self):
self.black_pixel = 255 * np.ones((4, 4), dtype=np.uint8)
self.black_pixel[1, 1] = 0
self.white_pixel = 255 - self.black_pixel
self.selems = [square(2), rectangle(2, 2),
rectangle(2, 1), rectangle(1, 2)]
def test_dilate_erode_symmetry(self):
for s in self.selems:
c = greyscale_erode(self.black_pixel, s)
d = greyscale_dilate(self.white_pixel, s)
assert np.all(c == (255 - d))
def test_open_black_pixel(self):
for s in self.selems:
grey_open = greyscale_open(self.black_pixel, s)
assert np.all(grey_open == self.black_pixel)
def test_close_white_pixel(self):
for s in self.selems:
grey_close = greyscale_close(self.white_pixel, s)
assert np.all(grey_close == self.white_pixel)
def test_open_white_pixel(self):
for s in self.selems:
assert np.all(greyscale_open(self.white_pixel, s) == 0)
def test_close_black_pixel(self):
for s in self.selems:
assert np.all(greyscale_close(self.black_pixel, s) == 255)
def test_white_tophat_white_pixel(self):
for s in self.selems:
tophat = greyscale_white_top_hat(self.white_pixel, s)
assert np.all(tophat == self.white_pixel)
def test_black_tophat_black_pixel(self):
for s in self.selems:
tophat = greyscale_black_top_hat(self.black_pixel, s)
assert np.all(tophat == (255 - self.black_pixel))
def test_white_tophat_black_pixel(self):
for s in self.selems:
tophat = greyscale_white_top_hat(self.black_pixel, s)
assert np.all(tophat == 0)
def test_black_tophat_white_pixel(self):
for s in self.selems:
tophat = greyscale_black_top_hat(self.white_pixel, s)
assert np.all(tophat == 0)
+15 -15
View File
@@ -249,32 +249,32 @@ def is_local_maximum(image, labels=None, footprint=None):
>>> image[3, 3] = 1
>>> image
array([[ 0., 0., 0., 0.],
[ 0., 0., 2., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 1.]])
[ 0., 0., 2., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 1.]])
>>> is_local_maximum(image)
array([[ True, False, False, False],
[ True, False, True, False],
[ True, False, False, False],
[ True, True, False, True]], dtype=bool)
[ True, False, True, False],
[ True, False, False, False],
[ True, True, False, True]], dtype='bool')
>>> image = np.arange(16).reshape((4, 4))
>>> labels = np.array([[1, 2], [3, 4]])
>>> labels = np.repeat(np.repeat(labels, 2, axis=0), 2, axis=1)
>>> labels
array([[1, 1, 2, 2],
[1, 1, 2, 2],
[3, 3, 4, 4],
[3, 3, 4, 4]])
[1, 1, 2, 2],
[3, 3, 4, 4],
[3, 3, 4, 4]])
>>> image
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
>>> is_local_maximum(image, labels=labels)
array([[False, False, False, False],
[False, True, False, True],
[False, False, False, False],
[False, True, False, True]], dtype=bool)
[False, True, False, True],
[False, False, False, False],
[False, True, False, True]], dtype='bool')
"""
if labels is None:
labels = np.ones(image.shape, dtype=np.uint8)