torch_em.util.segmentation

  1from typing import Optional, List
  2
  3import numpy as np
  4
  5from skimage.feature import peak_local_max
  6
  7import bioimage_cpp as bic
  8
  9import elf.segmentation as elseg
 10from elf.segmentation.utils import normalize_input
 11from elf.segmentation.mutex_watershed import mutex_watershed
 12
 13
 14#
 15# Segmentation Functionality
 16#
 17
 18
 19def size_filter(
 20    seg: np.ndarray, min_size: int, hmap: Optional[np.ndarray] = None, with_background: bool = False
 21) -> np.ndarray:
 22    """Apply size filter to a segmentation to remove small segments.
 23
 24    Args:
 25        seg: The input segmentation.
 26        min_size: The minimal segmentation size.
 27        hmap: A heightmap to use for watershed based segmentation filtering.
 28        with_background: Whether this is a segmentation problem with background.
 29
 30    Returns:
 31        The filtered segmentation.
 32    """
 33    if min_size == 0:
 34        return seg
 35
 36    if hmap is None:
 37        ids, sizes = np.unique(seg, return_counts=True)
 38        bg_ids = ids[sizes < min_size]
 39        seg[np.isin(seg, bg_ids)] = 0
 40        seg, _, _ = bic.segmentation.relabel_sequential(seg.astype(np.uint), offset=1)
 41    else:
 42        assert hmap.ndim in (seg.ndim, seg.ndim + 1)
 43        hmap_ = np.max(hmap[:seg.ndim], axis=0) if hmap.ndim > seg.ndim else hmap
 44        if with_background:
 45            seg, _ = elseg.watershed.apply_size_filter(seg + 1, hmap_, min_size, exclude=[1])
 46            seg[seg == 1] = 0
 47        else:
 48            seg, _ = elseg.watershed.apply_size_filter(seg, hmap_, min_size)
 49    return seg
 50
 51
 52def mutex_watershed_segmentation(
 53    foreground: np.ndarray,
 54    affinities: np.ndarray,
 55    offsets: List[List[int]],
 56    min_size: int = 50,
 57    threshold: float = 0.5,
 58    strides: Optional[List[int]] = None
 59) -> np.ndarray:
 60    """Compute the mutex watershed segmentation using the affinity map for given pixel offsets.
 61
 62    Args:
 63        foreground: The foreground/background probabilities.
 64        affinities: The input affinity maps.
 65        offsets: The pixel offsets corresponding to the affinity channels.
 66        min_size: The minimum pixel size for objects in the output segmentation.
 67        threshold: The threshold for the foreground predictions.
 68        strides: The strides used to subsample long range edges.
 69
 70    Returns:
 71        The instance segmentation.
 72    """
 73    mask = (foreground >= threshold)
 74    if strides is None:
 75        strides = [2] * foreground.ndim
 76
 77    seg = mutex_watershed(affinities, offsets=offsets, mask=mask, strides=strides, randomize_strides=True)
 78    seg = size_filter(seg.astype("uint32"), min_size=min_size, hmap=affinities, with_background=True)
 79
 80    return seg
 81
 82
 83def connected_components_with_boundaries(
 84    foreground: np.ndarray, boundaries: np.ndarray, threshold: float = 0.5
 85) -> np.ndarray:
 86    """Compute instance segmentation based on foreground and boundary predictions.
 87
 88    Args:
 89        foreground: The foreground probability predictions.
 90        boundaries: The boundary probability predictions.
 91        threshold: The threshold for finding connected components.
 92
 93    Returns:
 94        The instance segmentation.
 95    """
 96    input_ = np.clip(foreground - boundaries, 0, 1)
 97    seeds = bic.segmentation.label(input_ > threshold)
 98    mask = normalize_input(foreground > threshold)
 99    seg = bic.segmentation.watershed(boundaries, markers=seeds, mask=mask.astype(bool))
