torch_em.data.datasets.medical.arcade

The ARCADE dataset contains annotations for coronary artery segmentation and stenosis detection in X-ray coronary angiography images.

The dataset provides two tasks, each with 1000 training, 200 validation and 300 test images:

  • 'syntax': multiclass segmentation of 25 coronary artery segments, following the SYNTAX score methodology (see SYNTAX_LABELS).
  • 'stenosis': binary segmentation of atherosclerotic plaques (stenotic lesions).

The dataset is located at https://zenodo.org/records/10390295 (DOI: 10.5281/zenodo.10390295) and is distributed under the CC0 1.0 license.

This dataset is from the publication https://doi.org/10.1038/s41597-023-02871-z. Please cite it if you use this dataset for your research.

  1"""The ARCADE dataset contains annotations for coronary artery segmentation and stenosis
  2detection in X-ray coronary angiography images.
  3
  4The dataset provides two tasks, each with 1000 training, 200 validation and 300 test images:
  5- 'syntax': multiclass segmentation of 25 coronary artery segments, following the SYNTAX score
  6  methodology (see `SYNTAX_LABELS`).
  7- 'stenosis': binary segmentation of atherosclerotic plaques (stenotic lesions).
  8
  9The dataset is located at https://zenodo.org/records/10390295 (DOI: 10.5281/zenodo.10390295)
 10and is distributed under the CC0 1.0 license.
 11
 12This dataset is from the publication https://doi.org/10.1038/s41597-023-02871-z.
 13Please cite it if you use this dataset for your research.
 14"""
 15
 16import os
 17import json
 18from glob import glob
 19from tqdm import tqdm
 20from natsort import natsorted
 21from typing import Union, Tuple, List, Literal
 22
 23import numpy as np
 24from skimage.draw import polygon
 25
 26import imageio.v3 as imageio
 27
 28from torch.utils.data import Dataset, DataLoader
 29
 30import torch_em
 31
 32from .. import util
 33
 34
 35URL = "https://zenodo.org/records/10390295/files/arcade.zip"
 36CHECKSUM = "a396cdea7c92c55dc97bbf3dd8e3df517d76872b289a8bcb45513bdb3350837f"
 37
 38SYNTAX_LABELS = {
 39    0: "background", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", 10: "9a",
 40    11: "10", 12: "10a", 13: "11", 14: "12", 15: "12a", 16: "13", 17: "14", 18: "14a", 19: "15", 20: "16",
 41    21: "16a", 22: "16b", 23: "16c", 24: "12b", 25: "14b",
 42}
 43"""The label ids of the coronary artery segment classes for the 'syntax' task, following the SYNTAX
 44score segment definitions (https://syntaxscore.org/index.php/tutorial/definitions/14-appendix-i-segment-definitions).
 45"""
 46
 47STENOSIS_LABELS = {0: "background", 1: "stenosis"}
 48"""The label ids for the 'stenosis' task. The raw annotations use a single category id (26) for
 49all stenotic lesions, which is remapped to label id 1 here."""
 50
 51
 52def get_arcade_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 53    """Download the ARCADE dataset.
 54
 55    Args:
 56        path: Filepath to a folder where the data is downloaded for further processing.
 57        download: Whether to download the data if it is not present.
 58
 59    Returns:
 60        Filepath where the data is downloaded.
 61    """
 62    data_dir = os.path.join(path, "arcade")
 63    if os.path.exists(data_dir):
 64        return data_dir
 65
 66    os.makedirs(path, exist_ok=True)
 67
 68    zip_path = os.path.join(path, "arcade.zip")
 69    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
 70    util.unzip(zip_path=zip_path, dst=path)
 71
 72    return data_dir
 73
 74
 75def _rasterize_annotations(shape, annotations, task):
 76    labels = np.zeros(shape, dtype="uint8")
 77    for ann in annotations:
 78        # The 'stenosis' task only has one category (id 26, 'stenosis'), which is remapped to label 1.
 79        # The 'syntax' task categories (ids 1-25) already match the desired label ids.
 80        class_id = 1 if task == "stenosis" else ann["category_id"]
 81        for seg in ann["segmentation"]:
 82            c, r = np.asarray(seg[0::2]), np.asarray(seg[1::2])
 83            rr, cc = polygon(r, c, shape=shape)
 84            labels[rr, cc] = class_id
 85    return labels
 86
 87
 88def _preprocess_split(image_dir, annotation_path, preprocessed_dir, task):
 89    os.makedirs(preprocessed_dir, exist_ok=True)
 90
 91    with open(annotation_path) as f:
 92        annotations = json.load(f)
 93
 94    annotations_by_image = {}
 95    for ann in annotations["annotations"]:
 96        annotations_by_image.setdefault(ann["image_id"], []).append(ann)
 97
 98    image_paths, gt_paths = [], []
 99    for image_info in tqdm(annotations["images"], desc=f"Preprocessing labels for {image_dir}"):
