torch_em.data.datasets.medical.acrin_6698_dwi

The ACRIN-6698 dataset contains annotations for the whole breast tumor in diffusion-weighted MRI.

This module covers the 1103 'DWI SEG' objects of the ACRIN-6698 / I-SPY2 collection: manual whole-tumor segmentations on the apparent diffusion coefficient (ADC) map of breast cancer patients undergoing neoadjuvant chemotherapy. Each DICOM-SEG object is named '...DWI SEG: from S: Whole Tumor Manual', where '' is the DICOM series number of the ADC map it was drawn on; this module resolves that reference by series number rather than a DICOM reference, since the SEG objects in this collection carry none (neither a ReferencedSeriesSequence nor a per-frame derivation link).

NOTE: The collection's other, unrelated family of 1110 'ISPY2: VOLSER' functional-tumor-volume masks (thresholded from a signal-enhancement-ratio map, not a manual DWI segmentation) is covered separately by medical.acrin_6698.

NOTE: This requires the pydicom python package.

The dataset is located at https://doi.org/10.7937/TCIA.kk02-6d95 and is distributed under the CC BY 4.0 license. Please cite it if you use this dataset in your research.

  1"""The ACRIN-6698 dataset contains annotations for the whole breast tumor in diffusion-weighted MRI.
  2
  3This module covers the 1103 'DWI SEG' objects of the ACRIN-6698 / I-SPY2 collection: manual whole-tumor
  4segmentations on the apparent diffusion coefficient (ADC) map of breast cancer patients undergoing
  5neoadjuvant chemotherapy. Each DICOM-SEG object is named '...DWI SEG: from S<n>: Whole Tumor Manual',
  6where '<n>' is the DICOM series number of the ADC map it was drawn on; this module resolves that
  7reference by series number rather than a DICOM reference, since the SEG objects in this collection
  8carry none (neither a `ReferencedSeriesSequence` nor a per-frame derivation link).
  9
 10NOTE: The collection's other, unrelated family of 1110 'ISPY2: VOLSER' functional-tumor-volume masks
 11(thresholded from a signal-enhancement-ratio map, not a manual DWI segmentation) is covered separately
 12by `medical.acrin_6698`.
 13
 14NOTE: This requires the pydicom python package.
 15
 16The dataset is located at https://doi.org/10.7937/TCIA.kk02-6d95 and is distributed under the
 17CC BY 4.0 license.
 18Please cite it if you use this dataset in your research.
 19"""
 20
 21import os
 22import re
 23import json
 24from glob import glob
 25from tqdm import tqdm
 26from natsort import natsorted
 27from typing import Union, Tuple, List
 28
 29from torch.utils.data import Dataset, DataLoader
 30
 31import torch_em
 32
 33from .adrenal_acc import _load_dicom_volume, _load_dicom_seg, _resample_labels
 34from .. import util
 35
 36
 37COLLECTION = "ACRIN-6698"
 38
 39
 40def _get_series_metadata(path, download):
 41    """Get the metadata of all series in the collection from the NBIA REST API."""
 42    import requests
 43
 44    metadata_path = os.path.join(path, "acrin_6698_dwi_series.json")
 45    if not os.path.exists(metadata_path):
 46        if not download:
 47            raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.")
 48        response = requests.get(f"{util.NBIA_API_URL}getSeries", params={"Collection": COLLECTION})
 49        response.raise_for_status()
 50        with open(metadata_path, "w") as f:
 51            json.dump(response.json(), f, indent=2)
 52
 53    with open(metadata_path, "r") as f:
 54        return json.load(f)
 55
 56
 57def _is_dwi_seg(series):
 58    return series.get("Modality") == "SEG" and "DWI SEG" in (series.get("SeriesDescription") or "")
 59
 60
 61def _referenced_series_number(series):
 62    match = re.search(r"from S(\d+)", series["SeriesDescription"])
 63    return match.group(1) if match else None
 64
 65
 66def _find_adc_series(series_metadata, study_uid, series_number):
 67    """Find the ADC map a DWI SEG was drawn on, matched by the series number in its own name."""
 68    candidates = [
 69        series for series in series_metadata
 70        if series.get("StudyInstanceUID") == study_uid and series.get("Modality") == "MR"
 71        and f"ADC: from S{series_number}:" in (series.get("SeriesDescription") or "")
 72    ]
 73    return candidates[0]["SeriesInstanceUID"] if len(candidates) == 1 else None
 74
 75
 76def _preprocess_acrin_6698_dwi(dicom_dir, series_metadata, preprocessed_dir):
 77    import h5py
 78
 79    seg_series = [series for series in series_metadata if _is_dwi_seg(series)]
 80
 81    os.makedirs(preprocessed_dir, exist_ok=True)
 82    for series in tqdm(seg_series, desc="Preprocess ACRIN-6698 (DWI)"):
 83        seg_paths = glob(os.path.join(dicom_dir, series["SeriesInstanceUID"], "*.dcm"))
 84        if not seg_paths:
 85            continue
 86
 87        out_path = os.path.join(preprocessed_dir, f"{series['SeriesInstanceUID']}.h5")
 88        if os.path.exists(out_path):
 89            continue
 90
 91        series_number = _referenced_series_number(series)
 92        if series_number is None:
 93            continue
 94        adc_uid = _find_adc_series(series_metadata, series["StudyInstanceUID"], series_number)
 95        adc_dir = os.path.join(dicom_dir, adc_uid) if adc_uid else None
 96        if adc_dir is None or not glob(os.path.join(adc_dir, "*.dcm")):
 97            continue
 98
 99        volume, adc_affine = _load_dicom_volume(adc_dir)