100    return seg.astype("uint64")
101
102
103def watershed_from_components(
104    boundaries: np.ndarray,
105    foreground: np.ndarray,
106    min_size: int = 50,
107    threshold1: float = 0.5,
108    threshold2: float = 0.5,
109) -> np.ndarray:
110    """Compute an instance segmentation based on boundary and foreground predictions
111
112    The segmentation is computed as follows:
113    - Subtract the boundaries from the foreground to separate touching objects.
114    - Use the connected components of the result as seeds.
115    - Use the thresholded foreground predictions as mask to grow back the pieces
116      lost by subtracting the boundary prediction.
117
118    Args:
119        boundaries: The boundary probability predictions.
120        foreground: The foreground probability predictions.
121        min_size: The minimum pixel size for objects in the output segmentation.
122        threshold1: The threshold for finding connected components.
123        threshold2: The threshold for growing components via watershed on the boundary predictions.
124
125    Returns:
126        The instance segmentation.
127    """
128    seeds = bic.segmentation.label((foreground - boundaries) > threshold1)
129    mask = foreground > threshold2
130    seg = bic.segmentation.watershed(boundaries, seeds, mask=mask)
131    seg = size_filter(seg, min_size)
132    return seg
133
134
135def watershed_from_maxima(
136    boundaries: np.ndarray,
137    foreground: np.ndarray,
138    min_distance: int,
139    min_size: int = 50,
140    sigma: float = 1.0,
141    threshold1: float = 0.5,
142) -> np.ndarray:
143    """Compute an instance segmentation based on a seeded watershed from distance maxima.
144
145    This function thresholds the boundary probabilities, computes a distance transform,
146    finds the distance maxima, and then applies a seeded watershed to obtain the instance segmentation.
147    Compared to `watershed_from_components` this has the advantage that objects can be better separated,
148    but it may over-segment objects with complex shapes.
149
150    The min_distance parameter controls the minimal distance between seeds, which
151    corresponds to the minimal distance between object centers.
152
153    Args:
154        boundaries: The boundary probability predictions.
155        foreground: The foreground probability predictions.
156        min_size: The minimum pixel size for objects in the output segmentation.
157        min_distance: The minimum distance between peaks, see `from skimage.feature.peak_local_max`.
158        sigma: The standard deviation for smoothing the distance map before computing maxima.
159        threshold1: The threshold for foreground predictions.
160
161    Returns:
162        The instance segmentation.
163    """
164    mask = foreground > threshold1
165    boundary_distances = bic.distance.distance_transform(boundaries < 0.1)
166    boundary_distances[~mask] = 0
167    boundary_distances = bic.filters.gaussian_smoothing(boundary_distances, sigma)
168    seed_points = peak_local_max(boundary_distances, min_distance=min_distance, exclude_border=False)
169    seeds = np.zeros(mask.shape, dtype="uint32")
170    seeds[seed_points[:, 0], seed_points[:, 1]] = np.arange(1, len(seed_points) + 1)
171    seg = bic.segmentation.watershed(boundaries, markers=seeds, mask=foreground.astype(bool))
172    return size_filter(seg, min_size)
173
174
175def watershed_from_center_and_boundary_distances(
176    center_distances: np.ndarray,
177    boundary_distances: np.ndarray,
178    foreground_map: np.ndarray,
179    center_distance_threshold: float = 0.5,
180    boundary_distance_threshold: float = 0.5,
181    foreground_threshold: float = 0.5,
182    distance_smoothing: float = 1.6,
183    min_size: int = 0,
184    debug: bool = False,
185) -> np.ndarray:
186    """Compute an instance segmentation via a seeded watershed on distances to object centers and boundaries.
187
188    The seeds are computed by finding connected components where both distance predictions
189    are smaller than the respective thresholds. Using both distances is supposed to prevent merging
190    narrow adjacent objects (if only using the center distance) or finding multiple seeds for non-convex
191    cells (if only using the boundary distances).
192
193    Args:
194        center_distances: Distance prediction to the objcet centers.
195        boundary_distances: Inverted distance prediction to object boundaries.
196        foreground_map: Prediction for foreground probabilities.
197        center_distance_threshold: Center distance predictions below this value will be
198            used to find seeds (intersected with thresholded boundary distance predictions).
199        boundary_distance_threshold: oundary distance predictions below this value will be
200            used to find seeds (intersected with thresholded center distance predictions).
201        foreground_threshold: Foreground predictions above this value will be used as foreground mask.
202        distance_smoothing: Sigma value for smoothing the distance predictions.
203        min_size: Minimal object size in the segmentation result.
204        debug: Return all intermediate results in a dictionary for debugging.
205
206    Returns:
207        The instance segmentation.
208    """
209    if distance_smoothing > 0:
210        center_distances = bic.filters.gaussian_smoothing(center_distances, distance_smoothing)
211        boundary_distances = bic.filters.gaussian_smoothing(boundary_distances, distance_smoothing)
212
213    fg_mask = foreground_map > foreground_threshold
214
215    marker_map = np.logical_and(
216        center_distances < center_distance_threshold, boundary_distances < boundary_distance_threshold
217    )
218    marker_map[~fg_mask] = 0
219    markers = bic.segmentation.label(marker_map)
220
221    seg = bic.segmentation.watershed(boundary_distances, markers=markers, mask=fg_mask)
222    seg = size_filter(seg, min_size)
223
224    if debug:
225        debug_output = {
226            "center_distances": center_distances,
227            "boundary_distances": boundary_distances,
228            "foreground_mask": fg_mask,
229            "markers": markers,
230        }
231        return seg, debug_output
232
233    return seg
def size_filter( seg: numpy.ndarray, min_size: int, hmap: Optional[numpy.ndarray] = None, with_background: bool = False) -> numpy.ndarray:
20def size_filter(
21    seg: np.ndarray, min_size: int, hmap: Optional[np.ndarray] = None, with_background: bool = False
22) -> np.ndarray:
23    """Apply size filter to a segmentation to remove small segments.
24
25    Args:
26        seg: The input segmentation.
27        min_size: The minimal segmentation size.
28        hmap: A heightmap to use for watershed based segmentation filtering.
29        with_background: Whether this is a segmentation problem with background.
30
31    Returns:
32        The filtered segmentation.
33    """
34    if min_size == 0:
35        return seg
36
37    if hmap is None:
38        ids, sizes = np.unique(seg, return_counts=True)
39        bg_ids = ids[sizes < min_size]
40        seg[np.isin(seg, bg_ids)] = 0
41        seg, _, _ = bic.segmentation.relabel_sequential(seg.astype(np.uint), offset=1)
42    else:
43        assert hmap.ndim in (seg.ndim, seg.ndim + 1)
44        hmap_ = np.max(hmap[:seg.ndim], axis=0) if hmap.ndim > seg.ndim else hmap
45        if with_background:
46            seg, _ = elseg.watershed.apply_size_filter(seg + 1, hmap_, min_size, exclude=[1])
47            seg[seg == 1] = 0
48        else:
49            seg, _ = elseg.watershed.apply_size_filter(seg, hmap_, min_size)
50    return seg

