torch_em.data.datasets.medical.spine_endoscopic_atlas
The Spine Endoscopic Atlas (SEA) dataset contains annotations for surgical instrument segmentation in endoscopic spine surgery images.
The full archive ships 48,510 images in total, of which 10,662 come with an instrument segmentation mask (stored as an NRRD file in a sibling 'seg' folder, matched to its image by basename); the remaining ~37,848 images are unlabeled raw frames under 'unclassified' and are not used by this loader. Annotated images are organized by working-channel diameter ('big' or 'small'), spinal region ('cervical' or 'lumbar'), patient, difficulty ('normal' or 'difficult' scenario) and instrument type ('bipolar', 'grasping_forceps', 'drill', 'dissector', 'punch' or 'scissor'); the instrument type can be selected with the 'instrument' argument. This loader discovers the image-mask pairs on disk rather than relying on the advertised counts, and merges each NRRD segmentation (which may encode either a binary 0/255 mask or a per-segment integer label map, depending on the file) into a binary instrument mask during preprocessing.
The dataset is located at https://doi.org/10.6084/m9.figshare.27109312, released under a CC0 license.
1"""The Spine Endoscopic Atlas (SEA) dataset contains annotations for surgical instrument 2segmentation in endoscopic spine surgery images. 3 4The full archive ships 48,510 images in total, of which 10,662 come with an instrument 5segmentation mask (stored as an NRRD file in a sibling 'seg' folder, matched to its image by 6basename); the remaining ~37,848 images are unlabeled raw frames under 'unclassified' and are 7not used by this loader. Annotated images are organized by working-channel diameter ('big' or 8'small'), spinal region ('cervical' or 'lumbar'), patient, difficulty ('normal' or 'difficult' 9scenario) and instrument type ('bipolar', 'grasping_forceps', 'drill', 'dissector', 'punch' or 10'scissor'); the instrument type can be selected with the 'instrument' argument. This loader 11discovers the image-mask pairs on disk rather than relying on the advertised counts, and merges 12each NRRD segmentation (which may encode either a binary 0/255 mask or a per-segment integer 13label map, depending on the file) into a binary instrument mask during preprocessing. 14 15The dataset is located at https://doi.org/10.6084/m9.figshare.27109312, released under a CC0 license. 16""" 17 18import os 19from glob import glob 20from natsort import natsorted 21from typing import Union, Tuple, List, Optional 22 23from torch.utils.data import Dataset, DataLoader 24 25import torch_em 26 27from .. import util 28 29 30URL = "https://ndownloader.figshare.com/files/56202515" 31CHECKSUM = "bde2014aa786181140552ab248fad0b2" 32 33INSTRUMENTS = ["bipolar", "grasping_forceps", "drill", "dissector", "punch", "scissor"] 34 35 36def _convert_mask(nrrd_path, out_path): 37 import numpy as np 38 import SimpleITK as sitk 39 import tifffile 40 41 if os.path.exists(out_path): 42 return 43 44 data = sitk.GetArrayFromImage(sitk.ReadImage(nrrd_path)) 45 data = np.squeeze(data) 46 mask = (data > 0).astype("uint8") 47 48 tmp_path = f"{out_path}.{os.getpid()}.incomplete.tif" 49 tifffile.imwrite(tmp_path, mask) 50 os.replace(tmp_path, out_path) 51 52 53def _convert_raw(raw_path, out_path): 54 from PIL import Image 55 56 if os.path.exists(out_path): 57 return 58 59 image = Image.open(raw_path).convert("RGB") 60 61 tmp_path = f"{out_path}.{os.getpid()}.incomplete.png" 62 image.save(tmp_path) 63 os.replace(tmp_path, out_path) 64 65 66def get_spine_endoscopic_atlas_data(path: Union[os.PathLike, str], download: bool = False) -> str: 67 """Download the Spine Endoscopic Atlas dataset. 68 69 Args: 70 path: Filepath to a folder where the data is downloaded for further processing. 71 download: Whether to download the data if it is not present. 72 73 Returns: 74 Filepath where the data is downloaded. 75 """ 76 data_dir = os.path.join(path, "Spine endoscopic atlas", "classified") 77 if os.path.exists(data_dir): 78 return data_dir 79 80 os.makedirs(path, exist_ok=True) 81 82 zip_path = os.path.join(path, "spine_endoscopic_atlas.zip") 83 util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM) 84 util.unzip(zip_path=zip_path, dst=path, remove=False) 85 86 assert os.path.exists(data_dir), f"The extraction of the archive did not create the expected folder in '{path}'." 87 88 return data_dir 89 90 91def get_spine_endoscopic_atlas_paths( 92 path: Union[os.PathLike, str], instrument: Optional[str] = None, download: bool = False, 93) -> Tuple[List[str], List[str]]: 94 """Get paths to the Spine Endoscopic Atlas data. 95 96 Args: 97 path: Filepath to a folder where the data is downloaded for further processing. 98 instrument: The choice of instrument type to restrict the data to. By default all instrument 99 types are used. See `INSTRUMENTS` for the valid choices. 100 download: Whether to download the data if it is not present. 101 102 Returns: 103 List of filepaths for the image data. 104 List of filepaths for the label data. 105 """ 106 if instrument is not None and instrument not in INSTRUMENTS: 107 raise ValueError(f"'{instrument}' is not a valid instrument. Choose one of {INSTRUMENTS}.") 108 109 data_dir = get_spine_endoscopic_atlas_data(path, download) 110 mask_dir = os.path.join(path, "spine_endoscopic_atlas_masks") 111 raw_dir = os.path.join(path, "spine_endoscopic_atlas_raw") 112 os.makedirs(mask_dir, exist_ok=True) 113 os.makedirs(raw_dir, exist_ok=True) 114 115 pattern = os.path.join(data_dir, "*", "*", "*", "*", instrument or "*", "*.*") 116 raw_paths, label_paths = [], [] 117 for source_path in natsorted(glob(pattern)): 118 if source_path.lower().endswith((".jpg", ".png")) and os.path.basename(os.path.dirname(source_path)) != "seg": 119 base = os.path.splitext(os.path.basename(source_path))[0] 120 nrrd_path = os.path.join(os.path.dirname(source_path), "seg", f"{base}.nrrd") 121 if not os.path.exists(nrrd_path): 122 continue 123 124 # The raw images are re-encoded as RGB PNGs: some ship as RGBA, which breaks the 125 # fixed 3-channel assumption of the resize transform used by 'resize_inputs'. 126 raw_path = os.path.join(raw_dir, f"{base}.png") 127 _convert_raw(source_path, raw_path) 128 129 label_path = os.path.join(mask_dir, f"{base}.tif") 130 _convert_mask(nrrd_path, label_path) 131 132 raw_paths.append(raw_path) 133 label_paths.append(label_path) 134 135 assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0 136 137 return raw_paths, label_paths 138 139 140def get_spine_endoscopic_atlas_dataset( 141 path: Union[os.PathLike, str], 142 patch_shape: Tuple[int, int], 143 instrument: Optional[str] = None, 144 resize_inputs: bool = False, 145 download: bool = False, 146 **kwargs 147) -> Dataset: 148 """Get the Spine Endoscopic Atlas dataset for surgical instrument segmentation. 149 150 Args: 151 path: Filepath to a folder where the data is downloaded for further processing. 152 patch_shape: The patch shape to use for training. 153 instrument: The choice of instrument type to restrict the data to. 154 resize_inputs: Whether to resize the inputs to the patch shape. 155 download: Whether to download the data if it is not present. 156 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 157 158 Returns: 159 The segmentation dataset. 160 """ 161 raw_paths, label_paths = get_spine_endoscopic_atlas_paths(path, instrument, download) 162 163 if resize_inputs: 164 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 165 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 166 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 167 ) 168 169 return torch_em.default_segmentation_dataset( 170 raw_paths=raw_paths, 171 raw_key=None, 172 label_paths=label_paths, 173 label_key=None, 174 patch_shape=patch_shape, 175 is_seg_dataset=False, 176 **kwargs 177 ) 178 179 180def get_spine_endoscopic_atlas_loader( 181 path: Union[os.PathLike, str], 182 batch_size: int, 183 patch_shape: Tuple[int, int], 184 instrument: Optional[str] = None, 185 resize_inputs: bool = False, 186 download: bool = False, 187 **kwargs 188) -> DataLoader: 189 """Get the Spine Endoscopic Atlas dataloader for surgical instrument segmentation. 190 191 Args: 192 path: Filepath to a folder where the data is downloaded for further processing. 193 batch_size: The batch size for training. 194 patch_shape: The patch shape to use for training. 195 instrument: The choice of instrument type to restrict the data to. 196 resize_inputs: Whether to resize the inputs to the patch shape. 197 download: Whether to download the data if it is not present. 198 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 199 200 Returns: 201 The DataLoader. 202 """ 203 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 204 dataset = get_spine_endoscopic_atlas_dataset(path, patch_shape, instrument, resize_inputs, download, **ds_kwargs) 205 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
67def get_spine_endoscopic_atlas_data(path: Union[os.PathLike, str], download: bool = False) -> str: 68 """Download the Spine Endoscopic Atlas dataset. 69 70 Args: 71 path: Filepath to a folder where the data is downloaded for further processing. 72 download: Whether to download the data if it is not present. 73 74 Returns: 75 Filepath where the data is downloaded. 76 """ 77 data_dir = os.path.join(path, "Spine endoscopic atlas", "classified") 78 if os.path.exists(data_dir): 79 return data_dir 80 81 os.makedirs(path, exist_ok=True) 82 83 zip_path = os.path.join(path, "spine_endoscopic_atlas.zip") 84 util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM) 85 util.unzip(zip_path=zip_path, dst=path, remove=False) 86 87 assert os.path.exists(data_dir), f"The extraction of the archive did not create the expected folder in '{path}'." 88 89 return data_dir
Download the Spine Endoscopic Atlas dataset.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- download: Whether to download the data if it is not present.
Returns:
Filepath where the data is downloaded.
92def get_spine_endoscopic_atlas_paths( 93 path: Union[os.PathLike, str], instrument: Optional[str] = None, download: bool = False, 94) -> Tuple[List[str], List[str]]: 95 """Get paths to the Spine Endoscopic Atlas data. 96 97 Args: 98 path: Filepath to a folder where the data is downloaded for further processing. 99 instrument: The choice of instrument type to restrict the data to. By default all instrument 100 types are used. See `INSTRUMENTS` for the valid choices. 101 download: Whether to download the data if it is not present. 102 103 Returns: 104 List of filepaths for the image data. 105 List of filepaths for the label data. 106 """ 107 if instrument is not None and instrument not in INSTRUMENTS: 108 raise ValueError(f"'{instrument}' is not a valid instrument. Choose one of {INSTRUMENTS}.") 109 110 data_dir = get_spine_endoscopic_atlas_data(path, download) 111 mask_dir = os.path.join(path, "spine_endoscopic_atlas_masks") 112 raw_dir = os.path.join(path, "spine_endoscopic_atlas_raw") 113 os.makedirs(mask_dir, exist_ok=True) 114 os.makedirs(raw_dir, exist_ok=True) 115 116 pattern = os.path.join(data_dir, "*", "*", "*", "*", instrument or "*", "*.*") 117 raw_paths, label_paths = [], [] 118 for source_path in natsorted(glob(pattern)): 119 if source_path.lower().endswith((".jpg", ".png")) and os.path.basename(os.path.dirname(source_path)) != "seg": 120 base = os.path.splitext(os.path.basename(source_path))[0] 121 nrrd_path = os.path.join(os.path.dirname(source_path), "seg", f"{base}.nrrd") 122 if not os.path.exists(nrrd_path): 123 continue 124 125 # The raw images are re-encoded as RGB PNGs: some ship as RGBA, which breaks the 126 # fixed 3-channel assumption of the resize transform used by 'resize_inputs'. 127 raw_path = os.path.join(raw_dir, f"{base}.png") 128 _convert_raw(source_path, raw_path) 129 130 label_path = os.path.join(mask_dir, f"{base}.tif") 131 _convert_mask(nrrd_path, label_path) 132 133 raw_paths.append(raw_path) 134 label_paths.append(label_path) 135 136 assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0 137 138 return raw_paths, label_paths
Get paths to the Spine Endoscopic Atlas data.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- instrument: The choice of instrument type to restrict the data to. By default all instrument
types are used. See
INSTRUMENTSfor the valid choices. - 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.
141def get_spine_endoscopic_atlas_dataset( 142 path: Union[os.PathLike, str], 143 patch_shape: Tuple[int, int], 144 instrument: Optional[str] = None, 145 resize_inputs: bool = False, 146 download: bool = False, 147 **kwargs 148) -> Dataset: 149 """Get the Spine Endoscopic Atlas dataset for surgical instrument segmentation. 150 151 Args: 152 path: Filepath to a folder where the data is downloaded for further processing. 153 patch_shape: The patch shape to use for training. 154 instrument: The choice of instrument type to restrict the data to. 155 resize_inputs: Whether to resize the inputs to the patch shape. 156 download: Whether to download the data if it is not present. 157 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 158 159 Returns: 160 The segmentation dataset. 161 """ 162 raw_paths, label_paths = get_spine_endoscopic_atlas_paths(path, instrument, download) 163 164 if resize_inputs: 165 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 166 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 167 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 168 ) 169 170 return torch_em.default_segmentation_dataset( 171 raw_paths=raw_paths, 172 raw_key=None, 173 label_paths=label_paths, 174 label_key=None, 175 patch_shape=patch_shape, 176 is_seg_dataset=False, 177 **kwargs 178 )
Get the Spine Endoscopic Atlas dataset for surgical instrument segmentation.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- patch_shape: The patch shape to use for training.
- instrument: The choice of instrument type to restrict the data to.
- resize_inputs: Whether to resize the inputs to the 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.
181def get_spine_endoscopic_atlas_loader( 182 path: Union[os.PathLike, str], 183 batch_size: int, 184 patch_shape: Tuple[int, int], 185 instrument: Optional[str] = None, 186 resize_inputs: bool = False, 187 download: bool = False, 188 **kwargs 189) -> DataLoader: 190 """Get the Spine Endoscopic Atlas dataloader for surgical instrument segmentation. 191 192 Args: 193 path: Filepath to a folder where the data is downloaded for further processing. 194 batch_size: The batch size for training. 195 patch_shape: The patch shape to use for training. 196 instrument: The choice of instrument type to restrict the data to. 197 resize_inputs: Whether to resize the inputs to the patch shape. 198 download: Whether to download the data if it is not present. 199 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 200 201 Returns: 202 The DataLoader. 203 """ 204 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 205 dataset = get_spine_endoscopic_atlas_dataset(path, patch_shape, instrument, resize_inputs, download, **ds_kwargs) 206 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the Spine Endoscopic Atlas dataloader for surgical instrument 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.
- instrument: The choice of instrument type to restrict the data to.
- resize_inputs: Whether to resize the inputs to the 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.