100        seg_labels, seg_affine = _load_dicom_seg(seg_paths[0])
101        # The SEG only covers the slices where the tumor is present, cropped from the full ADC extent,
102        # so it is placed on the ADC's own grid rather than compared to it directly.
103        labels = _resample_labels(seg_labels, seg_affine, volume.shape, adc_affine)
104        if labels.max() == 0:
105            continue
106
107        with h5py.File(out_path, "w") as f:
108            f.create_dataset("raw", data=volume, compression="gzip")
109            f.create_dataset("labels", data=labels, compression="gzip")
110
111
112def get_acrin_6698_dwi_data(path: Union[os.PathLike, str], download: bool = False) -> str:
113    """Download the ACRIN-6698 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
125    os.makedirs(path, exist_ok=True)
126    series_metadata = _get_series_metadata(path, download)
127
128    seg_uids = [series["SeriesInstanceUID"] for series in series_metadata if _is_dwi_seg(series)]
129
130    dicom_dir = os.path.join(path, "dicom")
131    if download:  # The SEG series are downloaded first, so the ADC series they reference can be found.
132        util.download_tcia_series(seg_uids, dst=dicom_dir, csv_filename=os.path.join(path, "acrin_6698_dwi_seg"))
133
134        seg_by_uid = {series["SeriesInstanceUID"]: series for series in series_metadata if _is_dwi_seg(series)}
135        adc_uids = set()
136        for uid in seg_uids:
137            series = seg_by_uid[uid]
138            series_number = _referenced_series_number(series)
139            if series_number is None:
140                continue
141            adc_uid = _find_adc_series(series_metadata, series["StudyInstanceUID"], series_number)
142            if adc_uid:
143                adc_uids.add(adc_uid)
144        util.download_tcia_series(
145            sorted(adc_uids), dst=dicom_dir, csv_filename=os.path.join(path, "acrin_6698_dwi_adc")
146        )
147    elif not glob(os.path.join(dicom_dir, "*", "*.dcm")):
148        raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.")
149
150    _preprocess_acrin_6698_dwi(dicom_dir, series_metadata, preprocessed_dir)
151    return preprocessed_dir
152
153
154def get_acrin_6698_dwi_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
155    """Get paths to the ACRIN-6698 data.
156
157    Args:
158        path: Filepath to a folder where the data is downloaded for further processing.
159        download: Whether to download the data if it is not present.
160
161    Returns:
162        List of filepaths for the stored data.
163    """
164    preprocessed_dir = get_acrin_6698_dwi_data(path, download)
165    volume_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.h5")))
166    assert len(volume_paths) > 0, f"Could not find any preprocessed volumes in '{preprocessed_dir}'."
167    return volume_paths
168
169
170def get_acrin_6698_dwi_dataset(
171    path: Union[os.PathLike, str],
172    patch_shape: Tuple[int, ...],
173    resize_inputs: bool = False,
174    download: bool = False,
175    **kwargs
176) -> Dataset:
177    """Get the ACRIN-6698 dataset for breast tumor segmentation.
178
179    Args:
180        path: Filepath to a folder where the data is downloaded for further processing.
181        patch_shape: The patch shape to use for training.
182        resize_inputs: Whether to resize inputs to the desired patch shape.
183        download: Whether to download the data if it is not present.
184        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
185
186    Returns:
187        The segmentation dataset.
188    """
189    volume_paths = get_acrin_6698_dwi_paths(path, download)
190
191    if resize_inputs:
192        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
193        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
194            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
195        )
196
197    return torch_em.default_segmentation_dataset(
198        raw_paths=volume_paths,
199        raw_key="raw",
200        label_paths=volume_paths,
201        label_key="labels",
202        patch_shape=patch_shape,
203        is_seg_dataset=True,
204        **kwargs
205    )
206
207
208def get_acrin_6698_dwi_loader(
209    path: Union[os.PathLike, str],
210    batch_size: int,
211    patch_shape: Tuple[int, ...],
212    resize_inputs: bool = False,
213    download: bool = False,
214    **kwargs
215) -> DataLoader:
216    """Get the ACRIN-6698 dataloader for breast tumor segmentation.
217
218    Args:
219        path: Filepath to a folder where the data is downloaded for further processing.
220        batch_size: The batch size for training.
221        patch_shape: The patch shape to use for training.
222        resize_inputs: Whether to resize inputs to the desired patch shape.
223        download: Whether to download the data if it is not present.
224        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
225
226    Returns:
227        The DataLoader.
228    """
229    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
230    dataset = get_acrin_6698_dwi_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
231    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
COLLECTION = 'ACRIN-6698'
def get_acrin_6698_dwi_data(path: Union[os.PathLike, str], download: bool = False) -> str:
113def get_acrin_6698_dwi_data(path: Union[os.PathLike, str], download: bool = False) -> str:
114    """Download the ACRIN-6698 dataset.
115
116    Args:
117        path: Filepath to a folder where the data is downloaded for further processing.
118        download: Whether to download the data if it is not present.
119
120    Returns:
121        Filepath where the preprocessed data is stored.
122    """
123    # NOTE: The preprocessing below skips volumes that were converted already, so an interrupted run resumes.
124    preprocessed_dir = os.path.join(path, "preprocessed")
125
126    os.makedirs(path, exist_ok=True)
127    series_metadata = _get_series_metadata(path, download)
128
129    seg_uids = [series["SeriesInstanceUID"] for series in series_metadata if _is_dwi_seg(series)]
130
131    dicom_dir = os.path.join(path, "dicom")
132    if download:  # The SEG series are downloaded first, so the ADC series they reference can be found.
133        util.download_tcia_series(seg_uids, dst=dicom_dir, csv_filename=os.path.join(path, "acrin_6698_dwi_seg"))
134
135        seg_by_uid = {series["SeriesInstanceUID"]: series for series in series_metadata if _is_dwi_seg(series)}
136        adc_uids = set()
137        for uid in seg_uids:
138            series = seg_by_uid[uid]
139            series_number = _referenced_series_number(series)
140            if series_number is None:
141                continue
142            adc_uid = _find_adc_series(series_metadata, series["StudyInstanceUID"], series_number)
143            if adc_uid:
144                adc_uids.add(adc_uid)
145        util.download_tcia_series(
146            sorted(adc_uids), dst=dicom_dir, csv_filename=os.path.join(path, "acrin_6698_dwi_adc")
147        )
148    elif not glob(os.path.join(dicom_dir, "*", "*.dcm")):
149        raise RuntimeError(f"Cannot find the data at {path}, but download was set to False.")
150
151    _preprocess_acrin_6698_dwi(dicom_dir, series_metadata, preprocessed_dir)
152    return preprocessed_dir

Download the ACRIN-6698 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_acrin_6698_dwi_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
155def get_acrin_6698_dwi_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
156    """Get paths to the ACRIN-6698 data.
157
158    Args:
159        path: Filepath to a folder where the data is downloaded for further processing.
160        download: Whether to download the data if it is not present.
161
162    Returns:
163        List of filepaths for the stored data.
164    """
165    preprocessed_dir = get_acrin_6698_dwi_data(path, download)
166    volume_paths = natsorted(glob(os.path.join(preprocessed_dir, "*.h5")))
167    assert len(volume_paths) > 0, f"Could not find any preprocessed volumes in '{preprocessed_dir}'."
168    return volume_paths

Get paths to the ACRIN-6698 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_acrin_6698_dwi_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
171def get_acrin_6698_dwi_dataset(
172    path: Union[os.PathLike, str],
173    patch_shape: Tuple[int, ...],
174    resize_inputs: bool = False,
175    download: bool = False,
176    **kwargs
177) -> Dataset:
178    """Get the ACRIN-6698 dataset for breast tumor segmentation.
179
180    Args:
181        path: Filepath to a folder where the data is downloaded for further processing.
182        patch_shape: The patch shape to use for training.
183        resize_inputs: Whether to resize inputs to the desired patch shape.
184        download: Whether to download the data if it is not present.
185        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
186
187    Returns:
188        The segmentation dataset.
189    """
190    volume_paths = get_acrin_6698_dwi_paths(path, download)
191
192    if resize_inputs:
193        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
194        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
195            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
196        )
197
198    return torch_em.default_segmentation_dataset(
199        raw_paths=volume_paths,
200        raw_key="raw",
201        label_paths=volume_paths,
202        label_key="labels",
203        patch_shape=patch_shape,
204        is_seg_dataset=True,
205        **kwargs
206    )

Get the ACRIN-6698 dataset for breast tumor 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_acrin_6698_dwi_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:
209def get_acrin_6698_dwi_loader(
210    path: Union[os.PathLike, str],
211    batch_size: int,
212    patch_shape: Tuple[int, ...],
213    resize_inputs: bool = False,
214    download: bool = False,
215    **kwargs
216) -> DataLoader:
217    """Get the ACRIN-6698 dataloader for breast tumor segmentation.
218
219    Args:
220        path: Filepath to a folder where the data is downloaded for further processing.
221        batch_size: The batch size for training.
222        patch_shape: The patch shape to use for training.
223        resize_inputs: Whether to resize inputs to the desired patch shape.
224        download: Whether to download the data if it is not present.
225        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
226
227    Returns:
228        The DataLoader.
229    """
230    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
231    dataset = get_acrin_6698_dwi_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
232    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the ACRIN-6698 dataloader for breast tumor 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.