torch_em.data.datasets.medical.fumpe

The FUMPE dataset contains annotations for pulmonary embolism in computed tomography angiography.

The dataset consists of 35 CT angiography scans of different patients, with a binary mask marking the pulmonary emboli. The scans are distributed as DICOM series and the masks as MATLAB files, which this module converts into hdf5 files, one per patient.

NOTE: This requires the pydicom and scipy python packages.

NOTE: The slices of a scan are ordered by their instance number, which is the order the masks were created in. Two of the scans are acquired from head to feet, so sorting the slices along the slice normal instead would flip their masks.

The dataset is located at https://www.kaggle.com/datasets/andrewmvd/pulmonary-embolism-in-ct-images. This dataset is from the publication https://doi.org/10.1038/sdata.2018.180. Please cite it if you use this dataset in your research.

  1"""The FUMPE dataset contains annotations for pulmonary embolism in computed tomography angiography.
  2
  3The dataset consists of 35 CT angiography scans of different patients, with a binary mask marking the
  4pulmonary emboli. The scans are distributed as DICOM series and the masks as MATLAB files, which this
  5module converts into hdf5 files, one per patient.
  6
  7NOTE: This requires the pydicom and scipy python packages.
  8
  9NOTE: The slices of a scan are ordered by their instance number, which is the order the masks were
 10created in. Two of the scans are acquired from head to feet, so sorting the slices along the slice
 11normal instead would flip their masks.
 12
 13The dataset is located at https://www.kaggle.com/datasets/andrewmvd/pulmonary-embolism-in-ct-images.
 14This dataset is from the publication https://doi.org/10.1038/sdata.2018.180.
 15Please cite it if you use this dataset in your research.
 16"""
 17
 18import os
 19from glob import glob
 20from tqdm import tqdm
 21from natsort import natsorted
 22from typing import Union, Tuple, List
 23
 24import numpy as np
 25
 26from torch.utils.data import Dataset, DataLoader
 27
 28import torch_em
 29
 30from .. import util
 31
 32
 33KAGGLE_DATASET_NAME = "andrewmvd/pulmonary-embolism-in-ct-images"
 34
 35
 36def _load_dicom_volume(series_dir):
 37    """Stack a DICOM series into a volume with axes (z, y, x), ordered by the instance number.
 38
 39    The masks of this dataset were created in this order, so the slices must not be sorted along the
 40    slice normal, which would flip the scans that are acquired from head to feet.
 41    """
 42    import pydicom
 43
 44    slices = [pydicom.dcmread(p) for p in natsorted(glob(os.path.join(series_dir, "*.dcm")))]
 45    slices.sort(key=lambda dcm: int(dcm.InstanceNumber))
 46
 47    volume = np.stack([dcm.pixel_array for dcm in slices]).astype("float32")
 48    volume = volume * float(slices[0].RescaleSlope) + float(slices[0].RescaleIntercept)
 49    return np.round(volume).astype("int16")
 50
 51
 52def _preprocess_fumpe(data_dir, preprocessed_dir):
 53    import h5py
 54    import scipy.io as sio
 55
 56    os.makedirs(preprocessed_dir, exist_ok=True)
 57    patient_dirs = natsorted(glob(os.path.join(data_dir, "CT_scans", "*")))
 58    for patient_dir in tqdm(patient_dirs, desc="Preprocess FUMPE"):
 59        patient_id = os.path.basename(patient_dir)
 60        out_path = os.path.join(preprocessed_dir, f"{patient_id}.h5")
 61        if os.path.exists(out_path):
 62            continue
 63
 64        volume = _load_dicom_volume(patient_dir)
 65        # The masks are stored with axes (y, x, z) and have to be transposed to match the volume.
 66        labels = sio.loadmat(os.path.join(data_dir, "GroundTruth", f"{patient_id}.mat"))["Mask"]
 67        labels = np.transpose(labels, (2, 0, 1)).astype("uint8")
 68        assert labels.shape == volume.shape, f"The mask of '{patient_id}' does not match its scan."
 69
 70        with h5py.File(out_path, "w") as f:
 71            f.create_dataset("raw", data=volume, compression="gzip")
 72            f.create_dataset("labels", data=labels, compression="gzip")
 73
 74
 75def get_fumpe_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 76    """Download the FUMPE 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 preprocessed data is stored.
 84    """
 85    preprocessed_dir = os.path.join(path, "preprocessed")
 86    if os.path.exists(preprocessed_dir):
 87        return preprocessed_dir
 88
 89    os.makedirs(path, exist_ok=True)
 90    data_dir = os.path.join(path, "FUMPE")
 91    if not os.path.exists(data_dir):
 92        util.download_source_kaggle(path=path, dataset_name=KAGGLE_DATASET_NAME, download=download)
 93        util.unzip(zip_path=os.path.join(path, "pulmonary-embolism-in-ct-images.zip"), dst=path, remove=False)
 94
 95    _preprocess_fumpe(data_dir, preprocessed_dir)
 96    return preprocessed_dir
 97
 98
 99def get_fumpe_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
