torch_em.data.datasets.medical.psma_pet_ct
The PSMA-PET-CT-Lesions dataset contains annotations for tumor lesions in whole-body PSMA PET/CT.
The dataset consists of whole-body PSMA (prostate-specific membrane antigen) PET scans of prostate cancer patients, with an expert DICOM-SEG object marking the detected tumor lesions on each PET series. Lesions are frequently small metastatic foci, so most of a scan is background.
NOTE: This requires the pydicom python package.
The dataset is located at https://doi.org/10.7937/r7ep-3x37 and is distributed under the CC BY 4.0 license. Please cite it if you use this dataset in your research.
1"""The PSMA-PET-CT-Lesions dataset contains annotations for tumor lesions in whole-body PSMA PET/CT. 2 3The dataset consists of whole-body PSMA (prostate-specific membrane antigen) PET scans of prostate 4cancer patients, with an expert DICOM-SEG object marking the detected tumor lesions on each PET series. 5Lesions are frequently small metastatic foci, so most of a scan is background. 6 7NOTE: This requires the pydicom python package. 8 9The dataset is located at https://doi.org/10.7937/r7ep-3x37 and is distributed under the 10CC BY 4.0 license. 11Please cite it if you use this dataset in your research. 12""" 13 14import os 15import json 16from glob import glob 17from tqdm import tqdm 18from natsort import natsorted 19from typing import Union, Tuple, List 20 21from torch.utils.data import Dataset, DataLoader 22 23import torch_em 24 25from .adrenal_acc import _load_dicom_volume, _load_dicom_seg, _resample_labels 26from .. import util 27 28 29COLLECTION = "PSMA-PET-CT-Lesions" 30 31 32def _get_series_metadata(path, download): 33 """Get the metadata of all series in the collection from the NBIA REST API.""" 34 import requests 35 36 metadata_path = os.path.join(path, "psma_pet_ct_series.json") 37 if not os.path.exists(metadata_path): 38 if not download: 39 raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.") 40 response = requests.get(f"{util.NBIA_API_URL}getSeries", params={"Collection": COLLECTION}) 41 response.raise_for_status() 42 with open(metadata_path, "w") as f: 43 json.dump(response.json(), f, indent=2) 44 45 with open(metadata_path, "r") as f: 46 return json.load(f) 47 48 49def _referenced_series_uid(seg): 50 return str(seg.ReferencedSeriesSequence[0].SeriesInstanceUID) 51 52 53def _preprocess_psma_pet_ct(dicom_dir, series_metadata, preprocessed_dir): 54 import h5py 55 import pydicom 56 57 seg_series = [series for series in series_metadata if series.get("Modality") == "SEG"] 58 59 os.makedirs(preprocessed_dir, exist_ok=True) 60 for series in tqdm(seg_series, desc="Preprocess PSMA-PET-CT-Lesions"): 61 seg_paths = glob(os.path.join(dicom_dir, series["SeriesInstanceUID"], "*.dcm")) 62 if not seg_paths: 63 continue 64 65 out_path = os.path.join(preprocessed_dir, f"{series['SeriesInstanceUID']}.h5") 66 if os.path.exists(out_path): 67 continue 68 69 seg = pydicom.dcmread(seg_paths[0]) 70 pet_dir = os.path.join(dicom_dir, _referenced_series_uid(seg)) 71 if not glob(os.path.join(pet_dir, "*.dcm")): 72 continue 73 74 volume, pet_affine = _load_dicom_volume(pet_dir) 75 seg_labels, seg_affine = _load_dicom_seg(seg_paths[0]) 76 labels = _resample_labels(seg_labels, seg_affine, volume.shape, pet_affine) 77 if labels.max() == 0: 78 continue 79 80 with h5py.File(out_path, "w") as f: 81 f.create_dataset("raw", data=volume, compression="gzip") 82 f.create_dataset("labels", data=labels, compression="gzip") 83 84 85def get_psma_pet_ct_data(path: Union[os.PathLike, str], download: bool = False) -> str: 86 """Download the PSMA-PET-CT-Lesions dataset. 87 88 Args: 89 path: Filepath to a folder where the data is downloaded for further processing. 90 download: Whether to download the data if it is not present. 91 92 Returns: 93 Filepath where the preprocessed data is stored. 94 """ 95 # NOTE: The preprocessing below skips volumes that were converted already, so an interrupted run resumes. 96 preprocessed_dir = os.path.join(path, "preprocessed") 97 98 os.makedirs(path, exist_ok=True) 99 series_metadata = _get_series_metadata(path, download) 100 101 seg_uids = [series["SeriesInstanceUID"] for series in series_metadata if series.get("Modality") == "SEG"] 102 103 dicom_dir = os.path.join(path, "dicom") 104 if download: # The SEG series are downloaded first, so the PET series they reference can be found. 105 util.download_tcia_series(seg_uids, dst=dicom_dir, csv_filename=os.path.join(path, "psma_pet_ct_seg")) 106 107 import pydicom 108 pet_uids = set() 109 for uid in seg_uids: 110 seg_paths = glob(os.path.join(dicom_dir, uid, "*.dcm")) 111 if seg_paths: 112 seg = pydicom.dcmread(seg_paths[0]) 113 pet_uids.add(_referenced_series_uid(seg)) 114 util.download_tcia_series(sorted(pet_uids), dst=dicom_dir, csv_filename=os.path.join(path, "psma_pet_ct_pet")) 115 elif not glob(os.path.join(dicom_dir, "*", "*.dcm")): 116 raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.") 117 118 _preprocess_psma_pet_ct(dicom_dir, series_metadata, preprocessed_dir) 119 return preprocessed_dir 120 121 122def get_psma_pet_ct_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]: 123 """Get paths to the PSMA-PET-CT-Lesions data. 124 125 Args: 126 path: Filepath to a folder where the data is downloaded for further processing. 127 download: Whether to download the data if it is not present. 128 129 Returns: 130 List of filepaths for the stored data. 131 """ 132 preprocessed_dir = get_psma_pet_ct_data(path, download) 133 volume_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.h5"))) 134 assert len(volume_paths) > 0, f"Could not find any preprocessed volumes in '{preprocessed_dir}'." 135 return volume_paths 136 137 138def get_psma_pet_ct_dataset( 139 path: Union[os.PathLike, str], 140 patch_shape: Tuple[int, ...], 141 resize_inputs: bool = False, 142 download: bool = False, 143 **kwargs 144) -> Dataset: 145 """Get the PSMA-PET-CT-Lesions dataset for tumor lesion segmentation. 146 147 Args: 148 path: Filepath to a folder where the data is downloaded for further processing. 149 patch_shape: The patch shape to use for training. 150 resize_inputs: Whether to resize inputs to the desired patch shape. 151 download: Whether to download the data if it is not present. 152 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 153 154 Returns: 155 The segmentation dataset. 156 """ 157 volume_paths = get_psma_pet_ct_paths(path, download) 158 159 if resize_inputs: 160 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False} 161 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 162 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 163 ) 164 165 return torch_em.default_segmentation_dataset( 166 raw_paths=volume_paths, 167 raw_key="raw", 168 label_paths=volume_paths, 169 label_key="labels", 170 patch_shape=patch_shape, 171 is_seg_dataset=True, 172 **kwargs 173 ) 174 175 176def get_psma_pet_ct_loader( 177 path: Union[os.PathLike, str], 178 batch_size: int, 179 patch_shape: Tuple[int, ...], 180 resize_inputs: bool = False, 181 download: bool = False, 182 **kwargs 183) -> DataLoader: 184 """Get the PSMA-PET-CT-Lesions dataloader for tumor lesion segmentation. 185 186 Args: 187 path: Filepath to a folder where the data is downloaded for further processing. 188 batch_size: The batch size for training. 189 patch_shape: The patch shape to use for training. 190 resize_inputs: Whether to resize inputs to the desired patch shape. 191 download: Whether to download the data if it is not present. 192 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 193 194 Returns: 195 The DataLoader. 196 """ 197 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 198 dataset = get_psma_pet_ct_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs) 199 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
86def get_psma_pet_ct_data(path: Union[os.PathLike, str], download: bool = False) -> str: 87 """Download the PSMA-PET-CT-Lesions dataset. 88 89 Args: 90 path: Filepath to a folder where the data is downloaded for further processing. 91 download: Whether to download the data if it is not present. 92 93 Returns: 94 Filepath where the preprocessed data is stored. 95 """ 96 # NOTE: The preprocessing below skips volumes that were converted already, so an interrupted run resumes. 97 preprocessed_dir = os.path.join(path, "preprocessed") 98 99 os.makedirs(path, exist_ok=True) 100 series_metadata = _get_series_metadata(path, download) 101 102 seg_uids = [series["SeriesInstanceUID"] for series in series_metadata if series.get("Modality") == "SEG"] 103 104 dicom_dir = os.path.join(path, "dicom") 105 if download: # The SEG series are downloaded first, so the PET series they reference can be found. 106 util.download_tcia_series(seg_uids, dst=dicom_dir, csv_filename=os.path.join(path, "psma_pet_ct_seg")) 107 108 import pydicom 109 pet_uids = set() 110 for uid in seg_uids: 111 seg_paths = glob(os.path.join(dicom_dir, uid, "*.dcm")) 112 if seg_paths: 113 seg = pydicom.dcmread(seg_paths[0]) 114 pet_uids.add(_referenced_series_uid(seg)) 115 util.download_tcia_series(sorted(pet_uids), dst=dicom_dir, csv_filename=os.path.join(path, "psma_pet_ct_pet")) 116 elif not glob(os.path.join(dicom_dir, "*", "*.dcm")): 117 raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.") 118 119 _preprocess_psma_pet_ct(dicom_dir, series_metadata, preprocessed_dir) 120 return preprocessed_dir
Download the PSMA-PET-CT-Lesions 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.
123def get_psma_pet_ct_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]: 124 """Get paths to the PSMA-PET-CT-Lesions data. 125 126 Args: 127 path: Filepath to a folder where the data is downloaded for further processing. 128 download: Whether to download the data if it is not present. 129 130 Returns: 131 List of filepaths for the stored data. 132 """ 133 preprocessed_dir = get_psma_pet_ct_data(path, download) 134 volume_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.h5"))) 135 assert len(volume_paths) > 0, f"Could not find any preprocessed volumes in '{preprocessed_dir}'." 136 return volume_paths
Get paths to the PSMA-PET-CT-Lesions 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.
139def get_psma_pet_ct_dataset( 140 path: Union[os.PathLike, str], 141 patch_shape: Tuple[int, ...], 142 resize_inputs: bool = False, 143 download: bool = False, 144 **kwargs 145) -> Dataset: 146 """Get the PSMA-PET-CT-Lesions dataset for tumor lesion segmentation. 147 148 Args: 149 path: Filepath to a folder where the data is downloaded for further processing. 150 patch_shape: The patch shape to use for training. 151 resize_inputs: Whether to resize inputs to the desired patch shape. 152 download: Whether to download the data if it is not present. 153 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 154 155 Returns: 156 The segmentation dataset. 157 """ 158 volume_paths = get_psma_pet_ct_paths(path, download) 159 160 if resize_inputs: 161 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False} 162 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 163 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 164 ) 165 166 return torch_em.default_segmentation_dataset( 167 raw_paths=volume_paths, 168 raw_key="raw", 169 label_paths=volume_paths, 170 label_key="labels", 171 patch_shape=patch_shape, 172 is_seg_dataset=True, 173 **kwargs 174 )
Get the PSMA-PET-CT-Lesions dataset for tumor lesion 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.
177def get_psma_pet_ct_loader( 178 path: Union[os.PathLike, str], 179 batch_size: int, 180 patch_shape: Tuple[int, ...], 181 resize_inputs: bool = False, 182 download: bool = False, 183 **kwargs 184) -> DataLoader: 185 """Get the PSMA-PET-CT-Lesions dataloader for tumor lesion segmentation. 186 187 Args: 188 path: Filepath to a folder where the data is downloaded for further processing. 189 batch_size: The batch size for training. 190 patch_shape: The patch shape to use for training. 191 resize_inputs: Whether to resize inputs to the desired patch shape. 192 download: Whether to download the data if it is not present. 193 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 194 195 Returns: 196 The DataLoader. 197 """ 198 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 199 dataset = get_psma_pet_ct_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs) 200 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the PSMA-PET-CT-Lesions dataloader for tumor lesion 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_datasetor for the PyTorch DataLoader.
Returns:
The DataLoader.