torch_em.data.datasets.medical.fovea

FOVEA is a dataset for optic disc and retinal vessel segmentation in preoperative and intraoperative retinal fundus images, comprising 40 patients collected at Moorfields Eye Hospital (London, UK). For each patient and each domain (preoperative color fundus photography and intraoperative retinal microscopy), the green channel image used for annotation as well as binary optic disc and retinal vessel masks from two independent clinical research fellows are provided.

The dataset is located at https://doi.org/10.6084/m9.figshare.28329338. This dataset is from the publication https://doi.org/10.1038/s41597-025-04965-2. Please cite it if you use this dataset in your research.

  1"""FOVEA is a dataset for optic disc and retinal vessel segmentation in preoperative and
  2intraoperative retinal fundus images, comprising 40 patients collected at Moorfields Eye
  3Hospital (London, UK). For each patient and each domain (preoperative color fundus photography
  4and intraoperative retinal microscopy), the green channel image used for annotation as well as
  5binary optic disc and retinal vessel masks from two independent clinical research fellows are
  6provided.
  7
  8The dataset is located at https://doi.org/10.6084/m9.figshare.28329338.
  9This dataset is from the publication https://doi.org/10.1038/s41597-025-04965-2.
 10Please cite it if you use this dataset in your research.
 11"""
 12
 13import os
 14from glob import glob
 15from natsort import natsorted
 16from typing import Union, Tuple, Literal, List
 17
 18from torch.utils.data import Dataset, DataLoader
 19
 20import torch_em
 21
 22from .. import util
 23
 24
 25URL = "https://ndownloader.figshare.com/files/52512728"
 26CHECKSUM = "4f9b88468e79b89d9c561932c28e6bd0974bde1ce2a21b1027b7dca2e09bf24d"
 27
 28DOMAINS = {"preoperative": "p", "intraoperative": "i"}
 29ANNOTATIONS = {"optic_disc": "od", "vessels": "ve"}
 30
 31
 32def get_fovea_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 33    """Download the FOVEA dataset.
 34
 35    Args:
 36        path: Filepath to a folder where the data is downloaded for further processing.
 37        download: Whether to download the data if it is not present.
 38
 39    Returns:
 40        Filepath where the data is downloaded.
 41    """
 42    if os.path.exists(path) and glob(os.path.join(path, "FOVEA*_img.png")):
 43        return path
 44
 45    os.makedirs(path, exist_ok=True)
 46
 47    zip_path = os.path.join(path, "FOVEA.zip")
 48    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
 49    util.unzip(zip_path=zip_path, dst=path)
 50
 51    return path
 52
 53
 54def get_fovea_paths(
 55    path: Union[os.PathLike, str],
 56    domain: Literal["preoperative", "intraoperative", "both"] = "both",
 57    annotation: Literal["optic_disc", "vessels"] = "vessels",
 58    annotator: Literal[1, 2, "both"] = 1,
 59    download: bool = False,
 60) -> Tuple[List[str], List[str]]:
 61    """Get paths to the FOVEA data.
 62
 63    Args:
 64        path: Filepath to a folder where the data is downloaded for further processing.
 65        domain: The choice of imaging domain. One of 'preoperative', 'intraoperative' or 'both'.
 66        annotation: The choice of segmentation target. Either 'optic_disc' or 'vessels'.
 67        annotator: The choice of annotator. Either 1, 2 or 'both' (uses annotations from both annotators).
 68        download: Whether to download the data if it is not present.
 69
 70    Returns:
 71        List of filepaths for the image data.
 72        List of filepaths for the label data.
 73    """
 74    data_dir = get_fovea_data(path=path, download=download)
 75
 76    if domain == "both":
 77        domain_tags = list(DOMAINS.values())
 78    elif domain in DOMAINS:
 79        domain_tags = [DOMAINS[domain]]
 80    else:
 81        raise ValueError(f"'{domain}' is not a valid domain. Choose from {list(DOMAINS) + ['both']}.")
 82
 83    if annotation not in ANNOTATIONS:
 84        raise ValueError(f"'{annotation}' is not a valid annotation. Choose from {list(ANNOTATIONS)}.")
 85    annotation_tag = ANNOTATIONS[annotation]
 86
 87    if annotator == "both":
 88        annotators = [1, 2]
 89    elif annotator in (1, 2):
 90        annotators = [annotator]
 91    else:
 92        raise ValueError(f"'{annotator}' is not a valid annotator. Choose from 1, 2 or 'both'.")
 93
 94    image_paths, label_paths = [], []
 95    for domain_tag in domain_tags:
 96        for ann in annotators:
 97            label_glob = natsorted(glob(os.path.join(data_dir, f"FOVEA*_{domain_tag}_{annotation_tag}_{ann}.png")))
 98            for label_path in label_glob:
 99                image_path = label_path.replace(f"_{annotation_tag}_{ann}.png", "_img.png")
