torch_em.data.datasets.medical.lndb

The LNDb dataset contains annotations for lung nodule segmentation in chest CT.

It consists of 294 chest CT that were collected at the Centro Hospitalar e Universitario de Sao Joao in Porto between 2016 and 2018. The findings were marked by up to three radiologists out of a group of five, who segmented every finding that they considered a nodule with a diameter of at least 3 mm. Non-nodules and nodules below 3 mm were only marked with a centroid and are not segmented. This module uses the 236 CT of the challenge training set, which are the only scans with public segmentations. The 58 test CT are only released in the newer version of the dataset (https://doi.org/10.5281/zenodo.7153205) and come with nodule centroids, but without segmentations, so they are not included here.

The CT are distributed as MetaImage ('mhd' / 'raw') volumes and the annotations as one mask per radiologist, in which the voxel values are the finding ids of that radiologist. This module joins the per-radiologist findings into nodules with the official matching of 'trainNodules_gt.csv' (findings of different radiologists are the same nodule if their centroids are closer than the maximum of their radii and 3 mm) and stores the scan and the nodule masks together in hdf5 files. Each nodule gets one instance id and the files contain the nodule masks for all consensus levels ('labels/consensus_1' to 'labels/consensus_3'), where consensus level n contains the voxels that at least n radiologists marked as part of the nodule (level 1 is the union of all readings). The number of radiologists that annotated each voxel is stored in 'labels/n_readers' and the number of radiologists that read the scan in the 'n_readers' attribute (59 scans were read by one, 110 by two and 67 by three radiologists, so the higher consensus levels are empty for the scans with fewer readers). Findings that all readers considered non-nodules and findings that were not segmented are not included, while the findings below the equivalent diameter of 3 mm that the official example scripts filter out are kept.

The volumes are converted to the (Z, Y, X) axis order, which is the order the MetaImage files are stored in.

The data is located at https://doi.org/10.5281/zenodo.6613714. NOTE: The license field of the Zenodo record states CC BY 4.0, but its description states CC BY-NC-ND 4.0, so please make sure that you are allowed to use the data for your purpose.

This dataset is from the publication https://doi.org/10.48550/arXiv.1911.08434 and the challenge it was used for is described in https://doi.org/10.1016/j.media.2021.102027. Please cite them if you use this dataset in your research.

  1"""The LNDb dataset contains annotations for lung nodule segmentation in chest CT.
  2
  3It consists of 294 chest CT that were collected at the Centro Hospitalar e Universitario de Sao Joao in Porto
  4between 2016 and 2018. The findings were marked by up to three radiologists out of a group of five, who segmented
  5every finding that they considered a nodule with a diameter of at least 3 mm. Non-nodules and nodules below 3 mm
  6were only marked with a centroid and are not segmented. This module uses the 236 CT of the challenge training set,
  7which are the only scans with public segmentations. The 58 test CT are only released in the newer version of the
  8dataset (https://doi.org/10.5281/zenodo.7153205) and come with nodule centroids, but without segmentations, so
  9they are not included here.
 10
 11The CT are distributed as MetaImage ('mhd' / 'raw') volumes and the annotations as one mask per radiologist, in
 12which the voxel values are the finding ids of that radiologist. This module joins the per-radiologist findings
 13into nodules with the official matching of 'trainNodules_gt.csv' (findings of different radiologists are the same
 14nodule if their centroids are closer than the maximum of their radii and 3 mm) and stores the scan and the nodule
 15masks together in hdf5 files. Each nodule gets one instance id and the files contain the nodule masks for all
 16consensus levels ('labels/consensus_1' to 'labels/consensus_3'), where consensus level n contains the voxels that
 17at least n radiologists marked as part of the nodule (level 1 is the union of all readings). The number of
 18radiologists that annotated each voxel is stored in 'labels/n_readers' and the number of radiologists that read
 19the scan in the 'n_readers' attribute (59 scans were read by one, 110 by two and 67 by three radiologists,
 20so the higher consensus levels are empty for the scans with fewer readers). Findings that all readers
 21considered non-nodules and findings that were not segmented are not included, while the findings below the
 22equivalent diameter of 3 mm that the official example scripts filter out are kept.
 23
 24The volumes are converted to the (Z, Y, X) axis order, which is the order the MetaImage files are stored in.
 25
 26The data is located at https://doi.org/10.5281/zenodo.6613714. NOTE: The license field of the Zenodo record
 27states CC BY 4.0, but its description states CC BY-NC-ND 4.0, so please make sure that you are allowed to use
 28the data for your purpose.
 29
 30This dataset is from the publication https://doi.org/10.48550/arXiv.1911.08434 and the challenge it was used for
 31is described in https://doi.org/10.1016/j.media.2021.102027.
 32Please cite them if you use this dataset in your research.
 33"""
 34
 35import os
 36import csv
 37from glob import glob
 38from tqdm import tqdm
 39from natsort import natsorted
 40from collections import defaultdict
 41from typing import Union, Tuple, List
 42
 43import numpy as np
 44
 45from torch.utils.data import Dataset, DataLoader
 46
 47import torch_em
 48
 49from .. import util
 50
 51
 52URLS = {
 53    "data0": "https://zenodo.org/records/6613714/files/data0.rar",
 54    "data1": "https://zenodo.org/records/6613714/files/data1.rar",
 55    "data2": "https://zenodo.org/records/6613714/files/data2.rar",
 56    "data3": "https://zenodo.org/records/6613714/files/data3.rar",
 57    "data4": "https://zenodo.org/records/6613714/files/data4.rar",
 58    "data5": "https://zenodo.org/records/6613714/files/data5.rar",
 59    "masks": "https://zenodo.org/records/6613714/files/masks.rar",
 60    "csvs": "https://zenodo.org/records/6613714/files/trainset_csv.zip",
 61}
 62
 63CHECKSUMS = {
 64    "data0": "2ae6b7b760f5286bde2f51b32fcdedbf4e1e35aee575cb971bbde5d1aef517d7",
 65    "data1": "f5b33500127e09c0c320561c9c72e72a7934f02214bb091d717d95ed27977057",
 66    "data2": "a8819b5ad36023cb85d1bf840a136c413bbac38dbd3cbc2b4bddb2ef43acf0cf",
 67    "data3": "05a4b817c112aec650e78e6ba6908737a26550510de5a2b62d2b8c0dbc3059e7",
 68    "data4": "ffa1a2de6525a1409c7d5a0d0bbdf9ce758219cca7914c08471adf3cb4405d15",
 69    "data5": "6e94ebcc2650879feb6928b30f8bd6f8675a1c0557d9e7c06852fba81e5cea6d",
 70    "masks": "cdc9cfbbb868ec6fc6e2612afc9bc12cd6e07f8d345610a4dffbf76e1bcc7980",
 71    "csvs": "cda2e6377344dd90b7926f2c10da29d5260ca59337bd8f1c1490639ffa8fbb94",
 72}
 73
 74# The nodule masks are instance labels, so the label ids are the nodule ids of a scan.
 75LABEL_IDS = {"background": 0, "nodule": "1, 2, ... (one id per nodule)"}
 76
 77MAX_CONSENSUS_LEVEL = 3
 78
 79N_VOLUMES = 236
 80
 81N_MASKS = 480
 82
 83METAIMAGE_DTYPES = {
 84    "MET_CHAR": "int8",
 85    "MET_UCHAR": "uint8",
 86    "MET_SHORT": "int16",
 87    "MET_USHORT": "uint16",
 88    "MET_INT": "int32",
 89    "MET_UINT": "uint32",
 90    "MET_FLOAT": "float32",
 91    "MET_DOUBLE": "float64",
 92}
 93
 94
 95def _read_metaimage(mhd_path):
 96    """Read a MetaImage volume with the axis order (Z, Y, X).
 97
 98    The header is a plain text file that refers to the raw data of the volume, so no additional dependency
 99    (such as SimpleITK) is needed to read it.
100    """
101    header = {}
102    with open(mhd_path, "r") as f:
103        for line in f:
104            if "=" not in line:
105                continue
106            key, value = line.split("=", 1)
107            header[key.strip()] = value.strip()
108
109    shape = [int(v) for v in header["DimSize"].split()][::-1]
110    dtype = METAIMAGE_DTYPES[header["ElementType"]]
111    raw_path = os.path.join(os.path.dirname(mhd_path), os.path.basename(header["ElementDataFile"]))
112    return np.fromfile(raw_path, dtype=dtype).reshape(shape)
113
114
115def _read_csv(csv_path):
116    with open(csv_path, "r") as f:
117        return list(csv.DictReader(f))
118
119
120def _get_findings_per_scan(csv_dir):
121    """Group the joined findings of 'trainNodules_gt.csv' by scan.
122
123    Each finding is a list of the (radiologist id, finding id of that radiologist) pairs that were joined into it.
124    The findings that all readers considered non-nodules are skipped.
125    """
126    findings = defaultdict(list)
127    for row in _read_csv(os.path.join(csv_dir, "trainNodules_gt.csv")):
128        if int(row["Nodule"]) != 1:
129            continue
130        rad_ids = [int(v) for v in row["RadID"].split(",")]
131        rad_finding_ids = [int(v) for v in row["RadFindingID"].split(",")]
132        findings[int(row["LNDbID"])].append(list(zip(rad_ids, rad_finding_ids)))
133    return findings
134
135
136def _build_nodule_labels(mask_dir, scan_id, findings, shape):
137    """Derive the instance labels for all consensus levels from the per-radiologist masks of one scan."""
138    masks = {}
139    for mask_path in glob(os.path.join(mask_dir, f"LNDb-{scan_id:04d}_rad*.mhd")):
140        rad_id = int(os.path.basename(mask_path).split("_rad")[1][:-len(".mhd")])
141        masks[rad_id] = _read_metaimage(mask_path)
142
143    n_readers = np.zeros(shape, dtype="uint8")
144    consensus = {level: np.zeros(shape, dtype="uint16") for level in range(1, MAX_CONSENSUS_LEVEL + 1)}
145
146    nodule_id = 0
147    for finding in findings:
148        # Count how many radiologists annotated each voxel as part of this nodule.
149        counts = np.zeros(shape, dtype="uint8")
150        for rad_id, rad_finding_id in finding:
151            if rad_id in masks:
152                counts += (masks[rad_id] == rad_finding_id)
153
154        # Findings that were only marked with a centroid are not segmented and are skipped.
155        if not counts.any():
156            continue
157
158        nodule_id += 1
159        n_readers = np.maximum(n_readers, counts)
160        for level, labels in consensus.items():
161            labels[counts >= level] = nodule_id
162
163    return consensus, n_readers, len(masks)
164
165
166def _preprocess_lndb(data_dir, mask_dir, csv_dir, preprocessed_dir):
167    import h5py
168
169    findings_per_scan = _get_findings_per_scan(csv_dir)
170    scan_ids = [int(row["LNDbID"]) for row in _read_csv(os.path.join(csv_dir, "trainCTs.csv"))]
171    os.makedirs(preprocessed_dir, exist_ok=True)
172
173    for scan_id in tqdm(scan_ids, desc="Preprocessing the LNDb scans"):
174        volume_path = os.path.join(preprocessed_dir, f"LNDb-{scan_id:04d}.h5")
175        if os.path.exists(volume_path):
176            continue
177
178        raw = _read_metaimage(os.path.join(data_dir, f"LNDb-{scan_id:04d}.mhd"))
179        consensus, n_readers, n_masks = _build_nodule_labels(
180            mask_dir, scan_id, findings_per_scan[scan_id], raw.shape
181        )
182
183        # The file is written to a temporary path first, so that an interrupted run leaves no corrupt file.
184        with h5py.File(f"{volume_path}.tmp", "w") as f:
185            f.attrs["scan_id"] = scan_id
186            f.attrs["n_readers"] = n_masks
187            f.create_dataset("raw", data=raw, compression="gzip")
188            f.create_dataset("labels/n_readers", data=n_readers, compression="gzip")
189            for level, labels in consensus.items():
190                f.create_dataset(f"labels/consensus_{level}", data=labels, compression="gzip")
191
192        os.rename(f"{volume_path}.tmp", volume_path)
193
194
195def get_lndb_data(path: Union[os.PathLike, str], download: bool = False) -> str:
196    """Download the LNDb dataset.
197
198    Args:
199        path: Filepath to a folder where the data is downloaded for further processing.
200        download: Whether to download the data if it is not present.
201
202    Returns:
203        Filepath where the preprocessed data is stored.
204    """
205    preprocessed_dir = os.path.join(path, "preprocessed")
206    if len(glob(os.path.join(preprocessed_dir, "*.h5"))) == N_VOLUMES:
207        return preprocessed_dir
208
209    os.makedirs(path, exist_ok=True)
210
211    # The scans are spread over six rar archives, which all unpack into the same folder. A marker file is written
212    # for every archive that was unpacked, so that an interrupted run does not download the 24 GB again.
213    data_dir = os.path.join(path, "data")
214    for name in [f"data{i}" for i in range(6)]:
215        marker_path = os.path.join(path, f"{name}.extracted")
216        if os.path.exists(marker_path):
217            continue
218        rar_path = os.path.join(path, f"{name}.rar")
219        util.download_source(path=rar_path, url=URLS[name], download=download, checksum=CHECKSUMS[name])
220        util.unzip_rarfile(rar_path=rar_path, dst=data_dir)
221        open(marker_path, "w").close()
222
223    # There is one mask per radiologist and scan, which gives 480 masks for the 236 scans.
224    mask_dir = os.path.join(path, "masks")
225    if len(glob(os.path.join(mask_dir, "*.mhd"))) != N_MASKS:
226        rar_path = os.path.join(path, "masks.rar")
227        util.download_source(path=rar_path, url=URLS["masks"], download=download, checksum=CHECKSUMS["masks"])
228        util.unzip_rarfile(rar_path=rar_path, dst=path)
229
230    csv_dir = os.path.join(path, "trainset_csv")
231    if not os.path.exists(os.path.join(csv_dir, "trainNodules_gt.csv")):
232        zip_path = os.path.join(path, "trainset_csv.zip")
233        util.download_source(path=zip_path, url=URLS["csvs"], download=download, checksum=CHECKSUMS["csvs"])
234        util.unzip(zip_path=zip_path, dst=csv_dir)
235
236    _preprocess_lndb(data_dir, mask_dir, csv_dir, preprocessed_dir)
237    return preprocessed_dir
238
239
240def get_lndb_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
241    """Get paths to the LNDb data.
242
243    Args:
244        path: Filepath to a folder where the data is downloaded for further processing.
245        download: Whether to download the data if it is not present.
246
247    Returns:
248        List of filepaths for the hdf5 files, which contain the image data ('raw') and the label data
249        ('labels/consensus_<level>' and 'labels/n_readers').
250    """
251    data_dir = get_lndb_data(path, download)
252    volume_paths = natsorted(glob(os.path.join(data_dir, "*.h5")))
253    assert len(volume_paths) > 0
254
255    return volume_paths
256
257
258def get_lndb_dataset(
259    path: Union[os.PathLike, str],
260    patch_shape: Tuple[int, ...],
261    consensus_level: int = 1,
262    resize_inputs: bool = False,
263    download: bool = False,
264    **kwargs
265) -> Dataset:
266    """Get the LNDb dataset for lung nodule segmentation.
267
268    Args:
269        path: Filepath to a folder where the data is downloaded for further processing.
270        patch_shape: The patch shape to use for training.
271        consensus_level: The minimum number of radiologists (1 to 3) that have to agree on a voxel for it to be part
272            of a nodule. 1 corresponds to the union of all radiologist annotations.
273        resize_inputs: Whether to resize inputs to the desired patch shape.
274        download: Whether to download the data if it is not present.
275        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
276
277    Returns:
278        The segmentation dataset.
279    """
280    if consensus_level not in range(1, MAX_CONSENSUS_LEVEL + 1):
281        raise ValueError(f"'{consensus_level}' is not a valid consensus level. Please choose a value from 1 to 3.")
282
283    volume_paths = get_lndb_paths(path, download)
284
285    if resize_inputs:
286        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
287        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
288            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
289        )
290
291    return torch_em.default_segmentation_dataset(
292        raw_paths=volume_paths,
293        raw_key="raw",
294        label_paths=volume_paths,
295        label_key=f"labels/consensus_{consensus_level}",
296        patch_shape=patch_shape,
297        is_seg_dataset=True,
298        **kwargs
299    )
300
301
302def get_lndb_loader(
303    path: Union[os.PathLike, str],
304    batch_size: int,
305    patch_shape: Tuple[int, ...],
306    consensus_level: int = 1,
307    resize_inputs: bool = False,
308    download: bool = False,
309    **kwargs
310) -> DataLoader:
311    """Get the LNDb dataloader for lung nodule segmentation.
312
313    Args:
314        path: Filepath to a folder where the data is downloaded for further processing.
315        batch_size: The batch size for training.
316        patch_shape: The patch shape to use for training.
317        consensus_level: The minimum number of radiologists (1 to 3) that have to agree on a voxel for it to be part
318            of a nodule. 1 corresponds to the union of all radiologist annotations.
319        resize_inputs: Whether to resize inputs to the desired patch shape.
320        download: Whether to download the data if it is not present.
321        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
322
323    Returns:
324        The DataLoader.
325    """
326    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
327    dataset = get_lndb_dataset(path, patch_shape, consensus_level, resize_inputs, download, **ds_kwargs)
328    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URLS = {'data0': 'https://zenodo.org/records/6613714/files/data0.rar', 'data1': 'https://zenodo.org/records/6613714/files/data1.rar', 'data2': 'https://zenodo.org/records/6613714/files/data2.rar', 'data3': 'https://zenodo.org/records/6613714/files/data3.rar', 'data4': 'https://zenodo.org/records/6613714/files/data4.rar', 'data5': 'https://zenodo.org/records/6613714/files/data5.rar', 'masks': 'https://zenodo.org/records/6613714/files/masks.rar', 'csvs': 'https://zenodo.org/records/6613714/files/trainset_csv.zip'}
CHECKSUMS = {'data0': '2ae6b7b760f5286bde2f51b32fcdedbf4e1e35aee575cb971bbde5d1aef517d7', 'data1': 'f5b33500127e09c0c320561c9c72e72a7934f02214bb091d717d95ed27977057', 'data2': 'a8819b5ad36023cb85d1bf840a136c413bbac38dbd3cbc2b4bddb2ef43acf0cf', 'data3': '05a4b817c112aec650e78e6ba6908737a26550510de5a2b62d2b8c0dbc3059e7', 'data4': 'ffa1a2de6525a1409c7d5a0d0bbdf9ce758219cca7914c08471adf3cb4405d15', 'data5': '6e94ebcc2650879feb6928b30f8bd6f8675a1c0557d9e7c06852fba81e5cea6d', 'masks': 'cdc9cfbbb868ec6fc6e2612afc9bc12cd6e07f8d345610a4dffbf76e1bcc7980', 'csvs': 'cda2e6377344dd90b7926f2c10da29d5260ca59337bd8f1c1490639ffa8fbb94'}
LABEL_IDS = {'background': 0, 'nodule': '1, 2, ... (one id per nodule)'}
MAX_CONSENSUS_LEVEL = 3
N_VOLUMES = 236
N_MASKS = 480
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_lndb_data(path: Union[os.PathLike, str], download: bool = False) -> str:
196def get_lndb_data(path: Union[os.PathLike, str], download: bool = False) -> str:
197    """Download the LNDb dataset.
198
199    Args:
200        path: Filepath to a folder where the data is downloaded for further processing.
201        download: Whether to download the data if it is not present.
202
203    Returns:
204        Filepath where the preprocessed data is stored.
205    """
206    preprocessed_dir = os.path.join(path, "preprocessed")
207    if len(glob(os.path.join(preprocessed_dir, "*.h5"))) == N_VOLUMES:
208        return preprocessed_dir
209
210    os.makedirs(path, exist_ok=True)
211
212    # The scans are spread over six rar archives, which all unpack into the same folder. A marker file is written
213    # for every archive that was unpacked, so that an interrupted run does not download the 24 GB again.
214    data_dir = os.path.join(path, "data")
215    for name in [f"data{i}" for i in range(6)]:
216        marker_path = os.path.join(path, f"{name}.extracted")
217        if os.path.exists(marker_path):
218            continue
219        rar_path = os.path.join(path, f"{name}.rar")
220        util.download_source(path=rar_path, url=URLS[name], download=download, checksum=CHECKSUMS[name])
221        util.unzip_rarfile(rar_path=rar_path, dst=data_dir)
222        open(marker_path, "w").close()
223
224    # There is one mask per radiologist and scan, which gives 480 masks for the 236 scans.
225    mask_dir = os.path.join(path, "masks")
226    if len(glob(os.path.join(mask_dir, "*.mhd"))) != N_MASKS:
227        rar_path = os.path.join(path, "masks.rar")
228        util.download_source(path=rar_path, url=URLS["masks"], download=download, checksum=CHECKSUMS["masks"])
229        util.unzip_rarfile(rar_path=rar_path, dst=path)
230
231    csv_dir = os.path.join(path, "trainset_csv")
232    if not os.path.exists(os.path.join(csv_dir, "trainNodules_gt.csv")):
233        zip_path = os.path.join(path, "trainset_csv.zip")
234        util.download_source(path=zip_path, url=URLS["csvs"], download=download, checksum=CHECKSUMS["csvs"])
235        util.unzip(zip_path=zip_path, dst=csv_dir)
236
237    _preprocess_lndb(data_dir, mask_dir, csv_dir, preprocessed_dir)
238    return preprocessed_dir

