torch_em.data.datasets.medical.woives

The WOIVES dataset contains annotations for retinal vessel segmentation in ultra-widefield swept-source optical coherence tomography angiography (SS-OCTA) images.

The dataset consists of 206 eyes from 152 participants. Each eye has a superficial-retina OCTA image (1536x1280 pixels, 24x20 mm field of view) paired with a probabilistic soft-label vessel annotation. The soft labels are the pixel-wise average of five binary annotations that were made with SAM-OCTA assistance and then refined by two senior ophthalmologists. They are stored as uint8 images in [0, 255], where value / 255 is the vessel probability. The release ships no binary masks, and it also contains deep/full-retina and choroid slabs, fundus photographs and SLO images, which this loader does not use.

By default the soft labels are thresholded into binary vessel masks (see the 'binarize_threshold' argument). Please report the threshold you use. With 'binarize_threshold=None' the soft probability maps in [0, 1] are returned as float labels instead.

The official split is a subject-level five-fold cross-validation, selected with the 'fold' and 'split' arguments.

The data is located at https://doi.org/10.5281/zenodo.21904672, released under a CC-BY-4.0 license.

This dataset is from the publication https://arxiv.org/abs/2609.12574. Please cite it if you use this dataset for your research.

  1"""The WOIVES dataset contains annotations for retinal vessel segmentation in ultra-widefield
  2swept-source optical coherence tomography angiography (SS-OCTA) images.
  3
  4The dataset consists of 206 eyes from 152 participants. Each eye has a superficial-retina OCTA image
  5(1536x1280 pixels, 24x20 mm field of view) paired with a probabilistic soft-label vessel annotation. The
  6soft labels are the pixel-wise average of five binary annotations that were made with SAM-OCTA assistance
  7and then refined by two senior ophthalmologists. They are stored as uint8 images in [0, 255], where
  8value / 255 is the vessel probability. The release ships no binary masks, and it also contains
  9deep/full-retina and choroid slabs, fundus photographs and SLO images, which this loader does not use.
 10
 11By default the soft labels are thresholded into binary vessel masks (see the 'binarize_threshold'
 12argument). Please report the threshold you use. With 'binarize_threshold=None' the soft probability
 13maps in [0, 1] are returned as float labels instead.
 14
 15The official split is a subject-level five-fold cross-validation, selected with the 'fold' and 'split'
 16arguments.
 17
 18The data is located at https://doi.org/10.5281/zenodo.21904672, released under a CC-BY-4.0 license.
 19
 20This dataset is from the publication https://arxiv.org/abs/2609.12574.
 21Please cite it if you use this dataset for your research.
 22"""
 23
 24import os
 25import json
 26from glob import glob
 27from tqdm import tqdm
 28from natsort import natsorted
 29from typing import Union, Tuple, Literal, List, Optional
 30
 31import numpy as np
 32
 33from torch.utils.data import Dataset, DataLoader
 34
 35import torch_em
 36
 37from .. import util
 38
 39
 40URL = "https://zenodo.org/api/records/21904672/files/WOIVES_v1.0.zip/content"
 41CHECKSUM = "e921284a2b2dc5348607439ead2b567456f73881a5c79251176d700b91f6c757"
 42
 43SPLITS = ["train", "val", "test"]
 44FOLDS = [0, 1, 2, 3, 4]
 45
 46
 47def _convert_labels(label_dir, out_dir, binarize_threshold):
 48    import tifffile
 49    from PIL import Image
 50
 51    os.makedirs(out_dir, exist_ok=True)
 52    for label_path in tqdm(natsorted(glob(os.path.join(label_dir, "mask_*.png"))), desc="Convert the WOIVES labels"):
 53        out_path = os.path.join(out_dir, os.path.basename(label_path)[len("mask_"):].replace(".png", ".tif"))
 54        if os.path.exists(out_path):
 55            continue
 56
 57        soft = np.asarray(Image.open(label_path), dtype="float32") / 255.0
 58        label = soft if binarize_threshold is None else (soft >= binarize_threshold).astype("uint8")
 59
 60        tmp_path = f"{out_path}.{os.getpid()}.incomplete.tif"
 61        tifffile.imwrite(tmp_path, label, compression="zlib")
 62        os.replace(tmp_path, out_path)
 63
 64
 65def get_woives_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 66    """Download the WOIVES dataset.
 67
 68    Args:
 69        path: Filepath to a folder where the data is downloaded for further processing.
 70        download: Whether to download the data if it is not present.
 71
 72    Returns:
 73        Filepath to the extracted dataset folder.
 74    """
 75    data_dir = os.path.join(path, "WOIVES_v1.0")
 76    if os.path.exists(data_dir):
 77        return data_dir
 78
 79    os.makedirs(path, exist_ok=True)
 80
 81    zip_path = os.path.join(path, "WOIVES_v1.0.zip")
 82    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
 83    util.unzip(zip_path=zip_path, dst=path, remove=False)
 84
 85    assert os.path.exists(data_dir), f"The extraction of the WOIVES archive did not create '{data_dir}'."
 86
 87    return data_dir
 88
 89
 90def get_woives_paths(
 91    path: Union[os.PathLike, str],
 92    split: Literal["train", "val", "test"],
 93    fold: int = 0,
 94    binarize_threshold: Optional[float] = 0.5,
 95    download: bool = False,
 96) -> Tuple[List[str], List[str]]:
 97    """Get paths to the WOIVES data.
 98
 99    Args:
100        path: Filepath to a folder where the data is downloaded for further processing.
101        split: The choice of data split. One of 'train', 'val' or 'test'.
102        fold: The fold of the official five-fold cross-validation. One of 0 to 4.
103        binarize_threshold: The threshold on the vessel probability for creating binary masks (vessel if the
104            probability is at least the threshold). Set to None to use the soft probability maps as labels.
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    if split not in SPLITS:
112        raise ValueError(f"'{split}' is not a valid split. Choose one of {SPLITS}.")
113    if fold not in FOLDS:
114        raise ValueError(f"'{fold}' is not a valid fold. Choose one of {FOLDS}.")
115    if binarize_threshold is not None and not 0.0 < binarize_threshold <= 1.0:
116        raise ValueError(f"The threshold must be in (0, 1] or None, but got {binarize_threshold}.")
117
118    data_dir = get_woives_data(path, download)
119
120    label_name = "labels_soft" if binarize_threshold is None else f"labels_bin_{binarize_threshold:g}"
121    label_dir = os.path.join(path, label_name)
122    _convert_labels(os.path.join(data_dir, "Label"), label_dir, binarize_threshold)
123
124    with open(os.path.join(data_dir, "splits", "fold_split.json")) as f:
125        names = json.load(f)["folds"][f"fold{fold}"][split]
126
127    raw_paths = [os.path.join(data_dir, "Image", "OCTA", "OCTA_superficial retina", name) for name in names]
128    label_paths = [os.path.join(label_dir, name.replace(".png", ".tif")) for name in names]
129
130    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
131    assert all(os.path.exists(p) for p in raw_paths + label_paths)
132
133    return raw_paths, label_paths
134
135
136def get_woives_dataset(
137    path: Union[os.PathLike, str],
138    patch_shape: Tuple[int, int],
139    split: Literal["train", "val", "test"],
140    fold: int = 0,
141    binarize_threshold: Optional[float] = 0.5,
142    resize_inputs: bool = False,
143    download: bool = False,
144    **kwargs
145) -> Dataset:
146    """Get the WOIVES dataset for retinal vessel segmentation in ultra-widefield OCTA images.
147
148    Args:
149        path: Filepath to a folder where the data is downloaded for further processing.
150        patch_shape: The patch shape to use for training.
151        split: The choice of data split. One of 'train', 'val' or 'test'.
152        fold: The fold of the official five-fold cross-validation. One of 0 to 4.
153        binarize_threshold: The threshold on the vessel probability for creating binary masks.
154            Set to None to use the soft probability maps as labels.
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_woives_paths(path, split, fold, binarize_threshold, download)
163
164    if resize_inputs:
165        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
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        is_seg_dataset=False,
176        patch_shape=patch_shape,
177        **kwargs
178    )
179
180
181def get_woives_loader(
182    path: Union[os.PathLike, str],
183    batch_size: int,
184    patch_shape: Tuple[int, int],
185    split: Literal["train", "val", "test"],
186    fold: int = 0,
187    binarize_threshold: Optional[float] = 0.5,
188    resize_inputs: bool = False,
189    download: bool = False,
190    **kwargs
191) -> DataLoader:
192    """Get the WOIVES dataloader for retinal vessel segmentation in ultra-widefield OCTA images.
193
194    Args:
195        path: Filepath to a folder where the data is downloaded for further processing.
196        batch_size: The batch size for training.
197        patch_shape: The patch shape to use for training.
198        split: The choice of data split. One of 'train', 'val' or 'test'.
199        fold: The fold of the official five-fold cross-validation. One of 0 to 4.
200        binarize_threshold: The threshold on the vessel probability for creating binary masks.
201            Set to None to use the soft probability maps as labels.
202        resize_inputs: Whether to resize the inputs to the patch shape.
203        download: Whether to download the data if it is not present.
204        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
205
206    Returns:
207        The DataLoader.
208    """
209    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
210    dataset = get_woives_dataset(
211        path, patch_shape, split, fold, binarize_threshold, resize_inputs, download, **ds_kwargs
212    )
213    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://zenodo.org/api/records/21904672/files/WOIVES_v1.0.zip/content'
CHECKSUM = 'e921284a2b2dc5348607439ead2b567456f73881a5c79251176d700b91f6c757'
SPLITS = ['train', 'val', 'test']
FOLDS = [0, 1, 2, 3, 4]
def get_woives_data(path: Union[os.PathLike, str], download: bool = False) -> str:
66def get_woives_data(path: Union[os.PathLike, str], download: bool = False) -> str:
67    """Download the WOIVES 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 to the extracted dataset folder.
75    """
76    data_dir = os.path.join(path, "WOIVES_v1.0")
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, "WOIVES_v1.0.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 WOIVES archive did not create '{data_dir}'."
87
88    return data_dir