100                assert os.path.exists(image_path), f"The image at '{image_path}' does not exist."
101                image_paths.append(image_path)
102                label_paths.append(label_path)
103
104    assert len(image_paths) == len(label_paths) and len(image_paths) > 0
105
106    return image_paths, label_paths
107
108
109def get_fovea_dataset(
110    path: Union[os.PathLike, str],
111    patch_shape: Tuple[int, int],
112    domain: Literal["preoperative", "intraoperative", "both"] = "both",
113    annotation: Literal["optic_disc", "vessels"] = "vessels",
114    annotator: Literal[1, 2, "both"] = 1,
115    resize_inputs: bool = False,
116    download: bool = False,
117    **kwargs
118) -> Dataset:
119    """Get the FOVEA dataset for optic disc and retinal vessel segmentation in fundus images.
120
121    Args:
122        path: Filepath to a folder where the data is downloaded for further processing.
123        patch_shape: The patch shape to use for training.
124        domain: The choice of imaging domain.
125        annotation: The choice of segmentation target.
126        annotator: The choice of annotator.
127        resize_inputs: Whether to resize the inputs to the expected patch shape.
128        download: Whether to download the data if it is not present.
129        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
130
131    Returns:
132        The segmentation dataset.
133    """
134    image_paths, label_paths = get_fovea_paths(path, domain, annotation, annotator, download)
135
136    if resize_inputs:
137        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True}
138        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
139            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
140        )
141
142    return torch_em.default_segmentation_dataset(
143        raw_paths=image_paths,
144        raw_key=None,
145        label_paths=label_paths,
146        label_key=None,
147        patch_shape=patch_shape,
148        is_seg_dataset=False,
149        **kwargs
150    )
151
152
153def get_fovea_loader(
154    path: Union[os.PathLike, str],
155    batch_size: int,
156    patch_shape: Tuple[int, int],
157    domain: Literal["preoperative", "intraoperative", "both"] = "both",
158    annotation: Literal["optic_disc", "vessels"] = "vessels",
159    annotator: Literal[1, 2, "both"] = 1,
160    resize_inputs: bool = False,
161    download: bool = False,
162    **kwargs
163) -> DataLoader:
164    """Get the FOVEA dataloader for optic disc and retinal vessel segmentation in fundus images.
165
166    Args:
167        path: Filepath to a folder where the data is downloaded for further processing.
168        batch_size: The batch size for training.
169        patch_shape: The patch shape to use for training.
170        domain: The choice of imaging domain.
171        annotation: The choice of segmentation target.
172        annotator: The choice of annotator.
173        resize_inputs: Whether to resize the inputs to the expected patch shape.
174        download: Whether to download the data if it is not present.
175        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
176
177    Returns:
178        The DataLoader.
179    """
180    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
181    dataset = get_fovea_dataset(path, patch_shape, domain, annotation, annotator, resize_inputs, download, **ds_kwargs)
182    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://ndownloader.figshare.com/files/52512728'
CHECKSUM = '4f9b88468e79b89d9c561932c28e6bd0974bde1ce2a21b1027b7dca2e09bf24d'
DOMAINS = {'preoperative': 'p', 'intraoperative': 'i'}
ANNOTATIONS = {'optic_disc': 'od', 'vessels': 've'}
def get_fovea_data(path: Union[os.PathLike, str], download: bool = False) -> str:
33def get_fovea_data(path: Union[os.PathLike, str], download: bool = False) -> str:
34    """Download the FOVEA dataset.
35
36    Args:
37        path: Filepath to a folder where the data is downloaded for further processing.
38        download: Whether to download the data if it is not present.
39
40    Returns:
41        Filepath where the data is downloaded.
42    """
43    if os.path.exists(path) and glob(os.path.join(path, "FOVEA*_img.png")):
44        return path
45
46    os.makedirs(path, exist_ok=True)
47
48    zip_path = os.path.join(path, "FOVEA.zip")
49    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
50    util.unzip(zip_path=zip_path, dst=path)
51
52    return path