Download the LNDb 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_lndb_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
241def get_lndb_paths(path: Union[os.PathLike, str], download: bool = False) -> List[str]:
242    """Get paths to the LNDb data.
243
244    Args:
245        path: Filepath to a folder where the data is downloaded for further processing.
246        download: Whether to download the data if it is not present.
247
248    Returns:
249        List of filepaths for the hdf5 files, which contain the image data ('raw') and the label data
250        ('labels/consensus_<level>' and 'labels/n_readers').
251    """
252    data_dir = get_lndb_data(path, download)
253    volume_paths = natsorted(glob(os.path.join(data_dir, "*.h5")))
254    assert len(volume_paths) > 0
255
256    return volume_paths

Get paths to the LNDb 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/consensus_' and 'labels/n_readers').

def get_lndb_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], consensus_level: int = 1, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
259def get_lndb_dataset(
260    path: Union[os.PathLike, str],
261    patch_shape: Tuple[int, ...],
262    consensus_level: int = 1,
263    resize_inputs: bool = False,
264    download: bool = False,
265    **kwargs
266) -> Dataset:
267    """Get the LNDb dataset for lung nodule segmentation.
268
269    Args:
270        path: Filepath to a folder where the data is downloaded for further processing.
271        patch_shape: The patch shape to use for training.
272        consensus_level: The minimum number of radiologists (1 to 3) that have to agree on a voxel for it to be part
273            of a nodule. 1 corresponds to the union of all radiologist annotations.
274        resize_inputs: Whether to resize inputs to the desired patch shape.
275        download: Whether to download the data if it is not present.
276        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
277
278    Returns:
279        The segmentation dataset.
280    """
281    if consensus_level not in range(1, MAX_CONSENSUS_LEVEL + 1):
282        raise ValueError(f"'{consensus_level}' is not a valid consensus level. Please choose a value from 1 to 3.")
283
284    volume_paths = get_lndb_paths(path, download)
285
286    if resize_inputs:
287        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
288        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
289            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
290        )
291
292    return torch_em.default_segmentation_dataset(
293        raw_paths=volume_paths,
294        raw_key="raw",
295        label_paths=volume_paths,
296        label_key=f"labels/consensus_{consensus_level}",
297        patch_shape=patch_shape,
298        is_seg_dataset=True,
299        **kwargs
300    )

