From eaa5da3971ef61bbb040cdedbd11272aa6820767 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 12:22:12 +0100 Subject: [PATCH 01/13] refactor: introduce _VALID_DATA_TYPES attribute for preprocessor implementations --- junifer/preprocess/base.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/junifer/preprocess/base.py b/junifer/preprocess/base.py index 875590548..936b6e40c 100644 --- a/junifer/preprocess/base.py +++ b/junifer/preprocess/base.py @@ -5,7 +5,8 @@ # License: AGPL from abc import ABC, abstractmethod -from typing import Any, Optional, Union +from collections.abc import Sequence +from typing import Any, ClassVar, Optional, Union from ..pipeline import PipelineStepMixin, UpdateMetaMixin from ..utils import logger, raise_error @@ -31,17 +32,27 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): Raises ------ + AttributeError + If the preprocessor does not have `_VALID_DATA_TYPES` attribute. ValueError If required input data type(s) is(are) not found. """ + _VALID_DATA_TYPES: ClassVar[Sequence[str]] + def __init__( self, on: Optional[Union[list[str], str]] = None, required_data_types: Optional[Union[list[str], str]] = None, ) -> None: """Initialize the class.""" + # Check for missing data types attributes + if not hasattr(self, "_VALID_DATA_TYPES"): + raise_error( + msg="Missing `_VALID_DATA_TYPES` for the preprocessor", + klass=AttributeError, + ) # Use all data types if not provided if on is None: on = self.get_valid_inputs() @@ -89,7 +100,6 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): ) return [x for x in self._on if x in input] - @abstractmethod def get_valid_inputs(self) -> list[str]: """Get valid data types for input. @@ -100,10 +110,7 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): preprocessor. """ - raise_error( - msg="Concrete classes need to implement get_valid_inputs().", - klass=NotImplementedError, - ) + return list(self._VALID_DATA_TYPES) @abstractmethod def get_output_type(self, input_type: str) -> str: -- 2.52.0 From c53a0b12959aae2ad89585200f402c3905f7d490 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 12:30:16 +0100 Subject: [PATCH 02/13] update: adapt _VALID_DATA_TYPES attribute for preprocessor implementations --- junifer/preprocess/_temporal_filter.py | 16 ++------- junifer/preprocess/_temporal_slicer.py | 16 ++------- .../confounds/fmriprep_confound_remover.py | 16 ++------- junifer/preprocess/smoothing/smoothing.py | 13 ++------ junifer/preprocess/warping/space_warper.py | 33 +++++++------------ 5 files changed, 23 insertions(+), 71 deletions(-) diff --git a/junifer/preprocess/_temporal_filter.py b/junifer/preprocess/_temporal_filter.py index 517bfd6ca..f1ef7aa41 100644 --- a/junifer/preprocess/_temporal_filter.py +++ b/junifer/preprocess/_temporal_filter.py @@ -5,6 +5,7 @@ # Synchon Mandal # License: AGPL +from collections.abc import Sequence from typing import ( Any, ClassVar, @@ -56,6 +57,7 @@ class TemporalFilter(BasePreprocessor): """ _DEPENDENCIES: ClassVar[Dependencies] = {"numpy", "nilearn"} + _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["BOLD"] def __init__( self, @@ -74,19 +76,6 @@ class TemporalFilter(BasePreprocessor): self.t_r = t_r self.masks = masks - super().__init__(on="BOLD", required_data_types=["BOLD"]) - - def get_valid_inputs(self) -> list[str]: - """Get valid data types for input. - - Returns - ------- - list of str - The list of data types that can be used as input for this - preprocessor. - - """ - return ["BOLD"] def get_output_type(self, input_type: str) -> str: """Get output type. @@ -104,6 +93,7 @@ class TemporalFilter(BasePreprocessor): """ # Does not add any new keys return input_type + super().__init__() def _validate_data( self, diff --git a/junifer/preprocess/_temporal_slicer.py b/junifer/preprocess/_temporal_slicer.py index bd82d3640..c35fc8a9a 100644 --- a/junifer/preprocess/_temporal_slicer.py +++ b/junifer/preprocess/_temporal_slicer.py @@ -3,6 +3,7 @@ # Authors: Synchon Mandal # License: AGPL +from collections.abc import Sequence from typing import Any, ClassVar, Optional import nibabel as nib @@ -45,6 +46,7 @@ class TemporalSlicer(BasePreprocessor): """ _DEPENDENCIES: ClassVar[Dependencies] = {"nilearn"} + _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["BOLD"] def __init__( self, @@ -61,19 +63,6 @@ class TemporalSlicer(BasePreprocessor): self.stop = stop self.duration = duration self.t_r = t_r - super().__init__(on="BOLD", required_data_types=["BOLD"]) - - def get_valid_inputs(self) -> list[str]: - """Get valid data types for input. - - Returns - ------- - list of str - The list of data types that can be used as input for this - preprocessor. - - """ - return ["BOLD"] def get_output_type(self, input_type: str) -> str: """Get output type. @@ -91,6 +80,7 @@ class TemporalSlicer(BasePreprocessor): """ # Does not add any new keys return input_type + super().__init__() def preprocess( self, diff --git a/junifer/preprocess/confounds/fmriprep_confound_remover.py b/junifer/preprocess/confounds/fmriprep_confound_remover.py index 9bdf66533..da33bf33f 100644 --- a/junifer/preprocess/confounds/fmriprep_confound_remover.py +++ b/junifer/preprocess/confounds/fmriprep_confound_remover.py @@ -5,6 +5,7 @@ # Synchon Mandal # License: AGPL +from collections.abc import Sequence from typing import ( Any, ClassVar, @@ -175,6 +176,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor): """ _DEPENDENCIES: ClassVar[Dependencies] = {"numpy", "nilearn"} + _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["BOLD"] def __init__( self, @@ -251,19 +253,6 @@ class fMRIPrepConfoundRemover(BasePreprocessor): "include it in the future", klass=ValueError, ) - super().__init__(on="BOLD", required_data_types=["BOLD"]) - - def get_valid_inputs(self) -> list[str]: - """Get valid data types for input. - - Returns - ------- - list of str - The list of data types that can be used as input for this - preprocessor. - - """ - return ["BOLD"] def get_output_type(self, input_type: str) -> str: """Get output type. @@ -281,6 +270,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor): """ # Does not add any new keys return input_type + super().__init__() def _map_adhoc_to_fmriprep(self, input: dict[str, Any]) -> None: """Map the adhoc format to the fmpriprep format spec. diff --git a/junifer/preprocess/smoothing/smoothing.py b/junifer/preprocess/smoothing/smoothing.py index 810dcbb25..d85bd7093 100644 --- a/junifer/preprocess/smoothing/smoothing.py +++ b/junifer/preprocess/smoothing/smoothing.py @@ -3,6 +3,7 @@ # Authors: Synchon Mandal # License: AGPL +from collections.abc import Sequence from typing import Any, ClassVar, Optional, Union from ...api.decorators import register_preprocessor @@ -82,6 +83,7 @@ class Smoothing(BasePreprocessor): "depends_on": FSLSmoothing, }, ] + _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["T1w", "T2w", "BOLD"] def __init__( self, @@ -102,17 +104,6 @@ class Smoothing(BasePreprocessor): ) super().__init__(on=on) - def get_valid_inputs(self) -> list[str]: - """Get valid data types for input. - - Returns - ------- - list of str - The list of data types that can be used as input for this - preprocessor. - - """ - return ["T1w", "T2w", "BOLD"] def get_output_type(self, input_type: str) -> str: """Get output type. diff --git a/junifer/preprocess/warping/space_warper.py b/junifer/preprocess/warping/space_warper.py index a85e6955b..0b0e0f394 100644 --- a/junifer/preprocess/warping/space_warper.py +++ b/junifer/preprocess/warping/space_warper.py @@ -3,6 +3,7 @@ # Authors: Synchon Mandal # License: AGPL +from collections.abc import Sequence from typing import Any, ClassVar, Optional, Union from templateflow import api as tflow @@ -62,6 +63,17 @@ class SpaceWarper(BasePreprocessor): "depends_on": [FSLWarper, ANTsWarper], }, ] + _VALID_DATA_TYPES: ClassVar[Sequence[str]] = [ + "T1w", + "T2w", + "BOLD", + "VBM_GM", + "VBM_WM", + "VBM_CSF", + "fALFF", + "GCOR", + "LCOR", + ] def __init__( self, using: str, reference: str, on: Union[list[str], str] @@ -94,27 +106,6 @@ class SpaceWarper(BasePreprocessor): else: raise_error(f"Unknown reference: {self.reference}") - def get_valid_inputs(self) -> list[str]: - """Get valid data types for input. - - Returns - ------- - list of str - The list of data types that can be used as input for this - preprocessor. - - """ - return [ - "T1w", - "T2w", - "BOLD", - "VBM_GM", - "VBM_WM", - "VBM_CSF", - "fALFF", - "GCOR", - "LCOR", - ] def get_output_type(self, input_type: str) -> str: """Get output type. -- 2.52.0 From ee912994a9c8d2491d1d31cd039261389d77aa9a Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 12:33:20 +0100 Subject: [PATCH 03/13] refactor: remove get_output_type method from preprocessors --- junifer/preprocess/_temporal_filter.py | 17 ----------------- junifer/preprocess/_temporal_slicer.py | 17 ----------------- junifer/preprocess/base.py | 7 ++----- .../confounds/fmriprep_confound_remover.py | 17 ----------------- junifer/preprocess/smoothing/smoothing.py | 18 ------------------ junifer/preprocess/warping/space_warper.py | 18 ------------------ 6 files changed, 2 insertions(+), 92 deletions(-) diff --git a/junifer/preprocess/_temporal_filter.py b/junifer/preprocess/_temporal_filter.py index f1ef7aa41..5dce1f42c 100644 --- a/junifer/preprocess/_temporal_filter.py +++ b/junifer/preprocess/_temporal_filter.py @@ -76,23 +76,6 @@ class TemporalFilter(BasePreprocessor): self.t_r = t_r self.masks = masks - - def get_output_type(self, input_type: str) -> str: - """Get output type. - - Parameters - ---------- - input_type : str - The input to the preprocessor. - - Returns - ------- - str - The data type output by the preprocessor. - - """ - # Does not add any new keys - return input_type super().__init__() def _validate_data( diff --git a/junifer/preprocess/_temporal_slicer.py b/junifer/preprocess/_temporal_slicer.py index c35fc8a9a..d2e2ca3b0 100644 --- a/junifer/preprocess/_temporal_slicer.py +++ b/junifer/preprocess/_temporal_slicer.py @@ -63,23 +63,6 @@ class TemporalSlicer(BasePreprocessor): self.stop = stop self.duration = duration self.t_r = t_r - - def get_output_type(self, input_type: str) -> str: - """Get output type. - - Parameters - ---------- - input_type : str - The data type input to the preprocessor. - - Returns - ------- - str - The data type output by the preprocessor. - - """ - # Does not add any new keys - return input_type super().__init__() def preprocess( diff --git a/junifer/preprocess/base.py b/junifer/preprocess/base.py index 936b6e40c..064e098c5 100644 --- a/junifer/preprocess/base.py +++ b/junifer/preprocess/base.py @@ -112,7 +112,6 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): """ return list(self._VALID_DATA_TYPES) - @abstractmethod def get_output_type(self, input_type: str) -> str: """Get output type. @@ -127,10 +126,8 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): The data type output by the preprocessor. """ - raise_error( - msg="Concrete classes need to implement get_output_type().", - klass=NotImplementedError, - ) + # Does not add any new keys + return input_type @abstractmethod def preprocess( diff --git a/junifer/preprocess/confounds/fmriprep_confound_remover.py b/junifer/preprocess/confounds/fmriprep_confound_remover.py index da33bf33f..25305f729 100644 --- a/junifer/preprocess/confounds/fmriprep_confound_remover.py +++ b/junifer/preprocess/confounds/fmriprep_confound_remover.py @@ -253,23 +253,6 @@ class fMRIPrepConfoundRemover(BasePreprocessor): "include it in the future", klass=ValueError, ) - - def get_output_type(self, input_type: str) -> str: - """Get output type. - - Parameters - ---------- - input_type : str - The input to the preprocessor. - - Returns - ------- - str - The data type output by the preprocessor. - - """ - # Does not add any new keys - return input_type super().__init__() def _map_adhoc_to_fmriprep(self, input: dict[str, Any]) -> None: diff --git a/junifer/preprocess/smoothing/smoothing.py b/junifer/preprocess/smoothing/smoothing.py index d85bd7093..21126439f 100644 --- a/junifer/preprocess/smoothing/smoothing.py +++ b/junifer/preprocess/smoothing/smoothing.py @@ -104,24 +104,6 @@ class Smoothing(BasePreprocessor): ) super().__init__(on=on) - - def get_output_type(self, input_type: str) -> str: - """Get output type. - - Parameters - ---------- - input_type : str - The data type input to the preprocessor. - - Returns - ------- - str - The data type output by the preprocessor. - - """ - # Does not add any new keys - return input_type - def preprocess( self, input: dict[str, Any], diff --git a/junifer/preprocess/warping/space_warper.py b/junifer/preprocess/warping/space_warper.py index 0b0e0f394..66dcf82b8 100644 --- a/junifer/preprocess/warping/space_warper.py +++ b/junifer/preprocess/warping/space_warper.py @@ -106,24 +106,6 @@ class SpaceWarper(BasePreprocessor): else: raise_error(f"Unknown reference: {self.reference}") - - def get_output_type(self, input_type: str) -> str: - """Get output type. - - Parameters - ---------- - input_type : str - The data type input to the preprocessor. - - Returns - ------- - str - The data type output by the preprocessor. - - """ - # Does not add any new keys - return input_type - def preprocess( # noqa: C901 self, input: dict[str, Any], -- 2.52.0 From 90574448829f0112452aff028f4c9db2d54bf0ac Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 12:36:16 +0100 Subject: [PATCH 04/13] chore: improve docstring for BasePreprocessor --- junifer/preprocess/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junifer/preprocess/base.py b/junifer/preprocess/base.py index 064e098c5..a8f1d769d 100644 --- a/junifer/preprocess/base.py +++ b/junifer/preprocess/base.py @@ -16,15 +16,15 @@ __all__ = ["BasePreprocessor"] class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): - """Abstract base class for all preprocessors. + """Abstract base class for preprocessor. - For every interface that is required, one needs to provide a concrete + For every preprocessor, one needs to provide a concrete implementation of this abstract class. Parameters ---------- on : str or list of str or None, optional - The data type to apply the preprocessor on. If None, + The data type(s) to apply the preprocessor on. If None, will work on all available data types (default None). required_data_types : str or list of str, optional The data types needed for computation. If None, -- 2.52.0 From 8b34c0c10530af94e686425371c8e860a9934cce Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 12:37:07 +0100 Subject: [PATCH 05/13] update: fix required data types handling in BasePreprocessor --- junifer/preprocess/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/junifer/preprocess/base.py b/junifer/preprocess/base.py index a8f1d769d..217443252 100644 --- a/junifer/preprocess/base.py +++ b/junifer/preprocess/base.py @@ -69,6 +69,9 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): if required_data_types is None: self._required_data_types = on else: + # Convert data types to list + if not isinstance(required_data_types, list): + required_data_types = [required_data_types] self._required_data_types = required_data_types def validate_input(self, input: list[str]) -> list[str]: -- 2.52.0 From 3f24a000a492cf165a00b294b93184374f825e42 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 12:39:41 +0100 Subject: [PATCH 06/13] chore: update tests for BasePreprocessor --- junifer/preprocess/tests/test_preprocess_base.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/junifer/preprocess/tests/test_preprocess_base.py b/junifer/preprocess/tests/test_preprocess_base.py index c0481d952..bdc95a53a 100644 --- a/junifer/preprocess/tests/test_preprocess_base.py +++ b/junifer/preprocess/tests/test_preprocess_base.py @@ -4,6 +4,9 @@ # Synchon Mandal # License: AGPL +from collections.abc import Sequence +from typing import ClassVar + import pytest from junifer.preprocess.base import BasePreprocessor @@ -20,16 +23,12 @@ def test_base_preprocessor_subclassing() -> None: # Create concrete class class MyBasePreprocessor(BasePreprocessor): + _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["BOLD", "T1w"] + def __init__(self, on): self.parameter = 1 super().__init__(on=on) - def get_valid_inputs(self): - return ["BOLD", "T1w"] - - def get_output_type(self, input_type): - return input_type - def preprocess(self, input, extra_input=None): input["data"] = f"modified_{input['data']}" return input, extra_input -- 2.52.0 From c5fbf7b5baf862520172cb00848419522714f179 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 13:52:45 +0100 Subject: [PATCH 07/13] refactor: remove helper data type addition in BasePreprocessor.preprocess --- junifer/preprocess/base.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/junifer/preprocess/base.py b/junifer/preprocess/base.py index 217443252..f2200b212 100644 --- a/junifer/preprocess/base.py +++ b/junifer/preprocess/base.py @@ -137,7 +137,7 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): self, input: dict[str, Any], extra_input: Optional[dict[str, Any]] = None, - ) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]: + ) -> dict[str, Any]: """Preprocess. Parameters @@ -154,10 +154,6 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): ------- dict The computed result as dictionary. - dict or None - Extra "helper" data types as dictionary to add to the Junifer Data - object. If no new "helper" data type(s) is(are) created, None is to - be passed. """ raise_error( @@ -199,19 +195,10 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin): f"Extra data type for preprocess: {extra_input.keys()}" ) # Preprocess data - t_out, t_extra_input = self.preprocess( - input=t_input, extra_input=extra_input - ) + t_out = self.preprocess(input=t_input, extra_input=extra_input) # Set output to the Junifer Data object logger.debug(f"Adding {type_} to output") out[type_] = t_out - # Check if helper data types are to be added - if t_extra_input is not None: - logger.debug( - f"Adding helper data types: {t_extra_input.keys()} " - "to output" - ) - out.update(t_extra_input) # Update metadata for step self.update_meta(out[type_], "preprocess") return out -- 2.52.0 From 9dce829ff8dafeb066e077f5fee76ba892265b3a Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 13:54:22 +0100 Subject: [PATCH 08/13] refactor: adapt helper data type removal from preprocessor preprocess method --- junifer/preprocess/_temporal_filter.py | 7 ++----- junifer/preprocess/_temporal_slicer.py | 7 ++----- junifer/preprocess/confounds/fmriprep_confound_remover.py | 7 ++----- junifer/preprocess/smoothing/smoothing.py | 7 ++----- junifer/preprocess/warping/space_warper.py | 7 ++----- 5 files changed, 10 insertions(+), 25 deletions(-) diff --git a/junifer/preprocess/_temporal_filter.py b/junifer/preprocess/_temporal_filter.py index 5dce1f42c..17270c682 100644 --- a/junifer/preprocess/_temporal_filter.py +++ b/junifer/preprocess/_temporal_filter.py @@ -103,7 +103,7 @@ class TemporalFilter(BasePreprocessor): self, input: dict[str, Any], extra_input: Optional[dict[str, Any]] = None, - ) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]: + ) -> dict[str, Any]: """Preprocess. Parameters @@ -118,9 +118,6 @@ class TemporalFilter(BasePreprocessor): dict The computed result as dictionary. If `self.masks` is not None, then the target data computed mask is updated for further steps. - None - Extra "helper" data types as dictionary to add to the Junifer Data - object. """ # Validate data @@ -210,4 +207,4 @@ class TemporalFilter(BasePreprocessor): } ) - return input, None + return input diff --git a/junifer/preprocess/_temporal_slicer.py b/junifer/preprocess/_temporal_slicer.py index d2e2ca3b0..90dfcc922 100644 --- a/junifer/preprocess/_temporal_slicer.py +++ b/junifer/preprocess/_temporal_slicer.py @@ -69,7 +69,7 @@ class TemporalSlicer(BasePreprocessor): self, input: dict[str, Any], extra_input: Optional[dict[str, Any]] = None, - ) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]: + ) -> dict[str, Any]: """Preprocess. Parameters @@ -83,9 +83,6 @@ class TemporalSlicer(BasePreprocessor): ------- dict The computed result as dictionary. - None - Extra "helper" data types as dictionary to add to the Junifer Data - object. Raises ------ @@ -206,4 +203,4 @@ class TemporalSlicer(BasePreprocessor): } ) - return input, None + return input diff --git a/junifer/preprocess/confounds/fmriprep_confound_remover.py b/junifer/preprocess/confounds/fmriprep_confound_remover.py index 25305f729..753c21a38 100644 --- a/junifer/preprocess/confounds/fmriprep_confound_remover.py +++ b/junifer/preprocess/confounds/fmriprep_confound_remover.py @@ -594,7 +594,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor): self, input: dict[str, Any], extra_input: Optional[dict[str, Any]] = None, - ) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]: + ) -> dict[str, Any]: """Preprocess. Parameters @@ -609,9 +609,6 @@ class fMRIPrepConfoundRemover(BasePreprocessor): dict The computed result as dictionary. If `self.masks` is not None, then the target data computed mask is updated for further steps. - None - Extra "helper" data types as dictionary to add to the Junifer Data - object. """ # Validate data @@ -726,4 +723,4 @@ class fMRIPrepConfoundRemover(BasePreprocessor): } ) - return input, None + return input diff --git a/junifer/preprocess/smoothing/smoothing.py b/junifer/preprocess/smoothing/smoothing.py index 21126439f..99160b5c2 100644 --- a/junifer/preprocess/smoothing/smoothing.py +++ b/junifer/preprocess/smoothing/smoothing.py @@ -108,7 +108,7 @@ class Smoothing(BasePreprocessor): self, input: dict[str, Any], extra_input: Optional[dict[str, Any]] = None, - ) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]: + ) -> dict[str, Any]: """Preprocess. Parameters @@ -122,9 +122,6 @@ class Smoothing(BasePreprocessor): ------- dict The computed result as dictionary. - None - Extra "helper" data types as dictionary to add to the Junifer Data - object. """ logger.debug("Smoothing") @@ -142,4 +139,4 @@ class Smoothing(BasePreprocessor): **self.smoothing_params, ) - return input, None + return input diff --git a/junifer/preprocess/warping/space_warper.py b/junifer/preprocess/warping/space_warper.py index 66dcf82b8..fd7ebc848 100644 --- a/junifer/preprocess/warping/space_warper.py +++ b/junifer/preprocess/warping/space_warper.py @@ -110,7 +110,7 @@ class SpaceWarper(BasePreprocessor): self, input: dict[str, Any], extra_input: Optional[dict[str, Any]] = None, - ) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]: + ) -> dict[str, Any]: """Preprocess. Parameters @@ -124,9 +124,6 @@ class SpaceWarper(BasePreprocessor): ------- dict The computed result as dictionary. - None - Extra "helper" data types as dictionary to add to the Junifer Data - object. Raises ------ @@ -261,4 +258,4 @@ class SpaceWarper(BasePreprocessor): reference=self.reference, ) - return input, None + return input -- 2.52.0 From 8aefccdc537448561c1e8c0ee5c4d4f4d5b11cac Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 13:55:20 +0100 Subject: [PATCH 09/13] chore: update tests for BasePreprocessor --- junifer/preprocess/tests/test_preprocess_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/preprocess/tests/test_preprocess_base.py b/junifer/preprocess/tests/test_preprocess_base.py index bdc95a53a..24322ffc6 100644 --- a/junifer/preprocess/tests/test_preprocess_base.py +++ b/junifer/preprocess/tests/test_preprocess_base.py @@ -31,7 +31,7 @@ def test_base_preprocessor_subclassing() -> None: def preprocess(self, input, extra_input=None): input["data"] = f"modified_{input['data']}" - return input, extra_input + return input with pytest.raises(ValueError, match=r"cannot be computed on \['T2w'\]"): MyBasePreprocessor(on=["BOLD", "T2w"]) -- 2.52.0 From 1cf8b3068a2313efe9428819f90a3488dd31f6ee Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 13:56:58 +0100 Subject: [PATCH 10/13] docs: update extending/preprocessor.rst --- docs/extending/preprocessor.rst | 88 +++++++++------------------------ 1 file changed, 23 insertions(+), 65 deletions(-) diff --git a/docs/extending/preprocessor.rst b/docs/extending/preprocessor.rst index 3fb26828a..7dea78df7 100644 --- a/docs/extending/preprocessor.rst +++ b/docs/extending/preprocessor.rst @@ -14,16 +14,11 @@ new ones, you might need something specific and then you can create your own Preprocessor. While implementing your own Preprocessor, you need to always inherit from -:class:`.BasePreprocessor` and implement a few methods: +:class:`.BasePreprocessor` and implement a few methods and class attributes: -#. ``get_valid_inputs``: This method should return a list of strings - representing the valid data types that the Preprocessor can work on. - Check :ref:`data types ` for reference. -#. ``get_output_type``: This method should just return the input as it - is unused as of now. -#. ``preprocess``: The method that given the data, preprocesses the data. #. ``__init__``: The initialisation method, where the Preprocessor is configured. +#. ``preprocess``: The method that given the data, preprocesses the data. As an example, we will develop a ``NilearnSmoothing`` Preprocessor, which smoothens the data using :func:`nilearn.image.smooth_img`. This is often @@ -32,37 +27,16 @@ desirable in cases where your data is preprocessed using ``fMRIPrep``, as .. _extending_preprocessors_input_output: -Step 1: Configure input and output ----------------------------------- +Step 1: Configure input +----------------------- -In this step, we define the input and output data types of the Preprocessor. +In this step, we define the input data types of the Preprocessor. For input we can accept ``T1w``, ``T2w`` and ``BOLD`` -:ref:`data types `. +:ref:`data types ` and thus declare them in a class attribute: .. code-block:: python - ... - - - def get_valid_inputs(self) -> list[str]: - return ["T1w", "T2w", "BOLD"] - - - ... - -The output definition of the Preprocessor is unused now but is kept for -completeness. - -.. code-block:: python - - ... - - - def get_output_type(self, input_type: str) -> str: - return input_type - - - ... + _VALID_DATA_TYPES = ["T1w", "T2w", "BOLD"] .. _extending_preprocessors_init: @@ -77,7 +51,7 @@ you configure it. Our class will have the following arguments: pass the value to it. 2. ``on``: The data type we want the Preprocessor to work on. If the user does not specify, it will work on all the data types given by the - ``get_valid_inputs`` function. + ``_VALID_DATA_TYPES`` attribute. .. attention:: @@ -133,17 +107,6 @@ arguments: useful if you want to use other data (e.g., ``Warp`` can be used to provide the transformation matrix file for transformation to subject-native space). -and it has two return values: - -* First is the ``input`` dictionary with necessary data modified. Usually, you - want to replace the ``input["data"]`` with the preprocessed data. -* Second is a dictionary just like ``input`` or ``extra_input`` but with only - specific key-value pairs which you would like to pass down to the Markers. - For example, if your Preprocessor computes some mask with the preprocessed - data, you could pass it through this which would be added and available - in the Marker step with the same key you pass here. Usually, you would - want to pass ``None``. - .. code-block:: python from typing import Any @@ -158,9 +121,9 @@ and it has two return values: self, input: dict[str, Any], extra_input: dict[str, Any] | None = None, - ) -> tuple[dict[str, Any], dict[str, Any] | None]: + ) -> dict[str, Any]: input["data"] = nimg.smooth_img(imgs=input["data"], fwhm=self.fwhm) - return input, None + return input ... @@ -187,7 +150,8 @@ decorator and our final code should look like this: .. code-block:: python - from typing import Any, Literal + from collections.abc import Sequence + from typing import Any, ClassVar, Literal from junifer.api.decorators import register_preprocessor from junifer.preprocess import BasePreprocessor @@ -201,6 +165,8 @@ decorator and our final code should look like this: _DEPENDENCIES = {"nilearn"} + _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["T1w", "T2w", "BOLD"] + def __init__( self, fwhm: int | float | ArrayLike | Literal["fast"] | None, @@ -209,19 +175,13 @@ decorator and our final code should look like this: self.fwhm = fwhm super().__init__(on=on) - def get_valid_inputs(self) -> list[str]: - return ["T1w", "T2w", "BOLD"] - - def get_output_type(self, input_type: str) -> str: - return input_type - def preprocess( self, input: dict[str, Any], extra_input: dict[str, Any] | None = None, - ) -> tuple[dict[str, Any], dict[str, Any] | None]: + ) -> dict[str, Any]: input["data"] = nimg.smooth_img(imgs=input["data"], fwhm=self.fwhm) - return input, None + return input .. _extending_preprocessors_template: @@ -238,18 +198,16 @@ Template for a custom Preprocessor @register_preprocessor class TemplatePreprocessor(BasePreprocessor): + # TODO: add the dependencies + _DEPENDENCIES = {} + + # TODO: add the inputs + _VALID_DATA_TYPES = [] + def __init__(self, on=None): # TODO: add preprocessor-specific parameters super().__init__(on=on) - def get_valid_inputs(self): - # TODO: Complete with the valid inputs - valid = [] - return valid - - def get_output_type(self, input_type): - return input_type - def preprocess(self, input, extra_input): # TODO: add the preprocessor logic - return input, None + return input -- 2.52.0 From a1a653a23c3592244250ce9fc5058397a3fccf11 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 14:08:00 +0100 Subject: [PATCH 11/13] chore: update tests for fMRIPrepConfoundRemover --- .../confounds/tests/test_fmriprep_confound_remover.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py b/junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py index 2ed820fb1..ab026701e 100644 --- a/junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +++ b/junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py @@ -463,7 +463,7 @@ def test_fMRIPrepConfoundRemover_preprocess() -> None: pre_extra_input = { "BOLD": {"confounds": element_data["BOLD"]["confounds"]} } - output, _ = confound_remover.preprocess(pre_input, pre_extra_input) + output = confound_remover.preprocess(pre_input, pre_extra_input) trans_bold = output["data"].get_fdata() # Transformation is in place assert_array_equal( @@ -614,7 +614,7 @@ def test_fMRIPrepConfoundRemover_scrubbing() -> None: pre_extra_input = { "BOLD": {"confounds": element_data["BOLD"]["confounds"]} } - output, _ = confound_remover.preprocess(pre_input, pre_extra_input) + output = confound_remover.preprocess(pre_input, pre_extra_input) trans_bold = output["data"].get_fdata() # Transformation is in place assert_array_equal( -- 2.52.0 From 58c5ac3177c2998a42c6274296202f6a21955830 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 14:08:45 +0100 Subject: [PATCH 12/13] chore: update tests for SpaceWarper --- junifer/preprocess/warping/tests/test_space_warper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junifer/preprocess/warping/tests/test_space_warper.py b/junifer/preprocess/warping/tests/test_space_warper.py index e79b32a01..d301a091a 100644 --- a/junifer/preprocess/warping/tests/test_space_warper.py +++ b/junifer/preprocess/warping/tests/test_space_warper.py @@ -113,7 +113,7 @@ def test_SpaceWarper_native( # Read data element_data = DefaultDataReader().fit_transform(dg[element]) # Preprocess data - output, _ = SpaceWarper( + output = SpaceWarper( using=using, reference="T1w", on="BOLD", @@ -179,7 +179,7 @@ def test_SpaceWarper_multi_mni( element_data = DefaultDataReader().fit_transform(dg[element]) pre_xfm_data = element_data["T1w"]["data"].get_fdata().copy() # Preprocess data - output, _ = SpaceWarper( + output = SpaceWarper( using="ants", reference=space, on=["T1w"], -- 2.52.0 From 5b51d11be617e030078218355b1f034abced4017 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 31 Oct 2025 14:14:54 +0100 Subject: [PATCH 13/13] chore: add changelog 473.enh --- docs/changes/newsfragments/473.enh | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/473.enh diff --git a/docs/changes/newsfragments/473.enh b/docs/changes/newsfragments/473.enh new file mode 100644 index 000000000..b9a2706ac --- /dev/null +++ b/docs/changes/newsfragments/473.enh @@ -0,0 +1 @@ +Simplify ``Preprocess`` interface and implementations by `Synchon Mandal`_ -- 2.52.0