torch_em.data.datasets.medical.structseg

The StructSeg dataset contains annotations for organ-at-risk (OAR) and gross target volume (GTV) segmentation in head and neck and in chest CT scans of nasopharynx and lung cancer patients.

It comprises the training sets of the four tasks of the StructSeg2019 challenge (https://structseg2019.grand-challenge.org), each with 50 annotated CT volumes:

  • task1 ('HaN_OAR'): 22 organs at risk in head and neck CT scans of nasopharynx cancer patients.
  • task2 ('Naso_GTV'): the gross target volume of nasopharynx cancer, on the same 50 scans as task1.
  • task3 ('Thoracic_OAR'): 6 organs at risk in chest CT scans of lung cancer patients.
  • task4 ('Lung_GTV'): the gross target volume of lung cancer, on the same 50 scans as task3. The 10 test volumes per task are distributed without annotations and are therefore not included here.

NOTE: The label legend is as follows (see LABEL_IDS):

  • task1: background: 0, brainstem: 1, left eye: 2, right eye: 3, left lens: 4, right lens: 5, left optic nerve: 6, right optic nerve: 7, optic chiasm: 8, left temporal lobe: 9, right temporal lobe: 10, pituitary: 11, left parotid gland: 12, right parotid gland: 13, left inner ear: 14, right inner ear: 15, left middle ear: 16, right middle ear: 17, left temporomandibular joint: 18, right temporomandibular joint: 19, spinal cord: 20, left mandible: 21, right mandible: 22
  • task2: background: 0, nasopharynx cancer gross target volume: 1
  • task3: background: 0, left lung: 1, right lung: 2, heart: 3, esophagus: 4, trachea: 5, spinal cord: 6
  • task4: background: 0, lung cancer gross target volume: 1 The id order is taken from https://github.com/zhilothebest/Coronary_Calcium, which transcribes the readme of the official release: task1 lists 'Brain_Stem, Eye_L, Eye_R, Lens_L, Lens_R, Opt_Nerve_L, Opt_Nerve_R, Opt_Chiasma, Temporal_Lobes_L, Temporal_Lobes_R, Pituitary, Parotid_Gland_L, Parotid_Gland_R, Inner_Ear_L, Inner_Ear_R, Mid_Ear_L, Mid_Ear_R, T_M_Joint_L, T_M_Joint_R, Spinal_Cord, Mandible_L, Mandible_R' and task3 lists 'Lung_L, Lung_R, Heart, Esophagus, Trachea, Spinal_Cord', each 'corresponding to the label 1 to N in the annotation file'. NOTE: Neither order is the order in which the organs are listed on https://structseg2019.grand-challenge.org/Dataset/, and the per-label statistics published in https://github.com/openmedlab/Awesome-Medical-Dataset/blob/main/resources/StructSeg2019_Task3.md carry the task3 names in the website order instead of the id order, so they call id 3 the spinal cord when it is in fact the heart.

The task1 ids were verified on the data: all 50 volumes contain exactly the ids 0 - 22, the mean intensity identifies the bony structures (inner ear 1043 HU, mandible 734 HU, temporomandibular joint 408 HU) and the soft tissue ones (eye 25 HU, brainstem 29 HU, parotid gland 22 HU), the mean volumes match the expected anatomy (lens 0.27 cm3, pituitary 0.58 cm3, optic chiasm 1.05 cm3, eye 9.1 cm3, brainstem 27 cm3, temporal lobe 106 cm3), and in the LPS orientation of the volumes every structure named '_l' lies on the patient's left of its '_r' partner.

The task3 ids 1 - 4 were confirmed independently of that transcription, against the copy of the task3 volumes redistributed in https://huggingface.co/datasets/blueyo0/SA-Med3D-140K (its 'ct_general_StructSeg2019_subtask2' entries), which stores one named binary mask per organ. Over the 50 cases the volumes of its 'lung_left', 'lung_right', 'heart' and 'esophagus' masks match the statistics published for the ids 1, 2, 3 and 4 to within 0.3% (median 1324 / 1842 / 690 / 35.7 cm3 against 1325 / 1841 / 692 / 35.5 cm3, and the minima and maxima agree just as closely). The ids 5 and 6 could not be checked that way, since that copy holds neither a trachea nor a spinal cord mask, so they rest on the transcribed readme alone.

The data of 'task1' is downloaded from the redistribution at https://huggingface.co/datasets/Luffy503/VoCo_Downstream (the file 'Dataset190_Structseg19.zip'), which is published as part of the VoCo benchmark (https://github.com/Luffy03/Large-Scale-Medical) and is tagged Apache-2.0 by its uploader. NOTE: This is a third-party redistribution of data that the official release only hands out to registered challenge participants, and the StructSeg organizers did not grant a license for it. The participant agreement is quoted in https://github.com/zhilothebest/Coronary_Calcium as saying that 'the datasets can not be publicly posted, or distributed to anyone outside of this project', so please make sure that you are allowed to use the data for your purpose. The redistribution stores the data in the nnU-Net layout, which this module converts to the layout of the official release. Its content was verified against the official release: 50 CT volumes, the label ids 0 - 22, and the same file sizes as the copy of 'Task1_HaN_OAR.zip' that is linked from https://github.com/openmedlab/MedLSAM.

NOTE: There is no openly published copy of 'task2', 'task3' and 'task4'. They require registration and cannot be downloaded automatically. Please follow these steps:

  • Register for the challenge at https://structseg2019.grand-challenge.org and join it, then visit https://structseg2019.grand-challenge.org/Download/ and follow the instructions of the organizers to obtain the archives 'Task2_Naso_GTV.zip', 'Task3_Thoracic_OAR.zip' and 'Task4_Lung_GTV.zip' (the GTV tasks are also deposited at https://doi.org/10.21227/h75x-gt46, which needs a paid IEEE DataPort subscription and holds only the 83 byte file 'download url.txt' with a download url, not the images).
  • Extract the archives of the tasks you need into '', such that '/Naso_GTV//data.nii.gz' and '/Naso_GTV//label.nii.gz' exist (and equivalently '/Thoracic_OAR/...' and '/Lung_GTV/...'). The case ids are the numbers 1 - 50. An extra enclosing folder, e.g. '/Task3_Thoracic_OAR/Thoracic_OAR/1/data.nii.gz', is handled as well. A manual download of 'Task1_HaN_OAR.zip' is picked up in the same way and is then used instead of the redistribution. The only openly published parts of these three tasks that could be found are in https://huggingface.co/datasets/blueyo0/SA-Med3D-140K, which is not a usable substitute: it holds the 50 task3 volumes with masks for only 4 of the 6 organs (no spinal cord and no trachea), the 50 task2 volumes without any labels, and nothing at all of task4, all resampled to an isotropic spacing of 1.5 mm. Note that its task numbering differs from the challenge: its 'subtask2' entries are task3 and its 'subtask4' entries are task2.

The most promising lead for the two GTV tasks is https://rec.ustc.edu.cn/share/b812d430-f577-11ed-a202-03afc6a1d18f, which https://github.com/shijun18/GTV_AutoSeg offers as the code and data of a paper that evaluates on exactly task2 and task4, and whose author also made the IEEE DataPort deposit above, so it is probably the url that the deposit points to. It is not open either: listing that share needs a login at the hosting site.

The dataset is located at https://structseg2019.grand-challenge.org.

The challenge does not have a dedicated publication. Please cite the challenge website and, for the gross target volume tasks, the data record https://doi.org/10.21227/h75x-gt46 if you use this dataset in your research.

  1"""The StructSeg dataset contains annotations for organ-at-risk (OAR) and gross target volume (GTV)
  2segmentation in head and neck and in chest CT scans of nasopharynx and lung cancer patients.
  3
  4It comprises the training sets of the four tasks of the StructSeg2019 challenge
  5(https://structseg2019.grand-challenge.org), each with 50 annotated CT volumes:
  6- task1 ('HaN_OAR'): 22 organs at risk in head and neck CT scans of nasopharynx cancer patients.
  7- task2 ('Naso_GTV'): the gross target volume of nasopharynx cancer, on the same 50 scans as task1.
  8- task3 ('Thoracic_OAR'): 6 organs at risk in chest CT scans of lung cancer patients.
  9- task4 ('Lung_GTV'): the gross target volume of lung cancer, on the same 50 scans as task3.
 10The 10 test volumes per task are distributed without annotations and are therefore not included here.
 11
 12NOTE: The label legend is as follows (see `LABEL_IDS`):
 13- task1: background: 0, brainstem: 1, left eye: 2, right eye: 3, left lens: 4, right lens: 5,
 14  left optic nerve: 6, right optic nerve: 7, optic chiasm: 8, left temporal lobe: 9,
 15  right temporal lobe: 10, pituitary: 11, left parotid gland: 12, right parotid gland: 13,
 16  left inner ear: 14, right inner ear: 15, left middle ear: 16, right middle ear: 17,
 17  left temporomandibular joint: 18, right temporomandibular joint: 19, spinal cord: 20,
 18  left mandible: 21, right mandible: 22
 19- task2: background: 0, nasopharynx cancer gross target volume: 1
 20- task3: background: 0, left lung: 1, right lung: 2, heart: 3, esophagus: 4, trachea: 5, spinal cord: 6
 21- task4: background: 0, lung cancer gross target volume: 1
 22The id order is taken from https://github.com/zhilothebest/Coronary_Calcium, which transcribes the readme of
 23the official release: task1 lists 'Brain_Stem, Eye_L, Eye_R, Lens_L, Lens_R, Opt_Nerve_L, Opt_Nerve_R,
 24Opt_Chiasma, Temporal_Lobes_L, Temporal_Lobes_R, Pituitary, Parotid_Gland_L, Parotid_Gland_R, Inner_Ear_L,
 25Inner_Ear_R, Mid_Ear_L, Mid_Ear_R, T_M_Joint_L, T_M_Joint_R, Spinal_Cord, Mandible_L, Mandible_R' and task3
 26lists 'Lung_L, Lung_R, Heart, Esophagus, Trachea, Spinal_Cord', each 'corresponding to the label 1 to N in
 27the annotation file'. NOTE: Neither order is the order in which the organs are listed on
 28https://structseg2019.grand-challenge.org/Dataset/, and the per-label statistics published in
 29https://github.com/openmedlab/Awesome-Medical-Dataset/blob/main/resources/StructSeg2019_Task3.md carry the
 30task3 names in the website order instead of the id order, so they call id 3 the spinal cord when it is in
 31fact the heart.
 32
 33The task1 ids were verified on the data: all 50 volumes contain exactly the ids 0 - 22, the mean intensity
 34identifies the bony structures (inner ear 1043 HU, mandible 734 HU, temporomandibular joint 408 HU) and the
 35soft tissue ones (eye 25 HU, brainstem 29 HU, parotid gland 22 HU), the mean volumes match the expected
 36anatomy (lens 0.27 cm3, pituitary 0.58 cm3, optic chiasm 1.05 cm3, eye 9.1 cm3, brainstem 27 cm3,
 37temporal lobe 106 cm3), and in the LPS orientation of the volumes every structure named '_l' lies on the
 38patient's left of its '_r' partner.
 39
 40The task3 ids 1 - 4 were confirmed independently of that transcription, against the copy of the task3 volumes
 41redistributed in https://huggingface.co/datasets/blueyo0/SA-Med3D-140K (its 'ct_general_StructSeg2019_subtask2'
 42entries), which stores one named binary mask per organ. Over the 50 cases the volumes of its 'lung_left',
 43'lung_right', 'heart' and 'esophagus' masks match the statistics published for the ids 1, 2, 3 and 4 to within
 440.3% (median 1324 / 1842 / 690 / 35.7 cm3 against 1325 / 1841 / 692 / 35.5 cm3, and the minima and maxima agree
 45just as closely). The ids 5 and 6 could not be checked that way, since that copy holds neither a trachea nor a
 46spinal cord mask, so they rest on the transcribed readme alone.
 47
 48The data of 'task1' is downloaded from the redistribution at
 49https://huggingface.co/datasets/Luffy503/VoCo_Downstream (the file 'Dataset190_Structseg19.zip'), which is
 50published as part of the VoCo benchmark (https://github.com/Luffy03/Large-Scale-Medical) and is tagged
 51Apache-2.0 by its uploader. NOTE: This is a third-party redistribution of data that the official release
 52only hands out to registered challenge participants, and the StructSeg organizers did not grant a license
 53for it. The participant agreement is quoted in https://github.com/zhilothebest/Coronary_Calcium as saying
 54that 'the datasets can not be publicly posted, or distributed to anyone outside of this project', so please
 55make sure that you are allowed to use the data for your purpose. The redistribution
 56stores the data in the nnU-Net layout, which this module converts to the layout of the official release.
 57Its content was verified against the official release: 50 CT volumes, the label ids 0 - 22, and the same
 58file sizes as the copy of 'Task1_HaN_OAR.zip' that is linked from
 59https://github.com/openmedlab/MedLSAM.
 60
 61NOTE: There is no openly published copy of 'task2', 'task3' and 'task4'. They require registration and
 62cannot be downloaded automatically. Please follow these steps:
 63- Register for the challenge at https://structseg2019.grand-challenge.org and join it, then visit
 64  https://structseg2019.grand-challenge.org/Download/ and follow the instructions of the organizers to
 65  obtain the archives 'Task2_Naso_GTV.zip', 'Task3_Thoracic_OAR.zip' and 'Task4_Lung_GTV.zip' (the GTV
 66  tasks are also deposited at https://doi.org/10.21227/h75x-gt46, which needs a paid IEEE DataPort
 67  subscription and holds only the 83 byte file 'download url.txt' with a download url, not the images).
 68- Extract the archives of the tasks you need into '<path>', such that
 69  '<path>/Naso_GTV/<case_id>/data.nii.gz' and '<path>/Naso_GTV/<case_id>/label.nii.gz' exist
 70  (and equivalently '<path>/Thoracic_OAR/...' and '<path>/Lung_GTV/...').
 71  The case ids are the numbers 1 - 50. An extra enclosing folder, e.g.
 72  '<path>/Task3_Thoracic_OAR/Thoracic_OAR/1/data.nii.gz', is handled as well. A manual download of
 73  'Task1_HaN_OAR.zip' is picked up in the same way and is then used instead of the redistribution.
 74The only openly published parts of these three tasks that could be found are in
 75https://huggingface.co/datasets/blueyo0/SA-Med3D-140K, which is not a usable substitute: it holds the 50
 76task3 volumes with masks for only 4 of the 6 organs (no spinal cord and no trachea), the 50 task2 volumes
 77without any labels, and nothing at all of task4, all resampled to an isotropic spacing of 1.5 mm. Note that
 78its task numbering differs from the challenge: its 'subtask2' entries are task3 and its 'subtask4' entries
 79are task2.
 80
 81The most promising lead for the two GTV tasks is https://rec.ustc.edu.cn/share/b812d430-f577-11ed-a202-03afc6a1d18f,
 82which https://github.com/shijun18/GTV_AutoSeg offers as the code and data of a paper that evaluates on exactly
 83task2 and task4, and whose author also made the IEEE DataPort deposit above, so it is probably the url that
 84the deposit points to. It is not open either: listing that share needs a login at the hosting site.
 85
 86The dataset is located at https://structseg2019.grand-challenge.org.
 87
 88The challenge does not have a dedicated publication. Please cite the challenge website and, for the
 89gross target volume tasks, the data record https://doi.org/10.21227/h75x-gt46 if you use this dataset
 90in your research.
 91"""
 92
 93import os
 94import shutil
 95from glob import glob
 96from natsort import natsorted
 97from typing import Union, Tuple, Literal, List
 98
 99from torch.utils.data import Dataset, DataLoader
100
101import torch_em
102
103from .. import util
104
105
106TASK_FOLDERS = {
107    "task1": "HaN_OAR", "task2": "Naso_GTV", "task3": "Thoracic_OAR", "task4": "Lung_GTV",
108}
109
110# Only 'task1' has an openly published copy, see the module docstring.
111URLS = {"task1": "https://huggingface.co/datasets/Luffy503/VoCo_Downstream/resolve/main/Dataset190_Structseg19.zip"}
112
113CHECKSUMS = {"task1": "0e818cba20bbb783512c6062abc8e2adb43545f5870d630128df1912006c7703"}
114
115MIRROR_FOLDERS = {"task1": "Dataset190_Structseg19"}
116
117HAN_OAR_NAMES = [
118    "brainstem", "eye_l", "eye_r", "lens_l", "lens_r", "optic_nerve_l", "optic_nerve_r", "optic_chiasm",
119    "temporal_lobe_l", "temporal_lobe_r", "pituitary", "parotid_gland_l", "parotid_gland_r", "inner_ear_l",
120    "inner_ear_r", "middle_ear_l", "middle_ear_r", "tm_joint_l", "tm_joint_r", "spinal_cord", "mandible_l",
121    "mandible_r",
122]
123
124THORACIC_OAR_NAMES = ["lung_l", "lung_r", "heart", "esophagus", "trachea", "spinal_cord"]
125
126LABEL_IDS = {
127    "task1": {"background": 0, **{name: i + 1 for i, name in enumerate(HAN_OAR_NAMES)}},
128    "task2": {"background": 0, "nasopharynx_gtv": 1},
129    "task3": {"background": 0, **{name: i + 1 for i, name in enumerate(THORACIC_OAR_NAMES)}},
130    "task4": {"background": 0, "lung_gtv": 1},
131}
132
133
134def _find_task_dir(path, task):
135    folder = TASK_FOLDERS[task]
136    candidates = [os.path.join(path, folder), *glob(os.path.join(path, "*", folder))]
137    for candidate in candidates:
138        if len(glob(os.path.join(candidate, "*", "data.nii.gz"))) > 0:
139            return candidate
140    return None
141
142
143def _convert_mirror_layout(mirror_dir, data_dir):
144    """Convert the nnU-Net layout of the redistribution into the layout of the official release."""
145    image_paths = natsorted(glob(os.path.join(mirror_dir, "imagesTr", "*_0000.nii.gz")))
146    assert len(image_paths) > 0, f"Could not find any images in '{mirror_dir}'."
147
148    for image_path in image_paths:
149        case_id = os.path.basename(image_path)[:-len("_0000.nii.gz")]
150        case_dir = os.path.join(data_dir, case_id)
151        os.makedirs(case_dir, exist_ok=True)
152
153        label_path = os.path.join(mirror_dir, "labelsTr", f"{case_id}.nii.gz")
154        assert os.path.exists(label_path), f"Could not find the labels for case '{case_id}'."
155
156        shutil.move(image_path, os.path.join(case_dir, "data.nii.gz"))
157        shutil.move(label_path, os.path.join(case_dir, "label.nii.gz"))
158
159    shutil.rmtree(mirror_dir)
160
161
162def get_structseg_data(
163    path: Union[os.PathLike, str], task: Literal["task1", "task2", "task3", "task4"], download: bool = False
164) -> str:
165    """Obtain the StructSeg dataset.
166
167    Args:
168        path: Filepath to a folder where the data is stored.
169        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
170        download: Whether to download the data if it is not present.
171
172    Returns:
173        Filepath where the data of the chosen task is stored.
174    """
175    if task not in TASK_FOLDERS:
176        raise ValueError(f"'{task}' is not a valid task. Choose one of {list(TASK_FOLDERS.keys())}.")
177
178    data_dir = _find_task_dir(path, task)
179    if data_dir is not None:
180        return data_dir
181
182    if download and task in URLS:
183        os.makedirs(path, exist_ok=True)
184
185        zip_path = os.path.join(path, os.path.basename(URLS[task]))
186        util.download_source(path=zip_path, url=URLS[task], download=download, checksum=CHECKSUMS[task])
187        util.unzip(zip_path=zip_path, dst=path)
188
189        data_dir = os.path.join(path, TASK_FOLDERS[task])
190        _convert_mirror_layout(os.path.join(path, MIRROR_FOLDERS[task]), data_dir)
191        return data_dir
192
193    msg = f"Could not find the StructSeg '{task}' data ('{TASK_FOLDERS[task]}') at '{path}'. "
194    msg += "'torch_em' cannot download this dataset, as it requires registration for the challenge. "
195    msg += "Please register at https://structseg2019.grand-challenge.org, join the challenge and follow the "
196    msg += "instructions at https://structseg2019.grand-challenge.org/Download/ to obtain the archive "
197    msg += f"'Task{task[-1]}_{TASK_FOLDERS[task]}.zip'. Then extract it into '{path}', such that "
198    msg += f"'{os.path.join(path, TASK_FOLDERS[task], '1', 'data.nii.gz')}' exists."
199    if download:
200        raise NotImplementedError(msg)
201    else:
202        raise FileNotFoundError(msg)
203
204
205def get_structseg_paths(
206    path: Union[os.PathLike, str], task: Literal["task1", "task2", "task3", "task4"], download: bool = False
207) -> Tuple[List[str], List[str]]:
208    """Get paths to the StructSeg data.
209
210    Args:
211        path: Filepath to a folder where the data is stored.
212        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
213        download: Whether to download the data if it is not present.
214
215    Returns:
216        List of filepaths for the image data.
217        List of filepaths for the label data.
218    """
219    data_dir = get_structseg_data(path, task, download)
220
221    raw_paths = natsorted(glob(os.path.join(data_dir, "*", "data.nii.gz")))
222    label_paths = [os.path.join(os.path.dirname(p), "label.nii.gz") for p in raw_paths]
223    assert len(raw_paths) > 0 and all(os.path.exists(p) for p in label_paths)
224
225    return raw_paths, label_paths
226
227
228def get_structseg_dataset(
229    path: Union[os.PathLike, str],
230    patch_shape: Tuple[int, ...],
231    task: Literal["task1", "task2", "task3", "task4"] = "task1",
232    resize_inputs: bool = False,
233    download: bool = False,
234    **kwargs
235) -> Dataset:
236    """Get the StructSeg dataset for organ-at-risk and gross target volume segmentation.
237
238    Args:
239        path: Filepath to a folder where the data is stored.
240        patch_shape: The patch shape to use for training.
241        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
242        resize_inputs: Whether to resize inputs to the desired patch shape.
243        download: Whether to download the data if it is not present.
244        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
245
246    Returns:
247        The segmentation dataset.
248    """
249    raw_paths, label_paths = get_structseg_paths(path, task, download)
250
251    if resize_inputs:
252        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
253        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
254            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
255        )
256
257    return torch_em.default_segmentation_dataset(
258        raw_paths=raw_paths,
259        raw_key="data",
260        label_paths=label_paths,
261        label_key="data",
262        patch_shape=patch_shape,
263        is_seg_dataset=True,
264        **kwargs
265    )
266
267
268def get_structseg_loader(
269    path: Union[os.PathLike, str],
270    batch_size: int,
271    patch_shape: Tuple[int, ...],
272    task: Literal["task1", "task2", "task3", "task4"] = "task1",
273    resize_inputs: bool = False,
274    download: bool = False,
275    **kwargs
276) -> DataLoader:
277    """Get the StructSeg dataloader for organ-at-risk and gross target volume segmentation.
278
279    Args:
280        path: Filepath to a folder where the data is stored.
281        batch_size: The batch size for training.
282        patch_shape: The patch shape to use for training.
283        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
284        resize_inputs: Whether to resize inputs to the desired patch shape.
285        download: Whether to download the data if it is not present.
286        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
287
288    Returns:
289        The DataLoader.
290    """
291    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
292    dataset = get_structseg_dataset(path, patch_shape, task, resize_inputs, download, **ds_kwargs)
293    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
TASK_FOLDERS = {'task1': 'HaN_OAR', 'task2': 'Naso_GTV', 'task3': 'Thoracic_OAR', 'task4': 'Lung_GTV'}
URLS = {'task1': 'https://huggingface.co/datasets/Luffy503/VoCo_Downstream/resolve/main/Dataset190_Structseg19.zip'}
CHECKSUMS = {'task1': '0e818cba20bbb783512c6062abc8e2adb43545f5870d630128df1912006c7703'}
MIRROR_FOLDERS = {'task1': 'Dataset190_Structseg19'}
HAN_OAR_NAMES = ['brainstem', 'eye_l', 'eye_r', 'lens_l', 'lens_r', 'optic_nerve_l', 'optic_nerve_r', 'optic_chiasm', 'temporal_lobe_l', 'temporal_lobe_r', 'pituitary', 'parotid_gland_l', 'parotid_gland_r', 'inner_ear_l', 'inner_ear_r', 'middle_ear_l', 'middle_ear_r', 'tm_joint_l', 'tm_joint_r', 'spinal_cord', 'mandible_l', 'mandible_r']
THORACIC_OAR_NAMES = ['lung_l', 'lung_r', 'heart', 'esophagus', 'trachea', 'spinal_cord']
LABEL_IDS = {'task1': {'background': 0, 'brainstem': 1, 'eye_l': 2, 'eye_r': 3, 'lens_l': 4, 'lens_r': 5, 'optic_nerve_l': 6, 'optic_nerve_r': 7, 'optic_chiasm': 8, 'temporal_lobe_l': 9, 'temporal_lobe_r': 10, 'pituitary': 11, 'parotid_gland_l': 12, 'parotid_gland_r': 13, 'inner_ear_l': 14, 'inner_ear_r': 15, 'middle_ear_l': 16, 'middle_ear_r': 17, 'tm_joint_l': 18, 'tm_joint_r': 19, 'spinal_cord': 20, 'mandible_l': 21, 'mandible_r': 22}, 'task2': {'background': 0, 'nasopharynx_gtv': 1}, 'task3': {'background': 0, 'lung_l': 1, 'lung_r': 2, 'heart': 3, 'esophagus': 4, 'trachea': 5, 'spinal_cord': 6}, 'task4': {'background': 0, 'lung_gtv': 1}}
def get_structseg_data( path: Union[os.PathLike, str], task: Literal['task1', 'task2', 'task3', 'task4'], download: bool = False) -> str:
163def get_structseg_data(
164    path: Union[os.PathLike, str], task: Literal["task1", "task2", "task3", "task4"], download: bool = False
165) -> str:
166    """Obtain the StructSeg dataset.
167
168    Args:
169        path: Filepath to a folder where the data is stored.
170        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
171        download: Whether to download the data if it is not present.
172
173    Returns:
174        Filepath where the data of the chosen task is stored.
175    """
176    if task not in TASK_FOLDERS:
177        raise ValueError(f"'{task}' is not a valid task. Choose one of {list(TASK_FOLDERS.keys())}.")
178
179    data_dir = _find_task_dir(path, task)
180    if data_dir is not None:
181        return data_dir
182
183    if download and task in URLS:
184        os.makedirs(path, exist_ok=True)
185
186        zip_path = os.path.join(path, os.path.basename(URLS[task]))
187        util.download_source(path=zip_path, url=URLS[task], download=download, checksum=CHECKSUMS[task])
188        util.unzip(zip_path=zip_path, dst=path)
189
190        data_dir = os.path.join(path, TASK_FOLDERS[task])
191        _convert_mirror_layout(os.path.join(path, MIRROR_FOLDERS[task]), data_dir)
192        return data_dir
193
194    msg = f"Could not find the StructSeg '{task}' data ('{TASK_FOLDERS[task]}') at '{path}'. "
195    msg += "'torch_em' cannot download this dataset, as it requires registration for the challenge. "
196    msg += "Please register at https://structseg2019.grand-challenge.org, join the challenge and follow the "
197    msg += "instructions at https://structseg2019.grand-challenge.org/Download/ to obtain the archive "
198    msg += f"'Task{task[-1]}_{TASK_FOLDERS[task]}.zip'. Then extract it into '{path}', such that "
199    msg += f"'{os.path.join(path, TASK_FOLDERS[task], '1', 'data.nii.gz')}' exists."
200    if download:
201        raise NotImplementedError(msg)
202    else:
203        raise FileNotFoundError(msg)

