torch_em.data.datasets.medical.promise12

The PROMISE12 dataset contains annotations for prostate segmentation in T2-weighted MRI.

The data comes from the MICCAI 2012 'Prostate MR Image Segmentation' challenge (https://promise12.grand-challenge.org/) and covers 50 labeled training cases from four centers with different scanners, field strengths and protocols (with and without an endorectal coil). The label ids are: background: 0 and prostate: 1. The challenge test cases are not labeled and are therefore not exposed here.

The original MetaImage ('.mhd' / '.raw') volumes are converted to hdf5 once and then loaded from there.

The dataset is located at https://zenodo.org/records/8026660.

This dataset is from the publication https://doi.org/10.1016/j.media.2013.12.002. Please cite it if you use this dataset in your research.

  1"""The PROMISE12 dataset contains annotations for prostate segmentation in T2-weighted MRI.
  2
  3The data comes from the MICCAI 2012 'Prostate MR Image Segmentation' challenge (https://promise12.grand-challenge.org/)
  4and covers 50 labeled training cases from four centers with different scanners, field strengths and protocols
  5(with and without an endorectal coil). The label ids are: background: 0 and prostate: 1.
  6The challenge test cases are not labeled and are therefore not exposed here.
  7
  8The original MetaImage ('.mhd' / '.raw') volumes are converted to hdf5 once and then loaded from there.
  9
 10The dataset is located at https://zenodo.org/records/8026660.
 11
 12This dataset is from the publication https://doi.org/10.1016/j.media.2013.12.002.
 13Please cite it if you use this dataset in your research.
 14"""
 15
 16import os
 17from glob import glob
 18from tqdm import tqdm
 19from natsort import natsorted
 20from typing import Union, Tuple, List
 21
 22import numpy as np
 23
 24from torch.utils.data import Dataset, DataLoader
 25
 26import torch_em
 27
 28from .. import util
 29
 30
 31URL = "https://zenodo.org/records/8026660/files/training_data.zip?download=1"
 32CHECKSUM = "150287d0c74cd0105d8b70b43af4a7bf4f1fd3e1748829779177a0ccaf948f45"
 33
 34METAIMAGE_DTYPES = {
 35    "MET_CHAR": "int8", "MET_UCHAR": "uint8", "MET_SHORT": "int16", "MET_USHORT": "uint16",
 36    "MET_INT": "int32", "MET_UINT": "uint32", "MET_FLOAT": "float32", "MET_DOUBLE": "float64",
 37}
 38
 39
 40def _read_metaimage(path):
 41    """Read an uncompressed MetaImage ('.mhd' + '.raw') volume with numpy, returning it in 'zyx' order."""
 42    header = {}
 43    with open(path, "r") as f:
 44        for line in f:
 45            if "=" in line:
 46                key, value = line.split("=", 1)
 47                header[key.strip()] = value.strip()
 48
 49    if header.get("CompressedData", "False") == "True":
 50        raise NotImplementedError(f"Compressed MetaImage data is not supported: '{path}'.")
 51
 52    shape = tuple(int(s) for s in header["DimSize"].split())[::-1]
 53    dtype = np.dtype(METAIMAGE_DTYPES[header["ElementType"]])
 54    if header.get("BinaryDataByteOrderMSB", "False") == "True":
 55        dtype = dtype.newbyteorder(">")
 56
 57    raw_path = os.path.join(os.path.dirname(path), header["ElementDataFile"])
 58    return np.fromfile(raw_path, dtype=dtype).reshape(shape)
 59
 60
 61def get_promise12_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 62    """Download the PROMISE12 dataset and convert it to hdf5.
 63
 64    Args:
 65        path: Filepath to a folder where the data is downloaded for further processing.
 66        download: Whether to download the data if it is not present.
 67
 68    Returns:
 69        Filepath to the folder with the converted hdf5 volumes.
 70    """
 71    data_dir = os.path.join(path, "preprocessed")
 72    if os.path.exists(data_dir):
 73        return data_dir
 74
 75    os.makedirs(path, exist_ok=True)
 76
 77    zip_path = os.path.join(path, "training_data.zip")
 78    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
 79    raw_dir = os.path.join(path, "training_data")
 80    util.unzip(zip_path=zip_path, dst=raw_dir)
 81
 82    import h5py
 83
 84    os.makedirs(data_dir, exist_ok=True)
 85    label_paths = natsorted(glob(os.path.join(raw_dir, "Case*_segmentation.mhd")))
 86    for label_path in tqdm(label_paths, desc="Converting PROMISE12 volumes to hdf5"):
 87        raw_path = label_path.replace("_segmentation.mhd", ".mhd")
 88        raw = _read_metaimage(raw_path)
 89        labels = _read_metaimage(label_path).astype("uint8")
 90        assert raw.shape == labels.shape, f"Shape mismatch for '{raw_path}': {raw.shape} vs. {labels.shape}."
 91
 92        case_id = os.path.basename(raw_path).replace(".mhd", "")
 93        with h5py.File(os.path.join(data_dir, f"{case_id}.h5"), "w") as f:
 94            f.create_dataset("raw", data=raw, compression="gzip")
 95            f.create_dataset("labels", data=labels, compression="gzip")
 96
 97    return data_dir
 98
 99
100def get_promise12_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
101    """Get paths to the PROMISE12 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 hdf5 volumes, which store the image data at 'raw' and the label data at 'labels'.
109    """
110    data_dir = get_promise12_data(path, download)
111    volume_paths = natsorted(glob(os.path.join(data_dir, "Case*.h5")))
112    assert len(volume_paths) > 0, f"No PROMISE12 volumes found at '{data_dir}'."
113    return volume_paths
114
115
116def get_promise12_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 PROMISE12 dataset for prostate 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_promise12_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    )
152
153
154def get_promise12_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 PROMISE12 dataloader for prostate 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_promise12_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
177    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://zenodo.org/records/8026660/files/training_data.zip?download=1'
CHECKSUM = '150287d0c74cd0105d8b70b43af4a7bf4f1fd3e1748829779177a0ccaf948f45'
METAIMAGE_DTYPES = {'MET_CHAR': 'int8', 'MET_UCHAR': 'uint8', 'MET_SHORT': 'int16', 'MET_USHORT': 'uint16', 'MET_INT': 'int32', 'MET_UINT': 'uint32', 'MET_FLOAT': 'float32', 'MET_DOUBLE': 'float64'}
def get_promise12_data(path: Union[os.PathLike, str], download: bool = False) -> str:
62def get_promise12_data(path: Union[os.PathLike, str], download: bool = False) -> str:
63    """Download the PROMISE12 dataset and convert it to hdf5.
64
65    Args:
66        path: Filepath to a folder where the data is downloaded for further processing.
67        download: Whether to download the data if it is not present.
68
69    Returns:
70        Filepath to the folder with the converted hdf5 volumes.
71    """
72    data_dir = os.path.join(path, "preprocessed")
73    if os.path.exists(data_dir):
74        return data_dir
75
76    os.makedirs(path, exist_ok=True)
77
78    zip_path = os.path.join(path, "training_data.zip")
79    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
80    raw_dir = os.path.join(path, "training_data")
81    util.unzip(zip_path=zip_path, dst=raw_dir)
82
83    import h5py
84
85    os.makedirs(data_dir, exist_ok=True)
86    label_paths = natsorted(glob(os.path.join(raw_dir, "Case*_segmentation.mhd")))
87    for label_path in tqdm(label_paths, desc="Converting PROMISE12 volumes to hdf5"):
88        raw_path = label_path.replace("_segmentation.mhd", ".mhd")
89        raw = _read_metaimage(raw_path)
90        labels = _read_metaimage(label_path).astype("uint8")
91        assert raw.shape == labels.shape, f"Shape mismatch for '{raw_path}': {raw.shape} vs. {labels.shape}."
92
93        case_id = os.path.basename(raw_path).replace(".mhd", "")
94        with h5py.File(os.path.join(data_dir, f"{case_id}.h5"), "w") as f:
95            f.create_dataset("raw", data=raw, compression="gzip")
96            f.create_dataset("labels", data=labels, compression="gzip")
97
98    return data_dir

