torch_em.data.datasets.medical.longitudinal_mh_oct
This dataset contains pixel-level segmented longitudinal OCT scans of idiopathic full-thickness macular hole (iFTMH) surgery outcomes, collected at one preoperative ('baseline') and six postoperative time points ('2weeks', '3months', '6months', '12months', '24months', '48months').
The dataset contains 2,591 fovea-centered horizontal and vertical B-scans from 493 patients, with expert-validated pixel-level segmentation masks for 12 retinal structures and pathologies, both anatomical (e.g. external limiting membrane, ellipsoid zone, retinal pigment epithelium) and pathological (e.g. macular hole, cysts, epiretinal membrane, subretinal fluid).
NOTE: This is distinct from the OIMHS dataset (see 'torch_em/data/datasets/medical/ oimhs.py'), which is a different, non-longitudinal macular hole OCT dataset.
The dataset is located at https://doi.org/10.6084/m9.figshare.32605218 and is licensed under CC-BY-4.0.
This dataset is from the publication https://doi.org/10.1038/s41597-026-08154-7. Please cite it if you use this dataset for your research.
1"""This dataset contains pixel-level segmented longitudinal OCT scans of idiopathic 2full-thickness macular hole (iFTMH) surgery outcomes, collected at one preoperative 3('baseline') and six postoperative time points ('2weeks', '3months', '6months', 4'12months', '24months', '48months'). 5 6The dataset contains 2,591 fovea-centered horizontal and vertical B-scans from 493 7patients, with expert-validated pixel-level segmentation masks for 12 retinal 8structures and pathologies, both anatomical (e.g. external limiting membrane, 9ellipsoid zone, retinal pigment epithelium) and pathological (e.g. macular hole, 10cysts, epiretinal membrane, subretinal fluid). 11 12NOTE: This is distinct from the OIMHS dataset (see 'torch_em/data/datasets/medical/ 13oimhs.py'), which is a different, non-longitudinal macular hole OCT dataset. 14 15The dataset is located at https://doi.org/10.6084/m9.figshare.32605218 and is 16licensed under CC-BY-4.0. 17 18This dataset is from the publication https://doi.org/10.1038/s41597-026-08154-7. 19Please cite it if you use this dataset for your research. 20""" 21 22import os 23from glob import glob 24from pathlib import Path 25from natsort import natsorted 26from typing import Union, Tuple, List, Optional, Literal 27 28import imageio.v3 as imageio 29 30from torch.utils.data import Dataset, DataLoader 31 32import torch_em 33 34from .. import util 35 36 37URL = "https://ndownloader.figshare.com/files/65374524" 38CHECKSUM = "e9956c1587123e94342cc74dd9d2a50c9c833ffe52457688862ece8b849a4c97" 39 40TIMEPOINTS = ["baseline", "2weeks", "3months", "6months", "12months", "24months", "48months"] 41 42 43def get_longitudinal_mh_oct_data(path: Union[os.PathLike, str], download: bool = False) -> str: 44 """Download the longitudinal macular hole OCT data. 45 46 Args: 47 path: Filepath to a folder where the data is downloaded for further processing. 48 download: Whether to download the data if it is not present. 49 50 Returns: 51 Filepath where the data is downloaded. 52 """ 53 data_dir = os.path.join(path, "Dataset") 54 if os.path.exists(data_dir): 55 return data_dir 56 57 os.makedirs(path, exist_ok=True) 58 59 zip_path = os.path.join(path, "Dataset.zip") 60 util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM) 61 util.unzip(zip_path=zip_path, dst=path) 62 63 return data_dir 64 65 66def get_longitudinal_mh_oct_paths( 67 path: Union[os.PathLike, str], 68 timepoint: Optional[Literal["baseline", "2weeks", "3months", "6months", "12months", "24months", "48months"]] = None, # noqa 69 download: bool = False, 70) -> Tuple[List[str], List[str]]: 71 """Get paths to the longitudinal macular hole OCT data. 72 73 Args: 74 path: Filepath to a folder where the data is downloaded for further processing. 75 timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all 76 timepoints. 77 download: Whether to download the data if it is not present. 78 79 Returns: 80 List of filepaths for the image data. 81 List of filepaths for the label data. 82 """ 83 data_dir = get_longitudinal_mh_oct_data(path, download) 84 85 timepoints = TIMEPOINTS if timepoint is None else [timepoint] 86 87 pp_dir = os.path.join(data_dir, "preprocessed_images") 88 os.makedirs(pp_dir, exist_ok=True) 89 90 image_paths, gt_paths = [], [] 91 for this_timepoint in timepoints: 92 this_gt_paths = natsorted( 93 glob(os.path.join(data_dir, this_timepoint, f"{this_timepoint}_Masks", "*.png")) 94 ) 95 for gt_path in this_gt_paths: 96 org_image_path = os.path.join( 97 data_dir, this_timepoint, f"{this_timepoint}_OCT", f"{Path(gt_path).stem}.tiff" 98 ) 99 if not os.path.exists(org_image_path): 100 continue 101 102 # The raw B-scans are RGBA tiffs, but 'ImageCollectionDataset' expects RGB inputs. 103 # The alpha channel is dropped once here and the result cached as a '.tif' file. 104 image_path = os.path.join(pp_dir, f"{this_timepoint}_{Path(gt_path).stem}.tif") 105 if not os.path.exists(image_path): 106 image = imageio.imread(org_image_path) 107 if image.ndim == 3 and image.shape[-1] == 4: 108 image = image[..., :3] 109 imageio.imwrite(image_path, image, compression="zlib") 110 111 image_paths.append(image_path) 112 gt_paths.append(gt_path) 113 114 assert len(image_paths) == len(gt_paths) and len(image_paths) > 0, ( 115 "No image-mask pairs were found. The expected per-timepoint '<timepoint>_OCT' / '<timepoint>_Masks' " 116 "folder layout may not match the actual structure of the downloaded data. Please inspect the data at " 117 f"'{data_dir}'." 118 ) 119 120 return image_paths, gt_paths 121 122 123def get_longitudinal_mh_oct_dataset( 124 path: Union[os.PathLike, str], 125 patch_shape: Tuple[int, int], 126 timepoint: Optional[Literal["baseline", "2weeks", "3months", "6months", "12months", "24months", "48months"]] = None, # noqa 127 resize_inputs: bool = False, 128 download: bool = False, 129 **kwargs 130) -> Dataset: 131 """Get the longitudinal macular hole OCT dataset for segmentation of 12 retinal structures and pathologies. 132 133 Args: 134 path: Filepath to a folder where the data is downloaded for further processing. 135 patch_shape: The patch shape to use for training. 136 timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all 137 timepoints. 138 resize_inputs: Whether to resize inputs to the desired patch shape. 139 download: Whether to download the data if it is not present. 140 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 141 142 Returns: 143 The segmentation dataset. 144 """ 145 image_paths, gt_paths = get_longitudinal_mh_oct_paths(path, timepoint, download) 146 147 if resize_inputs: 148 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 149 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 150 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 151 ) 152 153 return torch_em.default_segmentation_dataset( 154 raw_paths=image_paths, 155 raw_key=None, 156 label_paths=gt_paths, 157 label_key=None, 158 patch_shape=patch_shape, 159 is_seg_dataset=False, 160 **kwargs 161 ) 162 163 164def get_longitudinal_mh_oct_loader( 165 path: Union[os.PathLike, str], 166 batch_size: int, 167 patch_shape: Tuple[int, int], 168 timepoint: Optional[Literal["baseline", "2weeks", "3months", "6months", "12months", "24months", "48months"]] = None, # noqa 169 resize_inputs: bool = False, 170 download: bool = False, 171 **kwargs 172) -> DataLoader: 173 """Get the longitudinal macular hole OCT dataloader for segmentation of 12 retinal structures and pathologies. 174 175 Args: 176 path: Filepath to a folder where the data is downloaded for further processing. 177 batch_size: The batch size for training. 178 patch_shape: The patch shape to use for training. 179 timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all 180 timepoints. 181 resize_inputs: Whether to resize inputs to the desired patch shape. 182 download: Whether to download the data if it is not present. 183 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 184 185 Returns: 186 The DataLoader. 187 """ 188 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 189 dataset = get_longitudinal_mh_oct_dataset(path, patch_shape, timepoint, resize_inputs, download, **ds_kwargs) 190 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
44def get_longitudinal_mh_oct_data(path: Union[os.PathLike, str], download: bool = False) -> str: 45 """Download the longitudinal macular hole OCT data. 46 47 Args: 48 path: Filepath to a folder where the data is downloaded for further processing. 49 download: Whether to download the data if it is not present. 50 51 Returns: 52 Filepath where the data is downloaded. 53 """ 54 data_dir = os.path.join(path, "Dataset") 55 if os.path.exists(data_dir): 56 return data_dir 57 58 os.makedirs(path, exist_ok=True) 59 60 zip_path = os.path.join(path, "Dataset.zip") 61 util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM) 62 util.unzip(zip_path=zip_path, dst=path) 63 64 return data_dir
Download the longitudinal macular hole OCT data.
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.
67def get_longitudinal_mh_oct_paths( 68 path: Union[os.PathLike, str], 69 timepoint: Optional[Literal["baseline", "2weeks", "3months", "6months", "12months", "24months", "48months"]] = None, # noqa 70 download: bool = False, 71) -> Tuple[List[str], List[str]]: 72 """Get paths to the longitudinal macular hole OCT data. 73 74 Args: 75 path: Filepath to a folder where the data is downloaded for further processing. 76 timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all 77 timepoints. 78 download: Whether to download the data if it is not present. 79 80 Returns: 81 List of filepaths for the image data. 82 List of filepaths for the label data. 83 """ 84 data_dir = get_longitudinal_mh_oct_data(path, download) 85 86 timepoints = TIMEPOINTS if timepoint is None else [timepoint] 87 88 pp_dir = os.path.join(data_dir, "preprocessed_images") 89 os.makedirs(pp_dir, exist_ok=True) 90 91 image_paths, gt_paths = [], [] 92 for this_timepoint in timepoints: 93 this_gt_paths = natsorted( 94 glob(os.path.join(data_dir, this_timepoint, f"{this_timepoint}_Masks", "*.png")) 95 ) 96 for gt_path in this_gt_paths: 97 org_image_path = os.path.join( 98 data_dir, this_timepoint, f"{this_timepoint}_OCT", f"{Path(gt_path).stem}.tiff" 99 ) 100 if not os.path.exists(org_image_path): 101 continue 102 103 # The raw B-scans are RGBA tiffs, but 'ImageCollectionDataset' expects RGB inputs. 104 # The alpha channel is dropped once here and the result cached as a '.tif' file. 105 image_path = os.path.join(pp_dir, f"{this_timepoint}_{Path(gt_path).stem}.tif") 106 if not os.path.exists(image_path): 107 image = imageio.imread(org_image_path) 108 if image.ndim == 3 and image.shape[-1] == 4: 109 image = image[..., :3] 110 imageio.imwrite(image_path, image, compression="zlib") 111 112 image_paths.append(image_path) 113 gt_paths.append(gt_path) 114 115 assert len(image_paths) == len(gt_paths) and len(image_paths) > 0, ( 116 "No image-mask pairs were found. The expected per-timepoint '<timepoint>_OCT' / '<timepoint>_Masks' " 117 "folder layout may not match the actual structure of the downloaded data. Please inspect the data at " 118 f"'{data_dir}'." 119 ) 120 121 return image_paths, gt_paths
Get paths to the longitudinal macular hole OCT data.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all timepoints.
- 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.
124def get_longitudinal_mh_oct_dataset( 125 path: Union[os.PathLike, str], 126 patch_shape: Tuple[int, int], 127 timepoint: Optional[Literal["baseline", "2weeks", "3months", "6months", "12months", "24months", "48months"]] = None, # noqa 128 resize_inputs: bool = False, 129 download: bool = False, 130 **kwargs 131) -> Dataset: 132 """Get the longitudinal macular hole OCT dataset for segmentation of 12 retinal structures and pathologies. 133 134 Args: 135 path: Filepath to a folder where the data is downloaded for further processing. 136 patch_shape: The patch shape to use for training. 137 timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all 138 timepoints. 139 resize_inputs: Whether to resize inputs to the desired patch shape. 140 download: Whether to download the data if it is not present. 141 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 142 143 Returns: 144 The segmentation dataset. 145 """ 146 image_paths, gt_paths = get_longitudinal_mh_oct_paths(path, timepoint, download) 147 148 if resize_inputs: 149 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 150 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 151 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 152 ) 153 154 return torch_em.default_segmentation_dataset( 155 raw_paths=image_paths, 156 raw_key=None, 157 label_paths=gt_paths, 158 label_key=None, 159 patch_shape=patch_shape, 160 is_seg_dataset=False, 161 **kwargs 162 )
Get the longitudinal macular hole OCT dataset for segmentation of 12 retinal structures and pathologies.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- patch_shape: The patch shape to use for training.
- timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all timepoints.
- 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.
165def get_longitudinal_mh_oct_loader( 166 path: Union[os.PathLike, str], 167 batch_size: int, 168 patch_shape: Tuple[int, int], 169 timepoint: Optional[Literal["baseline", "2weeks", "3months", "6months", "12months", "24months", "48months"]] = None, # noqa 170 resize_inputs: bool = False, 171 download: bool = False, 172 **kwargs 173) -> DataLoader: 174 """Get the longitudinal macular hole OCT dataloader for segmentation of 12 retinal structures and pathologies. 175 176 Args: 177 path: Filepath to a folder where the data is downloaded for further processing. 178 batch_size: The batch size for training. 179 patch_shape: The patch shape to use for training. 180 timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all 181 timepoints. 182 resize_inputs: Whether to resize inputs to the desired patch shape. 183 download: Whether to download the data if it is not present. 184 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 185 186 Returns: 187 The DataLoader. 188 """ 189 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 190 dataset = get_longitudinal_mh_oct_dataset(path, patch_shape, timepoint, resize_inputs, download, **ds_kwargs) 191 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the longitudinal macular hole OCT dataloader for segmentation of 12 retinal structures and pathologies.
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.
- timepoint: The choice of a specific postoperative (or 'baseline') timepoint. By default, loads all timepoints.
- 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.