Files
scikit-image/skimage/morphology/greyreconstruct.py
T
blink1073 b6dcf3c336 Update Travis build to use Anaconda.
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
2014-08-02 06:47:09 -05:00

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]