torch_em.data.datasets.electron_microscopy.densecell
The DenseCell dataset contains annotations for semantic segmentation of densely-packed cellular organelles in serial block-face scanning electron microscopy (SBF-SEM) images of platelet tissue.
The dataset was published in https://doi.org/10.1038/s41598-021-81590-0. Please cite this publication if you use the dataset in your research.
1"""The DenseCell dataset contains annotations for semantic segmentation of densely-packed cellular organelles 2in serial block-face scanning electron microscopy (SBF-SEM) images of platelet tissue. 3 4The dataset was published in https://doi.org/10.1038/s41598-021-81590-0. 5Please cite this publication if you use the dataset in your research. 6""" 7 8import os 9from shutil import rmtree 10from typing import Tuple, Union, Literal, Optional 11 12import numpy as np 13from scipy import ndimage 14from skimage.feature import peak_local_max 15from skimage.segmentation import watershed 16from skimage.measure import label as connected_components 17 18from torch.utils.data import Dataset, DataLoader 19 20import torch_em 21 22from .. import util 23 24 25URL = "https://www.dropbox.com/s/68yclbraqq1diza/platelet_data_1219.zip?dl=1" 26CHECKSUM = None 27 28ORGANELLES = { 29 1: "cell", 30 2: "mitochondrion", 31 3: "alpha_granule", 32 4: "canalicular_vessel", 33 5: "dense_granule", 34 6: "dense_core", 35} 36 37SPLIT_FILES = { 38 "train": {"images": "train-images.tif", "labels": "train-labels.tif"}, 39 "val": {"images": "eval-images.tif", "labels": "eval-labels.tif"}, 40 "test": {"images": "test-images.tif", "labels": "test-labels.tif"}, 41} 42 43 44def get_densecell_data( 45 path: Union[os.PathLike, str], split: Literal["train", "val", "test"], download: bool = False 46) -> str: 47 """Download the DenseCell dataset. 48 49 Args: 50 path: Filepath to a folder where the downloaded data will be saved. 51 split: The split to download. Either 'train', 'val', or 'test'. 52 download: Whether to download the data if it is not present. 53 54 Returns: 55 The filepath for the downloaded data. 56 """ 57 import h5py 58 import tifffile 59 60 data_path = os.path.join(path, f"densecell_{split}.h5") 61 if os.path.exists(data_path): 62 with h5py.File(data_path, "r") as f: 63 if "labels/original" in f: 64 return data_path 65 66 # Remove old file with outdated structure. 67 os.remove(data_path) 68 69 os.makedirs(path, exist_ok=True) 70 71 # Download and extract the ZIP if the source TIFFs are not available. 72 platelet_dir = os.path.join(path, "platelet_data") 73 if not os.path.exists(platelet_dir): 74 zip_path = os.path.join(path, "platelet_data_1219.zip") 75 util.download_source(zip_path, URL, download, checksum=CHECKSUM) 76 util.unzip(zip_path, path, remove=True) 77 78 assert os.path.exists(platelet_dir), f"Expected extracted directory at {platelet_dir}" 79 80 for _split, files in SPLIT_FILES.items(): 81 out_path = os.path.join(path, f"densecell_{_split}.h5") 82 if os.path.exists(out_path): 83 with h5py.File(out_path, "r") as f: 84 if "labels/original" in f: 85 continue 86 87 os.remove(out_path) 88 89 raw = tifffile.imread(os.path.join(platelet_dir, files["images"])) 90 labels = tifffile.imread(os.path.join(platelet_dir, files["labels"])) 91 assert raw.shape == labels.shape, f"Shape mismatch for {_split}: {raw.shape} vs {labels.shape}" 92 93 labels = labels.astype(np.uint8) 94 with h5py.File(out_path, "w") as f: 95 f.create_dataset("raw", data=raw, compression="gzip") 96 f.create_dataset("labels/original", data=labels, compression="gzip") 97 for label_id, name in ORGANELLES.items(): 98 # For cells, use all non-background labels to avoid holes from internal organelles. 99 if name == "cell": 100 binary_mask = (labels >= 1).astype(np.uint8) 101 else: 102 binary_mask = (labels == label_id).astype(np.uint8) 103 104 f.create_dataset(f"labels/{name}", data=binary_mask, compression="gzip") 105 106 rmtree(platelet_dir) 107 108 assert os.path.exists(data_path), data_path 109 return data_path 110 111 112CELL_INSTANCE_KEY = "labels/cell_instances" 113 114 115def _merge_without_neck(segments, distance, max_saddle_ratio, min_peak, min_z_overlap=0.5): 116 """Merge watershed segments of the same cell. 117 118 In-plane neighbours merge when the distance saddle on their boundary is high relative to their peaks (no 119 neck). Neighbours across z merge when their footprints overlap by at least *min_z_overlap* IoU. Segments 120 with a peak below *min_peak* are slivers that would bridge cells, so they are absorbed by their largest 121 contact instead. 122 """ 123 peaks = ndimage.maximum(distance, segments, index=np.arange(segments.max() + 1)) 124 small = peaks < min_peak 125 merged = np.arange(segments.max() + 1) 126 127 def union(u, v): 128 ru, rv = merged[u], merged[v] 129 if ru != rv: 130 merged[merged == max(ru, rv)] = min(ru, rv) 131 132 contact = {} 133 for axis in range(1, segments.ndim): 134 a = np.moveaxis(segments, axis, 0) 135 d = np.moveaxis(distance, axis, 0) 136 left, right = a[:-1], a[1:] 137 touch = (left != right) & (left > 0) & (right > 0) 138 saddle = np.minimum(d[:-1], d[1:])[touch] 139 for u, v, sd in zip(left[touch], right[touch], saddle): 140 if small[u] or small[v]: 141 contact[(u, v)] = contact.get((u, v), 0) + 1 142 contact[(v, u)] = contact.get((v, u), 0) + 1 143 elif sd > max_saddle_ratio * min(peaks[u], peaks[v]): 144 union(u, v) 145 146 for z in range(segments.shape[0] - 1): 147 lower, upper = segments[z], segments[z + 1] 148 touch = (lower != upper) & (lower > 0) & (upper > 0) 149 for u, v in set(zip(lower[touch].tolist(), upper[touch].tolist())): 150 if small[u] or small[v]: 151 continue 152 fu, fv = lower == u, upper == v 153 iou = np.logical_and(fu, fv).sum() / np.logical_or(fu, fv).sum() 154 if iou >= min_z_overlap: 155 union(u, v) 156 157 for u in np.nonzero(small)[0]: 158 neighbours = [(n, c) for (a, n), c in contact.items() if a == u and not small[n]] 159 if neighbours: 160 union(u, max(neighbours, key=lambda item: item[1])[0]) 161 return merged[segments] 162 163 164def _derive_cell_instances(cell_mask, sampling=(5, 1, 1), min_distance=60, max_saddle_ratio=0.6, min_size=2000): 165 """Split the binary cell mask into instances with a seeded 3D watershed. 166 167 Seeds are per-section 2D distance maxima at least *min_distance* apart, so small cells next to a large 168 neighbour are found. Fragments below *min_size* voxels are dropped. 169 """ 170 distance_3d = ndimage.distance_transform_edt(cell_mask, sampling=sampling) 171 markers = np.zeros(cell_mask.shape, dtype="int32") 172 next_id = 1 173 for z in range(cell_mask.shape[0]): 174 section = cell_mask[z] 175 if not section.any(): 176 continue 177 distance = ndimage.distance_transform_edt(section) 178 peaks = peak_local_max( 179 distance, min_distance=min_distance, labels=connected_components(section), exclude_border=False 180 ) 181 markers[z][tuple(peaks.T)] = np.arange(next_id, next_id + len(peaks)) 182 next_id += len(peaks) 183 segments = watershed(-distance_3d, markers, mask=cell_mask) 184 instances = _merge_without_neck(segments, distance_3d, max_saddle_ratio, min_peak=min_distance // 4) 185 ids, counts = np.unique(instances, return_counts=True) 186 instances[np.isin(instances, ids[(counts < min_size) & (ids > 0)])] = 0 187 return np.unique(instances, return_inverse=True)[1].reshape(cell_mask.shape).astype("uint32") 188 189 190def _add_cell_instances(data_path): 191 """Write the derived cell instances into the h5 file once.""" 192 import h5py 193 194 with h5py.File(data_path, "r") as f: 195 if CELL_INSTANCE_KEY in f: 196 return 197 cell_mask = f["labels/cell"][:] > 0 198 instances = _derive_cell_instances(cell_mask) 199 with h5py.File(data_path, "a") as f: 200 f.create_dataset(CELL_INSTANCE_KEY, data=instances, compression="gzip") 201 202 203def get_densecell_paths( 204 path: Union[os.PathLike, str], split: Literal["train", "val", "test"], download: bool = False 205) -> str: 206 """Get paths to the DenseCell data. 207 208 Args: 209 path: Filepath to a folder where the downloaded data will be saved. 210 split: The data split. Either 'train', 'val', or 'test'. 211 download: Whether to download the data if it is not present. 212 213 Returns: 214 The filepath for the stored data. 215 """ 216 get_densecell_data(path, split, download) 217 data_path = os.path.join(path, f"densecell_{split}.h5") 218 return data_path 219 220 221def get_densecell_dataset( 222 path: Union[os.PathLike, str], 223 split: Literal["train", "val", "test"], 224 patch_shape: Tuple[int, int, int], 225 label_choice: Optional[str] = None, 226 download: bool = False, 227 **kwargs 228) -> Dataset: 229 """Get dataset for segmentation of organelles in SBF-SEM platelet images. 230 231 Args: 232 path: Filepath to a folder where the downloaded data will be saved. 233 split: The data split. Either 'train', 'val', or 'test'. 234 patch_shape: The patch shape to use for training. 235 label_choice: The organelle to segment. Available choices are: 236 'cell', 'mitochondrion', 'alpha_granule', 'canalicular_vessel', 'dense_granule', 'dense_core', or 237 'cell_instances', which splits the binary cell mask into instances with a 3D distance-transform 238 watershed and caches the result in the h5 file. 239 If None, uses 'original' which contains all semantic labels (0-6). 240 download: Whether to download the data if it is not present. 241 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 242 243 Returns: 244 The segmentation dataset. 245 """ 246 assert split in ("train", "val", "test") 247 248 data_path = get_densecell_paths(path, split, download) 249 250 if label_choice is None: 251 label_key = "labels/original" 252 elif label_choice == "cell_instances": 253 _add_cell_instances(data_path) 254 label_key = CELL_INSTANCE_KEY 255 else: 256 valid_choices = list(ORGANELLES.values()) 257 assert label_choice in valid_choices, f"'{label_choice}' is not valid. Choose from {valid_choices}." 258 label_key = f"labels/{label_choice}" 259 260 return torch_em.default_segmentation_dataset( 261 raw_paths=data_path, 262 raw_key="raw", 263 label_paths=data_path, 264 label_key=label_key, 265 patch_shape=patch_shape, 266 **kwargs 267 ) 268 269 270def get_densecell_loader( 271 path: Union[os.PathLike, str], 272 split: Literal["train", "val", "test"], 273 patch_shape: Tuple[int, int, int], 274 batch_size: int, 275 label_choice: Optional[str] = None, 276 download: bool = False, 277 **kwargs 278) -> DataLoader: 279 """Get dataloader for segmentation of organelles in SBF-SEM platelet images. 280 281 Args: 282 path: Filepath to a folder where the downloaded data will be saved. 283 split: The data split. Either 'train', 'val', or 'test'. 284 patch_shape: The patch shape to use for training. 285 batch_size: The batch size for training. 286 label_choice: The organelle to segment. Available choices are: 287 'cell', 'mitochondrion', 'alpha_granule', 'canalicular_vessel', 'dense_granule', 'dense_core', or 288 'cell_instances', which splits the binary cell mask into instances with a 3D distance-transform 289 watershed and caches the result in the h5 file. 290 If None, uses 'original' which contains all semantic labels (0-6). 291 download: Whether to download the data if it is not present. 292 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 293 294 Returns: 295 The PyTorch DataLoader. 296 """ 297 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 298 dataset = get_densecell_dataset(path, split, patch_shape, label_choice=label_choice, download=download, **ds_kwargs) 299 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL =
'https://www.dropbox.com/s/68yclbraqq1diza/platelet_data_1219.zip?dl=1'
CHECKSUM =
None
ORGANELLES =
{1: 'cell', 2: 'mitochondrion', 3: 'alpha_granule', 4: 'canalicular_vessel', 5: 'dense_granule', 6: 'dense_core'}
SPLIT_FILES =
{'train': {'images': 'train-images.tif', 'labels': 'train-labels.tif'}, 'val': {'images': 'eval-images.tif', 'labels': 'eval-labels.tif'}, 'test': {'images': 'test-images.tif', 'labels': 'test-labels.tif'}}
def
get_densecell_data( path: Union[os.PathLike, str], split: Literal['train', 'val', 'test'], download: bool = False) -> str:
45def get_densecell_data( 46 path: Union[os.PathLike, str], split: Literal["train", "val", "test"], download: bool = False 47) -> str: 48 """Download the DenseCell dataset. 49 50 Args: 51 path: Filepath to a folder where the downloaded data will be saved. 52 split: The split to download. Either 'train', 'val', or 'test'. 53 download: Whether to download the data if it is not present. 54 55 Returns: 56 The filepath for the downloaded data. 57 """ 58 import h5py 59 import tifffile 60 61 data_path = os.path.join(path, f"densecell_{split}.h5") 62 if os.path.exists(data_path): 63 with h5py.File(data_path, "r") as f: 64 if "labels/original" in f: 65 return data_path 66 67 # Remove old file with outdated structure. 68 os.remove(data_path) 69 70 os.makedirs(path, exist_ok=True) 71 72 # Download and extract the ZIP if the source TIFFs are not available. 73 platelet_dir = os.path.join(path, "platelet_data") 74 if not os.path.exists(platelet_dir): 75 zip_path = os.path.join(path, "platelet_data_1219.zip") 76 util.download_source(zip_path, URL, download, checksum=CHECKSUM) 77 util.unzip(zip_path, path, remove=True) 78 79 assert os.path.exists(platelet_dir), f"Expected extracted directory at {platelet_dir}" 80 81 for _split, files in SPLIT_FILES.items(): 82 out_path = os.path.join(path, f"densecell_{_split}.h5") 83 if os.path.exists(out_path): 84 with h5py.File(out_path, "r") as f: 85 if "labels/original" in f: 86 continue 87 88 os.remove(out_path) 89 90 raw = tifffile.imread(os.path.join(platelet_dir, files["images"])) 91 labels = tifffile.imread(os.path.join(platelet_dir, files["labels"])) 92 assert raw.shape == labels.shape, f"Shape mismatch for {_split}: {raw.shape} vs {labels.shape}" 93 94 labels = labels.astype(np.uint8) 95 with h5py.File(out_path, "w") as f: 96 f.create_dataset("raw", data=raw, compression="gzip") 97 f.create_dataset("labels/original", data=labels, compression="gzip") 98 for label_id, name in ORGANELLES.items(): 99 # For cells, use all non-background labels to avoid holes from internal organelles. 100 if name == "cell": 101 binary_mask = (labels >= 1).astype(np.uint8) 102 else: 103 binary_mask = (labels == label_id).astype(np.uint8) 104 105 f.create_dataset(f"labels/{name}", data=binary_mask, compression="gzip") 106 107 rmtree(platelet_dir) 108 109 assert os.path.exists(data_path), data_path 110 return data_path
Download the DenseCell dataset.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- split: The split to download. Either 'train', 'val', or 'test'.
- download: Whether to download the data if it is not present.
Returns:
The filepath for the downloaded data.
CELL_INSTANCE_KEY =
'labels/cell_instances'
def
get_densecell_paths( path: Union[os.PathLike, str], split: Literal['train', 'val', 'test'], download: bool = False) -> str:
204def get_densecell_paths( 205 path: Union[os.PathLike, str], split: Literal["train", "val", "test"], download: bool = False 206) -> str: 207 """Get paths to the DenseCell data. 208 209 Args: 210 path: Filepath to a folder where the downloaded data will be saved. 211 split: The data split. Either 'train', 'val', or 'test'. 212 download: Whether to download the data if it is not present. 213 214 Returns: 215 The filepath for the stored data. 216 """ 217 get_densecell_data(path, split, download) 218 data_path = os.path.join(path, f"densecell_{split}.h5") 219 return data_path
Get paths to the DenseCell data.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- split: The data split. Either 'train', 'val', or 'test'.
- download: Whether to download the data if it is not present.
Returns:
The filepath for the stored data.
def
get_densecell_dataset( path: Union[os.PathLike, str], split: Literal['train', 'val', 'test'], patch_shape: Tuple[int, int, int], label_choice: Optional[str] = None, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
222def get_densecell_dataset( 223 path: Union[os.PathLike, str], 224 split: Literal["train", "val", "test"], 225 patch_shape: Tuple[int, int, int], 226 label_choice: Optional[str] = None, 227 download: bool = False, 228 **kwargs 229) -> Dataset: 230 """Get dataset for segmentation of organelles in SBF-SEM platelet images. 231 232 Args: 233 path: Filepath to a folder where the downloaded data will be saved. 234 split: The data split. Either 'train', 'val', or 'test'. 235 patch_shape: The patch shape to use for training. 236 label_choice: The organelle to segment. Available choices are: 237 'cell', 'mitochondrion', 'alpha_granule', 'canalicular_vessel', 'dense_granule', 'dense_core', or 238 'cell_instances', which splits the binary cell mask into instances with a 3D distance-transform 239 watershed and caches the result in the h5 file. 240 If None, uses 'original' which contains all semantic labels (0-6). 241 download: Whether to download the data if it is not present. 242 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 243 244 Returns: 245 The segmentation dataset. 246 """ 247 assert split in ("train", "val", "test") 248 249 data_path = get_densecell_paths(path, split, download) 250 251 if label_choice is None: 252 label_key = "labels/original" 253 elif label_choice == "cell_instances": 254 _add_cell_instances(data_path) 255 label_key = CELL_INSTANCE_KEY 256 else: 257 valid_choices = list(ORGANELLES.values()) 258 assert label_choice in valid_choices, f"'{label_choice}' is not valid. Choose from {valid_choices}." 259 label_key = f"labels/{label_choice}" 260 261 return torch_em.default_segmentation_dataset( 262 raw_paths=data_path, 263 raw_key="raw", 264 label_paths=data_path, 265 label_key=label_key, 266 patch_shape=patch_shape, 267 **kwargs 268 )
Get dataset for segmentation of organelles in SBF-SEM platelet images.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- split: The data split. Either 'train', 'val', or 'test'.
- patch_shape: The patch shape to use for training.
- label_choice: The organelle to segment. Available choices are: 'cell', 'mitochondrion', 'alpha_granule', 'canalicular_vessel', 'dense_granule', 'dense_core', or 'cell_instances', which splits the binary cell mask into instances with a 3D distance-transform watershed and caches the result in the h5 file. If None, uses 'original' which contains all semantic labels (0-6).
- download: Whether to download the data if it is not present.
- kwargs: Additional keyword arguments for
torch_em.default_segmentation_dataset.
Returns:
The segmentation dataset.
def
get_densecell_loader( path: Union[os.PathLike, str], split: Literal['train', 'val', 'test'], patch_shape: Tuple[int, int, int], batch_size: int, label_choice: Optional[str] = None, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
271def get_densecell_loader( 272 path: Union[os.PathLike, str], 273 split: Literal["train", "val", "test"], 274 patch_shape: Tuple[int, int, int], 275 batch_size: int, 276 label_choice: Optional[str] = None, 277 download: bool = False, 278 **kwargs 279) -> DataLoader: 280 """Get dataloader for segmentation of organelles in SBF-SEM platelet images. 281 282 Args: 283 path: Filepath to a folder where the downloaded data will be saved. 284 split: The data split. Either 'train', 'val', or 'test'. 285 patch_shape: The patch shape to use for training. 286 batch_size: The batch size for training. 287 label_choice: The organelle to segment. Available choices are: 288 'cell', 'mitochondrion', 'alpha_granule', 'canalicular_vessel', 'dense_granule', 'dense_core', or 289 'cell_instances', which splits the binary cell mask into instances with a 3D distance-transform 290 watershed and caches the result in the h5 file. 291 If None, uses 'original' which contains all semantic labels (0-6). 292 download: Whether to download the data if it is not present. 293 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 294 295 Returns: 296 The PyTorch DataLoader. 297 """ 298 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 299 dataset = get_densecell_dataset(path, split, patch_shape, label_choice=label_choice, download=download, **ds_kwargs) 300 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get dataloader for segmentation of organelles in SBF-SEM platelet images.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- split: The data split. Either 'train', 'val', or 'test'.
- patch_shape: The patch shape to use for training.
- batch_size: The batch size for training.
- label_choice: The organelle to segment. Available choices are: 'cell', 'mitochondrion', 'alpha_granule', 'canalicular_vessel', 'dense_granule', 'dense_core', or 'cell_instances', which splits the binary cell mask into instances with a 3D distance-transform watershed and caches the result in the h5 file. If None, uses 'original' which contains all semantic labels (0-6).
- download: Whether to download the data if it is not present.
- kwargs: Additional keyword arguments for
torch_em.default_segmentation_datasetor for the PyTorch DataLoader.
Returns:
The PyTorch DataLoader.