torch_em.data.datasets.medical.insect_anatomy
The InsectAnatomy dataset contains annotations for brain segmentation in micro-CT scans of ant heads.
It comprises 2d cross-sections (along the xy, xz and yz planes) of the head scans of 76 ant species with semi-manually segmented brain masks, split into an official training (60%) and testing (40%) set. The images and masks are stored as tif files. The label ids are: 0 = background, 1 = brain. This is the 'InsectAnatomy' out-of-distribution set of the nnInteractive benchmark (https://arxiv.org/abs/2503.08373), which reports 84 micro-CT volumes for it.
NOTE: The data is hosted on Dryad (https://doi.org/10.5061/dryad.qz612jmgv), which protects its downloads
with a browser check that automated downloads cannot pass. If the automatic download fails, please download
'training.zip' and 'testing.zip' from the Dryad page in your browser and place them at '
NOTE: The layout inside the archives is not documented by the authors. The images and masks are matched by
their file names (see MASK_KEYWORDS); adjust get_insect_anatomy_paths if the layout differs. The archives
mix three naming conventions and also contain unpaired files, i.e. species for which only the images or only
the masks were deposited, so those are skipped: this yields 35422 pairs for 'train' and 7366 for 'test'.
NOTE: The masks are 8 bit images with antialiased borders, where the brain is 255 and the background is 0. They are binarized to the label ids above by the dataset, so a custom 'label_transform' overrides this.
NOTE: The masks of the test split are inconsistent and it is therefore not recommended for training. In the training split the brain is marked correctly (247 of 250 sampled masks cover the brighter brain tissue, with a mean foreground of 22%), but in the test split most masks have an inverted polarity, i.e. they mark the background instead of the brain: of 179 sampled masks that cover more than half of the image, only 6 cover the brighter tissue. The deposit does not document this, and the polarity cannot be told apart from a correctly annotated slice of a cropped scan, so the masks are passed through as they are stored and a warning is raised.
The dataset is located at https://doi.org/10.5061/dryad.qz612jmgv.
This dataset is from the publication https://doi.org/10.1002/ntls.20230010. Please cite it if you use this dataset in your research.
1"""The InsectAnatomy dataset contains annotations for brain segmentation in micro-CT scans of ant heads. 2 3It comprises 2d cross-sections (along the xy, xz and yz planes) of the head scans of 76 ant species with 4semi-manually segmented brain masks, split into an official training (60%) and testing (40%) set. 5The images and masks are stored as tif files. The label ids are: 0 = background, 1 = brain. 6This is the 'InsectAnatomy' out-of-distribution set of the nnInteractive benchmark 7(https://arxiv.org/abs/2503.08373), which reports 84 micro-CT volumes for it. 8 9NOTE: The data is hosted on Dryad (https://doi.org/10.5061/dryad.qz612jmgv), which protects its downloads 10with a browser check that automated downloads cannot pass. If the automatic download fails, please download 11'training.zip' and 'testing.zip' from the Dryad page in your browser and place them at '<path>'. 12 13NOTE: The layout inside the archives is not documented by the authors. The images and masks are matched by 14their file names (see `MASK_KEYWORDS`); adjust `get_insect_anatomy_paths` if the layout differs. The archives 15mix three naming conventions and also contain unpaired files, i.e. species for which only the images or only 16the masks were deposited, so those are skipped: this yields 35422 pairs for 'train' and 7366 for 'test'. 17 18NOTE: The masks are 8 bit images with antialiased borders, where the brain is 255 and the background is 0. 19They are binarized to the label ids above by the dataset, so a custom 'label_transform' overrides this. 20 21NOTE: The masks of the test split are inconsistent and it is therefore not recommended for training. In the 22training split the brain is marked correctly (247 of 250 sampled masks cover the brighter brain tissue, with a 23mean foreground of 22%), but in the test split most masks have an inverted polarity, i.e. they mark the 24background instead of the brain: of 179 sampled masks that cover more than half of the image, only 6 cover the 25brighter tissue. The deposit does not document this, and the polarity cannot be told apart from a correctly 26annotated slice of a cropped scan, so the masks are passed through as they are stored and a warning is raised. 27 28The dataset is located at https://doi.org/10.5061/dryad.qz612jmgv. 29 30This dataset is from the publication https://doi.org/10.1002/ntls.20230010. 31Please cite it if you use this dataset in your research. 32""" 33 34import os 35import re 36import zipfile 37import warnings 38from glob import glob 39from natsort import natsorted 40from typing import Union, Tuple, Literal, List 41 42import numpy as np 43 44from torch.utils.data import Dataset, DataLoader 45 46import torch_em 47 48from .. import util 49 50 51URLS = { 52 "train": "https://datadryad.org/downloads/file_stream/2625340", 53 "test": "https://datadryad.org/downloads/file_stream/2625339", 54} 55 56ZIP_NAMES = {"train": "training.zip", "test": "testing.zip"} 57 58LABEL_IDS = {"background": 0, "brain": 1} 59 60MASK_KEYWORDS = ("mask", "label", "seg", "gt") 61 62MANUAL_DOWNLOAD_MSG = ( 63 "The automatic download of '{}' from Dryad failed. Dryad protects its downloads with a browser check. " 64 "Please download the file manually from https://doi.org/10.5061/dryad.qz612jmgv and place it at '{}'." 65) 66 67 68def _binarize_labels(labels): 69 # The masks are 8 bit images with antialiased borders, where the brain is 255 and the background is 0. 70 return (np.asarray(labels) > 127).astype("uint8") 71 72 73def get_insect_anatomy_data( 74 path: Union[os.PathLike, str], split: Literal["train", "test"], download: bool = False 75) -> str: 76 """Download the InsectAnatomy dataset. 77 78 Args: 79 path: Filepath to a folder where the data is downloaded for further processing. 80 split: The choice of data split. 81 download: Whether to download the data if it is not present. 82 83 Returns: 84 Filepath where the data is downloaded. 85 """ 86 if split not in URLS: 87 raise ValueError(f"'{split}' is not a valid split.") 88 89 data_dir = os.path.join(path, split) 90 if os.path.exists(data_dir): 91 return data_dir 92 93 os.makedirs(path, exist_ok=True) 94 95 zip_path = os.path.join(path, ZIP_NAMES[split]) 96 if not os.path.exists(zip_path): 97 if not download: 98 raise RuntimeError(f"Cannot find the data at {zip_path}, but download was set to False.") 99 100 try: 101 util.download_source(path=zip_path, url=URLS[split], download=download) 102 except Exception: 103 raise RuntimeError(MANUAL_DOWNLOAD_MSG.format(ZIP_NAMES[split], path)) 104 105 if not zipfile.is_zipfile(zip_path): 106 os.remove(zip_path) 107 raise RuntimeError(MANUAL_DOWNLOAD_MSG.format(ZIP_NAMES[split], path)) 108 109 util.unzip(zip_path=zip_path, dst=data_dir) 110 return data_dir 111 112 113def get_insect_anatomy_paths( 114 path: Union[os.PathLike, str], split: Literal["train", "test"], download: bool = False 115) -> Tuple[List[str], List[str]]: 116 """Get paths to the InsectAnatomy data. 117 118 Args: 119 path: Filepath to a folder where the data is downloaded for further processing. 120 split: The choice of data split. 121 download: Whether to download the data if it is not present. 122 123 Returns: 124 List of filepaths for the image data. 125 List of filepaths for the label data. 126 """ 127 data_dir = get_insect_anatomy_data(path, split, download) 128 129 if split == "test": 130 warnings.warn( 131 "The masks of the InsectAnatomy test split are inconsistent: most of them mark the background " 132 "instead of the brain. See 'torch_em.data.datasets.medical.insect_anatomy' for details." 133 ) 134 135 # The images and their masks are stored as tif files, the masks are marked by 'mask' in the file name. 136 # NOTE: The archives use three different naming conventions, so the name of the image is derived from the 137 # name of the mask by removing the first mask marker: '<name>.tif' pairs with '<name>_mask.tif', and 138 # '<species><slice>.tif' pairs with both '<species>__mask<slice>.tif' and '<species>_mask<slice>.tif'. 139 all_paths = natsorted(glob(os.path.join(data_dir, "**", "*.tif*"), recursive=True)) 140 141 # The archives contain the sidecar files that macOS adds when zipping, which are not image data. 142 all_paths = [p for p in all_paths if "__MACOSX" not in p and not os.path.basename(p).startswith("._")] 143 144 mask_paths, images_by_name = [], {} 145 for filepath in all_paths: 146 name = os.path.splitext(os.path.basename(filepath))[0] 147 if any(kw in name.lower() for kw in MASK_KEYWORDS): 148 mask_paths.append(filepath) 149 else: 150 images_by_name[name] = filepath 151 152 raw_paths, label_paths = [], [] 153 for filepath in mask_paths: 154 name = os.path.splitext(os.path.basename(filepath))[0] 155 image_name = re.sub(r"_{0,2}mask", "", name, count=1) 156 if image_name in images_by_name: 157 raw_paths.append(images_by_name[image_name]) 158 label_paths.append(filepath) 159 160 if len(raw_paths) == 0: 161 raise RuntimeError( 162 f"Found {len(images_by_name)} images and {len(mask_paths)} masks in '{data_dir}', but could not match " 163 "any of them. The images and masks are matched by their file names, see 'MASK_KEYWORDS'." 164 ) 165 166 return raw_paths, label_paths 167 168 169def get_insect_anatomy_dataset( 170 path: Union[os.PathLike, str], 171 patch_shape: Tuple[int, int], 172 split: Literal["train", "test"], 173 resize_inputs: bool = False, 174 download: bool = False, 175 **kwargs 176) -> Dataset: 177 """Get the InsectAnatomy dataset for ant brain segmentation. 178 179 Args: 180 path: Filepath to a folder where the data is downloaded for further processing. 181 patch_shape: The patch shape to use for training. 182 split: The choice of data split. 183 resize_inputs: Whether to resize inputs to the desired patch shape. 184 download: Whether to download the data if it is not present. 185 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 186 187 Returns: 188 The segmentation dataset. 189 """ 190 raw_paths, label_paths = get_insect_anatomy_paths(path, split, download) 191 192 # The masks are stored as 8 bit images with antialiased borders, so they are binarized to the documented ids. 193 kwargs = util.update_kwargs(kwargs, "label_transform", _binarize_labels) 194 195 if resize_inputs: 196 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False} 197 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 198 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 199 ) 200 201 return torch_em.default_segmentation_dataset( 202 raw_paths=raw_paths, 203 raw_key=None, 204 label_paths=label_paths, 205 label_key=None, 206 patch_shape=patch_shape, 207 is_seg_dataset=False, 208 **kwargs 209 ) 210 211 212def get_insect_anatomy_loader( 213 path: Union[os.PathLike, str], 214 batch_size: int, 215 patch_shape: Tuple[int, int], 216 split: Literal["train", "test"], 217 resize_inputs: bool = False, 218 download: bool = False, 219 **kwargs 220) -> DataLoader: 221 """Get the InsectAnatomy dataloader for ant brain segmentation. 222 223 Args: 224 path: Filepath to a folder where the data is downloaded for further processing. 225 batch_size: The batch size for training. 226 patch_shape: The patch shape to use for training. 227 split: The choice of data split. 228 resize_inputs: Whether to resize inputs to the desired patch shape. 229 download: Whether to download the data if it is not present. 230 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 231 232 Returns: 233 The DataLoader. 234 """ 235 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 236 dataset = get_insect_anatomy_dataset(path, patch_shape, split, resize_inputs, download, **ds_kwargs) 237 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
74def get_insect_anatomy_data( 75 path: Union[os.PathLike, str], split: Literal["train", "test"], download: bool = False 76) -> str: 77 """Download the InsectAnatomy dataset. 78 79 Args: 80 path: Filepath to a folder where the data is downloaded for further processing. 81 split: The choice of data split. 82 download: Whether to download the data if it is not present. 83 84 Returns: 85 Filepath where the data is downloaded. 86 """ 87 if split not in URLS: 88 raise ValueError(f"'{split}' is not a valid split.") 89 90 data_dir = os.path.join(path, split) 91 if os.path.exists(data_dir): 92 return data_dir 93 94 os.makedirs(path, exist_ok=True) 95 96 zip_path = os.path.join(path, ZIP_NAMES[split]) 97 if not os.path.exists(zip_path): 98 if not download: 99 raise RuntimeError(f"Cannot find the data at {zip_path}, but download was set to False.") 100 101 try: 102 util.download_source(path=zip_path, url=URLS[split], download=download) 103 except Exception: 104 raise RuntimeError(MANUAL_DOWNLOAD_MSG.format(ZIP_NAMES[split], path)) 105 106 if not zipfile.is_zipfile(zip_path): 107 os.remove(zip_path) 108 raise RuntimeError(MANUAL_DOWNLOAD_MSG.format(ZIP_NAMES[split], path)) 109 110 util.unzip(zip_path=zip_path, dst=data_dir) 111 return data_dir
Download the InsectAnatomy dataset.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- split: The choice of data split.
- download: Whether to download the data if it is not present.
Returns:
Filepath where the data is downloaded.
114def get_insect_anatomy_paths( 115 path: Union[os.PathLike, str], split: Literal["train", "test"], download: bool = False 116) -> Tuple[List[str], List[str]]: 117 """Get paths to the InsectAnatomy data. 118 119 Args: 120 path: Filepath to a folder where the data is downloaded for further processing. 121 split: The choice of data split. 122 download: Whether to download the data if it is not present. 123 124 Returns: 125 List of filepaths for the image data. 126 List of filepaths for the label data. 127 """ 128 data_dir = get_insect_anatomy_data(path, split, download) 129 130 if split == "test": 131 warnings.warn( 132 "The masks of the InsectAnatomy test split are inconsistent: most of them mark the background " 133 "instead of the brain. See 'torch_em.data.datasets.medical.insect_anatomy' for details." 134 ) 135 136 # The images and their masks are stored as tif files, the masks are marked by 'mask' in the file name. 137 # NOTE: The archives use three different naming conventions, so the name of the image is derived from the 138 # name of the mask by removing the first mask marker: '<name>.tif' pairs with '<name>_mask.tif', and 139 # '<species><slice>.tif' pairs with both '<species>__mask<slice>.tif' and '<species>_mask<slice>.tif'. 140 all_paths = natsorted(glob(os.path.join(data_dir, "**", "*.tif*"), recursive=True)) 141 142 # The archives contain the sidecar files that macOS adds when zipping, which are not image data. 143 all_paths = [p for p in all_paths if "__MACOSX" not in p and not os.path.basename(p).startswith("._")] 144 145 mask_paths, images_by_name = [], {} 146 for filepath in all_paths: 147 name = os.path.splitext(os.path.basename(filepath))[0] 148 if any(kw in name.lower() for kw in MASK_KEYWORDS): 149 mask_paths.append(filepath) 150 else: 151 images_by_name[name] = filepath 152 153 raw_paths, label_paths = [], [] 154 for filepath in mask_paths: 155 name = os.path.splitext(os.path.basename(filepath))[0] 156 image_name = re.sub(r"_{0,2}mask", "", name, count=1) 157 if image_name in images_by_name: 158 raw_paths.append(images_by_name[image_name]) 159 label_paths.append(filepath) 160 161 if len(raw_paths) == 0: 162 raise RuntimeError( 163 f"Found {len(images_by_name)} images and {len(mask_paths)} masks in '{data_dir}', but could not match " 164 "any of them. The images and masks are matched by their file names, see 'MASK_KEYWORDS'." 165 ) 166 167 return raw_paths, label_paths
Get paths to the InsectAnatomy data.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- split: The choice of data split.
- 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.
170def get_insect_anatomy_dataset( 171 path: Union[os.PathLike, str], 172 patch_shape: Tuple[int, int], 173 split: Literal["train", "test"], 174 resize_inputs: bool = False, 175 download: bool = False, 176 **kwargs 177) -> Dataset: 178 """Get the InsectAnatomy dataset for ant brain segmentation. 179 180 Args: 181 path: Filepath to a folder where the data is downloaded for further processing. 182 patch_shape: The patch shape to use for training. 183 split: The choice of data split. 184 resize_inputs: Whether to resize inputs to the desired patch shape. 185 download: Whether to download the data if it is not present. 186 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 187 188 Returns: 189 The segmentation dataset. 190 """ 191 raw_paths, label_paths = get_insect_anatomy_paths(path, split, download) 192 193 # The masks are stored as 8 bit images with antialiased borders, so they are binarized to the documented ids. 194 kwargs = util.update_kwargs(kwargs, "label_transform", _binarize_labels) 195 196 if resize_inputs: 197 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False} 198 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 199 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 200 ) 201 202 return torch_em.default_segmentation_dataset( 203 raw_paths=raw_paths, 204 raw_key=None, 205 label_paths=label_paths, 206 label_key=None, 207 patch_shape=patch_shape, 208 is_seg_dataset=False, 209 **kwargs 210 )
Get the InsectAnatomy dataset for ant brain segmentation.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- patch_shape: The patch shape to use for training.
- split: The choice of data split.
- resize_inputs: Whether to resize inputs to the desired patch shape.
- 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.
213def get_insect_anatomy_loader( 214 path: Union[os.PathLike, str], 215 batch_size: int, 216 patch_shape: Tuple[int, int], 217 split: Literal["train", "test"], 218 resize_inputs: bool = False, 219 download: bool = False, 220 **kwargs 221) -> DataLoader: 222 """Get the InsectAnatomy dataloader for ant brain segmentation. 223 224 Args: 225 path: Filepath to a folder where the data is downloaded for further processing. 226 batch_size: The batch size for training. 227 patch_shape: The patch shape to use for training. 228 split: The choice of data split. 229 resize_inputs: Whether to resize inputs to the desired patch shape. 230 download: Whether to download the data if it is not present. 231 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 232 233 Returns: 234 The DataLoader. 235 """ 236 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 237 dataset = get_insect_anatomy_dataset(path, patch_shape, split, resize_inputs, download, **ds_kwargs) 238 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the InsectAnatomy dataloader for ant brain segmentation.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- batch_size: The batch size for training.
- patch_shape: The patch shape to use for training.
- split: The choice of data split.
- resize_inputs: Whether to resize inputs to the desired patch shape.
- 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 DataLoader.