Get the LNDb dataset for lung nodule segmentation.

Arguments:
  • path: Filepath to a folder where the data is downloaded for further processing.
  • patch_shape: The patch shape to use for training.
  • consensus_level: The minimum number of radiologists (1 to 3) that have to agree on a voxel for it to be part of a nodule. 1 corresponds to the union of all radiologist annotations.
  • 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_lndb_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, ...], consensus_level: int = 1, resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
303def get_lndb_loader(
304    path: Union[os.PathLike, str],
305    batch_size: int,
306    patch_shape: Tuple[int, ...],
307    consensus_level: int = 1,
308    resize_inputs: bool = False,
309    download: bool = False,
310    **kwargs
311) -> DataLoader:
312    """Get the LNDb dataloader for lung nodule segmentation.
313
314    Args:
315        path: Filepath to a folder where the data is downloaded for further processing.
316        batch_size: The batch size for training.
317        patch_shape: The patch shape to use for training.
318        consensus_level: The minimum number of radiologists (1 to 3) that have to agree on a voxel for it to be part
319            of a nodule. 1 corresponds to the union of all radiologist annotations.
320        resize_inputs: Whether to resize inputs to the desired patch shape.
321        download: Whether to download the data if it is not present.
322        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
323
324    Returns:
325        The DataLoader.
326    """
327    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
328    dataset = get_lndb_dataset(path, patch_shape, consensus_level, resize_inputs, download, **ds_kwargs)
329    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the LNDb dataloader for lung nodule 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.
  • consensus_level: The minimum number of radiologists (1 to 3) that have to agree on a voxel for it to be part of a nodule. 1 corresponds to the union of all radiologist annotations.
  • 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.