| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- import numpy as np
- from scipy.ndimage import distance_transform_edt
- def expand_labels(label_image, distance=1, spacing=1):
- """Expand labels in label image by ``distance`` pixels without overlapping.
- Given a label image, ``expand_labels`` grows label regions (connected components)
- outwards by up to ``distance`` units without overflowing into neighboring regions.
- More specifically, each background pixel that is within Euclidean distance
- of <= ``distance`` pixels of a connected component is assigned the label of that
- connected component. The `spacing` parameter can be used to specify the spacing
- rate of the distance transform used to calculate the Euclidean distance for anisotropic
- images.
- Where multiple connected components are within ``distance`` pixels of a background
- pixel, the label value of the closest connected component will be assigned (see
- Notes for the case of multiple labels at equal distance).
- Parameters
- ----------
- label_image : ndarray of dtype int
- label image
- distance : float
- Euclidean distance in pixels by which to grow the labels. Default is one.
- spacing : float, or sequence of float, optional
- Spacing of elements along each dimension. If a sequence, must be of length
- equal to the input rank; if a single number, this is used for all axes. If
- not specified, a grid spacing of unity is implied.
- Returns
- -------
- enlarged_labels : ndarray of dtype int
- Labeled array, where all connected regions have been enlarged
- Notes
- -----
- Where labels are spaced more than ``distance`` pixels are apart, this is
- equivalent to a morphological dilation with a disc or hyperball of radius ``distance``.
- However, in contrast to a morphological dilation, ``expand_labels`` will
- not expand a label region into a neighboring region.
- This implementation of ``expand_labels`` is derived from CellProfiler [1]_, where
- it is known as module "IdentifySecondaryObjects (Distance-N)" [2]_.
- There is an important edge case when a pixel has the same distance to
- multiple regions, as it is not defined which region expands into that
- space. Here, the exact behavior depends on the upstream implementation
- of ``scipy.ndimage.distance_transform_edt``.
- See Also
- --------
- :func:`skimage.measure.label`, :func:`skimage.segmentation.watershed`, :func:`skimage.morphology.dilation`
- References
- ----------
- .. [1] https://cellprofiler.org
- .. [2] https://github.com/CellProfiler/CellProfiler/blob/082930ea95add7b72243a4fa3d39ae5145995e9c/cellprofiler/modules/identifysecondaryobjects.py#L559
- Examples
- --------
- >>> labels = np.array([0, 1, 0, 0, 0, 0, 2])
- >>> expand_labels(labels, distance=1)
- array([1, 1, 1, 0, 0, 2, 2])
- Labels will not overwrite each other:
- >>> expand_labels(labels, distance=3)
- array([1, 1, 1, 1, 2, 2, 2])
- In case of ties, behavior is undefined, but currently resolves to the
- label closest to ``(0,) * ndim`` in lexicographical order.
- >>> labels_tied = np.array([0, 1, 0, 2, 0])
- >>> expand_labels(labels_tied, 1)
- array([1, 1, 1, 2, 2])
- >>> labels2d = np.array(
- ... [[0, 1, 0, 0],
- ... [2, 0, 0, 0],
- ... [0, 3, 0, 0]]
- ... )
- >>> expand_labels(labels2d, 1)
- array([[2, 1, 1, 0],
- [2, 2, 0, 0],
- [2, 3, 3, 0]])
- >>> expand_labels(labels2d, 1, spacing=[1, 0.5])
- array([[1, 1, 1, 1],
- [2, 2, 2, 0],
- [3, 3, 3, 3]])
- """
- distances, nearest_label_coords = distance_transform_edt(
- label_image == 0, sampling=spacing, return_indices=True
- )
- labels_out = np.zeros_like(label_image)
- dilate_mask = distances <= distance
- # build the coordinates to find nearest labels,
- # in contrast to [1] this implementation supports label arrays
- # of any dimension
- masked_nearest_label_coords = [
- dimension_indices[dilate_mask] for dimension_indices in nearest_label_coords
- ]
- nearest_labels = label_image[tuple(masked_nearest_label_coords)]
- labels_out[dilate_mask] = nearest_labels
- return labels_out
|