Apply size filter to a segmentation to remove small segments.

Arguments:
  • seg: The input segmentation.
  • min_size: The minimal segmentation size.
  • hmap: A heightmap to use for watershed based segmentation filtering.
  • with_background: Whether this is a segmentation problem with background.
Returns:

The filtered segmentation.

def mutex_watershed_segmentation( foreground: numpy.ndarray, affinities: numpy.ndarray, offsets: List[List[int]], min_size: int = 50, threshold: float = 0.5, strides: Optional[List[int]] = None) -> numpy.ndarray:
53def mutex_watershed_segmentation(
54    foreground: np.ndarray,
55    affinities: np.ndarray,
56    offsets: List[List[int]],
57    min_size: int = 50,
58    threshold: float = 0.5,
59    strides: Optional[List[int]] = None
60) -> np.ndarray:
61    """Compute the mutex watershed segmentation using the affinity map for given pixel offsets.
62
63    Args:
64        foreground: The foreground/background probabilities.
65        affinities: The input affinity maps.
66        offsets: The pixel offsets corresponding to the affinity channels.
67        min_size: The minimum pixel size for objects in the output segmentation.
68        threshold: The threshold for the foreground predictions.
69        strides: The strides used to subsample long range edges.
70
71    Returns:
72        The instance segmentation.
73    """
74    mask = (foreground >= threshold)
75    if strides is None:
76        strides = [2] * foreground.ndim
77
78    seg = mutex_watershed(affinities, offsets=offsets, mask=mask, strides=strides, randomize_strides=True)
79    seg = size_filter(seg.astype("uint32"), min_size=min_size, hmap=affinities, with_background=True)
80
81    return seg

