BraTS 0.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brats/__init__.py +16 -0
- brats/constants.py +268 -0
- brats/core/__init__.py +0 -0
- brats/core/brats_algorithm.py +264 -0
- brats/core/docker.py +474 -0
- brats/core/inpainting_algorithms.py +155 -0
- brats/core/missing_mri_algorithms.py +185 -0
- brats/core/segmentation_algorithms.py +491 -0
- brats/core/singularity.py +268 -0
- brats/data/meta/adult_glioma_pre_and_post_treatment.yml +123 -0
- brats/data/meta/adult_glioma_pre_treatment.yml +64 -0
- brats/data/meta/africa.yml +185 -0
- brats/data/meta/goat.yml +84 -0
- brats/data/meta/inpainting.yml +163 -0
- brats/data/meta/meningioma.yml +96 -0
- brats/data/meta/meningioma_rt.yml +113 -0
- brats/data/meta/metastases.yml +94 -0
- brats/data/meta/missing_mri.yml +125 -0
- brats/data/meta/pediatric.yml +168 -0
- brats/data/parameters/brats23_africa_sparkunn.yml +63 -0
- brats/data/parameters/brats23_inpainting_domaso.yml +44 -0
- brats/data/parameters/brats23_missing_mri_onembyte.yml +5 -0
- brats/data/parameters/brats24_adult_glioma_mist.yml +6 -0
- brats/data/parameters/brats24_inpainting_smint.yml +37 -0
- brats/data/parameters/brats24_missing_mri_cgv.yml +72 -0
- brats/data/parameters/dummy.yml +0 -0
- brats/preprocessing.py +435 -0
- brats/utils/__init__.py +0 -0
- brats/utils/algorithm_config.py +102 -0
- brats/utils/data_handling.py +116 -0
- brats/utils/exceptions.py +25 -0
- brats/utils/logging.py +73 -0
- brats/utils/zenodo.py +217 -0
- brats-0.0.0.dev0.dist-info/METADATA +581 -0
- brats-0.0.0.dev0.dist-info/RECORD +37 -0
- brats-0.0.0.dev0.dist-info/WHEEL +4 -0
- brats-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
brats/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from loguru import logger
|
|
2
|
+
|
|
3
|
+
from brats.core.inpainting_algorithms import Inpainter
|
|
4
|
+
from brats.core.missing_mri_algorithms import MissingMRI
|
|
5
|
+
from brats.core.segmentation_algorithms import (
|
|
6
|
+
AdultGliomaPreTreatmentSegmenter,
|
|
7
|
+
AdultGliomaPreAndPostTreatmentSegmenter,
|
|
8
|
+
AfricaSegmenter,
|
|
9
|
+
GoATSegmenter,
|
|
10
|
+
MeningiomaSegmenter,
|
|
11
|
+
MetastasesSegmenter,
|
|
12
|
+
MeningiomaRTSegmenter,
|
|
13
|
+
PediatricSegmenter,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger.remove()
|
brats/constants.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
# TASK ENUM
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Backends(str, Enum):
|
|
8
|
+
"""Available backends for running the algorithms."""
|
|
9
|
+
|
|
10
|
+
DOCKER = "docker"
|
|
11
|
+
"""Run the algorithms using Docker containers."""
|
|
12
|
+
|
|
13
|
+
SINGULARITY = "singularity"
|
|
14
|
+
"""Run the algorithms using Singularity containers."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Task(str, Enum):
|
|
18
|
+
"""Available tasks."""
|
|
19
|
+
|
|
20
|
+
SEGMENTATION = "SEGMENTATION"
|
|
21
|
+
"""Segmentation task."""
|
|
22
|
+
|
|
23
|
+
INPAINTING = "INPAINTING"
|
|
24
|
+
"""Inpainting task."""
|
|
25
|
+
|
|
26
|
+
MISSING_MRI = "MISSING_MRI"
|
|
27
|
+
"""Missing MRI task."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ALGORITHM ENUMS
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Algorithms(str, Enum):
|
|
34
|
+
"""Parent class for constants of the available algorithms."""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AdultGliomaPreAndPostTreatmentAlgorithms(Algorithms):
|
|
40
|
+
"""Constants for the available adult glioma pre and post treatment segmentation algorithms."""
|
|
41
|
+
|
|
42
|
+
BraTS25_1 = "BraTS25_1"
|
|
43
|
+
""" BraTS25 Adult Glioma Segmentation 1st place """
|
|
44
|
+
BraTS25_2 = "BraTS25_2"
|
|
45
|
+
""" BraTS25 Adult Glioma Segmentation 2nd place """
|
|
46
|
+
BraTS25_3A = "BraTS25_3A"
|
|
47
|
+
""" BraTS25 Adult Glioma Segmentation 3rd place (tie) """
|
|
48
|
+
BraTS25_3B = "BraTS25_3B"
|
|
49
|
+
""" BraTS25 Adult Glioma Segmentation 3rd place (tie) """
|
|
50
|
+
|
|
51
|
+
BraTS24_1 = "BraTS24_1"
|
|
52
|
+
""" BraTS24 Adult Glioma Segmentation 1st place """
|
|
53
|
+
BraTS24_2 = "BraTS24_2"
|
|
54
|
+
""" BraTS24 Adult Glioma Segmentation 2nd place """
|
|
55
|
+
BraTS24_3 = "BraTS24_3"
|
|
56
|
+
""" BraTS24 Adult Glioma Segmentation 3rd place """
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AdultGliomaPreTreatmentAlgorithms(Algorithms):
|
|
60
|
+
"""Constants for the available adult glioma pre treatment segmentation algorithms."""
|
|
61
|
+
|
|
62
|
+
BraTS23_1 = "BraTS23_1"
|
|
63
|
+
"""BraTS23 Adult Glioma Segmentation 1st place (GPU only)"""
|
|
64
|
+
BraTS23_2 = "BraTS23_2"
|
|
65
|
+
"""BraTS23 Adult Glioma Segmentation 2nd place (GPU only)"""
|
|
66
|
+
BraTS23_3 = "BraTS23_3"
|
|
67
|
+
"""BraTS23 Adult Glioma Segmentation 3rd place (GPU only)"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class MeningiomaRTAlgorithms(Algorithms):
|
|
71
|
+
"""Constants for the available meningioma segmentation - Radio Therapy algorithms."""
|
|
72
|
+
|
|
73
|
+
BraTS25_1 = "BraTS25_1"
|
|
74
|
+
""" BraTS25 Meningioma Segmentation 1st place """
|
|
75
|
+
BraTS25_2 = "BraTS25_2"
|
|
76
|
+
""" BraTS25 Meningioma Segmentation 2nd place """
|
|
77
|
+
BraTS25_3 = "BraTS25_3"
|
|
78
|
+
""" BraTS25 Meningioma Segmentation 3rd place """
|
|
79
|
+
|
|
80
|
+
BraTS24_1 = "BraTS24_1"
|
|
81
|
+
""" BraTS24 Meningioma Segmentation 1st place """
|
|
82
|
+
BraTS24_2 = "BraTS24_2"
|
|
83
|
+
""" BraTS24 Meningioma Segmentation 2nd place """
|
|
84
|
+
BraTS24_3 = "BraTS24_3"
|
|
85
|
+
""" BraTS24 Meningioma Segmentation 3rd place """
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MeningiomaAlgorithms(Algorithms):
|
|
89
|
+
"""Constants for the available meningioma segmentation algorithms."""
|
|
90
|
+
|
|
91
|
+
BraTS25_1 = "BraTS25_1"
|
|
92
|
+
"""BraTS25 Meningioma Segmentation 1st place """
|
|
93
|
+
BraTS25_2 = "BraTS25_2"
|
|
94
|
+
"""BraTS25 Meningioma Segmentation 2nd place """
|
|
95
|
+
|
|
96
|
+
BraTS23_1 = "BraTS23_1"
|
|
97
|
+
"""BraTS23 Meningioma Segmentation 1st place (GPU only)"""
|
|
98
|
+
BraTS23_2 = "BraTS23_2"
|
|
99
|
+
"""BraTS23 Meningioma Segmentation 2nd place (GPU and CPU)"""
|
|
100
|
+
BraTS23_3 = "BraTS23_3"
|
|
101
|
+
"""BraTS23 Meningioma Segmentation 3rd place (GPU only)"""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class PediatricAlgorithms(Algorithms):
|
|
105
|
+
"""Constants for the available pediatric segmentation algorithms."""
|
|
106
|
+
|
|
107
|
+
BraTS25_1 = "BraTS25_1"
|
|
108
|
+
""" BraTS25 Pediatric Segmentation 1st place"""
|
|
109
|
+
BraTS25_2 = "BraTS25_2"
|
|
110
|
+
""" BraTS25 Pediatric Segmentation 2nd place"""
|
|
111
|
+
BraTS25_3 = "BraTS25_3"
|
|
112
|
+
""" BraTS25 Pediatric Segmentation 3rd place"""
|
|
113
|
+
|
|
114
|
+
BraTS24_1 = "BraTS24_1"
|
|
115
|
+
""" BraTS24 Pediatric Segmentation 1st place """
|
|
116
|
+
BraTS24_2 = "BraTS24_2"
|
|
117
|
+
""" BraTS24 Pediatric Segmentation 2nd place """
|
|
118
|
+
BraTS24_3 = "BraTS24_3"
|
|
119
|
+
""" BraTS24 Pediatric Segmentation 3rd place """
|
|
120
|
+
|
|
121
|
+
BraTS23_1 = "BraTS23_1"
|
|
122
|
+
"""BraTS23 Pediatric Segmentation 1st place (GPU only)"""
|
|
123
|
+
BraTS23_2 = "BraTS23_2"
|
|
124
|
+
"""BraTS23 Pediatric Segmentation 2nd place (GPU only)"""
|
|
125
|
+
BraTS23_3 = "BraTS23_3"
|
|
126
|
+
"""BraTS23 Pediatric Segmentation 3rd place (GPU only)"""
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AfricaAlgorithms(Algorithms):
|
|
130
|
+
"""Constants for the available africa segmentation algorithms."""
|
|
131
|
+
|
|
132
|
+
BraTS25_1 = "BraTS25_1"
|
|
133
|
+
""" BraTS25 BraTS-SSA Segmentation 1st place """
|
|
134
|
+
BraTS25_2A = "BraTS25_2A"
|
|
135
|
+
""" BraTS25 BraTS-SSA Segmentation 2nd place (Tie)"""
|
|
136
|
+
BraTS25_2B = "BraTS25_2B"
|
|
137
|
+
""" BraTS25 BraTS-SSA Segmentation 2nd place (Tie) """
|
|
138
|
+
BraTS25_3 = "BraTS25_3"
|
|
139
|
+
""" BraTS25 BraTS-SSA Segmentation 3rd place """
|
|
140
|
+
|
|
141
|
+
BraTS24_1 = "BraTS24_1"
|
|
142
|
+
""" BraTS24 BraTS-Africa Segmentation 1st place """
|
|
143
|
+
BraTS24_2 = "BraTS24_2"
|
|
144
|
+
""" BraTS24 BraTS-Africa Segmentation 2nd place """
|
|
145
|
+
BraTS24_3 = "BraTS24_3"
|
|
146
|
+
""" BraTS24 BraTS-Africa Segmentation 3rd place """
|
|
147
|
+
|
|
148
|
+
BraTS23_1 = "BraTS23_1"
|
|
149
|
+
"""BraTS23 BraTS-Africa Segmentation 1st place (GPU only)"""
|
|
150
|
+
BraTS23_2 = "BraTS23_2"
|
|
151
|
+
"""BraTS23 BraTS-Africa Segmentation 2nd place (GPU only)"""
|
|
152
|
+
BraTS23_3 = "BraTS23_3"
|
|
153
|
+
"""BraTS23 BraTS-Africa Segmentation 3rd place (GPU and CPU)"""
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class MetastasesAlgorithms(Algorithms):
|
|
157
|
+
"""Constants for the available Inpainting algorithms."""
|
|
158
|
+
|
|
159
|
+
BraTS25_1 = "BraTS25_1"
|
|
160
|
+
"""BraTS25 Brain Metastases Segmentation 1st place"""
|
|
161
|
+
BraTS25_2 = "BraTS25_2"
|
|
162
|
+
"""BraTS25 Brain Metastases Segmentation 2nd place"""
|
|
163
|
+
|
|
164
|
+
BraTS23_1 = "BraTS23_1"
|
|
165
|
+
"""BraTS23 Brain Metastases Segmentation 1st place (GPU only)"""
|
|
166
|
+
BraTS23_2 = "BraTS23_2"
|
|
167
|
+
"""BraTS23 Brain Metastases Segmentation 2nd place (GPU only)"""
|
|
168
|
+
BraTS23_3 = "BraTS23_3"
|
|
169
|
+
"""BraTS23 Brain Metastases Segmentation 3rd place (GPU only)"""
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class InpaintingAlgorithms(Algorithms):
|
|
173
|
+
"""Constants for the available BraTS Inpainting algorithms."""
|
|
174
|
+
|
|
175
|
+
BraTS25_1A = "BraTS25_1A"
|
|
176
|
+
""" BraTS25 Inpainting shared 1st place (tie) """
|
|
177
|
+
BraTS25_1B = "BraTS25_1B"
|
|
178
|
+
""" BraTS25 Inpainting shared 1st place (tie)"""
|
|
179
|
+
BraTS25_2 = "BraTS25_2"
|
|
180
|
+
""" BraTS25 Inpainting 2nd place """
|
|
181
|
+
|
|
182
|
+
BraTS24_1 = "BraTS24_1"
|
|
183
|
+
""" BraTS24 Inpainting 1st place """
|
|
184
|
+
BraTS24_2 = "BraTS24_2"
|
|
185
|
+
""" BraTS24 Inpainting 2nd place """
|
|
186
|
+
BraTS24_3 = "BraTS24_3"
|
|
187
|
+
""" BraTS24 Inpainting 3rd place """
|
|
188
|
+
|
|
189
|
+
BraTS23_1 = "BraTS23_1"
|
|
190
|
+
""" BraTS23 Inpainting 1st place """
|
|
191
|
+
BraTS23_2 = "BraTS23_2"
|
|
192
|
+
""" BraTS23 Inpainting 2nd place (Very Slow) """
|
|
193
|
+
BraTS23_3 = "BraTS23_3"
|
|
194
|
+
""" BraTS23 Inpainting 3rd place """
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class MissingMRIAlgorithms(Algorithms):
|
|
198
|
+
"""Constants for the available missing mri algorithms."""
|
|
199
|
+
|
|
200
|
+
BraTS25_1 = "BraTS25_1"
|
|
201
|
+
""" BraTS25 MissingMRI 1st place """
|
|
202
|
+
BraTS25_2 = "BraTS25_2"
|
|
203
|
+
""" BraTS25 MissingMRI 2nd place """
|
|
204
|
+
BraTS25_3 = "BraTS25_3"
|
|
205
|
+
""" BraTS25 MissingMRI 3rd place """
|
|
206
|
+
|
|
207
|
+
BraTS24_1 = "BraTS24_1"
|
|
208
|
+
""" BraTS24 MissingMRI 1st place """
|
|
209
|
+
BraTS24_2 = "BraTS24_2"
|
|
210
|
+
""" BraTS24 MissingMRI 2nd place """
|
|
211
|
+
BraTS24_3 = "BraTS24_3"
|
|
212
|
+
""" BraTS24 MissingMRI 3rd place """
|
|
213
|
+
|
|
214
|
+
BraTS23_1 = "BraTS23_1"
|
|
215
|
+
""" BraTS23 MissingMRI 1st place """
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class GoATAlgorithms(Algorithms):
|
|
219
|
+
"""Constants for the available missing mri algorithms."""
|
|
220
|
+
|
|
221
|
+
BraTS25_1A = "BraTS25_1A"
|
|
222
|
+
""" BraTS25 Generalizability Across Tumors (BraTS-GoAT) 1st place (tie) """
|
|
223
|
+
BraTS25_1B = "BraTS25_1B"
|
|
224
|
+
""" BraTS25 Generalizability Across Tumors (BraTS-GoAT) 1st place (tie) """
|
|
225
|
+
BraTS25_1C = "BraTS25_1C"
|
|
226
|
+
""" BraTS25 Generalizability Across Tumors (BraTS-GoAT) 1st place (tie) """
|
|
227
|
+
BraTS25_1D = "BraTS25_1D"
|
|
228
|
+
""" BraTS25 Generalizability Across Tumors (BraTS-GoAT) 1st place (tie) """
|
|
229
|
+
|
|
230
|
+
BraTS24_1 = "BraTS24_1"
|
|
231
|
+
""" BraTS24 Generalizability Across Tumors (BraTS-GoAT) 1st place (The only submission)"""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# DIRECTORIES
|
|
235
|
+
DATA_DIR = Path(__file__).parent / "data"
|
|
236
|
+
META_DIR = DATA_DIR / "meta"
|
|
237
|
+
PARAMETERS_DIR = DATA_DIR / "parameters"
|
|
238
|
+
ADDITIONAL_FILES_FOLDER = DATA_DIR / "additional_files"
|
|
239
|
+
|
|
240
|
+
# META DATA FILE PATHS
|
|
241
|
+
ADULT_GLIOMA_PRE_TREATMENT_SEGMENTATION_ALGORITHMS = (
|
|
242
|
+
META_DIR / "adult_glioma_pre_treatment.yml"
|
|
243
|
+
)
|
|
244
|
+
ADULT_GLIOMA_PRE_AND_POST_TREATMENT_SEGMENTATION_ALGORITHMS = (
|
|
245
|
+
META_DIR / "adult_glioma_pre_and_post_treatment.yml"
|
|
246
|
+
)
|
|
247
|
+
MENINGIOMA_SEGMENTATION_ALGORITHMS = META_DIR / "meningioma.yml"
|
|
248
|
+
MENINGIOMA_RT_SEGMENTATION_ALGORITHMS = META_DIR / "meningioma_rt.yml"
|
|
249
|
+
PEDIATRIC_SEGMENTATION_ALGORITHMS = META_DIR / "pediatric.yml"
|
|
250
|
+
AFRICA_SEGMENTATION_ALGORITHMS = META_DIR / "africa.yml"
|
|
251
|
+
METASTASES_SEGMENTATION_ALGORITHMS = META_DIR / "metastases.yml"
|
|
252
|
+
GOAT_SEGMENTATION_ALGORITHMS = META_DIR / "goat.yml"
|
|
253
|
+
INPAINTING_ALGORITHMS = META_DIR / "inpainting.yml"
|
|
254
|
+
MISSING_MRI_ALGORITHMS = META_DIR / "missing_mri.yml"
|
|
255
|
+
|
|
256
|
+
DUMMY_PARAMETERS = PARAMETERS_DIR / "dummy.yml"
|
|
257
|
+
|
|
258
|
+
# ZENODO
|
|
259
|
+
ZENODO_RECORD_BASE_URL = "https://zenodo.org/api/records"
|
|
260
|
+
|
|
261
|
+
OUTPUT_NAME_SCHEMA = {
|
|
262
|
+
Task.SEGMENTATION: "{subject_id}.nii.gz",
|
|
263
|
+
Task.INPAINTING: "{subject_id}-t1n-inference.nii.gz",
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# PACKAGE citation
|
|
268
|
+
PACKAGE_CITATION = "https://arxiv.org/abs/2506.13807"
|
brats/core/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Optional, Callable
|
|
7
|
+
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from brats.core.docker import run_container as run_docker_container
|
|
11
|
+
from brats.core.singularity import run_container as run_singularity_container
|
|
12
|
+
from brats.utils.algorithm_config import load_algorithms
|
|
13
|
+
from brats.constants import OUTPUT_NAME_SCHEMA, Algorithms, Task, Backends
|
|
14
|
+
from brats.utils.data_handling import InferenceSetup
|
|
15
|
+
from brats.utils.exceptions import AlgorithmConfigException
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BraTSAlgorithm(ABC):
|
|
19
|
+
"""
|
|
20
|
+
This class serves as the basis for all BraTS algorithms. It provides a common interface and implements the logic for single and batch inference.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
algorithm: Algorithms,
|
|
26
|
+
algorithms_file_path: Path,
|
|
27
|
+
task: Task,
|
|
28
|
+
cuda_devices: str = "0",
|
|
29
|
+
force_cpu: bool = False,
|
|
30
|
+
):
|
|
31
|
+
# inference device setup
|
|
32
|
+
self.force_cpu = force_cpu
|
|
33
|
+
self.cuda_devices = cuda_devices
|
|
34
|
+
|
|
35
|
+
self.task = task
|
|
36
|
+
self.algorithm_list = load_algorithms(file_path=algorithms_file_path)
|
|
37
|
+
# save algorithm identifier for logging etc.
|
|
38
|
+
self.algorithm_key = algorithm.value
|
|
39
|
+
if self.algorithm_key not in self.algorithm_list:
|
|
40
|
+
raise AlgorithmConfigException(
|
|
41
|
+
f"Algorithm {self.algorithm_key} not found in {algorithms_file_path}"
|
|
42
|
+
)
|
|
43
|
+
# data for selected algorithm
|
|
44
|
+
self.algorithm = self.algorithm_list[algorithm.value]
|
|
45
|
+
|
|
46
|
+
logger.info(
|
|
47
|
+
f"Instantiated {self.__class__.__name__} with algorithm: {self.algorithm_key} by {self.algorithm.meta.authors}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def _standardize_single_inputs(
|
|
52
|
+
self,
|
|
53
|
+
data_folder: Path,
|
|
54
|
+
subject_id: str,
|
|
55
|
+
inputs: dict[str, Path | str],
|
|
56
|
+
subject_modality_separator: str,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Standardize the input data to match the requirements of the selected algorithm.
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def _standardize_batch_inputs(
|
|
65
|
+
self, data_folder: Path, subjects: list[Path], input_name_schema: str
|
|
66
|
+
) -> Dict[str, str]:
|
|
67
|
+
"""
|
|
68
|
+
Standardize the input data to match the requirements of the selected algorithm.
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def extract_identifier_from_subject_id(self, subject_id: str) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Extract the index from the subject ID
|
|
75
|
+
Args:
|
|
76
|
+
subject_id (str): Subject ID of the input
|
|
77
|
+
Returns:
|
|
78
|
+
str: Extracted identifier
|
|
79
|
+
"""
|
|
80
|
+
return "-".join(subject_id.split("-")[-2:])
|
|
81
|
+
|
|
82
|
+
def _process_single_output(
|
|
83
|
+
self,
|
|
84
|
+
tmp_output_folder: Path | str,
|
|
85
|
+
subject_id: str,
|
|
86
|
+
output_file: Path,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Process the output of a single inference run and save it in the specified file.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
tmp_output_folder (Path | str): Folder with the algorithm output
|
|
93
|
+
subject_id (str): Subject ID of the output
|
|
94
|
+
output_file (Path): Path to the desired output file
|
|
95
|
+
"""
|
|
96
|
+
# rename output
|
|
97
|
+
if self.task == Task.MISSING_MRI:
|
|
98
|
+
# Missing MRI has no fixed names since the missing modality differs and is included in the name
|
|
99
|
+
algorithm_output = Path(tmp_output_folder).iterdir().__next__()
|
|
100
|
+
else:
|
|
101
|
+
# extract id from subject id, i.e. BraTS-MEN-00000-000 => 00000-000
|
|
102
|
+
identifier = self.extract_identifier_from_subject_id(subject_id)
|
|
103
|
+
possible_output = list(Path(tmp_output_folder).glob(f"*{identifier}*"))
|
|
104
|
+
if len(possible_output) == 0:
|
|
105
|
+
raise FileNotFoundError(
|
|
106
|
+
f"No output found for subject {subject_id} in {tmp_output_folder}"
|
|
107
|
+
)
|
|
108
|
+
algorithm_output = possible_output[0]
|
|
109
|
+
|
|
110
|
+
# ensure path exists and rename output to the desired path
|
|
111
|
+
output_file = Path(output_file).absolute()
|
|
112
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
shutil.move(algorithm_output, output_file)
|
|
114
|
+
|
|
115
|
+
def _process_batch_output(
|
|
116
|
+
self,
|
|
117
|
+
tmp_output_folder: Path | str,
|
|
118
|
+
output_folder: Path,
|
|
119
|
+
mapping: dict[str, str],
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Process the outputs of a batch inference run and save them in the specified folder.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
tmp_output_folder (Path | str): Folder with the algorithm outputs
|
|
126
|
+
output_folder (Path): Folder to save the outputs
|
|
127
|
+
mapping (dict[str, str]): Mapping from internal to external subject names
|
|
128
|
+
"""
|
|
129
|
+
# move outputs and change name back to initially provided one
|
|
130
|
+
output_folder = Path(output_folder)
|
|
131
|
+
output_folder.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
for internal_name, external_name in mapping.items():
|
|
133
|
+
if self.task == Task.MISSING_MRI:
|
|
134
|
+
# Missing MRI has no fixed names since the missing modality differs and is included in the name
|
|
135
|
+
algorithm_output = (
|
|
136
|
+
Path(tmp_output_folder).glob(f"*{internal_name}*").__next__()
|
|
137
|
+
)
|
|
138
|
+
try:
|
|
139
|
+
modality = algorithm_output.name.split("-")[-1].split(".")[0]
|
|
140
|
+
except IndexError:
|
|
141
|
+
logger.warning(
|
|
142
|
+
f"Could not extract modality from {algorithm_output.name}"
|
|
143
|
+
)
|
|
144
|
+
modality = None
|
|
145
|
+
output_file = (
|
|
146
|
+
output_folder
|
|
147
|
+
/ f"{external_name}{'-' + modality if modality else ''}.nii.gz"
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
identifier = self.extract_identifier_from_subject_id(internal_name)
|
|
151
|
+
possible_outputs = list(Path(tmp_output_folder).glob(f"*{identifier}*"))
|
|
152
|
+
if len(possible_outputs) == 0:
|
|
153
|
+
logger.error(
|
|
154
|
+
f"No output found for subject {internal_name} in {tmp_output_folder}"
|
|
155
|
+
)
|
|
156
|
+
continue
|
|
157
|
+
algorithm_output = possible_outputs[0]
|
|
158
|
+
|
|
159
|
+
output_file = output_folder / f"{external_name}.nii.gz"
|
|
160
|
+
shutil.move(algorithm_output, output_file)
|
|
161
|
+
|
|
162
|
+
def _get_backend_runner(self, backend: Backends) -> Optional[Callable]:
|
|
163
|
+
backend_dispatch = {
|
|
164
|
+
Backends.DOCKER: run_docker_container,
|
|
165
|
+
Backends.SINGULARITY: run_singularity_container,
|
|
166
|
+
}
|
|
167
|
+
runner = backend_dispatch.get(backend, None)
|
|
168
|
+
return runner
|
|
169
|
+
|
|
170
|
+
def _infer_single(
|
|
171
|
+
self,
|
|
172
|
+
inputs: dict[str, Path | str],
|
|
173
|
+
output_file: Path | str,
|
|
174
|
+
log_file: Optional[Path | str] = None,
|
|
175
|
+
backend: Backends = Backends.DOCKER,
|
|
176
|
+
) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Perform a single inference run with the provided inputs and save the output in the specified file.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
inputs (dict[str, Path | str]): Input Images for the task
|
|
182
|
+
output_file (Path | str): File to save the output
|
|
183
|
+
log_file (Optional[Path | str], optional): Log file with extra information. Defaults to None.
|
|
184
|
+
backend (Backends | str, optional): Backend to use for inference. Defaults to Backends.DOCKER.
|
|
185
|
+
"""
|
|
186
|
+
with InferenceSetup(log_file=log_file) as (tmp_data_folder, tmp_output_folder):
|
|
187
|
+
logger.info(f"Performing single inference")
|
|
188
|
+
|
|
189
|
+
# the id here is arbitrary
|
|
190
|
+
subject_id = self.algorithm.run_args.input_name_schema.format(id=0)
|
|
191
|
+
|
|
192
|
+
self._standardize_single_inputs(
|
|
193
|
+
data_folder=tmp_data_folder,
|
|
194
|
+
subject_id=subject_id,
|
|
195
|
+
inputs=inputs,
|
|
196
|
+
subject_modality_separator=self.algorithm.run_args.subject_modality_separator,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
runner = self._get_backend_runner(backend)
|
|
200
|
+
if runner is None:
|
|
201
|
+
raise ValueError(f"Unsupported backend: {backend}")
|
|
202
|
+
runner(
|
|
203
|
+
algorithm=self.algorithm,
|
|
204
|
+
data_path=tmp_data_folder,
|
|
205
|
+
output_path=tmp_output_folder,
|
|
206
|
+
cuda_devices=self.cuda_devices,
|
|
207
|
+
force_cpu=self.force_cpu,
|
|
208
|
+
)
|
|
209
|
+
self._process_single_output(
|
|
210
|
+
tmp_output_folder=tmp_output_folder,
|
|
211
|
+
subject_id=subject_id,
|
|
212
|
+
output_file=Path(output_file),
|
|
213
|
+
)
|
|
214
|
+
logger.info(f"Saved output to: {Path(output_file).absolute()}")
|
|
215
|
+
|
|
216
|
+
def _infer_batch(
|
|
217
|
+
self,
|
|
218
|
+
data_folder: Path | str,
|
|
219
|
+
output_folder: Path | str,
|
|
220
|
+
log_file: Optional[Path | str] = None,
|
|
221
|
+
backend: Backends = Backends.DOCKER,
|
|
222
|
+
):
|
|
223
|
+
"""Perform a batch inference run with the provided inputs and save the outputs in the specified folder.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
data_folder (Path | str): Folder with the input data
|
|
227
|
+
output_folder (Path | str): Folder to save the outputs
|
|
228
|
+
log_file (Optional[Path | str], optional): Log file with extra information. Defaults to None.
|
|
229
|
+
backend (Backends, optional): Backend to use for inference. Defaults to Backends.DOCKER.
|
|
230
|
+
"""
|
|
231
|
+
with InferenceSetup(log_file=log_file) as (tmp_data_folder, tmp_output_folder):
|
|
232
|
+
|
|
233
|
+
# find subjects
|
|
234
|
+
subjects = [f for f in Path(data_folder).iterdir() if f.is_dir()]
|
|
235
|
+
logger.info(
|
|
236
|
+
f"Found {len(subjects)} subjects: {', '.join([s.name for s in subjects][:5])} {' ...' if len(subjects) > 5 else '' }"
|
|
237
|
+
)
|
|
238
|
+
# map to brats names
|
|
239
|
+
internal_external_name_map = self._standardize_batch_inputs(
|
|
240
|
+
data_folder=tmp_data_folder,
|
|
241
|
+
subjects=subjects,
|
|
242
|
+
input_name_schema=self.algorithm.run_args.input_name_schema,
|
|
243
|
+
)
|
|
244
|
+
logger.info(f"Standardized input names to match algorithm requirements.")
|
|
245
|
+
runner = self._get_backend_runner(backend)
|
|
246
|
+
if runner is None:
|
|
247
|
+
raise ValueError(f"Unsupported backend: {backend}")
|
|
248
|
+
# run inference in container
|
|
249
|
+
runner(
|
|
250
|
+
algorithm=self.algorithm,
|
|
251
|
+
data_path=tmp_data_folder,
|
|
252
|
+
output_path=tmp_output_folder,
|
|
253
|
+
cuda_devices=self.cuda_devices,
|
|
254
|
+
force_cpu=self.force_cpu,
|
|
255
|
+
internal_external_name_map=internal_external_name_map,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
self._process_batch_output(
|
|
259
|
+
tmp_output_folder=tmp_output_folder,
|
|
260
|
+
output_folder=output_folder,
|
|
261
|
+
mapping=internal_external_name_map,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
logger.info(f"Saved outputs to: {Path(output_folder).absolute()}")
|