100        image_path = os.path.join(image_dir, image_info["file_name"])
101        if not os.path.exists(image_path):
102            continue
103
104        gt_path = os.path.join(preprocessed_dir, image_info["file_name"])
105        if not os.path.exists(gt_path):
106            image_annotations = annotations_by_image.get(image_info["id"], [])
107            shape = (image_info["height"], image_info["width"])
108            labels = _rasterize_annotations(shape, image_annotations, task)
109            imageio.imwrite(gt_path, labels)
110
111        image_paths.append(image_path)
112        gt_paths.append(gt_path)
113
114    return image_paths, gt_paths
115
116
117def get_arcade_paths(
118    path: Union[os.PathLike, str],
119    task: Literal["syntax", "stenosis"],
120    split: Literal["train", "val", "test"],
121    download: bool = False,
122) -> Tuple[List[str], List[str]]:
123    """Get paths to the ARCADE data.
124
125    Args:
126        path: Filepath to a folder where the data is downloaded for further processing.
127        task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or
128            'stenosis' (binary stenosis segmentation).
129        split: The choice of data split.
130        download: Whether to download the data if it is not present.
131
132    Returns:
133        List of filepaths for the image data.
134        List of filepaths for the label data.
135    """
136    if task not in ("syntax", "stenosis"):
137        raise ValueError(f"'{task}' is not a valid task. Please choose either 'syntax' or 'stenosis'.")
138
139    if split not in ("train", "val", "test"):
140        raise ValueError(f"'{split}' is not a valid split.")
141
142    data_dir = get_arcade_data(path, download)
143
144    image_dir = os.path.join(data_dir, task, split, "images")
145    annotation_path = os.path.join(data_dir, task, split, "annotations", f"{split}.json")
146    preprocessed_dir = os.path.join(data_dir, "preprocessed", task, split)
147
148    if os.path.exists(preprocessed_dir) and len(glob(os.path.join(preprocessed_dir, "*.png"))) > 0:
149        image_paths = natsorted(glob(os.path.join(image_dir, "*.png")))
150        gt_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.png")))
151        return image_paths, gt_paths
152
153    image_paths, gt_paths = _preprocess_split(image_dir, annotation_path, preprocessed_dir, task)
154    return natsorted(image_paths), natsorted(gt_paths)
155
156
157def get_arcade_dataset(
158    path: Union[os.PathLike, str],
159    patch_shape: Tuple[int, int],
160    task: Literal["syntax", "stenosis"],
161    split: Literal["train", "val", "test"],
162    resize_inputs: bool = False,
163    download: bool = False,
164    **kwargs
165) -> Dataset:
166    """Get the ARCADE dataset for coronary artery and stenosis segmentation in X-ray angiography.
167
168    Args:
169        path: Filepath to a folder where the data is downloaded for further processing.
170        patch_shape: The patch shape to use for training.
171        task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or
172            'stenosis' (binary stenosis segmentation).
173        split: The choice of data split.
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`.
177
178    Returns:
179        The segmentation dataset.
180    """
181    image_paths, gt_paths = get_arcade_paths(path, task, split, download)
182
183    if resize_inputs:
184        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
185        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
186            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
187        )
188
189    return torch_em.default_segmentation_dataset(
190        raw_paths=image_paths,
191        raw_key=None,
192        label_paths=gt_paths,
193        label_key=None,
194        patch_shape=patch_shape,
195        is_seg_dataset=False,
196        **kwargs
197    )
198
199
200def get_arcade_loader(
201    path: Union[os.PathLike, str],
202    batch_size: int,
203    patch_shape: Tuple[int, int],
204    task: Literal["syntax", "stenosis"],
205    split: Literal["train", "val", "test"],
206    resize_inputs: bool = False,
207    download: bool = False,
208    **kwargs
209) -> DataLoader:
210    """Get the ARCADE dataloader for coronary artery and stenosis segmentation in X-ray angiography.
211
212    Args:
213        path: Filepath to a folder where the data is downloaded for further processing.
214        batch_size: The batch size for training.
215        patch_shape: The patch shape to use for training.
216        task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or
217            'stenosis' (binary stenosis segmentation).
218        split: The choice of data split.
219        resize_inputs: Whether to resize the inputs to the expected patch shape.
220        download: Whether to download the data if it is not present.
221        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
222
223    Returns:
224        The DataLoader.
225    """
226    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
227    dataset = get_arcade_dataset(path, patch_shape, task, split, resize_inputs, download, **ds_kwargs)
228    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://zenodo.org/records/10390295/files/arcade.zip'
CHECKSUM = 'a396cdea7c92c55dc97bbf3dd8e3df517d76872b289a8bcb45513bdb3350837f'
SYNTAX_LABELS = {0: 'background', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '9a', 11: '10', 12: '10a', 13: '11', 14: '12', 15: '12a', 16: '13', 17: '14', 18: '14a', 19: '15', 20: '16', 21: '16a', 22: '16b', 23: '16c', 24: '12b', 25: '14b'}