Download the WOIVES 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 to the extracted dataset folder.

def get_woives_paths( path: Union[os.PathLike, str], split: Literal['train', 'val', 'test'], fold: int = 0, binarize_threshold: Optional[float] = 0.5, download: bool = False) -> Tuple[List[str], List[str]]:
 91def get_woives_paths(
 92    path: Union[os.PathLike, str],
 93    split: Literal["train", "val", "test"],
 94    fold: int = 0,
 95    binarize_threshold: Optional[float] = 0.5,
 96    download: bool = False,
 97) -> Tuple[List[str], List[str]]:
 98    """Get paths to the WOIVES data.
 99
100    Args:
101        path: Filepath to a folder where the data is downloaded for further processing.
102        split: The choice of data split. One of 'train', 'val' or 'test'.
103        fold: The fold of the official five-fold cross-validation. One of 0 to 4.
104        binarize_threshold: The threshold on the vessel probability for creating binary masks (vessel if the
105            probability is at least the threshold). Set to None to use the soft probability maps as labels.
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    if split not in SPLITS:
113        raise ValueError(f"'{split}' is not a valid split. Choose one of {SPLITS}.")
114    if fold not in FOLDS:
115        raise ValueError(f"'{fold}' is not a valid fold. Choose one of {FOLDS}.")
116    if binarize_threshold is not None and not 0.0 < binarize_threshold <= 1.0:
117        raise ValueError(f"The threshold must be in (0, 1] or None, but got {binarize_threshold}.")
118
119    data_dir = get_woives_data(path, download)
120
121    label_name = "labels_soft" if binarize_threshold is None else f"labels_bin_{binarize_threshold:g}"
122    label_dir = os.path.join(path, label_name)
123    _convert_labels(os.path.join(data_dir, "Label"), label_dir, binarize_threshold)
124
125    with open(os.path.join(data_dir, "splits", "fold_split.json")) as f:
126        names = json.load(f)["folds"][f"fold{fold}"][split]
127
128    raw_paths = [os.path.join(data_dir, "Image", "OCTA", "OCTA_superficial retina", name) for name in names]
129    label_paths = [os.path.join(label_dir, name.replace(".png", ".tif")) for name in names]
130
131    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
132    assert all(os.path.exists(p) for p in raw_paths + label_paths)
133
134    return raw_paths, label_paths

Get paths to the WOIVES data.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • split: The choice of data split. One of 'train', 'val' or 'test'.
  • fold: The fold of the official five-fold cross-validation. One of 0 to 4.
  • binarize_threshold: The threshold on the vessel probability for creating binary masks (vessel if the probability is at least the threshold). Set to None to use the soft probability maps as labels.
  • 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_woives_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int], split: Literal['train', 'val', 'test'], fold: int = 0, binarize_threshold: Optional[float] = 0.5, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
137def get_woives_dataset(
138    path: Union[os.PathLike, str],
139    patch_shape: Tuple[int, int],
140    split: Literal["train", "val", "test"],
141    fold: int = 0,
142    binarize_threshold: Optional[float] = 0.5,
143    resize_inputs: bool = False,
144    download: bool = False,
145    **kwargs
146) -> Dataset:
147    """Get the WOIVES dataset for retinal vessel segmentation in ultra-widefield OCTA images.
148
149    Args:
150        path: Filepath to a folder where the data is downloaded for further processing.
151        patch_shape: The patch shape to use for training.
152        split: The choice of data split. One of 'train', 'val' or 'test'.
153        fold: The fold of the official five-fold cross-validation. One of 0 to 4.
154        binarize_threshold: The threshold on the vessel probability for creating binary masks.
155            Set to None to use the soft probability maps as labels.
156        resize_inputs: Whether to resize the inputs to the patch shape.
157        download: Whether to download the data if it is not present.
158        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
159
160    Returns:
161        The segmentation dataset.
162    """
163    raw_paths, label_paths = get_woives_paths(path, split, fold, binarize_threshold, download)
164
165    if resize_inputs:
166        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
167        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
168            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
169        )
170
171    return torch_em.default_segmentation_dataset(
172        raw_paths=raw_paths,
173        raw_key=None,
174        label_paths=label_paths,
175        label_key=None,
176        is_seg_dataset=False,
177        patch_shape=patch_shape,
178        **kwargs
179    )

Get the WOIVES dataset for retinal vessel segmentation in ultra-widefield OCTA images.

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. One of 'train', 'val' or 'test'.
  • fold: The fold of the official five-fold cross-validation. One of 0 to 4.
  • binarize_threshold: The threshold on the vessel probability for creating binary masks. Set to None to use the soft probability maps as labels.
  • 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.

def get_woives_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int], split: Literal['train', 'val', 'test'], fold: int = 0, binarize_threshold: Optional[float] = 0.5, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
182def get_woives_loader(
183    path: Union[os.PathLike, str],
184    batch_size: int,
185    patch_shape: Tuple[int, int],
186    split: Literal["train", "val", "test"],
187    fold: int = 0,
188    binarize_threshold: Optional[float] = 0.5,
189    resize_inputs: bool = False,
190    download: bool = False,
191    **kwargs
192) -> DataLoader:
193    """Get the WOIVES dataloader for retinal vessel segmentation in ultra-widefield OCTA images.
194
195    Args:
196        path: Filepath to a folder where the data is downloaded for further processing.
197        batch_size: The batch size for training.
198        patch_shape: The patch shape to use for training.
199        split: The choice of data split. One of 'train', 'val' or 'test'.
200        fold: The fold of the official five-fold cross-validation. One of 0 to 4.
201        binarize_threshold: The threshold on the vessel probability for creating binary masks.
202            Set to None to use the soft probability maps as labels.
203        resize_inputs: Whether to resize the inputs to the patch shape.
204        download: Whether to download the data if it is not present.
205        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
206
207    Returns:
208        The DataLoader.
209    """
210    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
211    dataset = get_woives_dataset(
212        path, patch_shape, split, fold, binarize_threshold, resize_inputs, download, **ds_kwargs
213    )
214    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the WOIVES dataloader for retinal vessel segmentation in ultra-widefield OCTA 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.
  • split: The choice of data split. One of 'train', 'val' or 'test'.
  • fold: The fold of the official five-fold cross-validation. One of 0 to 4.
  • binarize_threshold: The threshold on the vessel probability for creating binary masks. Set to None to use the soft probability maps as labels.
  • 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 or for the PyTorch DataLoader.
Returns:

The DataLoader.