Compute the mutex watershed segmentation using the affinity map for given pixel offsets.

Arguments:
  • foreground: The foreground/background probabilities.
  • affinities: The input affinity maps.
  • offsets: The pixel offsets corresponding to the affinity channels.
  • min_size: The minimum pixel size for objects in the output segmentation.
  • threshold: The threshold for the foreground predictions.
  • strides: The strides used to subsample long range edges.
Returns:

The instance segmentation.

def connected_components_with_boundaries( foreground: numpy.ndarray, boundaries: numpy.ndarray, threshold: float = 0.5) -> numpy.ndarray:
 84def connected_components_with_boundaries(
 85    foreground: np.ndarray, boundaries: np.ndarray, threshold: float = 0.5
 86) -> np.ndarray:
 87    """Compute instance segmentation based on foreground and boundary predictions.
 88
 89    Args:
 90        foreground: The foreground probability predictions.
 91        boundaries: The boundary probability predictions.
 92        threshold: The threshold for finding connected components.
 93
 94    Returns:
 95        The instance segmentation.
 96    """
 97    input_ = np.clip(foreground - boundaries, 0, 1)
 98    seeds = bic.segmentation.label(input_ > threshold)
 99    mask = normalize_input(foreground > threshold)
100    seg = bic.segmentation.watershed(boundaries, markers=seeds, mask=mask.astype(bool))
101    return seg.astype("uint64")

Compute instance segmentation based on foreground and boundary predictions.

Arguments:
  • foreground: The foreground probability predictions.
  • boundaries: The boundary probability predictions.
  • threshold: The threshold for finding connected components.
Returns:

The instance segmentation.

def watershed_from_components( boundaries: numpy.ndarray, foreground: numpy.ndarray, min_size: int = 50, threshold1: float = 0.5, threshold2: float = 0.5) -> numpy.ndarray:
104def watershed_from_components(
105    boundaries: np.ndarray,
106    foreground: np.ndarray,
107    min_size: int = 50,
108    threshold1: float = 0.5,
109    threshold2: float = 0.5,
110) -> np.ndarray:
111    """Compute an instance segmentation based on boundary and foreground predictions
112
113    The segmentation is computed as follows:
114    - Subtract the boundaries from the foreground to separate touching objects.
115    - Use the connected components of the result as seeds.
116    - Use the thresholded foreground predictions as mask to grow back the pieces
117      lost by subtracting the boundary prediction.
118
119    Args:
120        boundaries: The boundary probability predictions.
121        foreground: The foreground probability predictions.
122        min_size: The minimum pixel size for objects in the output segmentation.
123        threshold1: The threshold for finding connected components.
124        threshold2: The threshold for growing components via watershed on the boundary predictions.
125
126    Returns:
127        The instance segmentation.
128    """
129    seeds = bic.segmentation.label((foreground - boundaries) > threshold1)
130    mask = foreground > threshold2
131    seg = bic.segmentation.watershed(boundaries, seeds, mask=mask)
132    seg = size_filter(seg, min_size)
133    return seg

