torch_em.data.datasets.medical.nsclc_radiomics

The NSCLC-Radiomics dataset (Lung1) contains annotations for the gross tumor volume and thoracic organs in pretreatment CT of non-small cell lung cancer patients.

It consists of 422 CT volumes with manual delineations by a radiation oncologist, which are distributed as DICOM RTSTRUCT contours. This module rasterizes the contours onto the CT grid (see torch_em.data.datasets.util.rasterize_rtstruct) and stores CT and labels in hdf5 files. The semantic label ids are: 1: tumor, 2: lung ('Lung-Left', 'Lung-Right' or 'Lungs-Total'), 3: heart, 4: esophagus, 5: spinal cord. The tumor id covers the primary gross tumor volume ('GTV-1', present for all but one patient) and any further 'gtv-*' contours, which are mostly involved lymph nodes and are named inconsistently ('gtv-2', 'gtv_10_r', 'gtv_supraclav', ...). Not all structures are annotated for every patient: of the 422 patients 422 have a tumor, 411 a lung, 355 an esophagus, 411 a spinal cord and 127 a heart contour. The structures to use can be selected via structures.

NOTE: This requires the pydicom python package.

The dataset is located at https://www.cancerimagingarchive.net/collection/nsclc-radiomics/.

This dataset is from the publication https://doi.org/10.1038/ncomms5006. The data was released at https://doi.org/10.7937/K9/TCIA.2015.PF0M9REI. Please cite it if you use this dataset in your research.

  1"""The NSCLC-Radiomics dataset (Lung1) contains annotations for the gross tumor volume and thoracic organs
  2in pretreatment CT of non-small cell lung cancer patients.
  3
  4It consists of 422 CT volumes with manual delineations by a radiation oncologist, which are distributed as
  5DICOM RTSTRUCT contours. This module rasterizes the contours onto the CT grid (see
  6`torch_em.data.datasets.util.rasterize_rtstruct`) and stores CT and labels in hdf5 files.
  7The semantic label ids are: 1: tumor, 2: lung ('Lung-Left', 'Lung-Right' or 'Lungs-Total'), 3: heart,
  84: esophagus, 5: spinal cord. The tumor id covers the primary gross tumor volume ('GTV-1', present for all but
  9one patient) and any further 'gtv-*' contours, which are mostly involved lymph nodes and are named
 10inconsistently ('gtv-2', 'gtv_10_r', 'gtv_supraclav', ...). Not all structures are annotated for every patient:
 11of the 422 patients 422 have a tumor, 411 a lung, 355 an esophagus, 411 a spinal cord and 127 a heart contour.
 12The structures to use can be selected via `structures`.
 13
 14NOTE: This requires the pydicom python package.
 15
 16The dataset is located at https://www.cancerimagingarchive.net/collection/nsclc-radiomics/.
 17
 18This dataset is from the publication https://doi.org/10.1038/ncomms5006.
 19The data was released at https://doi.org/10.7937/K9/TCIA.2015.PF0M9REI.
 20Please cite it if you use this dataset in your research.
 21"""
 22
 23import os
 24import csv
 25from glob import glob
 26from tqdm import tqdm
 27from natsort import natsorted
 28from typing import Union, Tuple, List, Optional, Sequence
 29
 30import numpy as np
 31
 32from torch.utils.data import Dataset, DataLoader
 33
 34import torch_em
 35from torch_em.transform.generic import Compose
 36
 37from .. import util
 38
 39
 40URL = "https://www.cancerimagingarchive.net/wp-content/uploads/NSCLC-Radiomics-Version-4-Oct-2020-NBIA-manifest.tcia"
 41
 42# The DICOM series are downloaded individually from TCIA.
 43CHECKSUM = None
 44
 45STRUCTURE_IDS = {"tumor": 1, "lung": 2, "heart": 3, "esophagus": 4, "spinal_cord": 5}
 46
 47
 48class SelectStructures:
 49    """Label transform that keeps only the given label ids and sets all other labels to background.
 50
 51    Args:
 52        label_ids: The label ids to keep.
 53    """
 54    def __init__(self, label_ids: Sequence[int]):
 55        self.label_ids = list(label_ids)
 56
 57    def __call__(self, labels: np.ndarray) -> np.ndarray:
 58        return np.where(np.isin(labels, self.label_ids), labels, 0)
 59
 60
 61def _get_structure_label(roi_number, roi_name):
 62    """Map the (inconsistently named) ROIs of the RTSTRUCT files to the semantic label ids."""
 63    name = roi_name.lower().replace("_", "-").strip()
 64    if name.startswith("gtv"):
 65        return STRUCTURE_IDS["tumor"]
 66    if name.startswith("lung"):
 67        return STRUCTURE_IDS["lung"]
 68    if name.startswith("heart"):
 69        return STRUCTURE_IDS["heart"]
 70    if name.startswith("esophagus"):
 71        return STRUCTURE_IDS["esophagus"]
 72    if name.startswith("spinal-cord") or name.startswith("spinalcord"):
 73        return STRUCTURE_IDS["spinal_cord"]
 74    return None
 75
 76
 77def _get_referenced_series(rtstruct_path):
 78    import pydicom
 79
 80    rtstruct = pydicom.dcmread(rtstruct_path, stop_before_pixels=True)
 81    return str(
 82        rtstruct.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0]
 83        .RTReferencedSeriesSequence[0].SeriesInstanceUID
 84    )
 85
 86
 87def _preprocess_nsclc_radiomics(dicom_dir, csv_path, preprocessed_dir):
 88    import h5py
 89
 90    with open(csv_path, "r") as f:
 91        rows = list(csv.DictReader(f))
 92    rtstruct_series = {row["Series UID"]: row["Subject ID"] for row in rows if row["Modality"] == "RTSTRUCT"}
 93
 94    os.makedirs(preprocessed_dir, exist_ok=True)
 95    for series_uid, subject_id in tqdm(sorted(rtstruct_series.items()), desc="Preprocess NSCLC-Radiomics"):
 96        out_path = os.path.join(preprocessed_dir, f"{subject_id}.h5")
 97        if os.path.exists(out_path):
 98            continue
 99
