From 6fe884db09b9008423f1943396f0b00221c3897f Mon Sep 17 00:00:00 2001 From: Marcel Rosier Date: Thu, 4 Sep 2025 13:29:00 +0200 Subject: [PATCH 1/6] Add return type --- brainles_preprocessing/normalization/normalizer_base.py | 3 ++- brainles_preprocessing/normalization/percentile_normalizer.py | 3 ++- brainles_preprocessing/normalization/windowing_normalizer.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/brainles_preprocessing/normalization/normalizer_base.py b/brainles_preprocessing/normalization/normalizer_base.py index 5356b84..8aeff4f 100644 --- a/brainles_preprocessing/normalization/normalizer_base.py +++ b/brainles_preprocessing/normalization/normalizer_base.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from numpy.typing import NDArray class Normalizer(ABC): @@ -10,7 +11,7 @@ def __init__(self): super().__init__() @abstractmethod - def normalize(self, image): + def normalize(self, image) -> NDArray: """ Normalize the input image based on the chosen method. diff --git a/brainles_preprocessing/normalization/percentile_normalizer.py b/brainles_preprocessing/normalization/percentile_normalizer.py index 0b88217..7766591 100644 --- a/brainles_preprocessing/normalization/percentile_normalizer.py +++ b/brainles_preprocessing/normalization/percentile_normalizer.py @@ -1,5 +1,6 @@ import numpy as np from .normalizer_base import Normalizer +from numpy.typing import NDArray class PercentileNormalizer(Normalizer): @@ -29,7 +30,7 @@ def __init__( self.lower_limit = lower_limit self.upper_limit = upper_limit - def normalize(self, image: np.ndarray): + def normalize(self, image: np.ndarray) -> NDArray: """ Normalize the input image using percentile-based mapping. diff --git a/brainles_preprocessing/normalization/windowing_normalizer.py b/brainles_preprocessing/normalization/windowing_normalizer.py index 1c6f695..010cc88 100644 --- a/brainles_preprocessing/normalization/windowing_normalizer.py +++ b/brainles_preprocessing/normalization/windowing_normalizer.py @@ -1,5 +1,6 @@ import numpy as np from .normalizer_base import Normalizer +from numpy.typing import NDArray class WindowingNormalizer(Normalizer): @@ -19,7 +20,7 @@ def __init__(self, center, width): self.center = center self.width = width - def normalize(self, image): + def normalize(self, image) -> NDArray: """ Normalize the input image using windowing. From 86f134fba74c55e29a2ee698cf263cd6c304e3c4 Mon Sep 17 00:00:00 2001 From: Marcel Rosier Date: Thu, 4 Sep 2025 13:29:17 +0200 Subject: [PATCH 2/6] Handle deface error --- .../atlas_centric_preprocessor.py | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/brainles_preprocessing/preprocessor/atlas_centric_preprocessor.py b/brainles_preprocessing/preprocessor/atlas_centric_preprocessor.py index 9ed4891..940aba3 100644 --- a/brainles_preprocessing/preprocessor/atlas_centric_preprocessor.py +++ b/brainles_preprocessing/preprocessor/atlas_centric_preprocessor.py @@ -323,29 +323,34 @@ def run_defacing( atlas_mask = self.center_modality.deface( defacer=self.defacer, defaced_dir_path=deface_dir ) - # looping over _all_ modalities since .deface is no applying the computed mask - for moving_modality in self.all_modalities: - logger.info(f"Applying deface mask to {moving_modality.modality_name}...") - moving_modality.apply_deface_mask( - defacer=self.defacer, - mask_path=atlas_mask, - deface_dir=deface_dir, - ) - - self._save_output( - src=deface_dir, - save_dir=save_dir_defacing, - ) - # now we save images that are skull-stripped - logger.info("Saving defaced images...") - for modality in self.all_modalities: - if modality.raw_defaced_output_path: - modality.save_current_image( - modality.raw_defaced_output_path, - normalization=False, + if atlas_mask is not None: + # looping over _all_ modalities since .deface is no applying the computed mask + for moving_modality in self.all_modalities: + logger.info( + f"Applying deface mask to {moving_modality.modality_name}..." ) - if modality.normalized_defaced_output_path: - modality.save_current_image( - modality.normalized_defaced_output_path, - normalization=True, + moving_modality.apply_deface_mask( + defacer=self.defacer, + mask_path=atlas_mask, + deface_dir=deface_dir, ) + + self._save_output( + src=deface_dir, + save_dir=save_dir_defacing, + ) + # now we save images that are skull-stripped + logger.info("Saving defaced images...") + for modality in self.all_modalities: + if modality.raw_defaced_output_path: + modality.save_current_image( + modality.raw_defaced_output_path, + normalization=False, + ) + if modality.normalized_defaced_output_path: + modality.save_current_image( + modality.normalized_defaced_output_path, + normalization=True, + ) + else: + logger.warning("Defacing was requested but no defacing mask was created.") From a1b1f1144c9595d912359ad7d497c939814763a7 Mon Sep 17 00:00:00 2001 From: Marcel Rosier Date: Thu, 4 Sep 2025 13:29:30 +0200 Subject: [PATCH 3/6] Add mask saving to example case --- example/example_atlas_centric_preprocessor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/example_atlas_centric_preprocessor.py b/example/example_atlas_centric_preprocessor.py index 2c252a8..447ac23 100644 --- a/example/example_atlas_centric_preprocessor.py +++ b/example/example_atlas_centric_preprocessor.py @@ -45,6 +45,8 @@ def preprocess(input_dir: Path, output_dir: Path): raw_defaced_output_path=raw_deface_dir / f"t1c_defaced_raw.nii.gz", normalizer=percentile_normalizer, atlas_correction=True, + bet_mask_output_path=output_dir / f"bet_mask.nii.gz", + defacing_mask_output_path=output_dir / f"defacing_mask.nii.gz", ) moving_modalities = [ Modality( @@ -92,6 +94,7 @@ def preprocess(input_dir: Path, output_dir: Path): save_dir_n4_bias_correction=output_dir / "n4_bias_correction", save_dir_brain_extraction=output_dir / "brain_extraction", save_dir_defacing=output_dir / "defacing", + save_dir_transformations=output_dir / "transformations", ) From b5ef2cc323a1781a9bde3cc3a2edaccf8573f161 Mon Sep 17 00:00:00 2001 From: Marcel Rosier Date: Thu, 4 Sep 2025 13:29:45 +0200 Subject: [PATCH 4/6] Add default values to abstract class to prevent type error --- brainles_preprocessing/registration/registrator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brainles_preprocessing/registration/registrator.py b/brainles_preprocessing/registration/registrator.py index 7530104..17fc486 100644 --- a/brainles_preprocessing/registration/registrator.py +++ b/brainles_preprocessing/registration/registrator.py @@ -36,7 +36,7 @@ def transform( transformed_image_path: Any, matrix_path: Any, log_file_path: str, - interpolator: str, + interpolator: str = "default", **kwargs ): """ @@ -61,7 +61,7 @@ def inverse_transform( transformed_image_path: Any, matrix_path: Any, log_file_path: str, - interpolator: str, + interpolator: str = "default", ): """ Abstract method for inverse transforming images. From 733de613f93bb43c801729d5c23cbdcbb1537421 Mon Sep 17 00:00:00 2001 From: Marcel Rosier Date: Thu, 4 Sep 2025 13:30:07 +0200 Subject: [PATCH 5/6] Save bet/deface masks if specified Fix typing issues --- brainles_preprocessing/modality.py | 38 ++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/brainles_preprocessing/modality.py b/brainles_preprocessing/modality.py index 152cf67..0f67cde 100644 --- a/brainles_preprocessing/modality.py +++ b/brainles_preprocessing/modality.py @@ -139,7 +139,9 @@ def __init__( else: self.normalized_defaced_output_path = None - self.steps = {k: None for k in PreprocessorSteps} + self.steps: Dict[PreprocessorSteps, Path | None] = { + k: None for k in PreprocessorSteps + } self.steps[PreprocessorSteps.INPUT] = self.input_path @property @@ -265,7 +267,7 @@ def register( moving_image_path=self.current, transformed_image_path=registered, matrix_path=registered_matrix, - log_file_path=registered_log, + log_file_path=str(registered_log), ) self.current = registered self.steps[step] = registered @@ -394,24 +396,24 @@ def transform( ), "Coregistration must be performed before applying atlas registration." registrator.transform( - fixed_image_path=fixed_image_path, - moving_image_path=self.steps[PreprocessorSteps.INPUT], - transformed_image_path=transformed, + fixed_image_path=str(fixed_image_path), + moving_image_path=str(self.steps[PreprocessorSteps.INPUT]), + transformed_image_path=str(transformed), matrix_path=[ self.transformation_paths[ PreprocessorSteps.COREGISTERED ], # coregistration matrix transformation_matrix_path, # atlas registration matrix ], - log_file_path=transformed_log, + log_file_path=str(transformed_log), ) else: registrator.transform( - fixed_image_path=fixed_image_path, - moving_image_path=self.current, - transformed_image_path=transformed, - matrix_path=transformation_matrix_path, - log_file_path=transformed_log, + fixed_image_path=str(fixed_image_path), + moving_image_path=str(self.current), + transformed_image_path=str(transformed), + matrix_path=str(transformation_matrix_path), + log_file_path=str(transformed_log), ) self.current = transformed @@ -638,6 +640,12 @@ def extract_brain_region( if self.bet: self.current = bet + + if self.bet_mask_output_path: + self.save_mask( + mask_path=mask_path, + output_path=self.bet_mask_output_path, + ) return mask_path def deface( @@ -684,7 +692,7 @@ def deface( moving_image_path=self.steps[PreprocessorSteps.BET], transformed_image_path=atlas_bet, matrix_path=atlas_bet_M, - log_file_path=defaced_dir_path / "atlas_bet.log", + log_file_path=str(defaced_dir_path / "atlas_bet.log"), ) deface_mask_atlas = defaced_dir_path / "deface_mask_atlas.nii.gz" @@ -706,6 +714,12 @@ def deface( mask_image_path=mask_path, ) + if self.defacing_mask_output_path: + self.save_mask( + mask_path=mask_path, + output_path=self.defacing_mask_output_path, + ) + return mask_path else: logger.warning( From d7299bb94bd5dbccaa857502959252629f9220e2 Mon Sep 17 00:00:00 2001 From: Marcel Rosier Date: Thu, 4 Sep 2025 13:33:01 +0200 Subject: [PATCH 6/6] Add save bet/ deface mask to native example --- example/example_native_space_preprocessor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/example_native_space_preprocessor.py b/example/example_native_space_preprocessor.py index 65a76fd..92f4fe6 100644 --- a/example/example_native_space_preprocessor.py +++ b/example/example_native_space_preprocessor.py @@ -38,6 +38,8 @@ def preprocess(input_dir: Path, output_dir: Path): normalized_skull_output_path=norm_skull_dir / f"t1c_skull_normalized.nii.gz", raw_defaced_output_path=raw_deface_dir / f"t1c_defaced_raw.nii.gz", normalizer=percentile_normalizer, + bet_mask_output_path=output_dir / f"bet_mask.nii.gz", + defacing_mask_output_path=output_dir / f"defacing_mask.nii.gz", ) moving_modalities = [ Modality(