torch_em.data.datasets.medical.enhance_pet

The ENHANCE.PET 1.6k dataset contains annotations for 130 anatomical structures in whole-/total-body CT volumes, co-acquired with [18F]FDG-PET as part of routine oncological imaging (~1,597 studies).

The per-structure binary masks are grouped into 7 category volumes ('Body-Composition', 'Cardiac', 'Muscles', 'Organs', 'Peripheral-Bones', 'Ribs', 'Vertebrae'), each registered to the CT grid, with structure ids restarting at 1 within each category. get_enhance_pet_data merges the 7 category volumes of a case into a single semantic label volume, where the label id of a structure is its (1-based) position in CLASS_NAMES (see CLASS_NAMES_BY_CATEGORY for the per-category structure lists this is built from). The PET volumes have a different grid than the CT / label volumes (no shared voxel grid without resampling) and are not exposed by this loader, only the CT volumes are used as raw data.

The data is located at the public, anonymously readable S3 bucket 'enhance-pet-1-6k' (https://registry.opendata.aws/enhance-pet-1-6k/, region 'us-west-2'), released under a CC-BY-4.0 license.

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

  1"""The ENHANCE.PET 1.6k dataset contains annotations for 130 anatomical structures in whole-/total-body CT
  2volumes, co-acquired with [18F]FDG-PET as part of routine oncological imaging (~1,597 studies).
  3
  4The per-structure binary masks are grouped into 7 category volumes ('Body-Composition', 'Cardiac', 'Muscles',
  5'Organs', 'Peripheral-Bones', 'Ribs', 'Vertebrae'), each registered to the CT grid, with structure ids
  6restarting at 1 within each category. `get_enhance_pet_data` merges the 7 category volumes of a case into a
  7single semantic label volume, where the label id of a structure is its (1-based) position in `CLASS_NAMES`
  8(see `CLASS_NAMES_BY_CATEGORY` for the per-category structure lists this is built from). The PET volumes have
  9a different grid than the CT / label volumes (no shared voxel grid without resampling) and are not exposed by
 10this loader, only the CT volumes are used as raw data.
 11
 12The data is located at the public, anonymously readable S3 bucket 'enhance-pet-1-6k'
 13(https://registry.opendata.aws/enhance-pet-1-6k/, region 'us-west-2'), released under a CC-BY-4.0 license.
 14
 15This dataset is from the publication https://doi.org/10.1038/s41597-026-07218-y.
 16Please cite it if you use this dataset for your research.
 17"""
 18
 19import os
 20from glob import glob
 21from concurrent import futures
 22from typing import Union, Tuple, List, Optional
 23
 24import numpy as np
 25from tqdm import tqdm
 26
 27from torch.utils.data import Dataset, DataLoader
 28
 29import torch_em
 30
 31from .. import util
 32
 33
 34BUCKET_URL = "https://enhance-pet-1-6k.s3.us-west-2.amazonaws.com"
 35
 36CLASS_NAMES_BY_CATEGORY = {
 37    "Body-Composition": ["skeletal_muscle", "subcutaneous_fat", "visceral_fat"],
 38    "Cardiac": [
 39        "heart_myocardium", "heart_atrium_left", "heart_atrium_right", "heart_ventricle_left",
 40        "heart_ventricle_right", "aorta", "iliac_artery_left", "iliac_artery_right", "iliac_vena_left",
 41        "iliac_vena_right", "inferior_vena_cava", "portal_splenic_vein", "pulmonary_artery",
 42    ],
 43    "Muscles": [
 44        "autochthon_left", "autochthon_right", "gluteus_maximus_left", "gluteus_maximus_right",
 45        "gluteus_medius_left", "gluteus_medius_right", "gluteus_minimus_left", "gluteus_minimus_right",
 46        "iliopsoas_left", "iliopsoas_right",
 47    ],
 48    "Organs": [
 49        "adrenal_gland_left", "adrenal_gland_right", "bladder", "brain", "gallbladder", "kidney_left",
 50        "kidney_right", "liver", "lung_lower_lobe_left", "lung_lower_lobe_right", "lung_middle_lobe_right",
 51        "lung_upper_lobe_left", "lung_upper_lobe_right", "pancreas", "spleen", "stomach", "thyroid_left",
 52        "thyroid_right",
 53    ],
 54    "Peripheral-Bones": [
 55        "carpal_left", "carpal_right", "clavicle_left", "clavicle_right", "femur_left", "femur_right",
 56        "fibula_left", "fibula_right", "fingers_left", "fingers_right", "humerus_left", "humerus_right",
 57        "metacarpal_left", "metacarpal_right", "metatarsal_left", "metatarsal_right", "patella_left",
 58        "patella_right", "radius_left", "radius_right", "scapula_left", "scapula_right", "skull", "tarsal_left",
 59        "tarsal_right", "tibia_left", "tibia_right", "toes_left", "toes_right", "ulna_left", "ulna_right",
 60    ],
 61    "Ribs": [f"rib_left_{i}" for i in range(1, 14)] + [f"rib_right_{i}" for i in range(1, 14)] + ["sternum"],
 62    "Vertebrae": (
 63        [f"vertebra_C{i}" for i in range(1, 8)] + [f"vertebra_T{i}" for i in range(1, 13)]
 64        + [f"vertebra_L{i}" for i in range(1, 7)] + ["hip_left", "hip_right", "sacrum"]
 65    ),
 66}
 67"""The anatomical structures of the ENHANCE.PET 1.6k dataset, grouped by their ground-truth category volume."""
 68
 69CATEGORIES = sorted(CLASS_NAMES_BY_CATEGORY.keys())
 70
 71CLASS_NAMES = [name for category in CATEGORIES for name in CLASS_NAMES_BY_CATEGORY[category]]
 72"""The anatomical structures of the ENHANCE.PET 1.6k dataset. The label id of a structure is its 1-based index."""
 73
 74CLASS_IDS = {name: i + 1 for i, name in enumerate(CLASS_NAMES)}
 75"""Mapping from the name of an anatomical structure to its label id in the merged label volumes."""
 76
 77
 78def _list_case_ids(n_workers=None):
 79    import requests
 80    from xml.etree import ElementTree
 81
 82    case_ids, token = [], None
 83    while True:
 84        params = {"list-type": "2", "prefix": "imaging-data/images/CT/"}
 85        if token:
 86            params["continuation-token"] = token
 87        response = requests.get(BUCKET_URL, params=params)
 88        response.raise_for_status()
 89        root = ElementTree.fromstring(response.content)
 90        ns = {"s3": "http://s3.amazonaws.com/doc/2006-03-01/"}
 91        for content in root.findall("s3:Contents", ns):
 92            key = content.find("s3:Key", ns).text
 93            case_ids.append(os.path.basename(key).replace(".nii.gz", ""))
 94        is_truncated = root.find("s3:IsTruncated", ns).text == "true"
 95        if not is_truncated:
 96            break
 97        token = root.find("s3:NextContinuationToken", ns).text
 98
 99    return sorted(case_ids)
100
101
102def _merge_segmentations(path: str, case_id: str) -> str:
103    """Merge the 7 per-category label volumes of one case into a single semantic label volume."""
104    import nibabel as nib
105
106    label_path = os.path.join(path, "imaging-data", "labels", f"{case_id}.nii.gz")
107    if os.path.exists(label_path):
108        return label_path
109
110    labels, affine, header, offset = None, None, None, 0
111    for category in CATEGORIES:
112        category_path = os.path.join(path, "imaging-data", "ground-truth", category, f"{case_id}.nii.gz")
113        category_nii = nib.load(category_path)
114        category_labels = np.round(np.asarray(category_nii.dataobj)).astype("uint8")
115        if labels is None:
116            labels = np.zeros(category_labels.shape, dtype="uint8")
117            affine, header = category_nii.affine, category_nii.header
118        foreground = category_labels > 0
119        labels[foreground] = category_labels[foreground] + offset
120        offset += len(CLASS_NAMES_BY_CATEGORY[category])
121
122    os.makedirs(os.path.dirname(label_path), exist_ok=True)
123    tmp_path = label_path.replace(".nii.gz", ".incomplete.nii.gz")
124    nib.save(nib.Nifti1Image(labels, affine, header), tmp_path)
125    os.replace(tmp_path, label_path)
126    return label_path
127
128
129def _download_case(case_id: str, path: str) -> None:
130    image_path = os.path.join(path, "imaging-data", "images", "CT", f"{case_id}.nii.gz")
131    util.download_source(
132        path=image_path, url=f"{BUCKET_URL}/imaging-data/images/CT/{case_id}.nii.gz", download=True,
133    )
134    for category in CATEGORIES:
135        category_path = os.path.join(path, "imaging-data", "ground-truth", category, f"{case_id}.nii.gz")
136        util.download_source(
137            path=category_path,
138            url=f"{BUCKET_URL}/imaging-data/ground-truth/{category}/{case_id}.nii.gz", download=True,
139        )
140    _merge_segmentations(path, case_id)
141
142
143def get_enhance_pet_data(
144    path: Union[os.PathLike, str], n_cases: Optional[int] = None, n_workers: Optional[int] = None,
145    download: bool = False,
146) -> str:
147    """Download the ENHANCE.PET 1.6k dataset and merge the per-category masks into semantic label volumes.
148
149    NOTE: The full collection is about 250 GB. Use `n_cases` to only download a subset for a quick start.
150
151    Args:
152        path: Filepath to a folder where the data is downloaded for further processing.
153        n_cases: The number of cases to download, sorted by case id. By default all ~1597 cases are downloaded.
154        n_workers: The number of parallel download / merging workers. By default the number of CPUs (at most
155            16) is used.
156        download: Whether to download the data if it is not present.
157
158    Returns:
159        Filepath where the data is downloaded.
160    """
161    os.makedirs(path, exist_ok=True)
162    case_ids = _list_case_ids()
163    if n_cases is not None:
164        case_ids = case_ids[:n_cases]
165
166    missing = [
167        case_id for case_id in case_ids
168        if not os.path.exists(os.path.join(path, "imaging-data", "labels", f"{case_id}.nii.gz"))
169    ]
170    if missing and not download:
171        raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.")
172
173    if n_workers is None:
174        n_cpus = len(os.sched_getaffinity(0)) if hasattr(os, "sched_getaffinity") else (os.cpu_count() or 1)
175        n_workers = min(16, n_cpus)
176
177    with futures.ThreadPoolExecutor(n_workers) as pool:
178        tasks = [pool.submit(_download_case, case_id, path) for case_id in missing]
179        for task in tqdm(futures.as_completed(tasks), total=len(tasks), desc="Download ENHANCE.PET cases"):
180            task.result()
181
182    return path
183
184
185def get_enhance_pet_paths(
186    path: Union[os.PathLike, str], n_cases: Optional[int] = None, download: bool = False,
187) -> Tuple[List[str], List[str]]:
188    """Get paths to the ENHANCE.PET 1.6k data.
189
190    Args:
191        path: Filepath to a folder where the data is downloaded for further processing.
192        n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
193        download: Whether to download the data if it is not present.
194
195    Returns:
196        List of filepaths for the CT image data.
197        List of filepaths for the label data.
198    """
199    get_enhance_pet_data(path, n_cases, download=download)
200
201    raw_paths = sorted(glob(os.path.join(path, "imaging-data", "images", "CT", "*.nii.gz")))
202    label_paths = [
203        os.path.join(path, "imaging-data", "labels", os.path.basename(p)) for p in raw_paths
204    ]
205
206    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
207    assert all(os.path.exists(p) for p in label_paths)
208    return raw_paths, label_paths
209
210
211def get_enhance_pet_dataset(
212    path: Union[os.PathLike, str],
213    patch_shape: Tuple[int, int, int],
214    n_cases: Optional[int] = None,
215    resize_inputs: bool = False,
216    download: bool = False,
217    **kwargs
218) -> Dataset:
219    """Get the ENHANCE.PET 1.6k dataset for segmentation of anatomical structures in whole-body CT.
220
221    Args:
222        path: Filepath to a folder where the data is downloaded for further processing.
223        patch_shape: The patch shape to use for training.
224        n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
225        resize_inputs: Whether to resize the inputs to the patch shape.
226        download: Whether to download the data if it is not present.
227        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
228
229    Returns:
230        The segmentation dataset.
231    """
232    raw_paths, label_paths = get_enhance_pet_paths(path, n_cases, download)
233
234    if resize_inputs:
235        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
236        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
237            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
238        )
239
240    return torch_em.default_segmentation_dataset(
241        raw_paths=raw_paths,
242        raw_key="data",
243        label_paths=label_paths,
244        label_key="data",
245        is_seg_dataset=True,
246        patch_shape=patch_shape,
247        ndim=3,
248        **kwargs
249    )
250
251
252def get_enhance_pet_loader(
253    path: Union[os.PathLike, str],
254    batch_size: int,
255    patch_shape: Tuple[int, int, int],
256    n_cases: Optional[int] = None,
257    resize_inputs: bool = False,
258    download: bool = False,
259    **kwargs
260) -> DataLoader:
261    """Get the ENHANCE.PET 1.6k dataloader for segmentation of anatomical structures in whole-body CT.
262
263    Args:
264        path: Filepath to a folder where the data is downloaded for further processing.
265        batch_size: The batch size for training.
266        patch_shape: The patch shape to use for training.
267        n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
268        resize_inputs: Whether to resize the inputs to the patch shape.
269        download: Whether to download the data if it is not present.
270        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
271
272    Returns:
273        The DataLoader.
274    """
275    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
276    dataset = get_enhance_pet_dataset(path, patch_shape, n_cases, resize_inputs, download, **ds_kwargs)
277    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
BUCKET_URL = 'https://enhance-pet-1-6k.s3.us-west-2.amazonaws.com'
CLASS_NAMES_BY_CATEGORY = {'Body-Composition': ['skeletal_muscle', 'subcutaneous_fat', 'visceral_fat'], 'Cardiac': ['heart_myocardium', 'heart_atrium_left', 'heart_atrium_right', 'heart_ventricle_left', 'heart_ventricle_right', 'aorta', 'iliac_artery_left', 'iliac_artery_right', 'iliac_vena_left', 'iliac_vena_right', 'inferior_vena_cava', 'portal_splenic_vein', 'pulmonary_artery'], 'Muscles': ['autochthon_left', 'autochthon_right', 'gluteus_maximus_left', 'gluteus_maximus_right', 'gluteus_medius_left', 'gluteus_medius_right', 'gluteus_minimus_left', 'gluteus_minimus_right', 'iliopsoas_left', 'iliopsoas_right'], 'Organs': ['adrenal_gland_left', 'adrenal_gland_right', 'bladder', 'brain', 'gallbladder', 'kidney_left', 'kidney_right', 'liver', 'lung_lower_lobe_left', 'lung_lower_lobe_right', 'lung_middle_lobe_right', 'lung_upper_lobe_left', 'lung_upper_lobe_right', 'pancreas', 'spleen', 'stomach', 'thyroid_left', 'thyroid_right'], 'Peripheral-Bones': ['carpal_left', 'carpal_right', 'clavicle_left', 'clavicle_right', 'femur_left', 'femur_right', 'fibula_left', 'fibula_right', 'fingers_left', 'fingers_right', 'humerus_left', 'humerus_right', 'metacarpal_left', 'metacarpal_right', 'metatarsal_left', 'metatarsal_right', 'patella_left', 'patella_right', 'radius_left', 'radius_right', 'scapula_left', 'scapula_right', 'skull', 'tarsal_left', 'tarsal_right', 'tibia_left', 'tibia_right', 'toes_left', 'toes_right', 'ulna_left', 'ulna_right'], 'Ribs': ['rib_left_1', 'rib_left_2', 'rib_left_3', 'rib_left_4', 'rib_left_5', 'rib_left_6', 'rib_left_7', 'rib_left_8', 'rib_left_9', 'rib_left_10', 'rib_left_11', 'rib_left_12', 'rib_left_13', 'rib_right_1', 'rib_right_2', 'rib_right_3', 'rib_right_4', 'rib_right_5', 'rib_right_6', 'rib_right_7', 'rib_right_8', 'rib_right_9', 'rib_right_10', 'rib_right_11', 'rib_right_12', 'rib_right_13', 'sternum'], 'Vertebrae': ['vertebra_C1', 'vertebra_C2', 'vertebra_C3', 'vertebra_C4', 'vertebra_C5', 'vertebra_C6', 'vertebra_C7', 'vertebra_T1', 'vertebra_T2', 'vertebra_T3', 'vertebra_T4', 'vertebra_T5', 'vertebra_T6', 'vertebra_T7', 'vertebra_T8', 'vertebra_T9', 'vertebra_T10', 'vertebra_T11', 'vertebra_T12', 'vertebra_L1', 'vertebra_L2', 'vertebra_L3', 'vertebra_L4', 'vertebra_L5', 'vertebra_L6', 'hip_left', 'hip_right', 'sacrum']}