The label ids of the coronary artery segment classes for the 'syntax' task, following the SYNTAX score segment definitions (https://syntaxscore.org/index.php/tutorial/definitions/14-appendix-i-segment-definitions).

STENOSIS_LABELS = {0: 'background', 1: 'stenosis'}

The label ids for the 'stenosis' task. The raw annotations use a single category id (26) for all stenotic lesions, which is remapped to label id 1 here.

def get_arcade_data(path: Union[os.PathLike, str], download: bool = False) -> str:
53def get_arcade_data(path: Union[os.PathLike, str], download: bool = False) -> str:
54    """Download the ARCADE dataset.
55
56    Args:
57        path: Filepath to a folder where the data is downloaded for further processing.
58        download: Whether to download the data if it is not present.
59
60    Returns:
61        Filepath where the data is downloaded.
62    """
63    data_dir = os.path.join(path, "arcade")
64    if os.path.exists(data_dir):
65        return data_dir
66
67    os.makedirs(path, exist_ok=True)
68
69    zip_path = os.path.join(path, "arcade.zip")
70    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
71    util.unzip(zip_path=zip_path, dst=path)
72
73    return data_dir

Download the ARCADE 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_arcade_paths( path: Union[os.PathLike, str], task: Literal['syntax', 'stenosis'], split: Literal['train', 'val', 'test'], download: bool = False) -> Tuple[List[str], List[str]]:
118def get_arcade_paths(
119    path: Union[os.PathLike, str],
120    task: Literal["syntax", "stenosis"],
121    split: Literal["train", "val", "test"],
122    download: bool = False,
123) -> Tuple[List[str], List[str]]:
124    """Get paths to the ARCADE data.
125
126    Args:
127        path: Filepath to a folder where the data is downloaded for further processing.
128        task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or
129            'stenosis' (binary stenosis segmentation).
130        split: The choice of data split.
131        download: Whether to download the data if it is not present.
132
133    Returns:
134        List of filepaths for the image data.
135        List of filepaths for the label data.
136    """
137    if task not in ("syntax", "stenosis"):
138        raise ValueError(f"'{task}' is not a valid task. Please choose either 'syntax' or 'stenosis'.")
139
140    if split not in ("train", "val", "test"):
141        raise ValueError(f"'{split}' is not a valid split.")
142
143    data_dir = get_arcade_data(path, download)
144
145    image_dir = os.path.join(data_dir, task, split, "images")
146    annotation_path = os.path.join(data_dir, task, split, "annotations", f"{split}.json")
147    preprocessed_dir = os.path.join(data_dir, "preprocessed", task, split)
148
149    if os.path.exists(preprocessed_dir) and len(glob(os.path.join(preprocessed_dir, "*.png"))) > 0:
150        image_paths = natsorted(glob(os.path.join(image_dir, "*.png")))
151        gt_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.png")))
152        return image_paths, gt_paths
153
154    image_paths, gt_paths = _preprocess_split(image_dir, annotation_path, preprocessed_dir, task)
155    return natsorted(image_paths), natsorted(gt_paths)

Get paths to the ARCADE data.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or 'stenosis' (binary stenosis segmentation).
  • 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.

def get_arcade_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int], task: Literal['syntax', 'stenosis'], split: Literal['train', 'val', 'test'], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
158def get_arcade_dataset(
159    path: Union[os.PathLike, str],
160    patch_shape: Tuple[int, int],
161    task: Literal["syntax", "stenosis"],
162    split: Literal["train", "val", "test"],
163    resize_inputs: bool = False,
164    download: bool = False,
165    **kwargs
166) -> Dataset:
167    """Get the ARCADE dataset for coronary artery and stenosis segmentation in X-ray angiography.
168
169    Args:
170        path: Filepath to a folder where the data is downloaded for further processing.
171        patch_shape: The patch shape to use for training.
172        task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or
173            'stenosis' (binary stenosis segmentation).
174        split: The choice of data split.
175        resize_inputs: Whether to resize the inputs to the expected patch shape.
176        download: Whether to download the data if it is not present.
177        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
178
179    Returns:
180        The segmentation dataset.
181    """
182    image_paths, gt_paths = get_arcade_paths(path, task, split, download)
183
184    if resize_inputs:
185        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
186        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
187            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
188        )
189
190    return torch_em.default_segmentation_dataset(
191        raw_paths=image_paths,
192        raw_key=None,
193        label_paths=gt_paths,
194        label_key=None,
195        patch_shape=patch_shape,
196        is_seg_dataset=False,
197        **kwargs
198    )

