torch_em.data.datasets.medical.ctpelvic1k

The CTPelvic1K dataset contains annotations for pelvic bones in CT scans.

This module provides the two subsets of the collection that are distributed with their own scans: 'clinic' with 103 annotated scans and 'clinic_metal' with 14 annotated scans of patients with metal implants. The label ids are 1: sacrum, 2: hip (left), 3: hip (right), 4: lumbar vertebra. See also CLASS_IDS.

NOTE: 'clinic_metal' ships 75 scans but only 14 of them are annotated, so only those are returned. Its scans carry a 'dataset7_' prefix that its annotations do not, unlike the consistently named 'clinic'.

NOTE: The label ids of the two hips are assigned by the position of the annotation in the scan, which is the reverse of the order that is usually quoted for this dataset. Every scan of the release is stored in LPS orientation, and the annotation with id 2 lies on the left of the patient in all of them.

NOTE: The remaining subsets of the collection hold annotations for scans of other public datasets, such as the colon task of the Medical Segmentation Decathlon and KiTS, and are not provided here.

The dataset is located at https://doi.org/10.5281/zenodo.4588403. This dataset is from the publication https://doi.org/10.1007/s11548-021-02363-8. Please cite it if you use this dataset in your research.

  1"""The CTPelvic1K dataset contains annotations for pelvic bones in CT scans.
  2
  3This module provides the two subsets of the collection that are distributed with their own scans:
  4'clinic' with 103 annotated scans and 'clinic_metal' with 14 annotated scans of patients with metal
  5implants. The label ids are 1: sacrum, 2: hip (left), 3: hip (right), 4: lumbar vertebra.
  6See also `CLASS_IDS`.
  7
  8NOTE: 'clinic_metal' ships 75 scans but only 14 of them are annotated, so only those are returned. Its
  9scans carry a 'dataset7_' prefix that its annotations do not, unlike the consistently named 'clinic'.
 10
 11NOTE: The label ids of the two hips are assigned by the position of the annotation in the scan, which is
 12the reverse of the order that is usually quoted for this dataset. Every scan of the release is stored in
 13LPS orientation, and the annotation with id 2 lies on the left of the patient in all of them.
 14
 15NOTE: The remaining subsets of the collection hold annotations for scans of other public datasets, such
 16as the colon task of the Medical Segmentation Decathlon and KiTS, and are not provided here.
 17
 18The dataset is located at https://doi.org/10.5281/zenodo.4588403.
 19This dataset is from the publication https://doi.org/10.1007/s11548-021-02363-8.
 20Please cite it if you use this dataset in your research.
 21"""
 22
 23import os
 24import re
 25from glob import glob
 26from natsort import natsorted
 27from typing import Union, Tuple, Literal, List
 28
 29from torch.utils.data import Dataset, DataLoader
 30
 31import torch_em
 32
 33from .. import util
 34
 35
 36URL = "https://zenodo.org/records/4588403/files/{filename}?download=1"
 37
 38SUBSETS = {
 39    "clinic": {"images": "CTPelvic1K_dataset6_data.tar.gz", "labels": "CTPelvic1K_dataset6_Anonymized_mask.tar.gz"},
 40    "clinic_metal": {"images": "CTPelvic1K_dataset7_data.tar.gz", "labels": "CTPelvic1K_dataset7_mask.tar.gz"},
 41}
 42
 43CHECKSUMS = {
 44    "clinic": {
 45        "images": "9b8f4747d256483062aa937eb990890e59954d8be74966edd982a6dcf3f4394e",
 46        "labels": "382b0780bcbf68536631b14e9d2a42a0bccb6fc3c30c9598078ddfa0e574bf95",
 47    },
 48    "clinic_metal": {
 49        "images": "9b71fdf37c9bbb7bf99b95ea577f4bac24f9c393644e44287f59aad0b52a9cbc",
 50        "labels": "4b85e4eb7300e77ce094329933d032a88183f4f0ec65cde95e4162074bb7cca3",
 51    },
 52}
 53
 54CLASS_NAMES = ["sacrum", "hip_left", "hip_right", "lumbar_vertebra"]
 55"""The pelvic bones of the CTPelvic1K dataset. The label id of a bone is its 1-based index."""
 56
 57CLASS_IDS = {name: i + 1 for i, name in enumerate(CLASS_NAMES)}
 58"""Mapping from the bone name to its label id."""
 59
 60LABEL_SUFFIX = "_mask_4label.nii.gz"
 61
 62IMAGE_SUFFIX = "_data.nii.gz"
 63
 64
 65def _case_id(filename, suffix):
 66    """Strip the suffix and the subset prefix that the scans carry but their annotations do not."""
 67    return re.sub(r"^dataset\d+_", "", filename[:-len(suffix)])
 68
 69
 70def get_ctpelvic1k_data(
 71    path: Union[os.PathLike, str], subset: Literal["clinic", "clinic_metal"] = "clinic", download: bool = False
 72) -> str:
 73    """Download the CTPelvic1K dataset.
 74
 75    Args:
 76        path: Filepath to a folder where the data is downloaded for further processing.
 77        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
 78        download: Whether to download the data if it is not present.
 79
 80    Returns:
 81        Filepath where the data is downloaded.
 82    """
 83    if subset not in SUBSETS:
 84        raise ValueError(f"'{subset}' is not a valid subset. Choose from {list(SUBSETS.keys())}.")
 85
 86    data_dir = os.path.join(path, subset)
 87    if os.path.exists(data_dir) and glob(os.path.join(data_dir, "**", f"*{LABEL_SUFFIX}"), recursive=True):
 88        return data_dir
 89
 90    os.makedirs(data_dir, exist_ok=True)
 91    for key in ("images", "labels"):
 92        filename = SUBSETS[subset][key]
 93        tar_path = os.path.join(path, filename)
 94        util.download_source(
 95            path=tar_path, url=URL.format(filename=filename), download=download, checksum=CHECKSUMS[subset][key]
 96        )
 97        util.unzip_tarfile(tar_path=tar_path, dst=data_dir, remove=False)
 98
 99    return data_dir
