From 62cd8f91ec2401467d0e04814314c14102fbda83 Mon Sep 17 00:00:00 2001 From: LMZimmer Date: Fri, 18 Sep 2026 14:42:50 +0200 Subject: [PATCH 1/3] Change interpolation defaults to "linear" Changed defaults for niftireg and ANTS backends to linear which was NN interpolation before. Added an argument to the registrator construction to set the interpolator --- brainles_preprocessing/modality.py | 11 +++++++++ .../registration/ANTs/ANTs.py | 23 ++++++++++++------- .../registration/niftyreg/niftyreg.py | 4 ++-- brainles_preprocessing/transform.py | 8 +++---- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/brainles_preprocessing/modality.py b/brainles_preprocessing/modality.py index 0f67cde..8513666 100644 --- a/brainles_preprocessing/modality.py +++ b/brainles_preprocessing/modality.py @@ -239,6 +239,7 @@ def register( registration_dir: Union[str, Path], moving_image_name: str, step: PreprocessorSteps, + interpolator: Optional[str] = None, ) -> Path: """ Register the current modality to a fixed image using the specified registrator. @@ -249,6 +250,7 @@ def register( registration_dir (str or Path): Directory to store registration results. moving_image_name (str): Name of the moving image. step (PreprocessorSteps): The current preprocessing step. + interpolator (Optional[str]): Interpolation method used during transformation. Available options depend on the chosen registrator. Returns: Path: Path to the registration matrix. @@ -262,12 +264,15 @@ def register( # Note, add file ending depending on registration backend! registered_matrix = registration_dir / f"M_{moving_image_name}" + interpolator_kwargs = {"interpolator": interpolator} if interpolator else {} + registrator.register( fixed_image_path=fixed_image_path, moving_image_path=self.current, transformed_image_path=registered, matrix_path=registered_matrix, log_file_path=str(registered_log), + **interpolator_kwargs, ) self.current = registered self.steps[step] = registered @@ -364,6 +369,7 @@ def transform( moving_image_name: str, transformation_matrix_path: Union[str, Path], step: PreprocessorSteps, + interpolator: Optional[str] = None, ) -> None: """ Transform the current modality using the specified registrator and transformation matrix. @@ -375,6 +381,7 @@ def transform( moving_image_name (str): Name of the moving image. transformation_matrix_path (str or Path): Path to the transformation matrix. step (PreprocessorSteps): The current preprocessing step. + interpolator (Optional[str]): Interpolation method used during transformation. Available options depend on the chosen registrator. Returns: None """ @@ -385,6 +392,8 @@ def transform( transformed = registration_dir_path / f"{moving_image_name}.nii.gz" transformed_log = registration_dir_path / f"{moving_image_name}.log" + interpolator_kwargs = {"interpolator": interpolator} if interpolator else {} + if ( isinstance(registrator, (ANTsRegistrator, NiftyRegRegistrator)) and step == PreprocessorSteps.ATLAS_REGISTERED @@ -406,6 +415,7 @@ def transform( transformation_matrix_path, # atlas registration matrix ], log_file_path=str(transformed_log), + **interpolator_kwargs, ) else: registrator.transform( @@ -414,6 +424,7 @@ def transform( transformed_image_path=str(transformed), matrix_path=str(transformation_matrix_path), log_file_path=str(transformed_log), + **interpolator_kwargs, ) self.current = transformed diff --git a/brainles_preprocessing/registration/ANTs/ANTs.py b/brainles_preprocessing/registration/ANTs/ANTs.py index fa0808e..7907fa1 100644 --- a/brainles_preprocessing/registration/ANTs/ANTs.py +++ b/brainles_preprocessing/registration/ANTs/ANTs.py @@ -61,6 +61,7 @@ def register( transformed_image_path: Union[str, Path], matrix_path: Union[str, Path], log_file_path: Union[str, Path], + interpolator: Optional[str] = None, **kwargs, ) -> None: """ @@ -72,6 +73,7 @@ def register( transformed_image_path (str or Path): Path to the transformed image (output). matrix_path (str or Path): Path to the transformation matrix (output). log_file_path (str or Path): Path to the log file. + interpolator (Optional[str]): Interpolator to use for the transformation. Defaults to the instantiated transformation parameters, or 'linear' if not set. **kwargs: Additional registration parameters to update the instantiated defaults. Raises: @@ -123,6 +125,7 @@ def register( transformed_image_path=transformed_image_path, matrix_path=matrix_path, log_file_path=log_file_path, + interpolator=interpolator, ) end_time = datetime.datetime.now() @@ -148,7 +151,7 @@ def transform( transformed_image_path: Union[str, Path], matrix_path: str | Path | List[str | Path], log_file_path: Union[str, Path], - interpolator: str = "nearestNeighbor", + interpolator: Optional[str] = None, **kwargs, ) -> None: """ @@ -161,7 +164,7 @@ def transform( transformed_image_path (str or Path): Path to the transformed image (output). matrix_path (str or Path or List[str | Path]): Path to the transformation matrix or a list of matrices. log_file_path (str or Path): Path to the log file. - interpolator (str): Interpolator to use for the transformation. Default is 'nearestNeighbor'. + interpolator (Optional[str]): Interpolator to use for the transformation. Defaults to the instantiated transformation parameters, or 'linear' if not set. **kwargs: Additional transformation parameters to update the instantiated defaults. Raises: AssertionError: If the interpolator is not valid. @@ -169,15 +172,19 @@ def transform( """ start_time = datetime.datetime.now() + # TODO - self.transformation_params + # we update the transformation parameters with the provided kwargs + transform_kwargs = {**self.transformation_params, **kwargs} + + # the interpolator argument takes precedence over the instantiated defaults + default_interpolator = transform_kwargs.pop("interpolator", "linear") + interpolator = interpolator or default_interpolator + assert interpolator in VALID_INTERPOLATORS, ( f"Invalid interpolator: {interpolator}. " f"Valid options are: {', '.join(VALID_INTERPOLATORS)}." ) - # TODO - self.transformation_params - # we update the transformation parameters with the provided kwargs - transform_kwargs = {**self.transformation_params, **kwargs} - # Convert all paths to Path objects fixed_image_path = Path(fixed_image_path) moving_image_path = Path(moving_image_path) @@ -243,7 +250,7 @@ def inverse_transform( transformed_image_path: Union[str, Path], matrix_path: str | Path | List[str | Path], log_file_path: Union[str, Path], - interpolator: str = "nearestNeighbor", + interpolator: Optional[str] = None, **kwargs, ) -> None: """ @@ -255,7 +262,7 @@ def inverse_transform( transformed_image_path (str or Path): Path to the transformed image (output). matrix_path (str or Path or List[str | Path]): Path to the transformation matrix or a list of matrices. log_file_path (str or Path): Path to the log file. - interpolator (str): Interpolator to use for the transformation. Default is 'nearestNeighbor'. + interpolator (Optional[str]): Interpolator to use for the transformation. Defaults to the instantiated transformation parameters, or 'linear' if not set. **kwargs: Additional transformation parameters to update the instantiated defaults. """ if not isinstance(matrix_path, list): diff --git a/brainles_preprocessing/registration/niftyreg/niftyreg.py b/brainles_preprocessing/registration/niftyreg/niftyreg.py index 3be4aa5..ec8a083 100644 --- a/brainles_preprocessing/registration/niftyreg/niftyreg.py +++ b/brainles_preprocessing/registration/niftyreg/niftyreg.py @@ -155,7 +155,7 @@ def transform( transformed_image_path: str, matrix_path: str | Path | List[str | Path], log_file_path: str, - interpolator: str = "0", + interpolator: str = "1", **kwargs: dict, ) -> None: """ @@ -236,7 +236,7 @@ def inverse_transform( transformed_image_path: str, matrix_path: str | Path | List[str | Path], log_file_path: str, - interpolator: str = "0", + interpolator: str = "1", ) -> None: """ Apply inverse transformation using NiftyReg. diff --git a/brainles_preprocessing/transform.py b/brainles_preprocessing/transform.py index 4447c90..95dc89c 100644 --- a/brainles_preprocessing/transform.py +++ b/brainles_preprocessing/transform.py @@ -58,8 +58,8 @@ def apply( Available options depend on the chosen registrator: - **ANTsRegistrator**: - - "linear" - - "nearestNeighbor" (default) + - "linear" (default) + - "nearestNeighbor" - "multiLabel" (deprecated, prefer "genericLabel") - "gaussian" - "bSpline" @@ -70,8 +70,8 @@ def apply( - "genericLabel" (recommended for label images) - **NiftyReg**: - - "0": nearest neighbor (default) - - "1": linear + - "0": nearest neighbor + - "1": linear (default) - "3": cubic spline - "4": sinc From 82bdfed3101c131a989bc755fc6500bb58e4957c Mon Sep 17 00:00:00 2001 From: LMZimmer Date: Tue, 6 Oct 2026 16:32:29 +0200 Subject: [PATCH 2/3] Use NN interpolation for deface mask; restrict register() interpolator to ANTs Co-Authored-By: Claude Fable 5.1 --- brainles_preprocessing/modality.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/brainles_preprocessing/modality.py b/brainles_preprocessing/modality.py index 8513666..eda39b1 100644 --- a/brainles_preprocessing/modality.py +++ b/brainles_preprocessing/modality.py @@ -250,10 +250,13 @@ def register( registration_dir (str or Path): Directory to store registration results. moving_image_name (str): Name of the moving image. step (PreprocessorSteps): The current preprocessing step. - interpolator (Optional[str]): Interpolation method used during transformation. Available options depend on the chosen registrator. + interpolator (Optional[str]): Interpolation method used during registration. Only supported by ANTsRegistrator; omit it for other registrators to use the backend default. Returns: Path: Path to the registration matrix. + + Raises: + ValueError: If an interpolator is passed for a registrator other than ANTsRegistrator. """ fixed_image_path = Path(fixed_image_path) registration_dir = Path(registration_dir) @@ -264,6 +267,12 @@ def register( # Note, add file ending depending on registration backend! registered_matrix = registration_dir / f"M_{moving_image_name}" + if interpolator is not None and not isinstance(registrator, ANTsRegistrator): + raise ValueError( + f"'interpolator' is only supported by ANTsRegistrator for registration, " + f"got {type(registrator).__name__}. Omit it to use the backend default." + ) + interpolator_kwargs = {"interpolator": interpolator} if interpolator else {} registrator.register( @@ -712,12 +721,23 @@ def deface( mask_image_path=deface_mask_atlas, ) + # the deface mask is binary, resample it with nearest neighbour + if isinstance(registrator, ANTsRegistrator): + nn_interpolator = "nearestNeighbor" + elif isinstance(registrator, NiftyRegRegistrator): + nn_interpolator = "0" + else: + raise NotImplementedError( + f"Nearest-neighbour mask resampling is not configured for {type(registrator).__name__}." + ) + registrator.inverse_transform( fixed_image_path=self.steps[PreprocessorSteps.BET], moving_image_path=deface_mask_atlas, transformed_image_path=mask_path, matrix_path=atlas_bet_M, log_file_path=defaced_dir_path / "inverse_transform.log", + interpolator=nn_interpolator, ) else: defacer.deface( From 9fc689aa8c752be846fbf83e5d52adb29d7b157d Mon Sep 17 00:00:00 2001 From: LMZimmer Date: Tue, 6 Oct 2026 16:53:21 +0200 Subject: [PATCH 3/3] Interpolator argument in Modality.register now only accepted when using ANTs CenterModality.deface now explicitly passes the correct keyword for interpolator based on the registrator backend --- brainles_preprocessing/modality.py | 1 - 1 file changed, 1 deletion(-) diff --git a/brainles_preprocessing/modality.py b/brainles_preprocessing/modality.py index eda39b1..6c49989 100644 --- a/brainles_preprocessing/modality.py +++ b/brainles_preprocessing/modality.py @@ -721,7 +721,6 @@ def deface( mask_image_path=deface_mask_atlas, ) - # the deface mask is binary, resample it with nearest neighbour if isinstance(registrator, ANTsRegistrator): nn_interpolator = "nearestNeighbor" elif isinstance(registrator, NiftyRegRegistrator):