Download the PROMISE12 dataset and convert it to hdf5.

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 to the folder with the converted hdf5 volumes.

def get_promise12_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
101def get_promise12_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
102    """Get paths to the PROMISE12 data.
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        List of filepaths for the hdf5 volumes, which store the image data at 'raw' and the label data at 'labels'.
110    """
111    data_dir = get_promise12_data(path, download)
112    volume_paths = natsorted(glob(os.path.join(data_dir, "Case*.h5")))
113    assert len(volume_paths) > 0, f"No PROMISE12 volumes found at '{data_dir}'."
114    return volume_paths

Get paths to the PROMISE12 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 volumes, which store the image data at 'raw' and the label data at 'labels'.

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

Get the PROMISE12 dataset for prostate 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_promise12_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:
155def get_promise12_loader(
156    path: Union[os.PathLike, str],
157    batch_size: int,
158    patch_shape: Tuple[int, ...],
159    resize_inputs: bool = False,
160    download: bool = False,
161    **kwargs
162) -> DataLoader:
163    """Get the PROMISE12 dataloader for prostate segmentation.
164
165    Args:
166        path: Filepath to a folder where the data is downloaded for further processing.
167        batch_size: The batch size for training.
168        patch_shape: The patch shape to use for training.
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` or for the PyTorch DataLoader.
172
173    Returns:
174        The DataLoader.
175    """
176    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
177    dataset = get_promise12_dataset(path, patch_shape, resize_inputs, download, **ds_kwargs)
178    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the PROMISE12 dataloader for prostate 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.