torch_em.data.datasets.medical.imagetbad

The ImageTBAD dataset contains annotations for type-B aortic dissection segmentation in computed tomography angiography (CTA).

The dataset consists of 100 3D CTA scans collected at the Guangdong Provincial People's Hospital between January 2013 and April 2019. The segmentation was performed by one cardiovascular radiologist and checked by a second one. It labels three substructures of the aortic dissection: the true lumen (TL), the false lumen (FL) and the false lumen thrombus (FLT), see LABEL_IDS. 68 of the 100 cases contain an FLT, the remaining 32 cases are free of it. The data is distributed as one 'image.nii.gz' / 'label.nii.gz' pair per case and hosted on Kaggle as a single, split zip archive (https://www.kaggle.com/datasets/xiaoweixumedicalai/ imagetbad), because the official GitHub repository (https://github.com/XiaoweiXu/Dataset_Type-B-Aortic- Dissection) does not host the data itself.

The Kaggle archive is itself split into 19 parts ('imageTBAD.change2zip', 'imageTBAD.z01' to 'imageTBAD.z18'): this module downloads all parts, joins them into a single zip with the 'zip' CLI (Info-ZIP) and extracts it.

NOTE: This requires a Kaggle account and API credentials (see https://www.kaggle.com/docs/api), as well as the 'zip' CLI (Info-ZIP) to join the split archives.

This dataset is from the publication https://doi.org/10.3389/fphys.2021.732711. Please cite it if you use this dataset in your research.

  1"""The ImageTBAD dataset contains annotations for type-B aortic dissection segmentation in
  2computed tomography angiography (CTA).
  3
  4The dataset consists of 100 3D CTA scans collected at the Guangdong Provincial People's Hospital between
  5January 2013 and April 2019. The segmentation was performed by one cardiovascular radiologist and checked
  6by a second one. It labels three substructures of the aortic dissection: the true lumen (TL), the false
  7lumen (FL) and the false lumen thrombus (FLT), see `LABEL_IDS`. 68 of the 100 cases contain an FLT, the
  8remaining 32 cases are free of it. The data is distributed as one 'image.nii.gz' / 'label.nii.gz' pair per
  9case and hosted on Kaggle as a single, split zip archive (https://www.kaggle.com/datasets/xiaoweixumedicalai/
 10imagetbad), because the official GitHub repository (https://github.com/XiaoweiXu/Dataset_Type-B-Aortic-
 11Dissection) does not host the data itself.
 12
 13The Kaggle archive is itself split into 19 parts ('imageTBAD.change2zip', 'imageTBAD.z01' to 'imageTBAD.z18'):
 14this module downloads all parts, joins them into a single zip with the 'zip' CLI (Info-ZIP) and extracts it.
 15
 16NOTE: This requires a Kaggle account and API credentials (see https://www.kaggle.com/docs/api), as well as
 17the 'zip' CLI (Info-ZIP) to join the split archives.
 18
 19This dataset is from the publication https://doi.org/10.3389/fphys.2021.732711.
 20Please cite it if you use this dataset in your research.
 21"""
 22
 23import os
 24from glob import glob
 25from shutil import which
 26from subprocess import run
 27from natsort import natsorted
 28from typing import Union, Tuple, List
 29
 30from torch.utils.data import Dataset, DataLoader
 31
 32import torch_em
 33
 34from .. import util
 35
 36
 37KAGGLE_DATASET = "xiaoweixumedicalai/imagetbad"
 38
 39N_PARTS = 18
 40"""The number of split zip parts ('imageTBAD.z01' to 'imageTBAD.z18') on top of 'imageTBAD.change2zip'."""
 41
 42LABEL_IDS = {"background": 0, "true_lumen": 1, "false_lumen": 2, "false_lumen_thrombus": 3}
 43
 44
 45def _download_kaggle_file(filename: str, dst_dir: str, download: bool) -> str:
 46    """Download a single file from the ImageTBAD Kaggle dataset.
 47
 48    Kaggle wraps every single-file download in an outer zip container (even if the file is itself
 49    already an archive), which is unpacked here to recover the original file.
 50    """
 51    out_path = os.path.join(dst_dir, filename)
 52    if os.path.exists(out_path):
 53        return out_path
 54    if not download:
 55        raise RuntimeError(f"Cannot find the data at {out_path}, but download was set to False.")
 56
 57    try:
 58        from kaggle.api.kaggle_api_extended import KaggleApi
 59    except ModuleNotFoundError:
 60        msg = "Please install the Kaggle API. You can do this using 'pip install kaggle'. "
 61        msg += "After you have installed kaggle, you would need an API token. "
 62        msg += "Follow the instructions at https://www.kaggle.com/docs/api."
 63        raise ModuleNotFoundError(msg)
 64
 65    os.makedirs(dst_dir, exist_ok=True)
 66    api = KaggleApi()
 67    api.authenticate()
 68    api.dataset_download_file(KAGGLE_DATASET, filename, path=dst_dir)
 69
 70    wrapper_path = os.path.join(dst_dir, f"{filename}.zip")
 71    util.unzip(zip_path=wrapper_path, dst=dst_dir)
 72    return out_path
 73
 74
 75def get_imagetbad_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 76    """Download the ImageTBAD dataset.
 77
 78    Args:
 79        path: Filepath to a folder where the data is downloaded for further processing.
 80        download: Whether to download the data if it is not present.
 81
 82    Returns:
 83        Filepath where the data is stored.
 84    """
 85    raw_dir = os.path.join(path, "data")
 86    if len(glob(os.path.join(raw_dir, "**", "*_image.nii.gz"), recursive=True)) >= 100:
 87        return raw_dir
 88
 89    os.makedirs(raw_dir, exist_ok=True)
 90
 91    zip_dir = os.path.join(path, "zips")
 92    parts = ["imageTBAD.change2zip"] + [f"imageTBAD.z{i:02d}" for i in range(1, N_PARTS + 1)]
 93    for part in parts:
 94        _download_kaggle_file(part, zip_dir, download)
 95
 96    base_zip = os.path.join(zip_dir, "imageTBAD.zip")
 97    if not os.path.exists(base_zip):
 98        os.rename(os.path.join(zip_dir, "imageTBAD.change2zip"), base_zip)
 99
100    merged_zip = os.path.join(zip_dir, "imageTBAD.merged.zip")
101    if not os.path.exists(merged_zip):
102        if which("zip") is None:
103            raise RuntimeError(
104                "Need the 'zip' CLI (Info-ZIP) to join the split zip archive of the ImageTBAD dataset. "
105                "You can install it via 'conda install -c conda-forge zip'."
106            )
107        run(["zip", "-s", "0", base_zip, "--out", merged_zip], check=True, cwd=zip_dir)
108
109    util.unzip(zip_path=merged_zip, dst=raw_dir, remove=False)
110
111    return raw_dir
112
113
114def get_imagetbad_paths(path: Union[os.PathLike, str], download: bool = False) -> Tuple[List[str], List[str]]:
115    """Get paths to the ImageTBAD data.
116
117    Args:
118        path: Filepath to a folder where the data is downloaded for further processing.
119        download: Whether to download the data if it is not present.
120
121    Returns:
122        List of filepaths for the image data.
123        List of filepaths for the label data.
124    """
125    raw_dir = get_imagetbad_data(path, download)
126
127    image_paths = natsorted(glob(os.path.join(raw_dir, "**", "*_image.nii.gz"), recursive=True))
128    label_paths = natsorted(glob(os.path.join(raw_dir, "**", "*_label.nii.gz"), recursive=True))
129    assert len(image_paths) > 0 and len(image_paths) == len(label_paths), \
130        f"Could not find a matching number of images and labels in '{raw_dir}'."
131
132    return image_paths, label_paths
133
134
135def get_imagetbad_dataset(
136    path: Union[os.PathLike, str],
137    patch_shape: Tuple[int, ...],
138    resize_inputs: bool = False,
139    download: bool = False,
140    **kwargs
141) -> Dataset:
142    """Get the ImageTBAD dataset for aortic dissection segmentation.
143
144    Args:
145        path: Filepath to a folder where the data is downloaded for further processing.
146        patch_shape: The patch shape to use for training.
147        resize_inputs: Whether to resize inputs to the desired patch shape.
148        download: Whether to download the data if it is not present.
149        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
150
151    Returns:
152        The segmentation dataset.
153    """
154    image_paths, label_paths = get_imagetbad_paths(path, download)
155
156    if resize_inputs:
157        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
158        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
159            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
160        )
161
162    return torch_em.default_segmentation_dataset(
163        raw_paths=image_paths,
164        raw_key="data",
165        label_paths=label_paths,
166        label_key="data",
167        patch_shape=patch_shape,
168        is_seg_dataset=True,
169        **kwargs
170    )
171
172
173def get_imagetbad_loader(
174    path: Union[os.PathLike, str],
175    batch_size: int,
176    patch_shape: Tuple[int, ...],
177    resize_inputs: bool = False,
178    download: bool = False,
179    **kwargs
180) -> DataLoader:
181    """Get the ImageTBAD dataloader for aortic dissection segmentation.
182
183    Args:
184        path: Filepath to a folder where the data is downloaded for further processing.
185        batch_size: The batch size for training.
186        patch_shape: The patch shape to use for training.
187        resize_inputs: Whether to resize inputs to the desired patch shape.
188        download: Whether to download the data if it is not present.
189        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
190
191    Returns:
192        The DataLoader.
193    """
194    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
195    dataset = get_imagetbad_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
196    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
KAGGLE_DATASET = 'xiaoweixumedicalai/imagetbad'
N_PARTS = 18

