torch_em.data.datasets.medical.four_d_lung

The 4D-Lung dataset contains longitudinal respiratory-correlated 4D fan-beam CT (4D-FBCT) and 4D cone-beam CT (4D-CBCT) acquisitions of 20 locally-advanced non-small cell lung cancer (NSCLC) patients, collected over the course of chemoradiotherapy at the University of Texas MD Anderson Cancer Center. Each patient contributes multiple 4D acquisitions (up to several hundred CT series in total, ~589 studies for the full collection), of which a subset are paired with DICOM RTSTRUCT gross tumor volume (GTV) contours drawn by radiation oncologists.

This loader only downloads the CT series that are referenced by an RTSTRUCT (rather than the full, ~183 GB collection), and rasterizes the GTV contours onto the CT grid with torch_em.data.datasets.util.rasterize_rtstruct, storing image and label pairs as hdf5 files. The labels are binary (foreground: GTV).

NOTE: This requires the pydicom python package.

The dataset is located at https://doi.org/10.7937/K9/TCIA.2016.ELN8YGLE and is fully public (no data use agreement required), released under a CC-BY-3.0 license.

This dataset is from the publication https://doi.org/10.1002/mp.12059. Please cite it if you use this dataset for your research.

  1"""The 4D-Lung dataset contains longitudinal respiratory-correlated 4D fan-beam CT (4D-FBCT) and
  24D cone-beam CT (4D-CBCT) acquisitions of 20 locally-advanced non-small cell lung cancer (NSCLC)
  3patients, collected over the course of chemoradiotherapy at the University of Texas MD Anderson
  4Cancer Center. Each patient contributes multiple 4D acquisitions (up to several hundred CT series
  5in total, ~589 studies for the full collection), of which a subset are paired with DICOM RTSTRUCT
  6gross tumor volume (GTV) contours drawn by radiation oncologists.
  7
  8This loader only downloads the CT series that are referenced by an RTSTRUCT (rather than the full,
  9~183 GB collection), and rasterizes the GTV contours onto the CT grid with
 10`torch_em.data.datasets.util.rasterize_rtstruct`, storing image and label pairs as hdf5 files.
 11The labels are binary (foreground: GTV).
 12
 13NOTE: This requires the pydicom python package.
 14
 15The dataset is located at https://doi.org/10.7937/K9/TCIA.2016.ELN8YGLE and is fully public
 16(no data use agreement required), released under a CC-BY-3.0 license.
 17
 18This dataset is from the publication https://doi.org/10.1002/mp.12059.
 19Please cite it if you use this dataset for your research.
 20"""
 21
 22import os
 23import json
 24from glob import glob
 25from natsort import natsorted
 26from typing import Union, Tuple, List
 27
 28import requests
 29from tqdm import tqdm
 30
 31from torch.utils.data import Dataset, DataLoader
 32
 33import torch_em
 34
 35from .. import util
 36
 37
 38NBIA_API_URL = "https://services.cancerimagingarchive.net/nbia-api/services/v1/"
 39COLLECTION = "4D-Lung"
 40
 41
 42def _get_series(path, modality):
 43    cache_path = os.path.join(path, f"series_{modality.lower()}.json")
 44    if os.path.exists(cache_path):
 45        with open(cache_path, "r") as f:
 46            return json.load(f)
 47
 48    response = requests.get(NBIA_API_URL + "getSeries", params={"Collection": COLLECTION, "Modality": modality})
 49    response.raise_for_status()
 50    series = response.json()
 51
 52    os.makedirs(path, exist_ok=True)
 53    with open(cache_path, "w") as f:
 54        json.dump(series, f)
 55
 56    return series
 57
 58
 59def _referenced_series_uid(rtstruct_path):
 60    import pydicom
 61
 62    rtstruct = pydicom.dcmread(rtstruct_path, stop_before_pixels=True)
 63    referenced_study = rtstruct.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0]
 64    return str(referenced_study.RTReferencedSeriesSequence[0].SeriesInstanceUID)
 65
 66
 67def _preprocess_four_d_lung(rtstruct_series, rtstruct_dir, image_dir, preprocessed_dir):
 68    import h5py
 69    import numpy as np
 70
 71    os.makedirs(preprocessed_dir, exist_ok=True)
 72
 73    for series in tqdm(rtstruct_series, desc="Preprocess 4D-Lung"):
 74        uid = series["SeriesInstanceUID"]
 75        patient_id = series["PatientID"]
 76        out_path = os.path.join(preprocessed_dir, f"{patient_id}_{uid[-8:]}.h5")
 77        if os.path.exists(out_path):
 78            continue
 79
 80        rtstruct_path = glob(os.path.join(rtstruct_dir, uid, "*.dcm"))[0]
 81        referenced_uid = _referenced_series_uid(rtstruct_path)
 82        image_series_dir = os.path.join(image_dir, referenced_uid)
 83        if not os.path.exists(image_series_dir):
 84            continue
 85
 86        volume, geometry = util.load_dicom_series(image_series_dir)
 87        volume = np.round(volume).astype("int16")
 88        labels = util.rasterize_rtstruct(rtstruct_path, geometry, volume.shape, lambda roi_number, roi_name: 1)
 89
 90        with h5py.File(out_path, "w") as f:
 91            f.create_dataset("raw", data=volume, compression="gzip")
 92            f.create_dataset("labels", data=labels, compression="gzip")
 93
 94
 95def get_four_d_lung_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 96    """Download the 4D-Lung dataset.
 97
 98    NOTE: This only downloads the CT series that have a matching RTSTRUCT (a small subset of the
 99    full, ~183 GB collection). Ensure enough disk space is available before running this with
100    `download=True`, and expect the initial call to take a while, since every RTSTRUCT of the
101    collection is inspected to find its referenced CT series.
102
103    Args:
104        path: Filepath to a folder where the data is downloaded for further processing.
105        download: Whether to download the data if it is not present.
106
107    Returns:
108        Filepath where the preprocessed data is stored.
109    """
110    preprocessed_dir = os.path.join(path, "preprocessed")
111    if glob(os.path.join(preprocessed_dir, "*.h5")):
112        return preprocessed_dir
113
114    os.makedirs(path, exist_ok=True)
115    if not download:
116        raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.")
117
118    rtstruct_series = _get_series(path, "RTSTRUCT")
119    rtstruct_dir = os.path.join(path, "rtstructs")
120    util.download_tcia_series(
121        [series["SeriesInstanceUID"] for series in rtstruct_series],
122        dst=rtstruct_dir, csv_filename=os.path.join(path, "rtstructs"),
123    )
124
125    referenced_uids = sorted({
126        _referenced_series_uid(glob(os.path.join(rtstruct_dir, series["SeriesInstanceUID"], "*.dcm"))[0])
127        for series in rtstruct_series
128    })
129    image_dir = os.path.join(path, "images")
130    util.download_tcia_series(referenced_uids, dst=image_dir, csv_filename=os.path.join(path, "images"))
131
132    _preprocess_four_d_lung(rtstruct_series, rtstruct_dir, image_dir, preprocessed_dir)
133    return preprocessed_dir
134
135
136def get_four_d_lung_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
137    """Get paths to the 4D-Lung data.
138
139    Args:
140        path: Filepath to a folder where the data is downloaded for further processing.
141        download: Whether to download the data if it is not present.
142
143    Returns:
144        List of filepaths for the hdf5 files, which contain the image data ('raw') and the label data ('labels').
145    """
146    data_dir = get_four_d_lung_data(path, download)
147    return natsorted(glob(os.path.join(data_dir, "*.h5")))
148
149
150def get_four_d_lung_dataset(
151    path: Union[os.PathLike, str],
152    patch_shape: Tuple[int, int, int],
153    resize_inputs: bool = False,
154    download: bool = False,
155    **kwargs
156) -> Dataset:
157    """Get the 4D-Lung dataset for GTV segmentation in 4D lung CT.
158
159    Args:
160        path: Filepath to a folder where the data is downloaded for further processing.
161        patch_shape: The patch shape to use for training.
162        resize_inputs: Whether to resize the inputs to the patch shape.
163        download: Whether to download the data if it is not present.
164        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
165
166    Returns:
167        The segmentation dataset.
168    """
169    volume_paths = get_four_d_lung_paths(path, download)
170
171    if resize_inputs:
172        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
173        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
174            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
175        )
176
177    return torch_em.default_segmentation_dataset(
178        raw_paths=volume_paths,
179        raw_key="raw",
180        label_paths=volume_paths,
181        label_key="labels",
182        patch_shape=patch_shape,
183        is_seg_dataset=True,
184        **kwargs
185    )
186
187
188def get_four_d_lung_loader(
189    path: Union[os.PathLike, str],
190    batch_size: int,
191    patch_shape: Tuple[int, int, int],
192    resize_inputs: bool = False,
193    download: bool = False,
194    **kwargs
195) -> DataLoader:
196    """Get the 4D-Lung dataloader for GTV segmentation in 4D lung CT.
197
198    Args:
199        path: Filepath to a folder where the data is downloaded for further processing.
200        batch_size: The batch size for training.
201        patch_shape: The patch shape to use for training.
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_four_d_lung_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
211    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
NBIA_API_URL = 'https://services.cancerimagingarchive.net/nbia-api/services/v1/'
COLLECTION = '4D-Lung'
def get_four_d_lung_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 96def get_four_d_lung_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 97    """Download the 4D-Lung dataset.
 98
 99    NOTE: This only downloads the CT series that have a matching RTSTRUCT (a small subset of the
100    full, ~183 GB collection). Ensure enough disk space is available before running this with
101    `download=True`, and expect the initial call to take a while, since every RTSTRUCT of the
102    collection is inspected to find its referenced CT series.
103
104    Args:
105        path: Filepath to a folder where the data is downloaded for further processing.
106        download: Whether to download the data if it is not present.
107
108    Returns:
109        Filepath where the preprocessed data is stored.
110    """
111    preprocessed_dir = os.path.join(path, "preprocessed")
112    if glob(os.path.join(preprocessed_dir, "*.h5")):
113        return preprocessed_dir
114
115    os.makedirs(path, exist_ok=True)
116    if not download:
117        raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.")
118
119    rtstruct_series = _get_series(path, "RTSTRUCT")
120    rtstruct_dir = os.path.join(path, "rtstructs")
121    util.download_tcia_series(
122        [series["SeriesInstanceUID"] for series in rtstruct_series],
123        dst=rtstruct_dir, csv_filename=os.path.join(path, "rtstructs"),
124    )
125
126    referenced_uids = sorted({
127        _referenced_series_uid(glob(os.path.join(rtstruct_dir, series["SeriesInstanceUID"], "*.dcm"))[0])
128        for series in rtstruct_series
129    })
130    image_dir = os.path.join(path, "images")
131    util.download_tcia_series(referenced_uids, dst=image_dir, csv_filename=os.path.join(path, "images"))
132
133    _preprocess_four_d_lung(rtstruct_series, rtstruct_dir, image_dir, preprocessed_dir)
134    return preprocessed_dir

Download the 4D-Lung dataset.

NOTE: This only downloads the CT series that have a matching RTSTRUCT (a small subset of the full, ~183 GB collection). Ensure enough disk space is available before running this with download=True, and expect the initial call to take a while, since every RTSTRUCT of the collection is inspected to find its referenced CT series.

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 preprocessed data is stored.

def get_four_d_lung_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
137def get_four_d_lung_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
138    """Get paths to the 4D-Lung data.
139
140    Args:
141        path: Filepath to a folder where the data is downloaded for further processing.
142        download: Whether to download the data if it is not present.
143
144    Returns:
145        List of filepaths for the hdf5 files, which contain the image data ('raw') and the label data ('labels').
146    """
147    data_dir = get_four_d_lung_data(path, download)
148    return natsorted(glob(os.path.join(data_dir, "*.h5")))

Get paths to the 4D-Lung 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 hdf5 files, which contain the image data ('raw') and the label data ('labels').

def get_four_d_lung_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int, int], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
151def get_four_d_lung_dataset(
152    path: Union[os.PathLike, str],
153    patch_shape: Tuple[int, int, int],
154    resize_inputs: bool = False,
155    download: bool = False,
156    **kwargs
157) -> Dataset:
158    """Get the 4D-Lung dataset for GTV segmentation in 4D lung CT.
159
160    Args:
161        path: Filepath to a folder where the data is downloaded for further processing.
162        patch_shape: The patch shape to use for training.
163        resize_inputs: Whether to resize the inputs to the patch shape.
164        download: Whether to download the data if it is not present.
165        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
166
167    Returns:
168        The segmentation dataset.
169    """
170    volume_paths = get_four_d_lung_paths(path, download)
171
172    if resize_inputs:
173        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
174        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
175            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
176        )
177
178    return torch_em.default_segmentation_dataset(
179        raw_paths=volume_paths,
180        raw_key="raw",
181        label_paths=volume_paths,
182        label_key="labels",
183        patch_shape=patch_shape,
184        is_seg_dataset=True,
185        **kwargs
186    )

Get the 4D-Lung dataset for GTV segmentation in 4D lung CT.

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 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_four_d_lung_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int, int], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
189def get_four_d_lung_loader(
190    path: Union[os.PathLike, str],
191    batch_size: int,
192    patch_shape: Tuple[int, int, int],
193    resize_inputs: bool = False,
194    download: bool = False,
195    **kwargs
196) -> DataLoader:
197    """Get the 4D-Lung dataloader for GTV segmentation in 4D lung CT.
198
199    Args:
200        path: Filepath to a folder where the data is downloaded for further processing.
201        batch_size: The batch size for training.
202        patch_shape: The patch shape to use for training.
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_four_d_lung_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
212    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the 4D-Lung dataloader for GTV segmentation in 4D lung 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.
  • 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.