Compute an instance segmentation based on boundary and foreground predictions

The segmentation is computed as follows:

  • Subtract the boundaries from the foreground to separate touching objects.
  • Use the connected components of the result as seeds.
  • Use the thresholded foreground predictions as mask to grow back the pieces lost by subtracting the boundary prediction.
Arguments:
  • boundaries: The boundary probability predictions.
  • foreground: The foreground probability predictions.
  • min_size: The minimum pixel size for objects in the output segmentation.
  • threshold1: The threshold for finding connected components.
  • threshold2: The threshold for growing components via watershed on the boundary predictions.
Returns:

The instance segmentation.

def watershed_from_maxima( boundaries: numpy.ndarray, foreground: numpy.ndarray, min_distance: int, min_size: int = 50, sigma: float = 1.0, threshold1: float = 0.5) -> numpy.ndarray:
136def watershed_from_maxima(
137    boundaries: np.ndarray,
138    foreground: np.ndarray,
139    min_distance: int,
140    min_size: int = 50,
141    sigma: float = 1.0,
142    threshold1: float = 0.5,
143) -> np.ndarray:
144    """Compute an instance segmentation based on a seeded watershed from distance maxima.
145
146    This function thresholds the boundary probabilities, computes a distance transform,
147    finds the distance maxima, and then applies a seeded watershed to obtain the instance segmentation.
148    Compared to `watershed_from_components` this has the advantage that objects can be better separated,
149    but it may over-segment objects with complex shapes.
150
151    The min_distance parameter controls the minimal distance between seeds, which
152    corresponds to the minimal distance between object centers.
153
154    Args:
155        boundaries: The boundary probability predictions.
156        foreground: The foreground probability predictions.
157        min_size: The minimum pixel size for objects in the output segmentation.
158        min_distance: The minimum distance between peaks, see `from skimage.feature.peak_local_max`.
159        sigma: The standard deviation for smoothing the distance map before computing maxima.
160        threshold1: The threshold for foreground predictions.
161
162    Returns:
163        The instance segmentation.
164    """
165    mask = foreground > threshold1
166    boundary_distances = bic.distance.distance_transform(boundaries < 0.1)
167    boundary_distances[~mask] = 0
168    boundary_distances = bic.filters.gaussian_smoothing(boundary_distances, sigma)
169    seed_points = peak_local_max(boundary_distances, min_distance=min_distance, exclude_border=False)
170    seeds = np.zeros(mask.shape, dtype="uint32")
171    seeds[seed_points[:, 0], seed_points[:, 1]] = np.arange(1, len(seed_points) + 1)
172    seg = bic.segmentation.watershed(boundaries, markers=seeds, mask=foreground.astype(bool))
173    return size_filter(seg, min_size)

Compute an instance segmentation based on a seeded watershed from distance maxima.

This function thresholds the boundary probabilities, computes a distance transform, finds the distance maxima, and then applies a seeded watershed to obtain the instance segmentation. Compared to watershed_from_components this has the advantage that objects can be better separated, but it may over-segment objects with complex shapes.

The min_distance parameter controls the minimal distance between seeds, which corresponds to the minimal distance between object centers.

Arguments:
  • boundaries: The boundary probability predictions.
  • foreground: The foreground probability predictions.
  • min_size: The minimum pixel size for objects in the output segmentation.
  • min_distance: The minimum distance between peaks, see from skimage.feature.peak_local_max.
  • sigma: The standard deviation for smoothing the distance map before computing maxima.
  • threshold1: The threshold for foreground predictions.
Returns:

The instance segmentation.