Download the FOVEA 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.

def get_fovea_paths( path: Union[os.PathLike, str], domain: Literal['preoperative', 'intraoperative', 'both'] = 'both', annotation: Literal['optic_disc', 'vessels'] = 'vessels', annotator: Literal[1, 2, 'both'] = 1, download: bool = False) -> Tuple[List[str], List[str]]:
 55def get_fovea_paths(
 56    path: Union[os.PathLike, str],
 57    domain: Literal["preoperative", "intraoperative", "both"] = "both",
 58    annotation: Literal["optic_disc", "vessels"] = "vessels",
 59    annotator: Literal[1, 2, "both"] = 1,
 60    download: bool = False,
 61) -> Tuple[List[str], List[str]]:
 62    """Get paths to the FOVEA data.
 63
 64    Args:
 65        path: Filepath to a folder where the data is downloaded for further processing.
 66        domain: The choice of imaging domain. One of 'preoperative', 'intraoperative' or 'both'.
 67        annotation: The choice of segmentation target. Either 'optic_disc' or 'vessels'.
 68        annotator: The choice of annotator. Either 1, 2 or 'both' (uses annotations from both annotators).
 69        download: Whether to download the data if it is not present.
 70
 71    Returns:
 72        List of filepaths for the image data.
 73        List of filepaths for the label data.
 74    """
 75    data_dir = get_fovea_data(path=path, download=download)
 76
 77    if domain == "both":
 78        domain_tags = list(DOMAINS.values())
 79    elif domain in DOMAINS:
 80        domain_tags = [DOMAINS[domain]]
 81    else:
 82        raise ValueError(f"'{domain}' is not a valid domain. Choose from {list(DOMAINS) + ['both']}.")
 83
 84    if annotation not in ANNOTATIONS:
 85        raise ValueError(f"'{annotation}' is not a valid annotation. Choose from {list(ANNOTATIONS)}.")
 86    annotation_tag = ANNOTATIONS[annotation]
 87
 88    if annotator == "both":
 89        annotators = [1, 2]
 90    elif annotator in (1, 2):
 91        annotators = [annotator]
 92    else:
 93        raise ValueError(f"'{annotator}' is not a valid annotator. Choose from 1, 2 or 'both'.")
 94
 95    image_paths, label_paths = [], []
 96    for domain_tag in domain_tags:
 97        for ann in annotators:
 98            label_glob = natsorted(glob(os.path.join(data_dir, f"FOVEA*_{domain_tag}_{annotation_tag}_{ann}.png")))
 99            for label_path in label_glob:
100                image_path = label_path.replace(f"_{annotation_tag}_{ann}.png", "_img.png")
101                assert os.path.exists(image_path), f"The image at '{image_path}' does not exist."
102                image_paths.append(image_path)
103                label_paths.append(label_path)
104
105    assert len(image_paths) == len(label_paths) and len(image_paths) > 0
106
107    return image_paths, label_paths

Get paths to the FOVEA data.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • domain: The choice of imaging domain. One of 'preoperative', 'intraoperative' or 'both'.
  • annotation: The choice of segmentation target. Either 'optic_disc' or 'vessels'.
  • annotator: The choice of annotator. Either 1, 2 or 'both' (uses annotations from both annotators).
  • 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.