The anatomical structures of the ENHANCE.PET 1.6k dataset, grouped by their ground-truth category volume.

CATEGORIES = ['Body-Composition', 'Cardiac', 'Muscles', 'Organs', 'Peripheral-Bones', 'Ribs', 'Vertebrae']
CLASS_NAMES = ['skeletal_muscle', 'subcutaneous_fat', 'visceral_fat', 'heart_myocardium', 'heart_atrium_left', 'heart_atrium_right', 'heart_ventricle_left', 'heart_ventricle_right', 'aorta', 'iliac_artery_left', 'iliac_artery_right', 'iliac_vena_left', 'iliac_vena_right', 'inferior_vena_cava', 'portal_splenic_vein', 'pulmonary_artery', 'autochthon_left', 'autochthon_right', 'gluteus_maximus_left', 'gluteus_maximus_right', 'gluteus_medius_left', 'gluteus_medius_right', 'gluteus_minimus_left', 'gluteus_minimus_right', 'iliopsoas_left', 'iliopsoas_right', 'adrenal_gland_left', 'adrenal_gland_right', 'bladder', 'brain', 'gallbladder', 'kidney_left', 'kidney_right', 'liver', 'lung_lower_lobe_left', 'lung_lower_lobe_right', 'lung_middle_lobe_right', 'lung_upper_lobe_left', 'lung_upper_lobe_right', 'pancreas', 'spleen', 'stomach', 'thyroid_left', 'thyroid_right', 'carpal_left', 'carpal_right', 'clavicle_left', 'clavicle_right', 'femur_left', 'femur_right', 'fibula_left', 'fibula_right', 'fingers_left', 'fingers_right', 'humerus_left', 'humerus_right', 'metacarpal_left', 'metacarpal_right', 'metatarsal_left', 'metatarsal_right', 'patella_left', 'patella_right', 'radius_left', 'radius_right', 'scapula_left', 'scapula_right', 'skull', 'tarsal_left', 'tarsal_right', 'tibia_left', 'tibia_right', 'toes_left', 'toes_right', 'ulna_left', 'ulna_right', 'rib_left_1', 'rib_left_2', 'rib_left_3', 'rib_left_4', 'rib_left_5', 'rib_left_6', 'rib_left_7', 'rib_left_8', 'rib_left_9', 'rib_left_10', 'rib_left_11', 'rib_left_12', 'rib_left_13', 'rib_right_1', 'rib_right_2', 'rib_right_3', 'rib_right_4', 'rib_right_5', 'rib_right_6', 'rib_right_7', 'rib_right_8', 'rib_right_9', 'rib_right_10', 'rib_right_11', 'rib_right_12', 'rib_right_13', 'sternum', 'vertebra_C1', 'vertebra_C2', 'vertebra_C3', 'vertebra_C4', 'vertebra_C5', 'vertebra_C6', 'vertebra_C7', 'vertebra_T1', 'vertebra_T2', 'vertebra_T3', 'vertebra_T4', 'vertebra_T5', 'vertebra_T6', 'vertebra_T7', 'vertebra_T8', 'vertebra_T9', 'vertebra_T10', 'vertebra_T11', 'vertebra_T12', 'vertebra_L1', 'vertebra_L2', 'vertebra_L3', 'vertebra_L4', 'vertebra_L5', 'vertebra_L6', 'hip_left', 'hip_right', 'sacrum']

