torch_em.data.datasets.light_microscopy.plantseg
This dataset contains confocal and lightsheet microscopy images of plant cells with annotations for cell and nucleus segmentation.
The dataset part of the publication https://doi.org/10.7554/eLife.57613. Please cite it if you use this dataset in your research.
1"""This dataset contains confocal and lightsheet microscopy images of plant cells 2with annotations for cell and nucleus segmentation. 3 4The dataset part of the publication https://doi.org/10.7554/eLife.57613. 5Please cite it if you use this dataset in your research. 6""" 7 8import os 9from glob import glob 10from tqdm import tqdm 11from typing import List, Optional, Tuple, Union 12 13from torch.utils.data import Dataset, DataLoader 14 15import torch_em 16 17from .. import util 18 19 20URLS = { 21 "root": { 22 "train": "https://files.de-1.osf.io/v1/resources/9x3g2/providers/osfstorage/?zip=", 23 "val": "https://files.de-1.osf.io/v1/resources/vs6gb/providers/osfstorage/?zip=", 24 "test": "https://files.de-1.osf.io/v1/resources/tn4xj/providers/osfstorage/?zip=", 25 }, 26 "nuclei": { 27 "train": "https://files.de-1.osf.io/v1/resources/thxzn/providers/osfstorage/?zip=", 28 }, 29 "ovules": { 30 "train": "https://files.de-1.osf.io/v1/resources/x9yns/providers/osfstorage/?zip=", 31 "val": "https://files.de-1.osf.io/v1/resources/xp5uf/providers/osfstorage/?zip=", 32 "test": "https://files.de-1.osf.io/v1/resources/8jz7e/providers/osfstorage/?zip=", 33 } 34} 35 36# FIXME somehow the checksums are not reliably, this is a bit weird. 37CHECKSUMS = { 38 "root": { 39 "train": None, "val": None, "test": None 40 # "train": "f72e9525ff716ef14b70ab1318efd4bf303bbf9e0772bf2981a2db6e22a75794", 41 # "val": "987280d9a56828c840e508422786431dcc3603e0ba4814aa06e7bf4424efcd9e", 42 # "test": "ad71b8b9d20effba85fb5e1b42594ae35939d1a0cf905f3403789fc9e6afbc58", 43 }, 44 "nuclei": { 45 "train": None 46 # "train": "9d19ddb61373e2a97effb6cf8bd8baae5f8a50f87024273070903ea8b1160396", 47 }, 48 "ovules": { 49 "train": None, "val": None, "test": None 50 # "train": "70379673f1ab1866df6eb09d5ce11db7d3166d6d15b53a9c8b47376f04bae413", 51 # "val": "872f516cb76879c30782d9a76d52df95236770a866f75365902c60c37b14fa36", 52 # "test": "a7272f6ad1d765af6d121e20f436ac4f3609f1a90b1cb2346aa938d8c52800b9", 53 } 54} 55 56CROPPING_VOLUMES = { 57 # root (train) 58 "Movie2_T00006_crop_gt.h5": slice(4, None), 59 "Movie2_T00008_crop_gt.h5": slice(None, -18), 60 "Movie2_T00010_crop_gt.h5": slice(None, -32), 61 "Movie2_T00012_crop_gt.h5": slice(None, -39), 62 "Movie2_T00014_crop_gt.h5": slice(None, -40), 63 "Movie2_T00016_crop_gt.h5": slice(None, -42), 64 # root (test) 65 "Movie2_T00020_crop_gt.h5": slice(None, -50), 66 # ovules (train) 67 "N_487_ds2x.h5": slice(17, None), 68 "N_535_ds2x.h5": slice(None, -1), 69 "N_534_ds2x.h5": slice(None, -1), 70 "N_451_ds2x.h5": slice(None, -1), 71 "N_425_ds2x.h5": slice(None, -1), 72 # ovules (val) 73 "N_420_ds2x.h5": slice(None, -1), 74} 75 76# These root volumes are byte-identical copies of test volumes and are excluded from the train split. 77DUPLICATE_ROOT_TRAIN_VOLUMES = ("Movie1_t00045_crop_gt.h5", "Movie2_T00010_crop_gt.h5") 78 79# In these root volumes the region outside the root carries an instance id instead of the background id 1. 80ROOT_BACKGROUND_IDS = { 81 "Movie2_T00000_crop_gt.h5": 286, "Movie3_T00004_crop_gt.h5": 1004, "Movie2_T00020_crop_gt.h5": 411, 82} 83 84# The resolution previous used for the resizing 85# I have removed this feature since it was not reliable, 86# but leaving this here for reference 87# (also implementing resizing would be a good idea, 88# but more general and not for each dataset individually) 89# NATIVE_RESOLUTION = (0.235, 0.075, 0.075) 90 91 92def _fix_inconsistent_volumes(data_path, name, split): 93 import h5py 94 95 file_paths = glob(os.path.join(data_path, "*.h5")) 96 if name not in ["root", "ovules"] and split not in ["train", "val"]: 97 return 98 99 for vol_path in tqdm(file_paths, desc="Fixing inconsistencies in volumes"): 100 fname = os.path.basename(vol_path) 101 if fname in ROOT_BACKGROUND_IDS: 102 with h5py.File(vol_path, "r+") as f: 103 labels = f["label"][:] 104 labels[labels == ROOT_BACKGROUND_IDS[fname]] = 1 105 f["label"][...] = labels 106 107 if fname not in CROPPING_VOLUMES: 108 continue 109 110 with h5py.File(vol_path, "r+") as f: 111 crop_slices = CROPPING_VOLUMES[fname] 112 for key in ("raw", "label", "label_with_ignore"): 113 if key not in f: 114 continue 115 ds = f[key] 116 cropped = ds[:][crop_slices] 117 ds.resize(cropped.shape) 118 ds[...] = cropped 119 120 121def get_plantseg_data(path: Union[os.PathLike, str], name: str, split: str, download: bool = False) -> str: 122 """Download the PlantSeg training data. 123 124 Args: 125 path: Filepath to a folder where the downloaded data will be saved. 126 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 127 split: The split to download. Either 'train', 'val' or 'test'. 128 download: Whether to download the data if it is not present. 129 130 Returns: 131 The filepath to the training data. 132 """ 133 url = URLS[name][split] 134 checksum = CHECKSUMS[name][split] 135 os.makedirs(path, exist_ok=True) 136 out_path = os.path.join(path, f"{name}_{split}") 137 if os.path.exists(out_path): 138 return out_path 139 tmp_path = os.path.join(path, f"{name}_{split}.zip") 140 util.download_source(tmp_path, url, download, checksum) 141 util.unzip(tmp_path, out_path, remove=True) 142 _fix_inconsistent_volumes(out_path, name, split) 143 return out_path 144 145 146def get_plantseg_paths( 147 path: Union[os.PathLike, str], 148 name: str, 149 split: str, 150 download: bool = False 151) -> List[str]: 152 """Get paths to the PlantSeg data. 153 154 Args: 155 path: Filepath to a folder where the downloaded data will be saved. 156 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 157 split: The split to download. Either 'train', 'val' or 'test'. 158 download: Whether to download the data if it is not present. 159 160 Returns: 161 List of filepaths for the data. 162 """ 163 data_path = get_plantseg_data(path, name, split, download) 164 file_paths = sorted(glob(os.path.join(data_path, "*.h5"))) 165 if name == "root" and split == "train": 166 file_paths = [p for p in file_paths if os.path.basename(p) not in DUPLICATE_ROOT_TRAIN_VOLUMES] 167 return file_paths 168 169 170def get_plantseg_dataset( 171 path: Union[os.PathLike, str], 172 name: str, 173 split: str, 174 patch_shape: Tuple[int, int, int], 175 download: bool = False, 176 offsets: Optional[List[List[int]]] = None, 177 boundaries: bool = False, 178 binary: bool = False, 179 with_ignore: bool = False, 180 **kwargs, 181) -> Dataset: 182 """Get the PlantSeg dataset for segmenting nuclei or cells. 183 184 Args: 185 path: Filepath to a folder where the downloaded data will be saved. 186 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 187 split: The split to download. Either 'train', 'val' or 'test'. 188 patch_shape: The patch shape to use for training. 189 download: Whether to download the data if it is not present. 190 offsets: Offset values for affinity computation used as target. 191 boundaries: Whether to compute boundaries as the target. 192 binary: Whether to use a binary segmentation target. 193 with_ignore: Whether to load the ovules labels with the unannotated regions marked as -1. 194 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 195 196 Returns: 197 The segmentation dataset. 198 """ 199 assert len(patch_shape) == 3 200 if with_ignore and name != "ovules": 201 raise ValueError("Labels with ignore regions are only available for 'ovules'.") 202 203 file_paths = get_plantseg_paths(path, name, split, download) 204 205 kwargs, _ = util.add_instance_label_transform( 206 kwargs, add_binary_target=binary, binary=binary, boundaries=boundaries, 207 offsets=offsets, binary_is_exclusive=False 208 ) 209 210 return torch_em.default_segmentation_dataset( 211 raw_paths=file_paths, 212 raw_key="raw", 213 label_paths=file_paths, 214 label_key="label_with_ignore" if with_ignore else "label", 215 patch_shape=patch_shape, 216 **kwargs 217 ) 218 219 220def get_plantseg_loader( 221 path: Union[os.PathLike, str], 222 name: str, 223 split: str, 224 patch_shape: Tuple[int, int, int], 225 batch_size: int, 226 download: bool = False, 227 offsets: Optional[List[List[int]]] = None, 228 boundaries: bool = False, 229 binary: bool = False, 230 with_ignore: bool = False, 231 **kwargs, 232) -> DataLoader: 233 """Get the PlantSeg dataloader for segmenting nuclei or cells. 234 235 Args: 236 path: Filepath to a folder where the downloaded data will be saved. 237 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 238 split: The split to download. Either 'train', 'val' or 'test'. 239 patch_shape: The patch shape to use for training. 240 batch_size: The batch size for training. 241 download: Whether to download the data if it is not present. 242 offsets: Offset values for affinity computation used as target. 243 boundaries: Whether to compute boundaries as the target. 244 binary: Whether to use a binary segmentation target. 245 with_ignore: Whether to load the ovules labels with the unannotated regions marked as -1. 246 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 247 248 Returns: 249 The DataLoader. 250 """ 251 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 252 dataset = get_plantseg_dataset( 253 path, name, split, patch_shape, download=download, offsets=offsets, 254 boundaries=boundaries, binary=binary, with_ignore=with_ignore, **ds_kwargs 255 ) 256 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URLS =
{'root': {'train': 'https://files.de-1.osf.io/v1/resources/9x3g2/providers/osfstorage/?zip=', 'val': 'https://files.de-1.osf.io/v1/resources/vs6gb/providers/osfstorage/?zip=', 'test': 'https://files.de-1.osf.io/v1/resources/tn4xj/providers/osfstorage/?zip='}, 'nuclei': {'train': 'https://files.de-1.osf.io/v1/resources/thxzn/providers/osfstorage/?zip='}, 'ovules': {'train': 'https://files.de-1.osf.io/v1/resources/x9yns/providers/osfstorage/?zip=', 'val': 'https://files.de-1.osf.io/v1/resources/xp5uf/providers/osfstorage/?zip=', 'test': 'https://files.de-1.osf.io/v1/resources/8jz7e/providers/osfstorage/?zip='}}
CHECKSUMS =
{'root': {'train': None, 'val': None, 'test': None}, 'nuclei': {'train': None}, 'ovules': {'train': None, 'val': None, 'test': None}}
CROPPING_VOLUMES =
{'Movie2_T00006_crop_gt.h5': slice(4, None, None), 'Movie2_T00008_crop_gt.h5': slice(None, -18, None), 'Movie2_T00010_crop_gt.h5': slice(None, -32, None), 'Movie2_T00012_crop_gt.h5': slice(None, -39, None), 'Movie2_T00014_crop_gt.h5': slice(None, -40, None), 'Movie2_T00016_crop_gt.h5': slice(None, -42, None), 'Movie2_T00020_crop_gt.h5': slice(None, -50, None), 'N_487_ds2x.h5': slice(17, None, None), 'N_535_ds2x.h5': slice(None, -1, None), 'N_534_ds2x.h5': slice(None, -1, None), 'N_451_ds2x.h5': slice(None, -1, None), 'N_425_ds2x.h5': slice(None, -1, None), 'N_420_ds2x.h5': slice(None, -1, None)}
DUPLICATE_ROOT_TRAIN_VOLUMES =
('Movie1_t00045_crop_gt.h5', 'Movie2_T00010_crop_gt.h5')
ROOT_BACKGROUND_IDS =
{'Movie2_T00000_crop_gt.h5': 286, 'Movie3_T00004_crop_gt.h5': 1004, 'Movie2_T00020_crop_gt.h5': 411}
def
get_plantseg_data( path: Union[os.PathLike, str], name: str, split: str, download: bool = False) -> str:
122def get_plantseg_data(path: Union[os.PathLike, str], name: str, split: str, download: bool = False) -> str: 123 """Download the PlantSeg training data. 124 125 Args: 126 path: Filepath to a folder where the downloaded data will be saved. 127 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 128 split: The split to download. Either 'train', 'val' or 'test'. 129 download: Whether to download the data if it is not present. 130 131 Returns: 132 The filepath to the training data. 133 """ 134 url = URLS[name][split] 135 checksum = CHECKSUMS[name][split] 136 os.makedirs(path, exist_ok=True) 137 out_path = os.path.join(path, f"{name}_{split}") 138 if os.path.exists(out_path): 139 return out_path 140 tmp_path = os.path.join(path, f"{name}_{split}.zip") 141 util.download_source(tmp_path, url, download, checksum) 142 util.unzip(tmp_path, out_path, remove=True) 143 _fix_inconsistent_volumes(out_path, name, split) 144 return out_path
Download the PlantSeg training data.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'.
- split: The split to download. Either 'train', 'val' or 'test'.
- download: Whether to download the data if it is not present.
Returns:
The filepath to the training data.
def
get_plantseg_paths( path: Union[os.PathLike, str], name: str, split: str, download: bool = False) -> List[str]:
147def get_plantseg_paths( 148 path: Union[os.PathLike, str], 149 name: str, 150 split: str, 151 download: bool = False 152) -> List[str]: 153 """Get paths to the PlantSeg data. 154 155 Args: 156 path: Filepath to a folder where the downloaded data will be saved. 157 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 158 split: The split to download. Either 'train', 'val' or 'test'. 159 download: Whether to download the data if it is not present. 160 161 Returns: 162 List of filepaths for the data. 163 """ 164 data_path = get_plantseg_data(path, name, split, download) 165 file_paths = sorted(glob(os.path.join(data_path, "*.h5"))) 166 if name == "root" and split == "train": 167 file_paths = [p for p in file_paths if os.path.basename(p) not in DUPLICATE_ROOT_TRAIN_VOLUMES] 168 return file_paths
Get paths to the PlantSeg data.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'.
- split: The split to download. Either 'train', 'val' or 'test'.
- download: Whether to download the data if it is not present.
Returns:
List of filepaths for the data.
def
get_plantseg_dataset( path: Union[os.PathLike, str], name: str, split: str, patch_shape: Tuple[int, int, int], download: bool = False, offsets: Optional[List[List[int]]] = None, boundaries: bool = False, binary: bool = False, with_ignore: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
171def get_plantseg_dataset( 172 path: Union[os.PathLike, str], 173 name: str, 174 split: str, 175 patch_shape: Tuple[int, int, int], 176 download: bool = False, 177 offsets: Optional[List[List[int]]] = None, 178 boundaries: bool = False, 179 binary: bool = False, 180 with_ignore: bool = False, 181 **kwargs, 182) -> Dataset: 183 """Get the PlantSeg dataset for segmenting nuclei or cells. 184 185 Args: 186 path: Filepath to a folder where the downloaded data will be saved. 187 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 188 split: The split to download. Either 'train', 'val' or 'test'. 189 patch_shape: The patch shape to use for training. 190 download: Whether to download the data if it is not present. 191 offsets: Offset values for affinity computation used as target. 192 boundaries: Whether to compute boundaries as the target. 193 binary: Whether to use a binary segmentation target. 194 with_ignore: Whether to load the ovules labels with the unannotated regions marked as -1. 195 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 196 197 Returns: 198 The segmentation dataset. 199 """ 200 assert len(patch_shape) == 3 201 if with_ignore and name != "ovules": 202 raise ValueError("Labels with ignore regions are only available for 'ovules'.") 203 204 file_paths = get_plantseg_paths(path, name, split, download) 205 206 kwargs, _ = util.add_instance_label_transform( 207 kwargs, add_binary_target=binary, binary=binary, boundaries=boundaries, 208 offsets=offsets, binary_is_exclusive=False 209 ) 210 211 return torch_em.default_segmentation_dataset( 212 raw_paths=file_paths, 213 raw_key="raw", 214 label_paths=file_paths, 215 label_key="label_with_ignore" if with_ignore else "label", 216 patch_shape=patch_shape, 217 **kwargs 218 )
Get the PlantSeg dataset for segmenting nuclei or cells.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'.
- split: The split to download. Either 'train', 'val' or 'test'.
- patch_shape: The patch shape to use for training.
- download: Whether to download the data if it is not present.
- offsets: Offset values for affinity computation used as target.
- boundaries: Whether to compute boundaries as the target.
- binary: Whether to use a binary segmentation target.
- with_ignore: Whether to load the ovules labels with the unannotated regions marked as -1.
- kwargs: Additional keyword arguments for
torch_em.default_segmentation_dataset.
Returns:
The segmentation dataset.
def
get_plantseg_loader( path: Union[os.PathLike, str], name: str, split: str, patch_shape: Tuple[int, int, int], batch_size: int, download: bool = False, offsets: Optional[List[List[int]]] = None, boundaries: bool = False, binary: bool = False, with_ignore: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
221def get_plantseg_loader( 222 path: Union[os.PathLike, str], 223 name: str, 224 split: str, 225 patch_shape: Tuple[int, int, int], 226 batch_size: int, 227 download: bool = False, 228 offsets: Optional[List[List[int]]] = None, 229 boundaries: bool = False, 230 binary: bool = False, 231 with_ignore: bool = False, 232 **kwargs, 233) -> DataLoader: 234 """Get the PlantSeg dataloader for segmenting nuclei or cells. 235 236 Args: 237 path: Filepath to a folder where the downloaded data will be saved. 238 name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'. 239 split: The split to download. Either 'train', 'val' or 'test'. 240 patch_shape: The patch shape to use for training. 241 batch_size: The batch size for training. 242 download: Whether to download the data if it is not present. 243 offsets: Offset values for affinity computation used as target. 244 boundaries: Whether to compute boundaries as the target. 245 binary: Whether to use a binary segmentation target. 246 with_ignore: Whether to load the ovules labels with the unannotated regions marked as -1. 247 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 248 249 Returns: 250 The DataLoader. 251 """ 252 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 253 dataset = get_plantseg_dataset( 254 path, name, split, patch_shape, download=download, offsets=offsets, 255 boundaries=boundaries, binary=binary, with_ignore=with_ignore, **ds_kwargs 256 ) 257 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the PlantSeg dataloader for segmenting nuclei or cells.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- name: The name of the data to load. Either 'root', 'nuclei' or 'ovules'.
- split: The split to download. Either 'train', 'val' or 'test'.
- patch_shape: The patch shape to use for training.
- batch_size: The batch size for training.
- download: Whether to download the data if it is not present.
- offsets: Offset values for affinity computation used as target.
- boundaries: Whether to compute boundaries as the target.
- binary: Whether to use a binary segmentation target.
- with_ignore: Whether to load the ovules labels with the unannotated regions marked as -1.
- kwargs: Additional keyword arguments for
torch_em.default_segmentation_datasetor for the PyTorch DataLoader.
Returns:
The DataLoader.