100
101
102def get_ctpelvic1k_paths(
103    path: Union[os.PathLike, str],
104    subset: Literal["clinic", "clinic_metal"] = "clinic",
105    download: bool = False,
106) -> Tuple[List[str], List[str]]:
107    """Get paths to the CTPelvic1K data.
108
109    Args:
110        path: Filepath to a folder where the data is downloaded for further processing.
111        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
112        download: Whether to download the data if it is not present.
113
114    Returns:
115        List of filepaths for the image data.
116        List of filepaths for the label data.
117    """
118    data_dir = get_ctpelvic1k_data(path, subset, download)
119
120    label_paths = natsorted(glob(os.path.join(data_dir, "**", f"*{LABEL_SUFFIX}"), recursive=True))
121    image_paths = {
122        _case_id(os.path.basename(p), IMAGE_SUFFIX): p
123        for p in glob(os.path.join(data_dir, "**", f"*{IMAGE_SUFFIX}"), recursive=True)
124    }
125
126    # Only a part of the scans of 'clinic_metal' is annotated, so the annotations drive the pairing.
127    raw_paths, valid_label_paths = [], []
128    for label_path in label_paths:
129        image_path = image_paths.get(_case_id(os.path.basename(label_path), LABEL_SUFFIX))
130        if image_path is not None:
131            raw_paths.append(image_path)
132            valid_label_paths.append(label_path)
133
134    assert len(raw_paths) == len(valid_label_paths) and len(raw_paths) > 0
135
136    return raw_paths, valid_label_paths
137
138
139def get_ctpelvic1k_dataset(
140    path: Union[os.PathLike, str],
141    patch_shape: Tuple[int, ...],
142    subset: Literal["clinic", "clinic_metal"] = "clinic",
143    resize_inputs: bool = False,
144    download: bool = False,
145    **kwargs
146) -> Dataset:
147    """Get the CTPelvic1K dataset for pelvic bone segmentation.
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        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
153        resize_inputs: Whether to resize inputs to the desired patch shape.
154        download: Whether to download the data if it is not present.
155        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
156
157    Returns:
158        The segmentation dataset.
159    """
160    raw_paths, label_paths = get_ctpelvic1k_paths(path, subset, download)
161
162    if resize_inputs:
163        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
164        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
165            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
166        )
167
168    return torch_em.default_segmentation_dataset(
169        raw_paths=raw_paths,
170        raw_key="data",
171        label_paths=label_paths,
172        label_key="data",
173        patch_shape=patch_shape,
174        is_seg_dataset=True,
175        **kwargs
176    )
177
178
179def get_ctpelvic1k_loader(
180    path: Union[os.PathLike, str],
181    batch_size: int,
182    patch_shape: Tuple[int, ...],
183    subset: Literal["clinic", "clinic_metal"] = "clinic",
184    resize_inputs: bool = False,
185    download: bool = False,
186    **kwargs
187) -> DataLoader:
188    """Get the CTPelvic1K dataloader for pelvic bone segmentation.
189
190    Args:
191        path: Filepath to a folder where the data is downloaded for further processing.
192        batch_size: The batch size for training.
193        patch_shape: The patch shape to use for training.
194        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
195        resize_inputs: Whether to resize inputs to the desired patch shape.
196        download: Whether to download the data if it is not present.
197        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
198
199    Returns:
200        The DataLoader.
201    """
202    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
203    dataset = get_ctpelvic1k_dataset(path, patch_shape, subset, resize_inputs, download, **ds_kwargs)
204    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://zenodo.org/records/4588403/files/{filename}?download=1'
SUBSETS = {'clinic': {'images': 'CTPelvic1K_dataset6_data.tar.gz', 'labels': 'CTPelvic1K_dataset6_Anonymized_mask.tar.gz'}, 'clinic_metal': {'images': 'CTPelvic1K_dataset7_data.tar.gz', 'labels': 'CTPelvic1K_dataset7_mask.tar.gz'}}
CHECKSUMS = {'clinic': {'images': '9b8f4747d256483062aa937eb990890e59954d8be74966edd982a6dcf3f4394e', 'labels': '382b0780bcbf68536631b14e9d2a42a0bccb6fc3c30c9598078ddfa0e574bf95'}, 'clinic_metal': {'images': '9b71fdf37c9bbb7bf99b95ea577f4bac24f9c393644e44287f59aad0b52a9cbc', 'labels': '4b85e4eb7300e77ce094329933d032a88183f4f0ec65cde95e4162074bb7cca3'}}
CLASS_NAMES = ['sacrum', 'hip_left', 'hip_right', 'lumbar_vertebra']