Obtain the StructSeg dataset.

Arguments:
  • path: Filepath to a folder where the data is stored.
  • task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
  • download: Whether to download the data if it is not present.
Returns:

Filepath where the data of the chosen task is stored.

def get_structseg_paths( path: Union[os.PathLike, str], task: Literal['task1', 'task2', 'task3', 'task4'], download: bool = False) -> Tuple[List[str], List[str]]:
206def get_structseg_paths(
207    path: Union[os.PathLike, str], task: Literal["task1", "task2", "task3", "task4"], download: bool = False
208) -> Tuple[List[str], List[str]]:
209    """Get paths to the StructSeg data.
210
211    Args:
212        path: Filepath to a folder where the data is stored.
213        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
214        download: Whether to download the data if it is not present.
215
216    Returns:
217        List of filepaths for the image data.
218        List of filepaths for the label data.
219    """
220    data_dir = get_structseg_data(path, task, download)
221
222    raw_paths = natsorted(glob(os.path.join(data_dir, "*", "data.nii.gz")))
223    label_paths = [os.path.join(os.path.dirname(p), "label.nii.gz") for p in raw_paths]
224    assert len(raw_paths) > 0 and all(os.path.exists(p) for p in label_paths)
225
226    return raw_paths, label_paths

Get paths to the StructSeg data.