The number of split zip parts ('imageTBAD.z01' to 'imageTBAD.z18') on top of 'imageTBAD.change2zip'.

LABEL_IDS = {'background': 0, 'true_lumen': 1, 'false_lumen': 2, 'false_lumen_thrombus': 3}
def get_imagetbad_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 76def get_imagetbad_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 77    """Download the ImageTBAD dataset.
 78
 79    Args:
 80        path: Filepath to a folder where the data is downloaded for further processing.
 81        download: Whether to download the data if it is not present.
 82
 83    Returns:
 84        Filepath where the data is stored.
 85    """
 86    raw_dir = os.path.join(path, "data")
 87    if len(glob(os.path.join(raw_dir, "**", "*_image.nii.gz"), recursive=True)) >= 100:
 88        return raw_dir
 89
 90    os.makedirs(raw_dir, exist_ok=True)
 91
 92    zip_dir = os.path.join(path, "zips")
 93    parts = ["imageTBAD.change2zip"] + [f"imageTBAD.z{i:02d}" for i in range(1, N_PARTS + 1)]
 94    for part in parts:
 95        _download_kaggle_file(part, zip_dir, download)
 96
 97    base_zip = os.path.join(zip_dir, "imageTBAD.zip")
 98    if not os.path.exists(base_zip):
 99        os.rename(os.path.join(zip_dir, "imageTBAD.change2zip"), base_zip)