The anatomical structures of the ENHANCE.PET 1.6k dataset. The label id of a structure is its 1-based index.

CLASS_IDS = {'skeletal_muscle': 1, 'subcutaneous_fat': 2, 'visceral_fat': 3, 'heart_myocardium': 4, 'heart_atrium_left': 5, 'heart_atrium_right': 6, 'heart_ventricle_left': 7, 'heart_ventricle_right': 8, 'aorta': 9, 'iliac_artery_left': 10, 'iliac_artery_right': 11, 'iliac_vena_left': 12, 'iliac_vena_right': 13, 'inferior_vena_cava': 14, 'portal_splenic_vein': 15, 'pulmonary_artery': 16, 'autochthon_left': 17, 'autochthon_right': 18, 'gluteus_maximus_left': 19, 'gluteus_maximus_right': 20, 'gluteus_medius_left': 21, 'gluteus_medius_right': 22, 'gluteus_minimus_left': 23, 'gluteus_minimus_right': 24, 'iliopsoas_left': 25, 'iliopsoas_right': 26, 'adrenal_gland_left': 27, 'adrenal_gland_right': 28, 'bladder': 29, 'brain': 30, 'gallbladder': 31, 'kidney_left': 32, 'kidney_right': 33, 'liver': 34, 'lung_lower_lobe_left': 35, 'lung_lower_lobe_right': 36, 'lung_middle_lobe_right': 37, 'lung_upper_lobe_left': 38, 'lung_upper_lobe_right': 39, 'pancreas': 40, 'spleen': 41, 'stomach': 42, 'thyroid_left': 43, 'thyroid_right': 44, 'carpal_left': 45, 'carpal_right': 46, 'clavicle_left': 47, 'clavicle_right': 48, 'femur_left': 49, 'femur_right': 50, 'fibula_left': 51, 'fibula_right': 52, 'fingers_left': 53, 'fingers_right': 54, 'humerus_left': 55, 'humerus_right': 56, 'metacarpal_left': 57, 'metacarpal_right': 58, 'metatarsal_left': 59, 'metatarsal_right': 60, 'patella_left': 61, 'patella_right': 62, 'radius_left': 63, 'radius_right': 64, 'scapula_left': 65, 'scapula_right': 66, 'skull': 67, 'tarsal_left': 68, 'tarsal_right': 69, 'tibia_left': 70, 'tibia_right': 71, 'toes_left': 72, 'toes_right': 73, 'ulna_left': 74, 'ulna_right': 75, 'rib_left_1': 76, 'rib_left_2': 77, 'rib_left_3': 78, 'rib_left_4': 79, 'rib_left_5': 80, 'rib_left_6': 81, 'rib_left_7': 82, 'rib_left_8': 83, 'rib_left_9': 84, 'rib_left_10': 85, 'rib_left_11': 86, 'rib_left_12': 87, 'rib_left_13': 88, 'rib_right_1': 89, 'rib_right_2': 90, 'rib_right_3': 91, 'rib_right_4': 92, 'rib_right_5': 93, 'rib_right_6': 94, 'rib_right_7': 95, 'rib_right_8': 96, 'rib_right_9': 97, 'rib_right_10': 98, 'rib_right_11': 99, 'rib_right_12': 100, 'rib_right_13': 101, 'sternum': 102, 'vertebra_C1': 103, 'vertebra_C2': 104, 'vertebra_C3': 105, 'vertebra_C4': 106, 'vertebra_C5': 107, 'vertebra_C6': 108, 'vertebra_C7': 109, 'vertebra_T1': 110, 'vertebra_T2': 111, 'vertebra_T3': 112, 'vertebra_T4': 113, 'vertebra_T5': 114, 'vertebra_T6': 115, 'vertebra_T7': 116, 'vertebra_T8': 117, 'vertebra_T9': 118, 'vertebra_T10': 119, 'vertebra_T11': 120, 'vertebra_T12': 121, 'vertebra_L1': 122, 'vertebra_L2': 123, 'vertebra_L3': 124, 'vertebra_L4': 125, 'vertebra_L5': 126, 'vertebra_L6': 127, 'hip_left': 128, 'hip_right': 129, 'sacrum': 130}