The pelvic bones of the CTPelvic1K dataset. The label id of a bone is its 1-based index.

CLASS_IDS = {'sacrum': 1, 'hip_left': 2, 'hip_right': 3, 'lumbar_vertebra': 4}

Mapping from the bone name to its label id.

LABEL_SUFFIX = '_mask_4label.nii.gz'
IMAGE_SUFFIX = '_data.nii.gz'
def get_ctpelvic1k_data( path: Union[os.PathLike, str], subset: Literal['clinic', 'clinic_metal'] = 'clinic', download: bool = False) -> str:
 71def get_ctpelvic1k_data(
 72    path: Union[os.PathLike, str], subset: Literal["clinic", "clinic_metal"] = "clinic", download: bool = False
 73) -> str:
 74    """Download the CTPelvic1K dataset.
 75
 76    Args:
 77        path: Filepath to a folder where the data is downloaded for further processing.
 78        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
 79        download: Whether to download the data if it is not present.
 80
 81    Returns:
 82        Filepath where the data is downloaded.
 83    """
 84    if subset not in SUBSETS:
 85        raise ValueError(f"'{subset}' is not a valid subset. Choose from {list(SUBSETS.keys())}.")
 86
 87    data_dir = os.path.join(path, subset)
 88    if os.path.exists(data_dir) and glob(os.path.join(data_dir, "**", f"*{LABEL_SUFFIX}"), recursive=True):
 89        return data_dir
 90
 91    os.makedirs(data_dir, exist_ok=True)
 92    for key in ("images", "labels"):
 93        filename = SUBSETS[subset][key]
 94        tar_path = os.path.join(path, filename)
 95        util.download_source(
 96            path=tar_path, url=URL.format(filename=filename), download=download, checksum=CHECKSUMS[subset][key]
 97        )
 98        util.unzip_tarfile(tar_path=tar_path, dst=data_dir, remove=False)
 99
100    return data_dir

Download the CTPelvic1K dataset.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
  • download: Whether to download the data if it is not present.
Returns:

Filepath where the data is downloaded.