100    """Get paths to the FUMPE data.
101
102    Args:
103        path: Filepath to a folder where the data is downloaded for further processing.
104        download: Whether to download the data if it is not present.
105
106    Returns:
107        List of filepaths for the stored data.
108    """
109    preprocessed_dir = get_fumpe_data(path, download)
110    volume_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.h5")))
111    assert len(volume_paths) > 0, f"Could not find any preprocessed volumes in '{preprocessed_dir}'."
112    return volume_paths
113
114
115def get_fumpe_dataset(
116    path: Union[os.PathLike, str],
117    patch_shape: Tuple[int, ...],
118    resize_inputs: bool = False,
119    download: bool = False,
120    **kwargs
121) -> Dataset:
122    """Get the FUMPE dataset for pulmonary embolism segmentation.
123
124    Args:
125        path: Filepath to a folder where the data is downloaded for further processing.
126        patch_shape: The patch shape to use for training.
127        resize_inputs: Whether to resize inputs to the desired patch shape.
128        download: Whether to download the data if it is not present.
129        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
130
131    Returns:
132        The segmentation dataset.
133    """
134    volume_paths = get_fumpe_paths(path, download)
135
136    if resize_inputs:
137        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
138        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
139            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
140        )
141
142    return torch_em.default_segmentation_dataset(
143        raw_paths=volume_paths,
144        raw_key="raw",
145        label_paths=volume_paths,
146        label_key="labels",
147        patch_shape=patch_shape,
148        is_seg_dataset=True,
149        **kwargs
150    )
151
152
153def get_fumpe_loader(
154    path: Union[os.PathLike, str],
155    batch_size: int,
156    patch_shape: Tuple[int, ...],
157    resize_inputs: bool = False,
158    download: bool = False,
159    **kwargs
160) -> DataLoader:
161    """Get the FUMPE dataloader for pulmonary embolism segmentation.
162
163    Args:
164        path: Filepath to a folder where the data is downloaded for further processing.
165        batch_size: The batch size for training.
166        patch_shape: The patch shape to use for training.
167        resize_inputs: Whether to resize inputs to the desired patch shape.
168        download: Whether to download the data if it is not present.
169        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
170
171    Returns:
172        The DataLoader.
173    """
174    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
175    dataset = get_fumpe_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
176    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
KAGGLE_DATASET_NAME = 'andrewmvd/pulmonary-embolism-in-ct-images'
def get_fumpe_data(path: Union[os.PathLike, str], download: bool = False) -> str:
76def get_fumpe_data(path: Union[os.PathLike, str], download: bool = False) -> str:
77    """Download the FUMPE 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 preprocessed data is stored.
85    """
86    preprocessed_dir = os.path.join(path, "preprocessed")
87    if os.path.exists(preprocessed_dir):
88        return preprocessed_dir
89
90    os.makedirs(path, exist_ok=True)
91    data_dir = os.path.join(path, "FUMPE")
92    if not os.path.exists(data_dir):
93        util.download_source_kaggle(path=path, dataset_name=KAGGLE_DATASET_NAME, download=download)
94        util.unzip(zip_path=os.path.join(path, "pulmonary-embolism-in-ct-images.zip"), dst=path, remove=False)
95
96    _preprocess_fumpe(data_dir, preprocessed_dir)
97    return preprocessed_dir

Download the FUMPE 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_fumpe_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
100def get_fumpe_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
101    """Get paths to the FUMPE data.
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        List of filepaths for the stored data.
109    """
110    preprocessed_dir = get_fumpe_data(path, download)
111    volume_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.h5")))
112    assert len(volume_paths) > 0, f"Could not find any preprocessed volumes in '{preprocessed_dir}'."
113    return volume_paths

Get paths to the FUMPE 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 stored data.

def get_fumpe_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
116def get_fumpe_dataset(
117    path: Union[os.PathLike, str],
118    patch_shape: Tuple[int, ...],
119    resize_inputs: bool = False,
120    download: bool = False,
121    **kwargs
122) -> Dataset:
123    """Get the FUMPE dataset for pulmonary embolism segmentation.
124
125    Args:
126        path: Filepath to a folder where the data is downloaded for further processing.
127        patch_shape: The patch shape to use for training.
128        resize_inputs: Whether to resize inputs to the desired patch shape.
129        download: Whether to download the data if it is not present.
130        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
131
132    Returns:
133        The segmentation dataset.
134    """
135    volume_paths = get_fumpe_paths(path, download)
136
137    if resize_inputs:
138        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
139        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
140            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
141        )
142
143    return torch_em.default_segmentation_dataset(
144        raw_paths=volume_paths,
145        raw_key="raw",
146        label_paths=volume_paths,
147        label_key="labels",
148        patch_shape=patch_shape,
149        is_seg_dataset=True,
150        **kwargs
151    )

Get the FUMPE dataset for pulmonary embolism 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_fumpe_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:
154def get_fumpe_loader(
155    path: Union[os.PathLike, str],
156    batch_size: int,
157    patch_shape: Tuple[int, ...],
158    resize_inputs: bool = False,
159    download: bool = False,
160    **kwargs
161) -> DataLoader:
162    """Get the FUMPE dataloader for pulmonary embolism segmentation.
163
164    Args:
165        path: Filepath to a folder where the data is downloaded for further processing.
166        batch_size: The batch size for training.
167        patch_shape: The patch shape to use for training.
168        resize_inputs: Whether to resize inputs to the desired patch shape.
169        download: Whether to download the data if it is not present.
170        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
171
172    Returns:
173        The DataLoader.
174    """
175    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
176    dataset = get_fumpe_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
177    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the FUMPE dataloader for pulmonary embolism 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.