From 18ba57ddf6568c060f1a6172c2a6bc07e34d3f3e Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 22 Sep 2023 10:49:21 +0200 Subject: [PATCH 1/9] update: introduce BOLDWarper for warping BOLD images --- junifer/preprocess/__init__.py | 1 + junifer/preprocess/fsl/__init__.py | 2 + junifer/preprocess/fsl/bold_warper.py | 114 ++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 junifer/preprocess/fsl/bold_warper.py diff --git a/junifer/preprocess/__init__.py b/junifer/preprocess/__init__.py index d73020c9e..9eed4d5f2 100644 --- a/junifer/preprocess/__init__.py +++ b/junifer/preprocess/__init__.py @@ -7,3 +7,4 @@ from .base import BasePreprocessor from .confounds import fMRIPrepConfoundRemover +from .fsl import BOLDWarper diff --git a/junifer/preprocess/fsl/__init__.py b/junifer/preprocess/fsl/__init__.py index 01c947063..7ae2765d1 100644 --- a/junifer/preprocess/fsl/__init__.py +++ b/junifer/preprocess/fsl/__init__.py @@ -2,3 +2,5 @@ # Authors: Synchon Mandal # License: AGPL + +from .bold_warper import BOLDWarper diff --git a/junifer/preprocess/fsl/bold_warper.py b/junifer/preprocess/fsl/bold_warper.py new file mode 100644 index 000000000..6369ad85f --- /dev/null +++ b/junifer/preprocess/fsl/bold_warper.py @@ -0,0 +1,114 @@ +"""Provide class for warping BOLD via FSL FLIRT.""" + +# Authors: Synchon Mandal +# License: AGPL + +from typing import ( + Any, + ClassVar, + Dict, + List, + Optional, + Tuple, + Union, +) + +from ...api.decorators import register_preprocessor +from ...utils import logger, raise_error +from ..base import BasePreprocessor +from .apply_warper import ApplyWarper + + +@register_preprocessor +class BOLDWarper(BasePreprocessor): + """Class for warping BOLD NifTI images via FSL FLIRT.""" + + _EXT_DEPENDENCIES: ClassVar[ + List[Dict[str, Union[str, bool, List[str]]]] + ] = [ + { + "name": "fsl", + "optional": True, + "commands": ["applywarp"], + }, + ] + + def __init__(self) -> None: + """Initialize the class.""" + super().__init__(on="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", "T1w", "Warp"] + + def get_output_type(self, input: List[str]) -> List[str]: + """Get output type. + + Parameters + ---------- + input : list of str + The input to the preprocessor. The list must contain the + available Junifer Data dictionary keys. + + Returns + ------- + list of str + The updated list of available Junifer Data object keys after + the pipeline step. + + """ + # Does not add any new keys + return input + + def preprocess( + self, + input: Dict[str, Any], + extra_input: Optional[Dict[str, Any]] = None, + ) -> Tuple[str, Dict[str, Any]]: + """Preprocess. + + Parameters + ---------- + input : dict + The BOLD input from the Junifer Data object. + extra_input : dict, optional + The other fields in the Junifer Data object. Must include the + ``T1w`` and ``Warp`` keys. + + Returns + ------- + str + The key to store the output in the Junifer Data object. + dict + The computed result as dictionary. This will be stored in the + Junifer Data object under the key ``data`` of the data type. + + Raises + ------ + ValueError + If ``extra_input`` is None. + + """ + logger.debug("Warping BOLD using BOLDWarper") + # Check for extra inputs + if extra_input is None: + raise_error( + "No extra input provided, requires `T1w` and `Warp` data " + "types in particular." + ) + # Initialize ApplyWarper for computation + apply_warper = ApplyWarper(ref="T1w", on="BOLD") + # Replace original BOLD data with warped BOLD data + input["data"] = apply_warper.preprocess( + input=input, + extra_input=extra_input, + ) + return "BOLD", input -- 2.52.0 From 576de183ddde6a5902d7dec79f4a455a50a95370 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 22 Sep 2023 12:05:22 +0200 Subject: [PATCH 2/9] update: add tests for BOLDWarper --- .../preprocess/fsl/tests/test_bold_warper.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 junifer/preprocess/fsl/tests/test_bold_warper.py diff --git a/junifer/preprocess/fsl/tests/test_bold_warper.py b/junifer/preprocess/fsl/tests/test_bold_warper.py new file mode 100644 index 000000000..e71377f78 --- /dev/null +++ b/junifer/preprocess/fsl/tests/test_bold_warper.py @@ -0,0 +1,58 @@ +"""Provide tests for BOLDWarper.""" + +# Authors: Synchon Mandal +# License: AGPL + +from typing import List + +import pytest + +# from junifer.datareader import DefaultDataReader +# from junifer.pipeline.utils import _check_fsl +from junifer.preprocess.fsl import BOLDWarper + + +def test_BOLDWarper_init() -> None: + """Test BOLDWarper init.""" + bold_warper = BOLDWarper(ref="T1w") + assert bold_warper._on == ["BOLD"] + + +def test_BOLDWarper_get_valid_inputs() -> None: + """Test BOLDWarper get_valid_inputs.""" + bold_warper = BOLDWarper(ref="T1w") + assert bold_warper.get_valid_inputs() == ["BOLD", "T1w", "Warp"] + + +@pytest.mark.parametrize( + "input_", + [ + ["BOLD", "T1w", "Warp"], + ["BOLD", "T1w"], + ["BOLD"], + ], +) +def test_BOLDWarper_get_output_type(input_: List[str]) -> None: + """Test BOLDWarper get_output_type. + + Parameters + ---------- + input_ : list of str + The input data types. + + """ + bold_warper = BOLDWarper(ref="T1w") + assert bold_warper.get_output_type(input_) == input_ + + +@pytest.mark.skip(reason="requires testing dataset") +# @pytest.mark.skipif( +# _check_fsl() is False, reason="requires fsl to be in PATH" +# ) +def test_BOLDWarper_preprocess() -> None: + """Test BOLDWarper preprocess.""" + # Initialize datareader + # reader = DefaultDataReader() + # Initialize preprocessor + # bold_warper = BOLDWarper(ref="T1w") + # TODO(synchon): setup datagrabber and run pipeline -- 2.52.0 From a2a1483aed07ec8e918e61f7d141c404492dea4c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 18 Oct 2023 11:45:03 +0200 Subject: [PATCH 3/9] update: adapt BOLDWarper to take reference as parameter --- junifer/preprocess/fsl/bold_warper.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/junifer/preprocess/fsl/bold_warper.py b/junifer/preprocess/fsl/bold_warper.py index 6369ad85f..0e1b95f16 100644 --- a/junifer/preprocess/fsl/bold_warper.py +++ b/junifer/preprocess/fsl/bold_warper.py @@ -21,7 +21,14 @@ from .apply_warper import ApplyWarper @register_preprocessor class BOLDWarper(BasePreprocessor): - """Class for warping BOLD NifTI images via FSL FLIRT.""" + """Class for warping BOLD NIfTI images via FSL FLIRT. + + Parameters + ---------- + ref : str + The data type to use as reference for warping. + + """ _EXT_DEPENDENCIES: ClassVar[ List[Dict[str, Union[str, bool, List[str]]]] @@ -33,8 +40,9 @@ class BOLDWarper(BasePreprocessor): }, ] - def __init__(self) -> None: + def __init__(self, ref: str) -> None: """Initialize the class.""" + self.ref = ref super().__init__(on="BOLD") def get_valid_inputs(self) -> List[str]: @@ -47,7 +55,8 @@ class BOLDWarper(BasePreprocessor): preprocessor. """ - return ["BOLD", "T1w", "Warp"] + # Constructed dynamically + return ["BOLD", self.ref, "Warp"] def get_output_type(self, input: List[str]) -> List[str]: """Get output type. @@ -81,7 +90,7 @@ class BOLDWarper(BasePreprocessor): The BOLD input from the Junifer Data object. extra_input : dict, optional The other fields in the Junifer Data object. Must include the - ``T1w`` and ``Warp`` keys. + ``Warp`` and ``ref`` value's keys. Returns ------- @@ -101,11 +110,11 @@ class BOLDWarper(BasePreprocessor): # Check for extra inputs if extra_input is None: raise_error( - "No extra input provided, requires `T1w` and `Warp` data " - "types in particular." + f"No extra input provided, requires `Warp` and `{self.ref}` " + "data types in particular." ) # Initialize ApplyWarper for computation - apply_warper = ApplyWarper(ref="T1w", on="BOLD") + apply_warper = ApplyWarper(ref=self.ref, on="BOLD") # Replace original BOLD data with warped BOLD data input["data"] = apply_warper.preprocess( input=input, -- 2.52.0 From 53a42b3dd75e88fd7c28363d81b799b6a2199da0 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 18 Oct 2023 11:46:25 +0200 Subject: [PATCH 4/9] update: adapt ApplyWarper to keep the resampled reference file and pass down its path --- junifer/preprocess/fsl/bold_warper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/preprocess/fsl/bold_warper.py b/junifer/preprocess/fsl/bold_warper.py index 0e1b95f16..53a38a3b2 100644 --- a/junifer/preprocess/fsl/bold_warper.py +++ b/junifer/preprocess/fsl/bold_warper.py @@ -116,7 +116,7 @@ class BOLDWarper(BasePreprocessor): # Initialize ApplyWarper for computation apply_warper = ApplyWarper(ref=self.ref, on="BOLD") # Replace original BOLD data with warped BOLD data - input["data"] = apply_warper.preprocess( + _, input = apply_warper.preprocess( input=input, extra_input=extra_input, ) -- 2.52.0 From afc74420923b400f76ab0e32085c6eef8abf0ad9 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 18 Oct 2023 11:48:31 +0200 Subject: [PATCH 5/9] update: rename ApplyWarper to _ApplyWarper --- junifer/preprocess/fsl/bold_warper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junifer/preprocess/fsl/bold_warper.py b/junifer/preprocess/fsl/bold_warper.py index 53a38a3b2..97dd218d9 100644 --- a/junifer/preprocess/fsl/bold_warper.py +++ b/junifer/preprocess/fsl/bold_warper.py @@ -16,7 +16,7 @@ from typing import ( from ...api.decorators import register_preprocessor from ...utils import logger, raise_error from ..base import BasePreprocessor -from .apply_warper import ApplyWarper +from .apply_warper import _ApplyWarper @register_preprocessor @@ -114,7 +114,7 @@ class BOLDWarper(BasePreprocessor): "data types in particular." ) # Initialize ApplyWarper for computation - apply_warper = ApplyWarper(ref=self.ref, on="BOLD") + apply_warper = _ApplyWarper(ref=self.ref, on="BOLD") # Replace original BOLD data with warped BOLD data _, input = apply_warper.preprocess( input=input, -- 2.52.0 From 628dba6be6ba3c994cb1a78a944fafe2eedca7a8 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 18 Oct 2023 11:50:45 +0200 Subject: [PATCH 6/9] update: rename ref to reference for _ApplyWarper --- junifer/preprocess/fsl/bold_warper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/preprocess/fsl/bold_warper.py b/junifer/preprocess/fsl/bold_warper.py index 97dd218d9..0a86b2d29 100644 --- a/junifer/preprocess/fsl/bold_warper.py +++ b/junifer/preprocess/fsl/bold_warper.py @@ -114,7 +114,7 @@ class BOLDWarper(BasePreprocessor): "data types in particular." ) # Initialize ApplyWarper for computation - apply_warper = _ApplyWarper(ref=self.ref, on="BOLD") + apply_warper = _ApplyWarper(reference=self.ref, on="BOLD") # Replace original BOLD data with warped BOLD data _, input = apply_warper.preprocess( input=input, -- 2.52.0 From 759737d3cc4a8a874a497521ac9c42f3d41bf5c9 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 18 Oct 2023 12:10:34 +0200 Subject: [PATCH 7/9] update: rename ref to reference for BOLDWarper --- junifer/preprocess/fsl/bold_warper.py | 6 +++--- junifer/preprocess/fsl/tests/test_bold_warper.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/junifer/preprocess/fsl/bold_warper.py b/junifer/preprocess/fsl/bold_warper.py index 0a86b2d29..1ecef8e5a 100644 --- a/junifer/preprocess/fsl/bold_warper.py +++ b/junifer/preprocess/fsl/bold_warper.py @@ -25,7 +25,7 @@ class BOLDWarper(BasePreprocessor): Parameters ---------- - ref : str + reference : str The data type to use as reference for warping. """ @@ -40,10 +40,10 @@ class BOLDWarper(BasePreprocessor): }, ] - def __init__(self, ref: str) -> None: + def __init__(self, reference: str) -> None: """Initialize the class.""" - self.ref = ref super().__init__(on="BOLD") + self.ref = reference def get_valid_inputs(self) -> List[str]: """Get valid data types for input. diff --git a/junifer/preprocess/fsl/tests/test_bold_warper.py b/junifer/preprocess/fsl/tests/test_bold_warper.py index e71377f78..4649a1b02 100644 --- a/junifer/preprocess/fsl/tests/test_bold_warper.py +++ b/junifer/preprocess/fsl/tests/test_bold_warper.py @@ -14,13 +14,13 @@ from junifer.preprocess.fsl import BOLDWarper def test_BOLDWarper_init() -> None: """Test BOLDWarper init.""" - bold_warper = BOLDWarper(ref="T1w") + bold_warper = BOLDWarper(reference="T1w") assert bold_warper._on == ["BOLD"] def test_BOLDWarper_get_valid_inputs() -> None: """Test BOLDWarper get_valid_inputs.""" - bold_warper = BOLDWarper(ref="T1w") + bold_warper = BOLDWarper(reference="T1w") assert bold_warper.get_valid_inputs() == ["BOLD", "T1w", "Warp"] @@ -41,7 +41,7 @@ def test_BOLDWarper_get_output_type(input_: List[str]) -> None: The input data types. """ - bold_warper = BOLDWarper(ref="T1w") + bold_warper = BOLDWarper(reference="T1w") assert bold_warper.get_output_type(input_) == input_ @@ -54,5 +54,5 @@ def test_BOLDWarper_preprocess() -> None: # Initialize datareader # reader = DefaultDataReader() # Initialize preprocessor - # bold_warper = BOLDWarper(ref="T1w") + # bold_warper = BOLDWarper(reference="T1w") # TODO(synchon): setup datagrabber and run pipeline -- 2.52.0 From 30f2d0d073d9b706e2b4c85ff7e8952639356ec4 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 18 Oct 2023 12:12:34 +0200 Subject: [PATCH 8/9] update: adapt BOLDWarper and tests to BasePreprocessor changes --- junifer/preprocess/fsl/bold_warper.py | 7 ++++--- junifer/preprocess/fsl/tests/test_bold_warper.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/junifer/preprocess/fsl/bold_warper.py b/junifer/preprocess/fsl/bold_warper.py index 1ecef8e5a..63d1c20a4 100644 --- a/junifer/preprocess/fsl/bold_warper.py +++ b/junifer/preprocess/fsl/bold_warper.py @@ -42,8 +42,10 @@ class BOLDWarper(BasePreprocessor): def __init__(self, reference: str) -> None: """Initialize the class.""" - super().__init__(on="BOLD") self.ref = reference + super().__init__( + on="BOLD", required_data_types=["BOLD", self.ref, "Warp"] + ) def get_valid_inputs(self) -> List[str]: """Get valid data types for input. @@ -55,8 +57,7 @@ class BOLDWarper(BasePreprocessor): preprocessor. """ - # Constructed dynamically - return ["BOLD", self.ref, "Warp"] + return ["BOLD"] def get_output_type(self, input: List[str]) -> List[str]: """Get output type. diff --git a/junifer/preprocess/fsl/tests/test_bold_warper.py b/junifer/preprocess/fsl/tests/test_bold_warper.py index 4649a1b02..71cfa5160 100644 --- a/junifer/preprocess/fsl/tests/test_bold_warper.py +++ b/junifer/preprocess/fsl/tests/test_bold_warper.py @@ -21,7 +21,7 @@ def test_BOLDWarper_init() -> None: def test_BOLDWarper_get_valid_inputs() -> None: """Test BOLDWarper get_valid_inputs.""" bold_warper = BOLDWarper(reference="T1w") - assert bold_warper.get_valid_inputs() == ["BOLD", "T1w", "Warp"] + assert bold_warper.get_valid_inputs() == ["BOLD"] @pytest.mark.parametrize( -- 2.52.0 From 643f42133bdb19457ba978f9b215e3aa8875274c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 18 Oct 2023 12:17:29 +0200 Subject: [PATCH 9/9] chore: add changelog 267.feature --- docs/changes/newsfragments/267.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/267.feature diff --git a/docs/changes/newsfragments/267.feature b/docs/changes/newsfragments/267.feature new file mode 100644 index 000000000..73b7626e5 --- /dev/null +++ b/docs/changes/newsfragments/267.feature @@ -0,0 +1 @@ +Introduce :class:`.BOLDWarper` for warping BOLD data via FSL's ``applywarp`` by `Synchon Mandal`_ -- 2.52.0