def get_fovea_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int], domain: Literal['preoperative', 'intraoperative', 'both'] = 'both', annotation: Literal['optic_disc', 'vessels'] = 'vessels', annotator: Literal[1, 2, 'both'] = 1, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
110def get_fovea_dataset(
111    path: Union[os.PathLike, str],
112    patch_shape: Tuple[int, int],
113    domain: Literal["preoperative", "intraoperative", "both"] = "both",
114    annotation: Literal["optic_disc", "vessels"] = "vessels",
115    annotator: Literal[1, 2, "both"] = 1,
116    resize_inputs: bool = False,
117    download: bool = False,
118    **kwargs
119) -> Dataset:
120    """Get the FOVEA dataset for optic disc and retinal vessel segmentation in fundus images.
121
122    Args:
123        path: Filepath to a folder where the data is downloaded for further processing.
124        patch_shape: The patch shape to use for training.
125        domain: The choice of imaging domain.
126        annotation: The choice of segmentation target.
127        annotator: The choice of annotator.
128        resize_inputs: Whether to resize the inputs to the expected patch shape.
129        download: Whether to download the data if it is not present.
130        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
131
132    Returns:
133        The segmentation dataset.
134    """
135    image_paths, label_paths = get_fovea_paths(path, domain, annotation, annotator, download)
136
137    if resize_inputs:
138        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True}
139        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
140            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
141        )
142
143    return torch_em.default_segmentation_dataset(
144        raw_paths=image_paths,
145        raw_key=None,
146        label_paths=label_paths,
147        label_key=None,
148        patch_shape=patch_shape,
149        is_seg_dataset=False,
150        **kwargs
151    )

Get the FOVEA dataset for optic disc and retinal vessel segmentation in fundus images.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • patch_shape: The patch shape to use for training.
  • domain: The choice of imaging domain.
  • annotation: The choice of segmentation target.
  • annotator: The choice of annotator.
  • resize_inputs: Whether to resize the inputs to the expected 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.

def get_fovea_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int], domain: Literal['preoperative', 'intraoperative', 'both'] = 'both', annotation: Literal['optic_disc', 'vessels'] = 'vessels', annotator: Literal[1, 2, 'both'] = 1, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
154def get_fovea_loader(
155    path: Union[os.PathLike, str],
156    batch_size: int,
157    patch_shape: Tuple[int, int],
158    domain: Literal["preoperative", "intraoperative", "both"] = "both",
159    annotation: Literal["optic_disc", "vessels"] = "vessels",
160    annotator: Literal[1, 2, "both"] = 1,
161    resize_inputs: bool = False,
162    download: bool = False,
163    **kwargs
164) -> DataLoader:
165    """Get the FOVEA dataloader for optic disc and retinal vessel segmentation in fundus images.
166
167    Args:
168        path: Filepath to a folder where the data is downloaded for further processing.
169        batch_size: The batch size for training.
170        patch_shape: The patch shape to use for training.
171        domain: The choice of imaging domain.
172        annotation: The choice of segmentation target.
173        annotator: The choice of annotator.
174        resize_inputs: Whether to resize the inputs to the expected patch shape.
175        download: Whether to download the data if it is not present.
176        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
177
178    Returns:
179        The DataLoader.
180    """
181    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
182    dataset = get_fovea_dataset(path, patch_shape, domain, annotation, annotator, resize_inputs, download, **ds_kwargs)
183    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the FOVEA dataloader for optic disc and retinal vessel segmentation in fundus images.

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.
  • domain: The choice of imaging domain.
  • annotation: The choice of segmentation target.
  • annotator: The choice of annotator.
  • resize_inputs: Whether to resize the inputs to the expected patch shape.
  • download: Whether to download the data if it is not present.
  • kwargs: Additional keyword arguments for torch_em.default_segmentation_dataset or for the PyTorch DataLoader.
Returns:

The DataLoader.