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/__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()}")