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/preprocessing.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
5
|
+
from brats.constants import (
|
|
6
|
+
Algorithms,
|
|
7
|
+
MissingMRIAlgorithms,
|
|
8
|
+
PediatricAlgorithms,
|
|
9
|
+
AdultGliomaPreAndPostTreatmentAlgorithms,
|
|
10
|
+
MeningiomaRTAlgorithms,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from brainles_preprocessing.modality import Modality, CenterModality
|
|
15
|
+
from brainles_preprocessing.preprocessor import (
|
|
16
|
+
AtlasCentricPreprocessor,
|
|
17
|
+
NativeSpacePreprocessor,
|
|
18
|
+
)
|
|
19
|
+
from brainles_preprocessing.constants import Atlas
|
|
20
|
+
from brainles_preprocessing.normalization import Normalizer
|
|
21
|
+
except ImportError as e:
|
|
22
|
+
raise ImportError(
|
|
23
|
+
"The `brainles_preprocessing` extra is required for preprocessing tasks, please ensure you installed it via `pip install brats[preprocessing]`. Note that this is only supported for Python >= 3.10."
|
|
24
|
+
) from e
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _coreg_atlasreg_bet(
|
|
28
|
+
atlas: Atlas,
|
|
29
|
+
t1_input: Optional[Union[str, Path]] = None,
|
|
30
|
+
t1c_input: Optional[Union[str, Path]] = None,
|
|
31
|
+
t2_input: Optional[Union[str, Path]] = None,
|
|
32
|
+
flair_input: Optional[Union[str, Path]] = None,
|
|
33
|
+
t1_output: Optional[Union[str, Path]] = None,
|
|
34
|
+
t1c_output: Optional[Union[str, Path]] = None,
|
|
35
|
+
t2_output: Optional[Union[str, Path]] = None,
|
|
36
|
+
flair_output: Optional[Union[str, Path]] = None,
|
|
37
|
+
allow_missing: bool = False,
|
|
38
|
+
normalizer: Optional[Normalizer] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
|
|
41
|
+
# Build modality mapping in a cleaner way
|
|
42
|
+
modality_pairs = [
|
|
43
|
+
("t1", t1_input, t1_output),
|
|
44
|
+
("t1c", t1c_input, t1c_output),
|
|
45
|
+
("t2", t2_input, t2_output),
|
|
46
|
+
("flair", flair_input, flair_output),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Filter valid modalities (both input and output must be provided)
|
|
50
|
+
valid_modalities = {
|
|
51
|
+
name: {"input_path": inp, "raw_bet_output_path": out}
|
|
52
|
+
for name, inp, out in modality_pairs
|
|
53
|
+
if inp is not None and out is not None
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Validate minimum requirements
|
|
57
|
+
min_required = 3 if allow_missing else 4
|
|
58
|
+
if len(valid_modalities) < min_required:
|
|
59
|
+
missing_desc = "at least three" if allow_missing else "all four"
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Need {missing_desc} modalities with valid input/output paths"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Choose center modality (prefer t1c, fallback to t1)
|
|
65
|
+
center_name = "t1c" if "t1c" in valid_modalities else "t1"
|
|
66
|
+
center_data = valid_modalities[center_name]
|
|
67
|
+
|
|
68
|
+
center = CenterModality(
|
|
69
|
+
modality_name=center_name,
|
|
70
|
+
input_path=center_data["input_path"],
|
|
71
|
+
raw_bet_output_path=center_data["raw_bet_output_path"],
|
|
72
|
+
atlas_correction=False,
|
|
73
|
+
normalizer=normalizer,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Create moving modalities (all except center)
|
|
77
|
+
moving_modalities = [
|
|
78
|
+
Modality(
|
|
79
|
+
modality_name=name,
|
|
80
|
+
input_path=data["input_path"],
|
|
81
|
+
raw_bet_output_path=data["raw_bet_output_path"],
|
|
82
|
+
atlas_correction=False,
|
|
83
|
+
normalizer=normalizer,
|
|
84
|
+
)
|
|
85
|
+
for name, data in valid_modalities.items()
|
|
86
|
+
if name != center_name
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
preprocessor = AtlasCentricPreprocessor(
|
|
90
|
+
center_modality=center,
|
|
91
|
+
moving_modalities=moving_modalities,
|
|
92
|
+
atlas_image_path=atlas,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
preprocessor.run()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _coreg_atlasreg_deface(
|
|
99
|
+
t1_input: Union[str, Path],
|
|
100
|
+
t1c_input: Union[str, Path],
|
|
101
|
+
t2_input: Union[str, Path],
|
|
102
|
+
flair_input: Union[str, Path],
|
|
103
|
+
t1_output: Union[str, Path],
|
|
104
|
+
t1c_output: Union[str, Path],
|
|
105
|
+
t2_output: Union[str, Path],
|
|
106
|
+
flair_output: Union[str, Path],
|
|
107
|
+
atlas: Atlas,
|
|
108
|
+
normalizer: Optional[Normalizer] = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
|
|
111
|
+
center = CenterModality(
|
|
112
|
+
normalizer=normalizer,
|
|
113
|
+
modality_name="t1c",
|
|
114
|
+
input_path=t1c_input,
|
|
115
|
+
raw_defaced_output_path=t1c_output,
|
|
116
|
+
atlas_correction=False,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
moving_modalities = [
|
|
120
|
+
Modality(
|
|
121
|
+
normalizer=normalizer,
|
|
122
|
+
modality_name="t1",
|
|
123
|
+
input_path=t1_input,
|
|
124
|
+
raw_defaced_output_path=t1_output,
|
|
125
|
+
atlas_correction=False,
|
|
126
|
+
),
|
|
127
|
+
Modality(
|
|
128
|
+
normalizer=normalizer,
|
|
129
|
+
modality_name="t2",
|
|
130
|
+
input_path=t2_input,
|
|
131
|
+
raw_defaced_output_path=t2_output,
|
|
132
|
+
atlas_correction=False,
|
|
133
|
+
),
|
|
134
|
+
Modality(
|
|
135
|
+
normalizer=normalizer,
|
|
136
|
+
modality_name="flair",
|
|
137
|
+
input_path=flair_input,
|
|
138
|
+
raw_defaced_output_path=flair_output,
|
|
139
|
+
atlas_correction=False,
|
|
140
|
+
),
|
|
141
|
+
]
|
|
142
|
+
preprocessor = AtlasCentricPreprocessor(
|
|
143
|
+
center_modality=center,
|
|
144
|
+
moving_modalities=moving_modalities,
|
|
145
|
+
atlas_image_path=atlas,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
preprocessor.run()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
#######
|
|
152
|
+
# Preprocessing functions for different BraTS challenges
|
|
153
|
+
#######
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def preprocess_deface_only(
|
|
157
|
+
t1c_input: Union[str, Path],
|
|
158
|
+
t1c_output: Union[str, Path],
|
|
159
|
+
normalizer: Optional[Normalizer] = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
preprocessor = NativeSpacePreprocessor(
|
|
162
|
+
center_modality=CenterModality(
|
|
163
|
+
normalizer=normalizer,
|
|
164
|
+
modality_name="t1c",
|
|
165
|
+
input_path=t1c_input,
|
|
166
|
+
raw_defaced_output_path=t1c_output,
|
|
167
|
+
atlas_correction=False,
|
|
168
|
+
),
|
|
169
|
+
moving_modalities=[],
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
preprocessor.run()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def preprocess_coreg_sri24reg_bet(
|
|
176
|
+
t1_input: Union[str, Path],
|
|
177
|
+
t1c_input: Union[str, Path],
|
|
178
|
+
t2_input: Union[str, Path],
|
|
179
|
+
flair_input: Union[str, Path],
|
|
180
|
+
t1_output: Union[str, Path],
|
|
181
|
+
t1c_output: Union[str, Path],
|
|
182
|
+
t2_output: Union[str, Path],
|
|
183
|
+
flair_output: Union[str, Path],
|
|
184
|
+
normalizer: Optional[Normalizer] = None,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""t1, t1c, t2, flair to SRI24 with co-registration and BET (most segmentation challenges and inpainting)
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
t1_input (Union[str, Path]): Path to the input T1 image.
|
|
190
|
+
t1c_input (Union[str, Path]): Path to the input T1c image
|
|
191
|
+
t2_input (Union[str, Path]): Path to the input T2 image.
|
|
192
|
+
flair_input (Union[str, Path]): Path to the input FLAIR image.
|
|
193
|
+
t1_output (Union[str, Path]): Path to the output preprocessed T1 image
|
|
194
|
+
t1c_output (Union[str, Path]): Path to the output preprocessed T1c image
|
|
195
|
+
t2_output (Union[str, Path]): Path to the output preprocessed T2 image
|
|
196
|
+
flair_output (Union[str, Path]): Path to the output preprocessed FLAIR image
|
|
197
|
+
normalizer (Optional[Normalizer]): Normalizer to apply during preprocessing
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
None
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
_coreg_atlasreg_bet(
|
|
204
|
+
t1_input=t1_input,
|
|
205
|
+
t1c_input=t1c_input,
|
|
206
|
+
t2_input=t2_input,
|
|
207
|
+
flair_input=flair_input,
|
|
208
|
+
t1_output=t1_output,
|
|
209
|
+
t1c_output=t1c_output,
|
|
210
|
+
t2_output=t2_output,
|
|
211
|
+
flair_output=flair_output,
|
|
212
|
+
atlas=Atlas.BRATS_SRI24,
|
|
213
|
+
normalizer=normalizer,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def preprocess_coreg_sri24reg_defacing(
|
|
218
|
+
t1_input: Union[str, Path],
|
|
219
|
+
t1c_input: Union[str, Path],
|
|
220
|
+
t2_input: Union[str, Path],
|
|
221
|
+
flair_input: Union[str, Path],
|
|
222
|
+
t1_output: Union[str, Path],
|
|
223
|
+
t1c_output: Union[str, Path],
|
|
224
|
+
t2_output: Union[str, Path],
|
|
225
|
+
flair_output: Union[str, Path],
|
|
226
|
+
normalizer: Optional[Normalizer] = None,
|
|
227
|
+
) -> None:
|
|
228
|
+
"""t1, t1c, t2, flair to SRI24 with co-registration and defacing (pediatric challenge)
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
t1_input (Union[str, Path]): Path to the input T1 image.
|
|
232
|
+
t1c_input (Union[str, Path]): Path to the input T1c image
|
|
233
|
+
t2_input (Union[str, Path]): Path to the input T2 image.
|
|
234
|
+
flair_input (Union[str, Path]): Path to the input FLAIR image.
|
|
235
|
+
t1_output (Union[str, Path]): Path to the output preprocessed T1 image
|
|
236
|
+
t1c_output (Union[str, Path]): Path to the output preprocessed T1c image
|
|
237
|
+
t2_output (Union[str, Path]): Path to the output preprocessed T2 image
|
|
238
|
+
flair_output (Union[str, Path]): Path to the output preprocessed FLAIR image
|
|
239
|
+
normalizer (Optional[Normalizer]): Normalizer to apply during preprocessing
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
None
|
|
243
|
+
|
|
244
|
+
"""
|
|
245
|
+
_coreg_atlasreg_deface(
|
|
246
|
+
t1_input=t1_input,
|
|
247
|
+
t1c_input=t1c_input,
|
|
248
|
+
t2_input=t2_input,
|
|
249
|
+
flair_input=flair_input,
|
|
250
|
+
t1_output=t1_output,
|
|
251
|
+
t1c_output=t1c_output,
|
|
252
|
+
t2_output=t2_output,
|
|
253
|
+
flair_output=flair_output,
|
|
254
|
+
atlas=Atlas.BRATS_SRI24,
|
|
255
|
+
normalizer=normalizer,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def preprocess_coreg_mni152reg_bet(
|
|
260
|
+
t1_input: Union[str, Path],
|
|
261
|
+
t1c_input: Union[str, Path],
|
|
262
|
+
t2_input: Union[str, Path],
|
|
263
|
+
flair_input: Union[str, Path],
|
|
264
|
+
t1_output: Union[str, Path],
|
|
265
|
+
t1c_output: Union[str, Path],
|
|
266
|
+
t2_output: Union[str, Path],
|
|
267
|
+
flair_output: Union[str, Path],
|
|
268
|
+
normalizer: Optional[Normalizer] = None,
|
|
269
|
+
) -> None:
|
|
270
|
+
"""t1, t1c, t2, flair to MNI152 with co-registration and BET (GLI Post)
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
t1_input (Union[str, Path]): Path to the input T1 image.
|
|
274
|
+
t1c_input (Union[str, Path]): Path to the input T1c image
|
|
275
|
+
t2_input (Union[str, Path]): Path to the input T2 image.
|
|
276
|
+
flair_input (Union[str, Path]): Path to the input FLAIR image.
|
|
277
|
+
t1_output (Union[str, Path]): Path to the output preprocessed T1 image
|
|
278
|
+
t1c_output (Union[str, Path]): Path to the output preprocessed T1c image
|
|
279
|
+
t2_output (Union[str, Path]): Path to the output preprocessed T2 image
|
|
280
|
+
flair_output (Union[str, Path]): Path to the output preprocessed FLAIR image
|
|
281
|
+
normalizer (Optional[Normalizer]): Normalizer to apply during preprocessing
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
None
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
_coreg_atlasreg_bet(
|
|
288
|
+
t1_input=t1_input,
|
|
289
|
+
t1c_input=t1c_input,
|
|
290
|
+
t2_input=t2_input,
|
|
291
|
+
flair_input=flair_input,
|
|
292
|
+
t1_output=t1_output,
|
|
293
|
+
t1c_output=t1c_output,
|
|
294
|
+
t2_output=t2_output,
|
|
295
|
+
flair_output=flair_output,
|
|
296
|
+
atlas=Atlas.BRATS_MNI152,
|
|
297
|
+
normalizer=normalizer,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def preprocess_coreg_sri24reg_bet_allow_missing(
|
|
302
|
+
t1_input: Optional[Union[str, Path]] = None,
|
|
303
|
+
t1c_input: Optional[Union[str, Path]] = None,
|
|
304
|
+
t2_input: Optional[Union[str, Path]] = None,
|
|
305
|
+
flair_input: Optional[Union[str, Path]] = None,
|
|
306
|
+
t1_output: Optional[Union[str, Path]] = None,
|
|
307
|
+
t1c_output: Optional[Union[str, Path]] = None,
|
|
308
|
+
t2_output: Optional[Union[str, Path]] = None,
|
|
309
|
+
flair_output: Optional[Union[str, Path]] = None,
|
|
310
|
+
normalizer: Optional[Normalizer] = None,
|
|
311
|
+
) -> None:
|
|
312
|
+
"""t1, t1c, t2, flair to SRI24 with co-registration and BET while allowing one missing modality (missing MRI challenge)
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
t1_input (Optional[Union[str, Path]]): Path to the input T1 image.
|
|
316
|
+
t1c_input (Optional[Union[str, Path]]): Path to the input T1c image
|
|
317
|
+
t2_input (Optional[Union[str, Path]]): Path to the input T2 image.
|
|
318
|
+
flair_input (Optional[Union[str, Path]]): Path to the input FLAIR image.
|
|
319
|
+
t1_output (Optional[Union[str, Path]]): Path to the output preprocessed T1 image
|
|
320
|
+
t1c_output (Optional[Union[str, Path]]): Path to the output preprocessed T1c image
|
|
321
|
+
t2_output (Optional[Union[str, Path]]): Path to the output preprocessed T2 image
|
|
322
|
+
flair_output (Optional[Union[str, Path]]): Path to the output preprocessed FLAIR image
|
|
323
|
+
normalizer (Optional[Normalizer]): Normalizer to apply during preprocessing
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
None
|
|
327
|
+
|
|
328
|
+
"""
|
|
329
|
+
_coreg_atlasreg_bet(
|
|
330
|
+
t1_input=t1_input,
|
|
331
|
+
t1c_input=t1c_input,
|
|
332
|
+
t2_input=t2_input,
|
|
333
|
+
flair_input=flair_input,
|
|
334
|
+
t1_output=t1_output,
|
|
335
|
+
t1c_output=t1c_output,
|
|
336
|
+
t2_output=t2_output,
|
|
337
|
+
flair_output=flair_output,
|
|
338
|
+
atlas=Atlas.BRATS_SRI24,
|
|
339
|
+
allow_missing=True,
|
|
340
|
+
normalizer=normalizer,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def preprocess_for_challenge(
|
|
345
|
+
challenge: Algorithms,
|
|
346
|
+
t1_input: Optional[Union[str, Path]] = None,
|
|
347
|
+
t1c_input: Optional[Union[str, Path]] = None,
|
|
348
|
+
t2_input: Optional[Union[str, Path]] = None,
|
|
349
|
+
flair_input: Optional[Union[str, Path]] = None,
|
|
350
|
+
t1_output: Optional[Union[str, Path]] = None,
|
|
351
|
+
t1c_output: Optional[Union[str, Path]] = None,
|
|
352
|
+
t2_output: Optional[Union[str, Path]] = None,
|
|
353
|
+
flair_output: Optional[Union[str, Path]] = None,
|
|
354
|
+
normalizer: Optional[Normalizer] = None,
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Automatically select the correct preprocessing pipeline for a specific BraTS challenge.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
challenge (Algorithms): The BraTS challenge algorithm enum
|
|
360
|
+
t1_input (Optional[Union[str, Path]]): Path to the input T1 image.
|
|
361
|
+
t1c_input (Optional[Union[str, Path]]): Path to the input T1c image
|
|
362
|
+
t2_input (Optional[Union[str, Path]]): Path to the input T2 image.
|
|
363
|
+
flair_input (Optional[Union[str, Path]]): Path to the input FLAIR image.
|
|
364
|
+
t1_output (Optional[Union[str, Path]]): Path to the output preprocessed T1 image
|
|
365
|
+
t1c_output (Optional[Union[str, Path]]): Path to the output preprocessed T1c image
|
|
366
|
+
t2_output (Optional[Union[str, Path]]): Path to the output preprocessed T2 image
|
|
367
|
+
flair_output (Optional[Union[str, Path]]): Path to the output preprocessed FLAIR image
|
|
368
|
+
normalizer (Optional[Normalizer]): Normalizer to apply during preprocessing
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
ValueError: If required modalities are missing for the challenge
|
|
372
|
+
"""
|
|
373
|
+
challenge_name = str(challenge)
|
|
374
|
+
|
|
375
|
+
# Helper to validate all modalities are present
|
|
376
|
+
def _require_all_modalities():
|
|
377
|
+
all_paths = [
|
|
378
|
+
t1_input,
|
|
379
|
+
t1c_input,
|
|
380
|
+
t2_input,
|
|
381
|
+
flair_input,
|
|
382
|
+
t1_output,
|
|
383
|
+
t1c_output,
|
|
384
|
+
t2_output,
|
|
385
|
+
flair_output,
|
|
386
|
+
]
|
|
387
|
+
if any(path is None for path in all_paths):
|
|
388
|
+
raise ValueError(
|
|
389
|
+
f"All modalities required for {challenge_name} preprocessing"
|
|
390
|
+
)
|
|
391
|
+
return all_paths # Type checker knows these are not None after the check
|
|
392
|
+
|
|
393
|
+
# Route to appropriate preprocessing function
|
|
394
|
+
if str(AdultGliomaPreAndPostTreatmentAlgorithms.__name__) in challenge_name:
|
|
395
|
+
paths = _require_all_modalities()
|
|
396
|
+
preprocess_coreg_mni152reg_bet(
|
|
397
|
+
*paths, # type: ignore
|
|
398
|
+
normalizer=normalizer,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
elif str(PediatricAlgorithms.__name__) in challenge_name:
|
|
402
|
+
paths = _require_all_modalities()
|
|
403
|
+
preprocess_coreg_sri24reg_defacing(
|
|
404
|
+
*paths, # type: ignore
|
|
405
|
+
normalizer=normalizer,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
elif str(MissingMRIAlgorithms.__name__) in challenge_name:
|
|
409
|
+
preprocess_coreg_sri24reg_bet_allow_missing(
|
|
410
|
+
t1_input,
|
|
411
|
+
t1c_input,
|
|
412
|
+
t2_input,
|
|
413
|
+
flair_input,
|
|
414
|
+
t1_output,
|
|
415
|
+
t1c_output,
|
|
416
|
+
t2_output,
|
|
417
|
+
flair_output,
|
|
418
|
+
normalizer=normalizer,
|
|
419
|
+
)
|
|
420
|
+
elif str(MeningiomaRTAlgorithms.__name__) in challenge_name:
|
|
421
|
+
if t1c_input is None or t1c_output is None:
|
|
422
|
+
raise ValueError(
|
|
423
|
+
f"T1c modality required for {challenge_name} preprocessing"
|
|
424
|
+
)
|
|
425
|
+
preprocess_deface_only(
|
|
426
|
+
t1c_input=t1c_input,
|
|
427
|
+
t1c_output=t1c_output,
|
|
428
|
+
normalizer=normalizer,
|
|
429
|
+
)
|
|
430
|
+
else: # Most challenges use SRI24 with BET
|
|
431
|
+
paths = _require_all_modalities()
|
|
432
|
+
preprocess_coreg_sri24reg_bet(
|
|
433
|
+
*paths, # type: ignore
|
|
434
|
+
normalizer=normalizer,
|
|
435
|
+
)
|
brats/utils/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
from brats.utils.exceptions import AlgorithmConfigException
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from dacite import DaciteError, from_dict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class MetaData:
|
|
12
|
+
"""Dataclass for the meta data"""
|
|
13
|
+
|
|
14
|
+
authors: str
|
|
15
|
+
"""The authors of the algorithm"""
|
|
16
|
+
paper: str
|
|
17
|
+
"""If available, a url to the paper of the algorithm"""
|
|
18
|
+
challenge: str
|
|
19
|
+
"""The challenge name the algorithm was submitted to"""
|
|
20
|
+
challenge_manuscript: str
|
|
21
|
+
"""Arxiv url to the manuscript of the challenge"""
|
|
22
|
+
rank: str
|
|
23
|
+
"""The rank of the algorithm in the challenge"""
|
|
24
|
+
year: int
|
|
25
|
+
"""The year the algorithm was submitted"""
|
|
26
|
+
dataset_manuscript: Optional[str] = None
|
|
27
|
+
"""If available, a url to the dataset manuscript of the algorithm. Optional since some algorithms do not have a dataset manuscript"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class RunArgs:
|
|
32
|
+
"""Dataclass for the run arguments"""
|
|
33
|
+
|
|
34
|
+
docker_image: str
|
|
35
|
+
"""The Docker image containing the algorithm"""
|
|
36
|
+
input_name_schema: str
|
|
37
|
+
"""The input name schema for the algorithm"""
|
|
38
|
+
parameters_file: bool = False
|
|
39
|
+
"""Whether the algorithm requires a parameters file"""
|
|
40
|
+
requires_root: bool = False
|
|
41
|
+
"""Whether the Docker container requires root access. This is !discouraged! but some submission do not work without it"""
|
|
42
|
+
shm_size: Optional[str] = "4gb"
|
|
43
|
+
"""The required shared memory size for the Docker container"""
|
|
44
|
+
cpu_compatible: Optional[bool] = False
|
|
45
|
+
"""Whether the algorithm is compatible with CPU"""
|
|
46
|
+
subject_modality_separator: str = "-"
|
|
47
|
+
"""The separator between the subject ID and the modality, differs e.g. for BraTS24 Meningioma Challenge"""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class AdditionalFilesData:
|
|
52
|
+
"""Dataclass for the additional files data"""
|
|
53
|
+
|
|
54
|
+
record_id: str
|
|
55
|
+
"""The Zenodo record ID of the additional files"""
|
|
56
|
+
param_name: Optional[List[str]] = field(default_factory=lambda: ["weights"])
|
|
57
|
+
"""The parameter(s) that specify additional file(s) in the algorithm execution, typically 'weights' but differs for some and can be multiple"""
|
|
58
|
+
param_path: Optional[List[str]] = None
|
|
59
|
+
"""The path(s) to specific file(s) / folder(s) in the additional files folder. Not required since some algorithms accept the entire additional files folder"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class AlgorithmData:
|
|
64
|
+
"""Dataclass for the algorithm data"""
|
|
65
|
+
|
|
66
|
+
meta: MetaData
|
|
67
|
+
"""The meta data of the algorithm"""
|
|
68
|
+
run_args: RunArgs
|
|
69
|
+
"""The run arguments of the algorithm"""
|
|
70
|
+
additional_files: Optional[AdditionalFilesData]
|
|
71
|
+
"""The additional files of the algorithm. Optional since some algorithms include them in the docker image"""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class AlgorithmList:
|
|
76
|
+
algorithms: Dict[str, AlgorithmData]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_algorithms(file_path: Path) -> Dict[str, AlgorithmData]:
|
|
80
|
+
"""Load the algorithms data from the specified yaml file
|
|
81
|
+
|
|
82
|
+
Params:
|
|
83
|
+
file_path (str): The path to the yaml file
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
FileNotFoundError: If the file is not found
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Dict[str, AlgorithmData]: Dict of algorithm @AlgorithmKeys:@AlgorithmData pairs
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
with open(file_path, "r") as file:
|
|
93
|
+
data = yaml.safe_load(file)
|
|
94
|
+
except FileNotFoundError:
|
|
95
|
+
raise FileNotFoundError("Algorithm meta data file not found")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Convert the dictionary to the dataclass
|
|
99
|
+
algorithms = from_dict(data_class=AlgorithmList, data=data).algorithms
|
|
100
|
+
except DaciteError as e:
|
|
101
|
+
raise AlgorithmConfigException(f"Error loading algorithm data: {e}")
|
|
102
|
+
return algorithms
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Generator, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
import nibabel as nib
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def remove_tmp_folder(folder: Path):
|
|
14
|
+
"""Remove a temporary folder and log a warning if it fails.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
folder (Path): Path to the folder to be removed
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
shutil.rmtree(folder)
|
|
21
|
+
except PermissionError as e:
|
|
22
|
+
logger.warning(
|
|
23
|
+
f"Failed to remove temporary folder {folder}. This is most likely caused by bad permission management of the docker container. \nError: {e}"
|
|
24
|
+
)
|
|
25
|
+
except FileNotFoundError as e:
|
|
26
|
+
logger.warning(f"Failed to delete folder {folder}. {e}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def add_log_file_handler(log_file: Path | str) -> int:
|
|
30
|
+
"""
|
|
31
|
+
Add a log file handler to the logger.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
log_file (Path | str): Path to the log file
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
int: The logger id
|
|
38
|
+
"""
|
|
39
|
+
log_file = Path(log_file)
|
|
40
|
+
logger_id = logger.add(log_file, level="DEBUG", catch=True)
|
|
41
|
+
logger.info(
|
|
42
|
+
f"Logging console logs and further debug information to: {log_file.absolute()}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return logger_id
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@contextmanager
|
|
49
|
+
def InferenceSetup(
|
|
50
|
+
log_file: Optional[Path | str] = None,
|
|
51
|
+
) -> Generator[Tuple[Path, Path], None, None]:
|
|
52
|
+
"""
|
|
53
|
+
Context manager for setting up the inference process. Creates temporary data and output folders and adds a log file handler if requested.
|
|
54
|
+
|
|
55
|
+
Yields:
|
|
56
|
+
(data folder, output folder) (Tuple[Path, Path]): Two temporary folders (data folder, output folder)
|
|
57
|
+
"""
|
|
58
|
+
if log_file is not None:
|
|
59
|
+
logger_id = add_log_file_handler(log_file)
|
|
60
|
+
|
|
61
|
+
tmp_data_folder = Path(tempfile.mkdtemp(prefix="data_"))
|
|
62
|
+
tmp_output_folder = Path(tempfile.mkdtemp(prefix="output_"))
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
yield tmp_data_folder, tmp_output_folder
|
|
66
|
+
finally:
|
|
67
|
+
remove_tmp_folder(tmp_data_folder)
|
|
68
|
+
remove_tmp_folder(tmp_output_folder)
|
|
69
|
+
|
|
70
|
+
if log_file is not None:
|
|
71
|
+
logger.remove(logger_id)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def input_sanity_check(
|
|
75
|
+
t1n: Optional[Path | str] = None,
|
|
76
|
+
t1c: Optional[Path | str] = None,
|
|
77
|
+
t2f: Optional[Path | str] = None,
|
|
78
|
+
t2w: Optional[Path | str] = None,
|
|
79
|
+
mask: Optional[Path | str] = None,
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
Check if input images have the default shape (240, 240, 155) and log a warning if not.
|
|
83
|
+
Supports different input combinations for segmentation and inpainting tasks.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
t1n (Path | str): T1n image path (required for segmentation and inpainting)
|
|
87
|
+
t1c (Path | str, optional): T1c image path (required for segmentation)
|
|
88
|
+
t2f (Path | str, optional): T2f image path (required for segmentation)
|
|
89
|
+
t2w (Path | str, optional): T2w image path (required for segmentation)
|
|
90
|
+
mask (Path | str, optional): Mask image path (required for inpainting)
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
# Filter out None values to only include provided images
|
|
94
|
+
images = {
|
|
95
|
+
"t1n": t1n,
|
|
96
|
+
"t1c": t1c,
|
|
97
|
+
"t2f": t2f,
|
|
98
|
+
"t2w": t2w,
|
|
99
|
+
"mask": mask,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Load and check shapes
|
|
103
|
+
shapes = {
|
|
104
|
+
label: nib.load(img).shape for label, img in images.items() if img is not None
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
assert shapes, "No input images provided. At least one image is required."
|
|
108
|
+
|
|
109
|
+
if any(shape != (240, 240, 155) for shape in shapes.values()):
|
|
110
|
+
logger.warning(
|
|
111
|
+
"Input images do not have the default shape (240, 240, 155). This might cause issues with some algorithms and could lead to errors."
|
|
112
|
+
)
|
|
113
|
+
logger.warning(f"Image shapes: {shapes}")
|
|
114
|
+
logger.warning(
|
|
115
|
+
"If your data is not preprocessed yet, consider using our preprocessing package: https://github.com/BrainLesion/preprocessing"
|
|
116
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dacite import DaciteError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AlgorithmNotCPUCompatibleException(Exception):
|
|
5
|
+
"""Exception raised when an CPU-incompatible algorithm tries to run on CPU."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BraTSContainerException(Exception):
|
|
11
|
+
"""Exception raised when an algorithm container fails"""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AlgorithmConfigException(DaciteError):
|
|
17
|
+
"""Exception raised when the algorithm config file has issues."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ZenodoException(Exception):
|
|
23
|
+
"""Exception raised when Zenodo is unreachable or returns an error."""
|
|
24
|
+
|
|
25
|
+
pass
|