100        rtstruct_path = glob(os.path.join(dicom_dir, series_uid, "*.dcm"))[0]
101        ct_dir = os.path.join(dicom_dir, _get_referenced_series(rtstruct_path))
102        volume, geometry = util.load_dicom_series(ct_dir)
103        volume = np.round(volume).astype("int16")
104        labels = util.rasterize_rtstruct(rtstruct_path, geometry, volume.shape, _get_structure_label)
105
106        with h5py.File(out_path, "w") as f:
107            f.create_dataset("raw", data=volume, compression="gzip")
108            f.create_dataset("labels", data=labels, compression="gzip")
109
110
111def get_nsclc_radiomics_data(path: Union[os.PathLike, str], download: bool = False) -> str:
112    """Download the NSCLC-Radiomics dataset.
113
114    Args:
115        path: Filepath to a folder where the data is downloaded for further processing.
116        download: Whether to download the data if it is not present.
117
118    Returns:
119        Filepath where the preprocessed data is stored.
120    """
121    # NOTE: The preprocessing below skips volumes that were converted already, so an interrupted run resumes.
122    preprocessed_dir = os.path.join(path, "preprocessed")
123    os.makedirs(path, exist_ok=True)
124
125    # Download the DICOM series (CT, RTSTRUCT and SEG) from the TCIA manifest. The series metadata are written
126    # after all series are downloaded, so their presence means the download is complete.
127    dicom_dir = os.path.join(path, "dicom")
128    csv_path = os.path.join(path, "nsclc_radiomics_series")
129    if not os.path.exists(f"{csv_path}.csv"):
130        util.download_source_tcia(
131            path=os.path.join(path, os.path.basename(URL)), url=URL, dst=dicom_dir, csv_filename=csv_path,
132            download=download,
133        )
134
135    _preprocess_nsclc_radiomics(dicom_dir, f"{csv_path}.csv", preprocessed_dir)
136    return preprocessed_dir
137
138
139def get_nsclc_radiomics_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
140    """Get paths to the NSCLC-Radiomics data.
141
142    Args:
143        path: Filepath to a folder where the data is downloaded for further processing.
144        download: Whether to download the data if it is not present.
145
146    Returns:
147        List of filepaths for the hdf5 files, which contain the image data ('raw') and the label data ('labels').
148    """
149    data_dir = get_nsclc_radiomics_data(path, download)
150    volume_paths = natsorted(glob(os.path.join(data_dir, "*.h5")))
151    return volume_paths
152
153
154def get_nsclc_radiomics_dataset(
155    path: Union[os.PathLike, str],
156    patch_shape: Tuple[int, ...],
157    structures: Optional[Sequence[str]] = None,
158    resize_inputs: bool = False,
159    download: bool = False,
160    **kwargs
161) -> Dataset:
162    """Get the NSCLC-Radiomics dataset for tumor and thoracic organ segmentation in CT.
163
164    Args:
165        path: Filepath to a folder where the data is downloaded for further processing.
166        patch_shape: The patch shape to use for training.
167        structures: The structures to use as labels, a subset of 'tumor', 'lung', 'heart', 'esophagus'
168            and 'spinal_cord'. All other structures are set to background. By default all structures are used.
169        resize_inputs: Whether to resize inputs to the desired patch shape.
170        download: Whether to download the data if it is not present.
171        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
172
173    Returns:
174        The segmentation dataset.
175    """
176    volume_paths = get_nsclc_radiomics_paths(path, download)
177
178    if structures is not None:
179        assert all(structure in STRUCTURE_IDS for structure in structures), f"Invalid structures: {structures}"
180        select_trafo = SelectStructures([STRUCTURE_IDS[structure] for structure in structures])
181        if "label_transform" in kwargs:
182            kwargs["label_transform"] = Compose(select_trafo, kwargs["label_transform"], is_multi_tensor=False)
183        else:
184            kwargs["label_transform"] = select_trafo
185
186    if resize_inputs:
187        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
188        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
189            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
190        )
191
192    return torch_em.default_segmentation_dataset(
193        raw_paths=volume_paths,
194        raw_key="raw",
195        label_paths=volume_paths,
196        label_key="labels",
197        patch_shape=patch_shape,
198        is_seg_dataset=True,
199        **kwargs
200    )
201
202
203def get_nsclc_radiomics_loader(
204    path: Union[os.PathLike, str],
205    batch_size: int,
206    patch_shape: Tuple[int, ...],
207    structures: Optional[Sequence[str]] = None,
208    resize_inputs: bool = False,
209    download: bool = False,
210    **kwargs
211) -> DataLoader:
212    """Get the NSCLC-Radiomics dataloader for tumor and thoracic organ segmentation in CT.
213
214    Args:
215        path: Filepath to a folder where the data is downloaded for further processing.
216        batch_size: The batch size for training.
217        patch_shape: The patch shape to use for training.
218        structures: The structures to use as labels, a subset of 'tumor', 'lung', 'heart', 'esophagus'
219            and 'spinal_cord'. All other structures are set to background. By default all structures are used.
220        resize_inputs: Whether to resize inputs to the desired 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_nsclc_radiomics_dataset(path, patch_shape, structures, resize_inputs, download, **ds_kwargs)
229    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://www.cancerimagingarchive.net/wp-content/uploads/NSCLC-Radiomics-Version-4-Oct-2020-NBIA-manifest.tcia'
CHECKSUM = None
STRUCTURE_IDS = {'tumor': 1, 'lung': 2, 'heart': 3, 'esophagus': 4, 'spinal_cord': 5}
class SelectStructures:
49class SelectStructures:
50    """Label transform that keeps only the given label ids and sets all other labels to background.
51
52    Args:
53        label_ids: The label ids to keep.
54    """
55    def __init__(self, label_ids: Sequence[int]):
56        self.label_ids = list(label_ids)
57
58    def __call__(self, labels: np.ndarray) -> np.ndarray:
59        return np.where(np.isin(labels, self.label_ids), labels, 0)

Label transform that keeps only the given label ids and sets all other labels to background.

Arguments:
  • label_ids: The label ids to keep.
SelectStructures(label_ids: Sequence[int])
55    def __init__(self, label_ids: Sequence[int]):
56        self.label_ids = list(label_ids)
label_ids
def get_nsclc_radiomics_data(path: Union[os.PathLike, str], download: bool = False) -> str:
112def get_nsclc_radiomics_data(path: Union[os.PathLike, str], download: bool = False) -> str:
113    """Download the NSCLC-Radiomics dataset.
114
115    Args:
116        path: Filepath to a folder where the data is downloaded for further processing.
117        download: Whether to download the data if it is not present.
118
119    Returns:
120        Filepath where the preprocessed data is stored.
121    """
122    # NOTE: The preprocessing below skips volumes that were converted already, so an interrupted run resumes.
123    preprocessed_dir = os.path.join(path, "preprocessed")
124    os.makedirs(path, exist_ok=True)
125
126    # Download the DICOM series (CT, RTSTRUCT and SEG) from the TCIA manifest. The series metadata are written
127    # after all series are downloaded, so their presence means the download is complete.
128    dicom_dir = os.path.join(path, "dicom")
129    csv_path = os.path.join(path, "nsclc_radiomics_series")
130    if not os.path.exists(f"{csv_path}.csv"):
131        util.download_source_tcia(
132            path=os.path.join(path, os.path.basename(URL)), url=URL, dst=dicom_dir, csv_filename=csv_path,
133            download=download,
134        )
135
136    _preprocess_nsclc_radiomics(dicom_dir, f"{csv_path}.csv", preprocessed_dir)
137    return preprocessed_dir

Download the NSCLC-Radiomics 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 preprocessed data is stored.

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

Get paths to the NSCLC-Radiomics 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_nsclc_radiomics_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], structures: Optional[Sequence[str]] = None, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
155def get_nsclc_radiomics_dataset(
156    path: Union[os.PathLike, str],
157    patch_shape: Tuple[int, ...],
158    structures: Optional[Sequence[str]] = None,
159    resize_inputs: bool = False,
160    download: bool = False,
161    **kwargs
162) -> Dataset:
163    """Get the NSCLC-Radiomics dataset for tumor and thoracic organ segmentation in CT.
164
165    Args:
166        path: Filepath to a folder where the data is downloaded for further processing.
167        patch_shape: The patch shape to use for training.
168        structures: The structures to use as labels, a subset of 'tumor', 'lung', 'heart', 'esophagus'
169            and 'spinal_cord'. All other structures are set to background. By default all structures are used.
170        resize_inputs: Whether to resize inputs to the desired patch shape.
171        download: Whether to download the data if it is not present.
172        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
173
174    Returns:
175        The segmentation dataset.
176    """
177    volume_paths = get_nsclc_radiomics_paths(path, download)
178
179    if structures is not None:
180        assert all(structure in STRUCTURE_IDS for structure in structures), f"Invalid structures: {structures}"
181        select_trafo = SelectStructures([STRUCTURE_IDS[structure] for structure in structures])
182        if "label_transform" in kwargs:
183            kwargs["label_transform"] = Compose(select_trafo, kwargs["label_transform"], is_multi_tensor=False)
184        else:
185            kwargs["label_transform"] = select_trafo
186
187    if resize_inputs:
188        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
189        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
190            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
191        )
192
193    return torch_em.default_segmentation_dataset(
194        raw_paths=volume_paths,
195        raw_key="raw",
196        label_paths=volume_paths,
197        label_key="labels",
198        patch_shape=patch_shape,
199        is_seg_dataset=True,
200        **kwargs
201    )

Get the NSCLC-Radiomics dataset for tumor and thoracic organ segmentation in CT.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • patch_shape: The patch shape to use for training.
  • structures: The structures to use as labels, a subset of 'tumor', 'lung', 'heart', 'esophagus' and 'spinal_cord'. All other structures are set to background. By default all structures are used.
  • 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_nsclc_radiomics_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, ...], structures: Optional[Sequence[str]] = None, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
204def get_nsclc_radiomics_loader(
205    path: Union[os.PathLike, str],
206    batch_size: int,
207    patch_shape: Tuple[int, ...],
208    structures: Optional[Sequence[str]] = None,
209    resize_inputs: bool = False,
210    download: bool = False,
211    **kwargs
212) -> DataLoader:
213    """Get the NSCLC-Radiomics dataloader for tumor and thoracic organ segmentation in CT.
214
215    Args:
216        path: Filepath to a folder where the data is downloaded for further processing.
217        batch_size: The batch size for training.
218        patch_shape: The patch shape to use for training.
219        structures: The structures to use as labels, a subset of 'tumor', 'lung', 'heart', 'esophagus'
220            and 'spinal_cord'. All other structures are set to background. By default all structures are used.
221        resize_inputs: Whether to resize inputs to the desired patch shape.
222        download: Whether to download the data if it is not present.
223        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
224
225    Returns:
226        The DataLoader.
227    """
228    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
229    dataset = get_nsclc_radiomics_dataset(path, patch_shape, structures, resize_inputs, download, **ds_kwargs)
230    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the NSCLC-Radiomics dataloader for tumor and thoracic organ segmentation in 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.
  • structures: The structures to use as labels, a subset of 'tumor', 'lung', 'heart', 'esophagus' and 'spinal_cord'. All other structures are set to background. By default all structures are used.
  • 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.