mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-01 05:25:01 +08:00
Merge pull request #62 from tonysyu/fix_morph_symmetry
Fix morph symmetry
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user