ENH: Add reconstruction by erosion.

This commit is contained in:
Tony S Yu
2012-08-18 17:59:44 -04:00
parent 29c84a8a7b
commit 969772c036
+36 -18
View File
@@ -14,14 +14,12 @@ import numpy as np
from skimage.filter.rank_order import rank_order
def reconstruction(image, mask, selem=None, offset=None):
def reconstruction(image, mask, selem=None, offset=None, method='dilation'):
"""Perform a morphological reconstruction of an image.
Reconstruction requires a "seed" image and a "mask" image. Currently, this
only implements reconstruction by dilation, such that the seed image is
dilated until it is constrained by the mask. Thus, he "seed" and "mask"
images will be the minimum and maximum possible values of the reconstructed
image, respectively.
Reconstruction requires a "seed" image and a "mask" image of equal shape.
These images set the minimum and maximum possible values of the
reconstructed image.
Parameters
----------
@@ -31,6 +29,11 @@ def reconstruction(image, mask, selem=None, offset=None):
The maximum allowed value at each point.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
method : {'dilation'|'erosion'}
Perform reconstruction by dilation or erosion. In dilation (erosion),
the seed image is dilated (eroded) until limited by the mask image.
For dilation, each seed value must be less than or equal to the
corresponding mask value; for erosion, the reverse is true.
Returns
-------
@@ -48,7 +51,6 @@ def reconstruction(image, mask, selem=None, offset=None):
[1] Vincent, L., "Morphological Grayscale Reconstruction in Image Analysis:
Applications and Efficient Algorithms", IEEE Transactions on Image
Processing (1993)
[2] Soille, P., "Morphological Image Analysis: Principles and Applications",
Chapter 6, 2nd edition (2003), ISBN 3540429883.
@@ -62,7 +64,7 @@ def reconstruction(image, mask, selem=None, offset=None):
we want to extract:
>>> import numpy as np
>>> from scikits.image.morphology.grey import grey_reconstruction
>>> from skimage.morphology import reconstruction
>>> y, x = np.mgrid[:20:0.5, :20:0.5]
>>> bumps = np.sin(x) + np.sin(y)
@@ -71,7 +73,7 @@ def reconstruction(image, mask, selem=None, offset=None):
>>> h = 0.3
>>> seed = bumps - h
>>> rec = grey_reconstruction(seed, bumps)
>>> rec = reconstruction(seed, bumps)
The resulting reconstructed image looks exactly like the original image,
but with the peaks of the bumps cut off. Subtracting this reconstructed
@@ -86,7 +88,12 @@ def reconstruction(image, mask, selem=None, offset=None):
"""
assert tuple(image.shape) == tuple(mask.shape)
assert np.all(image <= mask)
if method == 'dilation' and np.any(image > mask):
raise ValueError("Intensity of seed image must be less than that "
"of the mask image for reconstruction by dilation.")
elif method == 'erosion' and np.any(image < mask):
raise ValueError("Intensity of seed image must be greater than that "
"of the mask image for reconstruction by erosion.")
try:
from ._greyreconstruct import reconstruction_loop
except ImportError:
@@ -112,7 +119,11 @@ def reconstruction(image, mask, selem=None, offset=None):
inside_slices = [slice(p, -p) for p in padding]
# Set padded region to minimum image intensity and mask along first axis so
# we can interleave image and mask pixels when sorting.
values = np.ones(dims) * np.min(image)
if method == 'dilation':
pad_value = np.min(image)
elif method == 'erosion':
pad_value = np.max(image)
values = np.ones(dims) * pad_value
values[[0] + inside_slices] = image
values[[1] + inside_slices] = mask
@@ -126,23 +137,30 @@ def reconstruction(image, mask, selem=None, offset=None):
nb_strides = np.array([np.sum(value_stride * selem_offset)
for selem_offset in selem_offsets], np.int32)
values = values.flatten()
value_sort = np.lexsort([-values]).astype(np.int32)
index_sorted = np.argsort(-values).astype(np.int32)
if method == 'erosion':
index_sorted = index_sorted[::-1]
# Make a linked list of pixels sorted by value. -1 is the list terminator.
prev = -np.ones(len(values), np.int32)
next = -np.ones(len(values), np.int32)
prev[value_sort[1:]] = value_sort[:-1]
next[value_sort[:-1]] = value_sort[1:]
prev[index_sorted[1:]] = index_sorted[:-1]
next[index_sorted[:-1]] = index_sorted[1:]
# Create a rank-order value array so that the Cython inner-loop
# can operate on a uniform data type
rec_img, value_map = rank_order(values)
current = value_sort[0]
if method == 'dilation':
value_rank, value_map = rank_order(values)
elif method == 'erosion':
value_rank, value_map = rank_order(-values)
value_map = -value_map
current = index_sorted[0]
reconstruction_loop(rec_img, prev, next, nb_strides, current, image_stride)
reconstruction_loop(value_rank, prev, next, nb_strides, current,
image_stride)
# Reshape reconstructed image to original image shape and remove padding.
rec_img = value_map[rec_img[:image_stride]]
rec_img = value_map[value_rank[:image_stride]]
rec_img.shape = np.array(image.shape) + 2 * padding
return rec_img[inside_slices]