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
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.
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.
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.
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.
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.
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.