mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-29 03:37:34 +08:00
b6dcf3c336
Update Travis build to use Anaconda Travis updates and fixes More travis fixes Another travis attempt Revert changes Use PIL and Pillow Refactor travis into 4 different builds Fix activation error Remove explicit mpl in build_versions.py Make matplotlib an explicit requirement Rearrange travis Make pillow a hard requirement Try again to make Pillow optional Fix bash syntax error Fix bash syntax error Bump required cython version More rearrangments Remove mpl from build_versions, rearrange travis Fix version check Make matplotlib explicit again Conda install into test env Check for proper install Allow tests to skip if networkx is not available Allow tests to skip if networkx is not available Try swapping pillow for matplotlib Allow tests to pass when matplotlib is not present Remove matplotlib from build_versions Print PIL version Get pillow from PIP Allow tests to skip if matplotlib is not present. Allow tests to skip if networkx is not present. travis fix Remove unused mpl import that caused test error Use nose-cov and do not run doctests without optional libs Bump required numpy version and fix nose calls Make overlay test repeatable bump numpy version again Move low-end numpy to python 2.7 Play with minimum versions Add version requirements and use functions Add version requirements and use functions Allow require to skip a test More implementation of require decorator Update require decorator and clean up tests Only use requires decorator when needed Fix python3 error in version_requirements Fix build errors Fix handling of require with tests More fixes for require handler Use latest miniconda Fix more build errors Fix another dict comprehension and travis file. Fix missing imports Fix dictionary again Fix import warning Fix last failing test on 2.6 Skip doc examples on python2.6 Do not run doctests on python2.6 Fix typo in travis.yml Make numpy-1.6 compatibility changes Use numpy-1.6 in travis python2.6 Add tests for version requirements Fix line noise in PR Add additional io plugins Fix simpleitk test. Fix python 3 error in freeimage_plugin. Install imread in Travis. Put matplotlib settings in XDG recommended directory Fix formatting in travis yml Fix formatting in travis yml Make sure to close PIL file atexit Fix name of apt package xcftools Fix pil fp closing Fix matplotlibrc creation Only download SimpleITK on py2x, run coverage on py27 Fix travis yml syntax error Run coveralls on py2.7 Install SimpleITK on py3.3 and run coverage on py3.3 Make simpleitk install quiet Use standard nose and clean up incantation Fix travis yml syntax error Put in miniconda workout for libc error. Fix imread plugin. Fix travis syntax Remove unused import Remove miniconda libpng in favor of system png Fix imread install and move libm removal to after optional pkg install. Fix png header copy in travis yml Another attempt to use png headers Debug freeimage Add jpeg library for freeimage and debug imread. More debug for imread and freeimage More freeimage and imread debugging More debugging Use correct paths for test env Make sure imread is tied to libpng15 Add a TODO note for simpleitk test causing error. Fix typo in yml Cleanup and add more comments to travis yml Update comment Try and add 3.2 support. Docstring formatting Add more travis comments. Try numpy 1.6 on python 2.7 Fix travis syntax error Rename CONDA to ENV for clarity Alias python on python 3.2 Use python 3.2 as the system python Clean up libfreeimage install Fix order on py3.2 pre_install Move old numpy back to py26 Use the appropriate python calls. Debug 3.2 build. Update comment Fix syntax error Another fix for syntax error. Install scipy after downloading import tools More debugging for py32 Do not install conda on py3.2 (duh) Fix typo in travis yml Fix py32 qt install, separate pyfits and imread to find error Fix syntax error and front-load option lib check for debug pyfits is not supported in py3.2, try imread now imread is also not supported on py3.2 install imread before pyfits to show relationship with libs Make pip builds quiet Minor formatting to retrigger build Allow simpleitk to fail to download without breaking the build Use travis_retry for SimpleITK See what breaks when we keep libm in Now remove libm again
197 lines
8.1 KiB
Python
197 lines
8.1 KiB
Python
"""
|
|
This morphological reconstruction routine was adapted from CellProfiler, code
|
|
licensed under both GPL and BSD licenses.
|
|
|
|
Website: http://www.cellprofiler.org
|
|
Copyright (c) 2003-2009 Massachusetts Institute of Technology
|
|
Copyright (c) 2009-2011 Broad Institute
|
|
All rights reserved.
|
|
Original author: Lee Kamentsky
|
|
|
|
"""
|
|
import numpy as np
|
|
|
|
from skimage.filter._rank_order import rank_order
|
|
|
|
|
|
def reconstruction(seed, mask, method='dilation', selem=None, offset=None):
|
|
"""Perform a morphological reconstruction of an image.
|
|
|
|
Morphological reconstruction by dilation is similar to basic morphological
|
|
dilation: high-intensity values will replace nearby low-intensity values.
|
|
The basic dilation operator, however, uses a structuring element to
|
|
determine how far a value in the input image can spread. In contrast,
|
|
reconstruction uses two images: a "seed" image, which specifies the values
|
|
that spread, and a "mask" image, which gives the maximum allowed value at
|
|
each pixel. The mask image, like the structuring element, limits the spread
|
|
of high-intensity values. Reconstruction by erosion is simply the inverse:
|
|
low-intensity values spread from the seed image and are limited by the mask
|
|
image, which represents the minimum allowed value.
|
|
|
|
Alternatively, you can think of reconstruction as a way to isolate the
|
|
connected regions of an image. For dilation, reconstruction connects
|
|
regions marked by local maxima in the seed image: neighboring pixels
|
|
less-than-or-equal-to those seeds are connected to the seeded region.
|
|
Local maxima with values larger than the seed image will get truncated to
|
|
the seed value.
|
|
|
|
Parameters
|
|
----------
|
|
seed : ndarray
|
|
The seed image (a.k.a. marker image), which specifies the values that
|
|
are dilated or eroded.
|
|
mask : ndarray
|
|
The maximum (dilation) / minimum (erosion) allowed value at each pixel.
|
|
method : {'dilation'|'erosion'}
|
|
Perform reconstruction by dilation or erosion. In dilation (or
|
|
erosion), the seed image is dilated (or 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.
|
|
selem : ndarray
|
|
The neighborhood expressed as a 2-D array of 1's and 0's.
|
|
|
|
Returns
|
|
-------
|
|
reconstructed : ndarray
|
|
The result of morphological reconstruction.
|
|
|
|
Examples
|
|
--------
|
|
>>> import numpy as np
|
|
>>> from skimage.morphology import reconstruction
|
|
|
|
First, we create a sinusoidal mask image with peaks at middle and ends.
|
|
|
|
>>> x = np.linspace(0, 4 * np.pi)
|
|
>>> y_mask = np.cos(x)
|
|
|
|
Then, we create a seed image initialized to the minimum mask value (for
|
|
reconstruction by dilation, min-intensity values don't spread) and add
|
|
"seeds" to the left and right peak, but at a fraction of peak value (1).
|
|
|
|
>>> y_seed = y_mask.min() * np.ones_like(x)
|
|
>>> y_seed[0] = 0.5
|
|
>>> y_seed[-1] = 0
|
|
>>> y_rec = reconstruction(y_seed, y_mask)
|
|
|
|
The reconstructed image (or curve, in this case) is exactly the same as the
|
|
mask image, except that the peaks are truncated to 0.5 and 0. The middle
|
|
peak disappears completely: Since there were no seed values in this peak
|
|
region, its reconstructed value is truncated to the surrounding value (-1).
|
|
|
|
As a more practical example, we try to extract the bright features of an
|
|
image by subtracting a background image created by reconstruction.
|
|
|
|
>>> y, x = np.mgrid[:20:0.5, :20:0.5]
|
|
>>> bumps = np.sin(x) + np.sin(y)
|
|
|
|
To create the background image, set the mask image to the original image,
|
|
and the seed image to the original image with an intensity offset, `h`.
|
|
|
|
>>> h = 0.3
|
|
>>> seed = bumps - h
|
|
>>> background = 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
|
|
image from the original image leaves just the peaks of the bumps
|
|
|
|
>>> hdome = bumps - background
|
|
|
|
This operation is known as the h-dome of the image and leaves features
|
|
of height `h` in the subtracted image.
|
|
|
|
Notes
|
|
-----
|
|
The algorithm is taken from [1]_. Applications for greyscale reconstruction
|
|
are discussed in [2]_ and [3]_.
|
|
|
|
References
|
|
----------
|
|
.. [1] Robinson, "Efficient morphological reconstruction: a downhill
|
|
filter", Pattern Recognition Letters 25 (2004) 1759-1767.
|
|
.. [2] Vincent, L., "Morphological Grayscale Reconstruction in Image
|
|
Analysis: Applications and Efficient Algorithms", IEEE Transactions
|
|
on Image Processing (1993)
|
|
.. [3] Soille, P., "Morphological Image Analysis: Principles and
|
|
Applications", Chapter 6, 2nd edition (2003), ISBN 3540429883.
|
|
"""
|
|
assert tuple(seed.shape) == tuple(mask.shape)
|
|
if method == 'dilation' and np.any(seed > 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(seed < 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:
|
|
raise ImportError("_greyreconstruct extension not available.")
|
|
|
|
if selem is None:
|
|
selem = np.ones([3] * seed.ndim, dtype=bool)
|
|
else:
|
|
selem = selem.astype(bool)
|
|
|
|
if offset is None:
|
|
if not all([d % 2 == 1 for d in selem.shape]):
|
|
raise ValueError("Footprint dimensions must all be odd")
|
|
offset = np.array([d // 2 for d in selem.shape])
|
|
# Cross out the center of the selem
|
|
selem[[slice(d, d + 1) for d in offset]] = False
|
|
|
|
# Make padding for edges of reconstructed image so we can ignore boundaries
|
|
padding = (np.array(selem.shape) / 2).astype(int)
|
|
dims = np.zeros(seed.ndim + 1, dtype=int)
|
|
dims[1:] = np.array(seed.shape) + 2 * padding
|
|
dims[0] = 2
|
|
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.
|
|
if method == 'dilation':
|
|
pad_value = np.min(seed)
|
|
elif method == 'erosion':
|
|
pad_value = np.max(seed)
|
|
images = np.ones(dims) * pad_value
|
|
images[[0] + inside_slices] = seed
|
|
images[[1] + inside_slices] = mask
|
|
|
|
# Create a list of strides across the array to get the neighbors within
|
|
# a flattened array
|
|
value_stride = np.array(images.strides[1:]) // images.dtype.itemsize
|
|
image_stride = images.strides[0] // images.dtype.itemsize
|
|
selem_mgrid = np.mgrid[[slice(-o, d - o)
|
|
for d, o in zip(selem.shape, offset)]]
|
|
selem_offsets = selem_mgrid[:, selem].transpose()
|
|
nb_strides = np.array([np.sum(value_stride * selem_offset)
|
|
for selem_offset in selem_offsets], np.int32)
|
|
|
|
images = images.flatten()
|
|
|
|
# Erosion goes smallest to largest; dilation goes largest to smallest.
|
|
index_sorted = np.argsort(images).astype(np.int32)
|
|
if method == 'dilation':
|
|
index_sorted = index_sorted[::-1]
|
|
|
|
# Make a linked list of pixels sorted by value. -1 is the list terminator.
|
|
prev = -np.ones(len(images), np.int32)
|
|
next = -np.ones(len(images), np.int32)
|
|
prev[index_sorted[1:]] = index_sorted[:-1]
|
|
next[index_sorted[:-1]] = index_sorted[1:]
|
|
|
|
# Cython inner-loop compares the rank of pixel values.
|
|
if method == 'dilation':
|
|
value_rank, value_map = rank_order(images)
|
|
elif method == 'erosion':
|
|
value_rank, value_map = rank_order(-images)
|
|
value_map = -value_map
|
|
|
|
start = index_sorted[0]
|
|
reconstruction_loop(value_rank, prev, next, nb_strides, start,
|
|
image_stride)
|
|
|
|
# Reshape reconstructed image to original image shape and remove padding.
|
|
rec_img = value_map[value_rank[:image_stride]]
|
|
rec_img.shape = np.array(seed.shape) + 2 * padding
|
|
return rec_img[inside_slices]
|