def get_ctpelvic1k_paths( path: Union[os.PathLike, str], subset: Literal['clinic', 'clinic_metal'] = 'clinic', download: bool = False) -> Tuple[List[str], List[str]]:
103def get_ctpelvic1k_paths(
104    path: Union[os.PathLike, str],
105    subset: Literal["clinic", "clinic_metal"] = "clinic",
106    download: bool = False,
107) -> Tuple[List[str], List[str]]:
108    """Get paths to the CTPelvic1K data.
109
110    Args:
111        path: Filepath to a folder where the data is downloaded for further processing.
112        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
113        download: Whether to download the data if it is not present.
114
115    Returns:
116        List of filepaths for the image data.
117        List of filepaths for the label data.
118    """
119    data_dir = get_ctpelvic1k_data(path, subset, download)
120
121    label_paths = natsorted(glob(os.path.join(data_dir, "**", f"*{LABEL_SUFFIX}"), recursive=True))
122    image_paths = {
123        _case_id(os.path.basename(p), IMAGE_SUFFIX): p
124        for p in glob(os.path.join(data_dir, "**", f"*{IMAGE_SUFFIX}"), recursive=True)
125    }
126
127    # Only a part of the scans of 'clinic_metal' is annotated, so the annotations drive the pairing.
128    raw_paths, valid_label_paths = [], []
129    for label_path in label_paths:
130        image_path = image_paths.get(_case_id(os.path.basename(label_path), LABEL_SUFFIX))
131        if image_path is not None:
132            raw_paths.append(image_path)
133            valid_label_paths.append(label_path)
134
135    assert len(raw_paths) == len(valid_label_paths) and len(raw_paths) > 0
136
137    return raw_paths, valid_label_paths

Get paths to the CTPelvic1K data.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
  • 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_ctpelvic1k_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], subset: Literal['clinic', 'clinic_metal'] = 'clinic', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
140def get_ctpelvic1k_dataset(
141    path: Union[os.PathLike, str],
142    patch_shape: Tuple[int, ...],
143    subset: Literal["clinic", "clinic_metal"] = "clinic",
144    resize_inputs: bool = False,
145    download: bool = False,
146    **kwargs
147) -> Dataset:
148    """Get the CTPelvic1K dataset for pelvic bone segmentation.
149
150    Args:
151        path: Filepath to a folder where the data is downloaded for further processing.
152        patch_shape: The patch shape to use for training.
153        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
154        resize_inputs: Whether to resize inputs to the desired patch shape.
155        download: Whether to download the data if it is not present.
156        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
157
158    Returns:
159        The segmentation dataset.
160    """
161    raw_paths, label_paths = get_ctpelvic1k_paths(path, subset, download)
162
163    if resize_inputs:
164        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
165        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
166            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
167        )
168
169    return torch_em.default_segmentation_dataset(
170        raw_paths=raw_paths,
171        raw_key="data",
172        label_paths=label_paths,
173        label_key="data",
174        patch_shape=patch_shape,
175        is_seg_dataset=True,
176        **kwargs
177    )

Get the CTPelvic1K dataset for pelvic bone segmentation.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • patch_shape: The patch shape to use for training.
  • subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
  • 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_ctpelvic1k_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, ...], subset: Literal['clinic', 'clinic_metal'] = 'clinic', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
180def get_ctpelvic1k_loader(
181    path: Union[os.PathLike, str],
182    batch_size: int,
183    patch_shape: Tuple[int, ...],
184    subset: Literal["clinic", "clinic_metal"] = "clinic",
185    resize_inputs: bool = False,
186    download: bool = False,
187    **kwargs
188) -> DataLoader:
189    """Get the CTPelvic1K dataloader for pelvic bone segmentation.
190
191    Args:
192        path: Filepath to a folder where the data is downloaded for further processing.
193        batch_size: The batch size for training.
194        patch_shape: The patch shape to use for training.
195        subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
196        resize_inputs: Whether to resize inputs to the desired patch shape.
197        download: Whether to download the data if it is not present.
198        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
199
200    Returns:
201        The DataLoader.
202    """
203    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
204    dataset = get_ctpelvic1k_dataset(path, patch_shape, subset, resize_inputs, download, **ds_kwargs)
205    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the CTPelvic1K dataloader for pelvic bone 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.
  • subset: The choice of subset. Either 'clinic' with 103 scans or 'clinic_metal' with 14 scans.
  • 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.