torch_em.data.datasets.histopathology.ocelot
The OCELOT dataset contains tissue segmentation masks for H&E histopathology images sourced from TCGA, covering bladder, endometrium, head-and-neck, kidney, prostate, and stomach cancer.
The dataset is located at https://zenodo.org/records/8417503. The data is licensed under CC BY-NC 4.0. This dataset is from the publication https://doi.org/10.1109/CVPR52729.2023.02289. Please cite it in your research.
NOTE: OCELOT also ships point annotations for cell detection, which this module does not expose, since torch-em only integrates the tissue segmentation masks.
1"""The OCELOT dataset contains tissue segmentation masks for H&E histopathology images 2sourced from TCGA, covering bladder, endometrium, head-and-neck, kidney, prostate, and 3stomach cancer. 4 5The dataset is located at https://zenodo.org/records/8417503. The data is licensed under 6CC BY-NC 4.0. This dataset is from the publication https://doi.org/10.1109/CVPR52729.2023.02289. 7Please cite it in your research. 8 9NOTE: OCELOT also ships point annotations for cell detection, which this module does not expose, 10since torch-em only integrates the tissue segmentation masks. 11""" 12 13import os 14import stat 15import zipfile 16from glob import glob 17from pathlib import PurePosixPath 18from typing import List, Literal, Optional, Tuple, Union 19 20from torch.utils.data import Dataset, DataLoader 21 22import torch_em 23 24from .. import util 25 26 27URL = "https://zenodo.org/records/8417503/files/ocelot2023_v1.0.1.zip?download=1" 28CHECKSUM = "74f46b79e3c4076ca0012d403629ab1e1e412591faf79a910d4d8bdd92c47920" 29SPLITS = ("train", "val", "test") 30 31 32def _validate_archive(zip_path): 33 with zipfile.ZipFile(zip_path, "r") as archive: 34 members = archive.infolist() 35 36 file_members = [] 37 for member in members: 38 member_path = PurePosixPath(member.filename) 39 first_part = member_path.parts[0] if member_path.parts else "" 40 if ( 41 not member_path.parts 42 or member_path.is_absolute() 43 or ".." in member_path.parts 44 or "\\" in member.filename 45 or ":" in first_part 46 or first_part != "ocelot2023_v1.0.1" 47 ): 48 raise RuntimeError(f"Unsafe archive member: {member.filename}") 49 50 file_type = stat.S_IFMT(member.external_attr >> 16) 51 if file_type not in (0, stat.S_IFREG, stat.S_IFDIR): 52 raise RuntimeError(f"Unsupported archive member type: {member.filename}") 53 if not member.is_dir(): 54 file_members.append(member) 55 56 extracted_size = sum(member.file_size for member in file_members) 57 archive_size = os.path.getsize(zip_path) 58 if len(file_members) > 20000 or extracted_size > 3_000_000_000 or extracted_size > 5 * archive_size: 59 raise RuntimeError("The OCELOT archive exceeds the expected extraction limits.") 60 61 62def _has_data(data_dir): 63 return all( 64 os.path.isdir(os.path.join(data_dir, "images", split, "tissue")) 65 and os.path.isdir(os.path.join(data_dir, "annotations", split, "tissue")) 66 for split in SPLITS 67 ) 68 69 70def get_ocelot_data(path: Union[os.PathLike, str], download: bool = False) -> str: 71 """Download the OCELOT dataset. 72 73 Args: 74 path: Filepath to a folder where the downloaded data will be saved. 75 download: Whether to download the data if it is not present. 76 77 Returns: 78 Filepath to the folder where the data is stored. 79 """ 80 data_dir = os.path.join(path, "ocelot2023_v1.0.1") 81 if _has_data(data_dir): 82 return data_dir 83 84 os.makedirs(path, exist_ok=True) 85 zip_path = os.path.join(path, "ocelot2023_v1.0.1.zip") 86 util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM) 87 _validate_archive(zip_path) 88 util.unzip(zip_path=zip_path, dst=path) 89 90 if not _has_data(data_dir): 91 raise RuntimeError("The OCELOT archive does not contain the expected image and mask folders.") 92 return data_dir 93 94 95def get_ocelot_paths( 96 path: Union[os.PathLike, str], 97 split: Optional[Literal["train", "val", "test"]] = None, 98 download: bool = False, 99) -> Tuple[List[str], List[str]]: 100 """Get paths to the OCELOT tissue images and semantic segmentation masks. 101 102 Args: 103 path: Filepath to a folder where the downloaded data will be saved. 104 split: The split to use. By default all splits ('train', 'val', 'test') are used. 105 download: Whether to download the data if it is not present. 106 107 Returns: 108 List of filepaths for the image data. 109 List of filepaths for the label data. 110 """ 111 data_dir = get_ocelot_data(path, download) 112 splits = SPLITS if split is None else (split,) 113 114 raw_paths, label_paths = [], [] 115 for this_split in splits: 116 split_raw_paths = sorted(glob(os.path.join(data_dir, "images", this_split, "tissue", "*.jpg"))) 117 split_label_paths = sorted(glob(os.path.join(data_dir, "annotations", this_split, "tissue", "*.png"))) 118 if not split_raw_paths or len(split_raw_paths) != len(split_label_paths): 119 raise RuntimeError(f"Invalid OCELOT raw-label pairing for split '{this_split}'.") 120 if any( 121 os.path.splitext(os.path.basename(raw_path))[0] != os.path.splitext(os.path.basename(label_path))[0] 122 for raw_path, label_path in zip(split_raw_paths, split_label_paths) 123 ): 124 raise RuntimeError(f"Mismatched OCELOT raw-label names for split '{this_split}'.") 125 raw_paths.extend(split_raw_paths) 126 label_paths.extend(split_label_paths) 127 128 return raw_paths, label_paths 129 130 131def get_ocelot_dataset( 132 path: Union[os.PathLike, str], 133 patch_shape: Tuple[int, int], 134 split: Optional[Literal["train", "val", "test"]] = None, 135 resize_inputs: bool = False, 136 download: bool = False, 137 **kwargs, 138) -> Dataset: 139 """Get the OCELOT dataset for tissue segmentation. 140 141 The masks use label 1 for background, 2 for cancer area, and 255 for unlabeled pixels. 142 143 Args: 144 path: Filepath to a folder where the downloaded data will be saved. 145 patch_shape: The patch shape to use for training. 146 split: The split to use. By default all splits ('train', 'val', 'test') are used. 147 resize_inputs: Whether to resize the inputs. 148 download: Whether to download the data if it is not present. 149 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 150 151 Returns: 152 The segmentation dataset. 153 """ 154 raw_paths, label_paths = get_ocelot_paths(path, split, download) 155 156 if resize_inputs: 157 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 158 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 159 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 160 ) 161 162 return torch_em.default_segmentation_dataset( 163 raw_paths=raw_paths, 164 raw_key=None, 165 label_paths=label_paths, 166 label_key=None, 167 patch_shape=patch_shape, 168 is_seg_dataset=False, 169 ndim=2, 170 with_channels=True, 171 **kwargs, 172 ) 173 174 175def get_ocelot_loader( 176 path: Union[os.PathLike, str], 177 batch_size: int, 178 patch_shape: Tuple[int, int], 179 split: Optional[Literal["train", "val", "test"]] = None, 180 resize_inputs: bool = False, 181 download: bool = False, 182 **kwargs, 183) -> DataLoader: 184 """Get the OCELOT dataloader for tissue segmentation. 185 186 Args: 187 path: Filepath to a folder where the downloaded data will be saved. 188 batch_size: The batch size for training. 189 patch_shape: The patch shape to use for training. 190 split: The split to use. By default all splits ('train', 'val', 'test') are used. 191 resize_inputs: Whether to resize the inputs. 192 download: Whether to download the data if it is not present. 193 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or the PyTorch DataLoader. 194 195 Returns: 196 The DataLoader. 197 """ 198 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 199 dataset = get_ocelot_dataset(path, patch_shape, split, resize_inputs, download, **ds_kwargs) 200 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
71def get_ocelot_data(path: Union[os.PathLike, str], download: bool = False) -> str: 72 """Download the OCELOT dataset. 73 74 Args: 75 path: Filepath to a folder where the downloaded data will be saved. 76 download: Whether to download the data if it is not present. 77 78 Returns: 79 Filepath to the folder where the data is stored. 80 """ 81 data_dir = os.path.join(path, "ocelot2023_v1.0.1") 82 if _has_data(data_dir): 83 return data_dir 84 85 os.makedirs(path, exist_ok=True) 86 zip_path = os.path.join(path, "ocelot2023_v1.0.1.zip") 87 util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM) 88 _validate_archive(zip_path) 89 util.unzip(zip_path=zip_path, dst=path) 90 91 if not _has_data(data_dir): 92 raise RuntimeError("The OCELOT archive does not contain the expected image and mask folders.") 93 return data_dir
Download the OCELOT dataset.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- download: Whether to download the data if it is not present.
Returns:
Filepath to the folder where the data is stored.
96def get_ocelot_paths( 97 path: Union[os.PathLike, str], 98 split: Optional[Literal["train", "val", "test"]] = None, 99 download: bool = False, 100) -> Tuple[List[str], List[str]]: 101 """Get paths to the OCELOT tissue images and semantic segmentation masks. 102 103 Args: 104 path: Filepath to a folder where the downloaded data will be saved. 105 split: The split to use. By default all splits ('train', 'val', 'test') are used. 106 download: Whether to download the data if it is not present. 107 108 Returns: 109 List of filepaths for the image data. 110 List of filepaths for the label data. 111 """ 112 data_dir = get_ocelot_data(path, download) 113 splits = SPLITS if split is None else (split,) 114 115 raw_paths, label_paths = [], [] 116 for this_split in splits: 117 split_raw_paths = sorted(glob(os.path.join(data_dir, "images", this_split, "tissue", "*.jpg"))) 118 split_label_paths = sorted(glob(os.path.join(data_dir, "annotations", this_split, "tissue", "*.png"))) 119 if not split_raw_paths or len(split_raw_paths) != len(split_label_paths): 120 raise RuntimeError(f"Invalid OCELOT raw-label pairing for split '{this_split}'.") 121 if any( 122 os.path.splitext(os.path.basename(raw_path))[0] != os.path.splitext(os.path.basename(label_path))[0] 123 for raw_path, label_path in zip(split_raw_paths, split_label_paths) 124 ): 125 raise RuntimeError(f"Mismatched OCELOT raw-label names for split '{this_split}'.") 126 raw_paths.extend(split_raw_paths) 127 label_paths.extend(split_label_paths) 128 129 return raw_paths, label_paths
Get paths to the OCELOT tissue images and semantic segmentation masks.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- split: The split to use. By default all splits ('train', 'val', 'test') are used.
- download: Whether to download the data if it is not present.
Returns:
List of filepaths for the image data. List of filepaths for the label data.
132def get_ocelot_dataset( 133 path: Union[os.PathLike, str], 134 patch_shape: Tuple[int, int], 135 split: Optional[Literal["train", "val", "test"]] = None, 136 resize_inputs: bool = False, 137 download: bool = False, 138 **kwargs, 139) -> Dataset: 140 """Get the OCELOT dataset for tissue segmentation. 141 142 The masks use label 1 for background, 2 for cancer area, and 255 for unlabeled pixels. 143 144 Args: 145 path: Filepath to a folder where the downloaded data will be saved. 146 patch_shape: The patch shape to use for training. 147 split: The split to use. By default all splits ('train', 'val', 'test') are used. 148 resize_inputs: Whether to resize the inputs. 149 download: Whether to download the data if it is not present. 150 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 151 152 Returns: 153 The segmentation dataset. 154 """ 155 raw_paths, label_paths = get_ocelot_paths(path, split, download) 156 157 if resize_inputs: 158 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 159 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 160 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 161 ) 162 163 return torch_em.default_segmentation_dataset( 164 raw_paths=raw_paths, 165 raw_key=None, 166 label_paths=label_paths, 167 label_key=None, 168 patch_shape=patch_shape, 169 is_seg_dataset=False, 170 ndim=2, 171 with_channels=True, 172 **kwargs, 173 )
Get the OCELOT dataset for tissue segmentation.
The masks use label 1 for background, 2 for cancer area, and 255 for unlabeled pixels.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- patch_shape: The patch shape to use for training.
- split: The split to use. By default all splits ('train', 'val', 'test') are used.
- resize_inputs: Whether to resize the inputs.
- 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.
176def get_ocelot_loader( 177 path: Union[os.PathLike, str], 178 batch_size: int, 179 patch_shape: Tuple[int, int], 180 split: Optional[Literal["train", "val", "test"]] = None, 181 resize_inputs: bool = False, 182 download: bool = False, 183 **kwargs, 184) -> DataLoader: 185 """Get the OCELOT dataloader for tissue segmentation. 186 187 Args: 188 path: Filepath to a folder where the downloaded data will be saved. 189 batch_size: The batch size for training. 190 patch_shape: The patch shape to use for training. 191 split: The split to use. By default all splits ('train', 'val', 'test') are used. 192 resize_inputs: Whether to resize the inputs. 193 download: Whether to download the data if it is not present. 194 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or the PyTorch DataLoader. 195 196 Returns: 197 The DataLoader. 198 """ 199 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 200 dataset = get_ocelot_dataset(path, patch_shape, split, resize_inputs, download, **ds_kwargs) 201 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the OCELOT dataloader for tissue segmentation.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- batch_size: The batch size for training.
- patch_shape: The patch shape to use for training.
- split: The split to use. By default all splits ('train', 'val', 'test') are used.
- resize_inputs: Whether to resize the inputs.
- download: Whether to download the data if it is not present.
- kwargs: Additional keyword arguments for
torch_em.default_segmentation_datasetor the PyTorch DataLoader.
Returns:
The DataLoader.