Mapping from the name of an anatomical structure to its label id in the merged label volumes.

def get_enhance_pet_data( path: Union[os.PathLike, str], n_cases: Optional[int] = None, n_workers: Optional[int] = None, download: bool = False) -> str:
144def get_enhance_pet_data(
145    path: Union[os.PathLike, str], n_cases: Optional[int] = None, n_workers: Optional[int] = None,
146    download: bool = False,
147) -> str:
148    """Download the ENHANCE.PET 1.6k dataset and merge the per-category masks into semantic label volumes.
149
150    NOTE: The full collection is about 250 GB. Use `n_cases` to only download a subset for a quick start.
151
152    Args:
153        path: Filepath to a folder where the data is downloaded for further processing.
154        n_cases: The number of cases to download, sorted by case id. By default all ~1597 cases are downloaded.
155        n_workers: The number of parallel download / merging workers. By default the number of CPUs (at most
156            16) is used.
157        download: Whether to download the data if it is not present.
158
159    Returns:
160        Filepath where the data is downloaded.
161    """
162    os.makedirs(path, exist_ok=True)
163    case_ids = _list_case_ids()
164    if n_cases is not None:
165        case_ids = case_ids[:n_cases]
166
167    missing = [
168        case_id for case_id in case_ids
169        if not os.path.exists(os.path.join(path, "imaging-data", "labels", f"{case_id}.nii.gz"))
170    ]
171    if missing and not download:
172        raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.")
173
174    if n_workers is None:
175        n_cpus = len(os.sched_getaffinity(0)) if hasattr(os, "sched_getaffinity") else (os.cpu_count() or 1)
176        n_workers = min(16, n_cpus)
177
178    with futures.ThreadPoolExecutor(n_workers) as pool:
179        tasks = [pool.submit(_download_case, case_id, path) for case_id in missing]
180        for task in tqdm(futures.as_completed(tasks), total=len(tasks), desc="Download ENHANCE.PET cases"):
181            task.result()
182
183    return path