100
101    merged_zip = os.path.join(zip_dir, "imageTBAD.merged.zip")
102    if not os.path.exists(merged_zip):
103        if which("zip") is None:
104            raise RuntimeError(
105                "Need the 'zip' CLI (Info-ZIP) to join the split zip archive of the ImageTBAD dataset. "
106                "You can install it via 'conda install -c conda-forge zip'."
107            )
108        run(["zip", "-s", "0", base_zip, "--out", merged_zip], check=True, cwd=zip_dir)
109
110    util.unzip(zip_path=merged_zip, dst=raw_dir, remove=False)
111
112    return raw_dir

Download the ImageTBAD 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 stored.

def get_imagetbad_paths( path: Union[os.PathLike, str], download: bool = False) -> Tuple[List[str], List[str]]:
115def get_imagetbad_paths(path: Union[os.PathLike, str], download: bool = False) -> Tuple[List[str], List[str]]:
116    """Get paths to the ImageTBAD data.
117
118    Args:
119        path: Filepath to a folder where the data is downloaded for further processing.
120        download: Whether to download the data if it is not present.
121
122    Returns:
123        List of filepaths for the image data.
124        List of filepaths for the label data.
125    """
126    raw_dir = get_imagetbad_data(path, download)
127
128    image_paths = natsorted(glob(os.path.join(raw_dir, "**", "*_image.nii.gz"), recursive=True))
129    label_paths = natsorted(glob(os.path.join(raw_dir, "**", "*_label.nii.gz"), recursive=True))
130    assert len(image_paths) > 0 and len(image_paths) == len(label_paths), \
131        f"Could not find a matching number of images and labels in '{raw_dir}'."
132
133    return image_paths, label_paths

Get paths to the ImageTBAD 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:

List of filepaths for the image data. List of filepaths for the label data.

def get_imagetbad_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
136def get_imagetbad_dataset(
137    path: Union[os.PathLike, str],
138    patch_shape: Tuple[int, ...],
139    resize_inputs: bool = False,
140    download: bool = False,
141    **kwargs
142) -> Dataset:
143    """Get the ImageTBAD dataset for aortic dissection segmentation.
144
145    Args:
146        path: Filepath to a folder where the data is downloaded for further processing.
147        patch_shape: The patch shape to use for training.
148        resize_inputs: Whether to resize inputs to the desired patch shape.
149        download: Whether to download the data if it is not present.
150        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
151
152    Returns:
153        The segmentation dataset.
154    """
155    image_paths, label_paths = get_imagetbad_paths(path, download)
156
157    if resize_inputs:
158        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
159        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
160            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
161        )
162
163    return torch_em.default_segmentation_dataset(
164        raw_paths=image_paths,
165        raw_key="data",
166        label_paths=label_paths,
167        label_key="data",
168        patch_shape=patch_shape,
169        is_seg_dataset=True,
170        **kwargs
171    )

Get the ImageTBAD dataset for aortic dissection segmentation.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • patch_shape: The patch shape to use for training.
  • 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.

def get_imagetbad_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, ...], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
174def get_imagetbad_loader(
175    path: Union[os.PathLike, str],
176    batch_size: int,
177    patch_shape: Tuple[int, ...],
178    resize_inputs: bool = False,
179    download: bool = False,
180    **kwargs
181) -> DataLoader:
182    """Get the ImageTBAD dataloader for aortic dissection segmentation.
183
184    Args:
185        path: Filepath to a folder where the data is downloaded for further processing.
186        batch_size: The batch size for training.
187        patch_shape: The patch shape to use for training.
188        resize_inputs: Whether to resize inputs to the desired patch shape.
189        download: Whether to download the data if it is not present.
190        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
191
192    Returns:
193        The DataLoader.
194    """
195    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
196    dataset = get_imagetbad_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
197    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the ImageTBAD dataloader for aortic dissection 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.
  • 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 or for the PyTorch DataLoader.
Returns:

The DataLoader.