Get the ARCADE dataset for coronary artery and stenosis segmentation in X-ray angiography.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • patch_shape: The patch shape to use for training.
  • task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or 'stenosis' (binary stenosis segmentation).
  • split: The choice of data split.
  • 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_arcade_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int], task: Literal['syntax', 'stenosis'], split: Literal['train', 'val', 'test'], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
201def get_arcade_loader(
202    path: Union[os.PathLike, str],
203    batch_size: int,
204    patch_shape: Tuple[int, int],
205    task: Literal["syntax", "stenosis"],
206    split: Literal["train", "val", "test"],
207    resize_inputs: bool = False,
208    download: bool = False,
209    **kwargs
210) -> DataLoader:
211    """Get the ARCADE dataloader for coronary artery and stenosis segmentation in X-ray angiography.
212
213    Args:
214        path: Filepath to a folder where the data is downloaded for further processing.
215        batch_size: The batch size for training.
216        patch_shape: The patch shape to use for training.
217        task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or
218            'stenosis' (binary stenosis segmentation).
219        split: The choice of data split.
220        resize_inputs: Whether to resize the inputs to the expected patch shape.
221        download: Whether to download the data if it is not present.
222        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
223
224    Returns:
225        The DataLoader.
226    """
227    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
228    dataset = get_arcade_dataset(path, patch_shape, task, split, resize_inputs, download, **ds_kwargs)
229    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the ARCADE dataloader for coronary artery and stenosis segmentation in X-ray angiography.

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.
  • task: The choice of task, either 'syntax' (multiclass vessel segment segmentation) or 'stenosis' (binary stenosis segmentation).
  • split: The choice of data split.
  • 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.