Download the ENHANCE.PET 1.6k dataset and merge the per-category masks into semantic label volumes.

NOTE: The full collection is about 250 GB. Use n_cases to only download a subset for a quick start.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • n_cases: The number of cases to download, sorted by case id. By default all ~1597 cases are downloaded.
  • n_workers: The number of parallel download / merging workers. By default the number of CPUs (at most 16) is used.
  • download: Whether to download the data if it is not present.
Returns:

Filepath where the data is downloaded.

def get_enhance_pet_paths( path: Union[os.PathLike, str], n_cases: Optional[int] = None, download: bool = False) -> Tuple[List[str], List[str]]:
186def get_enhance_pet_paths(
187    path: Union[os.PathLike, str], n_cases: Optional[int] = None, download: bool = False,
188) -> Tuple[List[str], List[str]]:
189    """Get paths to the ENHANCE.PET 1.6k data.
190
191    Args:
192        path: Filepath to a folder where the data is downloaded for further processing.
193        n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
194        download: Whether to download the data if it is not present.
195
196    Returns:
197        List of filepaths for the CT image data.
198        List of filepaths for the label data.
199    """
200    get_enhance_pet_data(path, n_cases, download=download)
201
202    raw_paths = sorted(glob(os.path.join(path, "imaging-data", "images", "CT", "*.nii.gz")))
203    label_paths = [
204        os.path.join(path, "imaging-data", "labels", os.path.basename(p)) for p in raw_paths
205    ]
206
207    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
208    assert all(os.path.exists(p) for p in label_paths)
209    return raw_paths, label_paths