def watershed_from_center_and_boundary_distances( center_distances: numpy.ndarray, boundary_distances: numpy.ndarray, foreground_map: numpy.ndarray, center_distance_threshold: float = 0.5, boundary_distance_threshold: float = 0.5, foreground_threshold: float = 0.5, distance_smoothing: float = 1.6, min_size: int = 0, debug: bool = False) -> numpy.ndarray:
176def watershed_from_center_and_boundary_distances(
177    center_distances: np.ndarray,
178    boundary_distances: np.ndarray,
179    foreground_map: np.ndarray,
180    center_distance_threshold: float = 0.5,
181    boundary_distance_threshold: float = 0.5,
182    foreground_threshold: float = 0.5,
183    distance_smoothing: float = 1.6,
184    min_size: int = 0,
185    debug: bool = False,
186) -> np.ndarray:
187    """Compute an instance segmentation via a seeded watershed on distances to object centers and boundaries.
188
189    The seeds are computed by finding connected components where both distance predictions
190    are smaller than the respective thresholds. Using both distances is supposed to prevent merging
191    narrow adjacent objects (if only using the center distance) or finding multiple seeds for non-convex
192    cells (if only using the boundary distances).
193
194    Args:
195        center_distances: Distance prediction to the objcet centers.
196        boundary_distances: Inverted distance prediction to object boundaries.
197        foreground_map: Prediction for foreground probabilities.
198        center_distance_threshold: Center distance predictions below this value will be
199            used to find seeds (intersected with thresholded boundary distance predictions).
200        boundary_distance_threshold: oundary distance predictions below this value will be
201            used to find seeds (intersected with thresholded center distance predictions).
202        foreground_threshold: Foreground predictions above this value will be used as foreground mask.
203        distance_smoothing: Sigma value for smoothing the distance predictions.
204        min_size: Minimal object size in the segmentation result.
205        debug: Return all intermediate results in a dictionary for debugging.
206
207    Returns:
208        The instance segmentation.
209    """
210    if distance_smoothing > 0:
211        center_distances = bic.filters.gaussian_smoothing(center_distances, distance_smoothing)
212        boundary_distances = bic.filters.gaussian_smoothing(boundary_distances, distance_smoothing)
213
214    fg_mask = foreground_map > foreground_threshold
215
216    marker_map = np.logical_and(
217        center_distances < center_distance_threshold, boundary_distances < boundary_distance_threshold
218    )
219    marker_map[~fg_mask] = 0
220    markers = bic.segmentation.label(marker_map)
221
222    seg = bic.segmentation.watershed(boundary_distances, markers=markers, mask=fg_mask)
223    seg = size_filter(seg, min_size)
224
225    if debug:
226        debug_output = {
227            "center_distances": center_distances,
228            "boundary_distances": boundary_distances,
229            "foreground_mask": fg_mask,
230            "markers": markers,
231        }
232        return seg, debug_output
233
234    return seg

Compute an instance segmentation via a seeded watershed on distances to object centers and boundaries.

The seeds are computed by finding connected components where both distance predictions are smaller than the respective thresholds. Using both distances is supposed to prevent merging narrow adjacent objects (if only using the center distance) or finding multiple seeds for non-convex cells (if only using the boundary distances).

Arguments:
  • center_distances: Distance prediction to the objcet centers.
  • boundary_distances: Inverted distance prediction to object boundaries.
  • foreground_map: Prediction for foreground probabilities.
  • center_distance_threshold: Center distance predictions below this value will be used to find seeds (intersected with thresholded boundary distance predictions).
  • boundary_distance_threshold: oundary distance predictions below this value will be used to find seeds (intersected with thresholded center distance predictions).
  • foreground_threshold: Foreground predictions above this value will be used as foreground mask.
  • distance_smoothing: Sigma value for smoothing the distance predictions.
  • min_size: Minimal object size in the segmentation result.
  • debug: Return all intermediate results in a dictionary for debugging.
Returns:

The instance segmentation.