Arguments:
  • path: Filepath to a folder where the data is stored.
  • task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
  • download: Whether to download the data if it is not present.
Returns:

List of filepaths for the image data. List of filepaths for the label data.

def get_structseg_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], task: Literal['task1', 'task2', 'task3', 'task4'] = 'task1', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
229def get_structseg_dataset(
230    path: Union[os.PathLike, str],
231    patch_shape: Tuple[int, ...],
232    task: Literal["task1", "task2", "task3", "task4"] = "task1",
233    resize_inputs: bool = False,
234    download: bool = False,
235    **kwargs
236) -> Dataset:
237    """Get the StructSeg dataset for organ-at-risk and gross target volume segmentation.
238
239    Args:
240        path: Filepath to a folder where the data is stored.
241        patch_shape: The patch shape to use for training.
242        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
243        resize_inputs: Whether to resize inputs to the desired patch shape.
244        download: Whether to download the data if it is not present.
245        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
246
247    Returns:
248        The segmentation dataset.
249    """
250    raw_paths, label_paths = get_structseg_paths(path, task, download)
251
252    if resize_inputs:
253        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
254        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
255            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
256        )
257
258    return torch_em.default_segmentation_dataset(
259        raw_paths=raw_paths,
260        raw_key="data",
261        label_paths=label_paths,
262        label_key="data",
263        patch_shape=patch_shape,
264        is_seg_dataset=True,
265        **kwargs
266    )