Get paths to the ENHANCE.PET 1.6k data.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
  • download: Whether to download the data if it is not present.
Returns:

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

def get_enhance_pet_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int, int], n_cases: Optional[int] = None, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
212def get_enhance_pet_dataset(
213    path: Union[os.PathLike, str],
214    patch_shape: Tuple[int, int, int],
215    n_cases: Optional[int] = None,
216    resize_inputs: bool = False,
217    download: bool = False,
218    **kwargs
219) -> Dataset:
220    """Get the ENHANCE.PET 1.6k dataset for segmentation of anatomical structures in whole-body CT.
221
222    Args:
223        path: Filepath to a folder where the data is downloaded for further processing.
224        patch_shape: The patch shape to use for training.
225        n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
226        resize_inputs: Whether to resize the inputs to the patch shape.
227        download: Whether to download the data if it is not present.
228        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
229
230    Returns:
231        The segmentation dataset.
232    """
233    raw_paths, label_paths = get_enhance_pet_paths(path, n_cases, download)
234
235    if resize_inputs:
236        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
237        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
238            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
239        )
240
241    return torch_em.default_segmentation_dataset(
242        raw_paths=raw_paths,
243        raw_key="data",
244        label_paths=label_paths,
245        label_key="data",
246        is_seg_dataset=True,
247        patch_shape=patch_shape,
248        ndim=3,
249        **kwargs
250    )

