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.
Files changed (37) hide show
  1. brats/__init__.py +16 -0
  2. brats/constants.py +268 -0
  3. brats/core/__init__.py +0 -0
  4. brats/core/brats_algorithm.py +264 -0
  5. brats/core/docker.py +474 -0
  6. brats/core/inpainting_algorithms.py +155 -0
  7. brats/core/missing_mri_algorithms.py +185 -0
  8. brats/core/segmentation_algorithms.py +491 -0
  9. brats/core/singularity.py +268 -0
  10. brats/data/meta/adult_glioma_pre_and_post_treatment.yml +123 -0
  11. brats/data/meta/adult_glioma_pre_treatment.yml +64 -0
  12. brats/data/meta/africa.yml +185 -0
  13. brats/data/meta/goat.yml +84 -0
  14. brats/data/meta/inpainting.yml +163 -0
  15. brats/data/meta/meningioma.yml +96 -0
  16. brats/data/meta/meningioma_rt.yml +113 -0
  17. brats/data/meta/metastases.yml +94 -0
  18. brats/data/meta/missing_mri.yml +125 -0
  19. brats/data/meta/pediatric.yml +168 -0
  20. brats/data/parameters/brats23_africa_sparkunn.yml +63 -0
  21. brats/data/parameters/brats23_inpainting_domaso.yml +44 -0
  22. brats/data/parameters/brats23_missing_mri_onembyte.yml +5 -0
  23. brats/data/parameters/brats24_adult_glioma_mist.yml +6 -0
  24. brats/data/parameters/brats24_inpainting_smint.yml +37 -0
  25. brats/data/parameters/brats24_missing_mri_cgv.yml +72 -0
  26. brats/data/parameters/dummy.yml +0 -0
  27. brats/preprocessing.py +435 -0
  28. brats/utils/__init__.py +0 -0
  29. brats/utils/algorithm_config.py +102 -0
  30. brats/utils/data_handling.py +116 -0
  31. brats/utils/exceptions.py +25 -0
  32. brats/utils/logging.py +73 -0
  33. brats/utils/zenodo.py +217 -0
  34. brats-0.0.0.dev0.dist-info/METADATA +581 -0
  35. brats-0.0.0.dev0.dist-info/RECORD +37 -0
  36. brats-0.0.0.dev0.dist-info/WHEEL +4 -0
  37. 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
+ )
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