Get the StructSeg dataset for organ-at-risk and gross target volume segmentation.

Arguments:
  • path: Filepath to a folder where the data is stored.
  • patch_shape: The patch shape to use for training.
  • task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
  • 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_structseg_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, ...], task: Literal['task1', 'task2', 'task3', 'task4'] = 'task1', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
269def get_structseg_loader(
270    path: Union[os.PathLike, str],
271    batch_size: int,
272    patch_shape: Tuple[int, ...],
273    task: Literal["task1", "task2", "task3", "task4"] = "task1",
274    resize_inputs: bool = False,
275    download: bool = False,
276    **kwargs
277) -> DataLoader:
278    """Get the StructSeg dataloader for organ-at-risk and gross target volume segmentation.
279
280    Args:
281        path: Filepath to a folder where the data is stored.
282        batch_size: The batch size for training.
283        patch_shape: The patch shape to use for training.
284        task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
285        resize_inputs: Whether to resize inputs to the desired patch shape.
286        download: Whether to download the data if it is not present.
287        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
288
289    Returns:
290        The DataLoader.
291    """
292    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
293    dataset = get_structseg_dataset(path, patch_shape, task, resize_inputs, download, **ds_kwargs)
294    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the StructSeg dataloader for organ-at-risk and gross target volume segmentation.

Arguments:
  • path: Filepath to a folder where the data is stored.
  • batch_size: The batch size for training.
  • patch_shape: The patch shape to use for training.
  • task: The task of the challenge. Either 'task1', 'task2', 'task3' or 'task4'.
  • 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.