Get the ENHANCE.PET 1.6k dataset for segmentation of anatomical structures in whole-body CT.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • patch_shape: The patch shape to use for training.
  • n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
  • 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_enhance_pet_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int, int], n_cases: Optional[int] = None, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
253def get_enhance_pet_loader(
254    path: Union[os.PathLike, str],
255    batch_size: int,
256    patch_shape: Tuple[int, int, int],
257    n_cases: Optional[int] = None,
258    resize_inputs: bool = False,
259    download: bool = False,
260    **kwargs
261) -> DataLoader:
262    """Get the ENHANCE.PET 1.6k dataloader for segmentation of anatomical structures in whole-body CT.
263
264    Args:
265        path: Filepath to a folder where the data is downloaded for further processing.
266        batch_size: The batch size for training.
267        patch_shape: The patch shape to use for training.
268        n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
269        resize_inputs: Whether to resize the inputs to the patch shape.
270        download: Whether to download the data if it is not present.
271        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
272
273    Returns:
274        The DataLoader.
275    """
276    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
277    dataset = get_enhance_pet_dataset(path, patch_shape, n_cases, resize_inputs, download, **ds_kwargs)
278    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the ENHANCE.PET 1.6k dataloader for segmentation of anatomical structures in whole-body CT.

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.
  • n_cases: The number of cases to use, sorted by case id. By default all downloaded cases are used.
  • 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.