From 51d32a4f36c1b2ccb089b09bb2e64ae87bf73938 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 20 Sep 2023 15:24:52 +0200 Subject: [PATCH 01/12] update: introduce ApplyWarper preprocessor --- junifer/preprocess/__init__.py | 2 + junifer/preprocess/fsl/__init__.py | 6 + junifer/preprocess/fsl/apply_warper.py | 197 +++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 junifer/preprocess/fsl/__init__.py create mode 100644 junifer/preprocess/fsl/apply_warper.py diff --git a/junifer/preprocess/__init__.py b/junifer/preprocess/__init__.py index 99db94390..54e5f62ec 100644 --- a/junifer/preprocess/__init__.py +++ b/junifer/preprocess/__init__.py @@ -2,7 +2,9 @@ # Authors: Federico Raimondo # Leonard Sasse +# Synchon Mandal # License: AGPL from .base import BasePreprocessor from .confounds import fMRIPrepConfoundRemover +from .fsl import ApplyWarper diff --git a/junifer/preprocess/fsl/__init__.py b/junifer/preprocess/fsl/__init__.py new file mode 100644 index 000000000..c57c16c3e --- /dev/null +++ b/junifer/preprocess/fsl/__init__.py @@ -0,0 +1,6 @@ +"""Provide imports for fsl sub-package.""" + +# Authors: Synchon Mandal +# License: AGPL + +from .apply_warper import ApplyWarper diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py new file mode 100644 index 000000000..1be65eb22 --- /dev/null +++ b/junifer/preprocess/fsl/apply_warper.py @@ -0,0 +1,197 @@ +"""Provide class for warping via FSL FLIRT.""" + +# Authors: Synchon Mandal +# License: AGPL + +import subprocess +import tempfile +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Dict, + List, + Optional, + Tuple, + Union, + cast, +) + +import nibabel as nib + +from ...api.decorators import register_preprocessor +from ...utils import logger, raise_error +from ..base import BasePreprocessor + + +if TYPE_CHECKING: + from nibabel import Nifti1Image + + +@register_preprocessor +class ApplyWarper(BasePreprocessor): + """Class for warping NIfTI images via FSL FLIRT. + + Wraps FSL FLIRT ``applywarp``. + + """ + + _EXT_DEPENDENCIES: ClassVar[ + List[Dict[str, Union[str, bool, List[str]]]] + ] = [ + { + "name": "fsl", + "optional": False, + "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 _run_applywarp( + self, + input_path: Path, + ref_path: Path, + warp_path: Path, + ) -> "Nifti1Image": + """Run ``applywarp``. + + Parameters + ---------- + input_path : pathlib.Path + The path to the input file. + ref_path : pathlib.Path + The path to the reference file. + warp_path : pathlib.Path + The path to the warp file. + + Returns + ------- + Niimg-like object + + """ + # Create a tempfile for warped BOLD output + applywarp_out_path = Path(tempfile.mkdtemp()) / "bold_warped.nii.gz" + # Set applywarp command + applywarp_cmd = [ + "applywarp", + "--interp=spline", + f"-i {input_path.resolve()}", + f"-r {ref_path.resolve()}", + f"-w {warp_path.resolve()}", + f"-o {applywarp_out_path.resolve()}", + ] + # Call applywarp + applywarp_cmd_str = " ".join(applywarp_cmd) + logger.info(f"applywarp command to be executed: {applywarp_cmd_str}") + applywarp_process = subprocess.run( + applywarp_cmd_str, # string needed with shell=True + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, # needed for respecting $PATH + check=False, + ) + if applywarp_process.returncode == 0: + logger.info( + "applywarp succeeded with the following output: " + f"{applywarp_process.stdout}" + ) + else: + raise_error( + msg="applywarp failed with the following error: " + f"{applywarp_process.stdout}", + klass=RuntimeError, + ) + + # Load nifti + output_img = nib.load(applywarp_out_path) + # Delete file + applywarp_out_path.unlink() + + # Stupid casting + output_img = cast("Nifti1Image", output_img) + return output_img + + def preprocess( + self, + input: Dict[str, Any], + extra_input: Optional[Dict[str, Any]] = None, + ) -> Tuple[str, Dict[str, Any]]: + """Preprocess. + + Parameters + ---------- + input : dict + A single input from the Junifer Data object in which to preprocess. + 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. + + """ + # Check for extra inputs + if extra_input is None: + raise_error( + "No extra input provided, requires `T1w` and `Warp` data " + "types in particular." + ) + # Retrieve BOLD data + bold = input + # Retrieve T1w data + t1w = extra_input["T1w"] + # Retrieve Warp data + warp = extra_input["Warp"] + # Replace original BOLD data with warped BOLD data + input["data"] = self._run_applywarp( + input_path=bold["path"], + ref_path=t1w["path"], + warp_path=warp["path"], + ) + return "BOLD", input -- 2.52.0 From 5cd2e08f322b73b1dc675c23143f49b27603c131 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 22 Sep 2023 12:05:05 +0200 Subject: [PATCH 02/12] update: add tests for ApplyWarper --- .../preprocess/fsl/tests/test_apply_warper.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 junifer/preprocess/fsl/tests/test_apply_warper.py diff --git a/junifer/preprocess/fsl/tests/test_apply_warper.py b/junifer/preprocess/fsl/tests/test_apply_warper.py new file mode 100644 index 000000000..26b21f5cd --- /dev/null +++ b/junifer/preprocess/fsl/tests/test_apply_warper.py @@ -0,0 +1,73 @@ +"""Provide tests for ApplyWarper.""" + +# 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.apply_warper import ApplyWarper + + +def test_ApplyWarper_init() -> None: + """Test ApplyWarper init.""" + apply_warper = ApplyWarper(ref="T1w", on="BOLD") + assert apply_warper.ref == "T1w" + assert apply_warper.on == "BOLD" + assert apply_warper._on == ["BOLD"] + + +def test_ApplyWarper_get_valid_inputs() -> None: + """Test ApplyWarper get_valid_inputs.""" + apply_warper = ApplyWarper(ref="T1w", on="BOLD") + assert apply_warper.get_valid_inputs() == ["BOLD", "T1w", "Warp"] + + +@pytest.mark.parametrize( + "input_", + [ + ["BOLD", "T1w", "Warp"], + ["BOLD", "T1w"], + ["BOLD"], + ], +) +def test_ApplyWarper_get_output_type(input_: List[str]) -> None: + """Test ApplyWarper get_output_type. + + Parameters + ---------- + input_ : list of str + The input data types. + + """ + apply_warper = ApplyWarper(ref="T1w", on="BOLD") + assert apply_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_ApplyWarper__run_applywarp() -> None: + """Test ApplyWarper _run_applywarp.""" + # Initialize datareader + # reader = DefaultDataReader() + # Initialize preprocessor + # bold_warper = ApplyWarper(ref="T1w", on="BOLD") + # TODO(synchon): setup datagrabber and run pipeline + + +@pytest.mark.skip(reason="requires testing dataset") +# @pytest.mark.skipif( +# _check_fsl() is False, reason="requires fsl to be in PATH" +# ) +def test_ApplyWarper_preprocess() -> None: + """Test ApplyWarper preprocess.""" + # Initialize datareader + # reader = DefaultDataReader() + # Initialize preprocessor + # bold_warper = ApplyWarper(ref="T1w", on="BOLD") + # TODO(synchon): setup datagrabber and run pipeline -- 2.52.0 From 46818ea84447a5d50e3152247578967715d29920 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 18:35:03 +0200 Subject: [PATCH 03/12] update: modify ApplyWarper to be a generic FSL warper wrapper --- junifer/preprocess/fsl/apply_warper.py | 111 ++++++++++++++++++++----- 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 1be65eb22..125f4a5dd 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -19,6 +19,7 @@ from typing import ( ) import nibabel as nib +import numpy as np from ...api.decorators import register_preprocessor from ...utils import logger, raise_error @@ -35,6 +36,18 @@ class ApplyWarper(BasePreprocessor): Wraps FSL FLIRT ``applywarp``. + Parameters + ---------- + ref : str + The data type to use as reference for warping. + on : str + The data type to use for warping. + + Raises + ------ + ValueError + If a list was passed for ``on``. + """ _EXT_DEPENDENCIES: ClassVar[ @@ -47,9 +60,14 @@ class ApplyWarper(BasePreprocessor): }, ] - def __init__(self) -> None: + def __init__(self, ref: str, on: str) -> None: """Initialize the class.""" - super().__init__(on="BOLD") + self.ref = ref + # Check only single data type is passed + if isinstance(on, list): + raise_error("Can only work on single data type, list was passed.") + self.on = on # needed for the base validation to work + super().__init__(on=self.on) def get_valid_inputs(self) -> List[str]: """Get valid data types for input. @@ -61,7 +79,8 @@ class ApplyWarper(BasePreprocessor): preprocessor. """ - return ["BOLD", "T1w", "Warp"] + # Constructed dynamically + return [self.on, self.ref, "Warp"] def get_output_type(self, input: List[str]) -> List[str]: """Get output type. @@ -84,7 +103,7 @@ class ApplyWarper(BasePreprocessor): def _run_applywarp( self, - input_path: Path, + input_data: Dict, ref_path: Path, warp_path: Path, ) -> "Nifti1Image": @@ -92,8 +111,8 @@ class ApplyWarper(BasePreprocessor): Parameters ---------- - input_path : pathlib.Path - The path to the input file. + input_data : dict + The input data. ref_path : pathlib.Path The path to the reference file. warp_path : pathlib.Path @@ -103,15 +122,63 @@ class ApplyWarper(BasePreprocessor): ------- Niimg-like object + Raises + ------ + RuntimeError + If FSL commands fail. + """ - # Create a tempfile for warped BOLD output - applywarp_out_path = Path(tempfile.mkdtemp()) / "bold_warped.nii.gz" + # Get the min of the voxel sizes from input and use it as the + # resolution + resolution = np.min(input_data["data"].header.get_zooms()[:3]) + + # Create tempdir + tempdir = Path(tempfile.mkdtemp()) + + # Create a tempfile for resampled reference output + flirt_out_path = tempdir / "reference_resampled.nii.gz" + # Set flirt command + flirt_cmd = [ + "flirt", + "-interp spline", + f"-in {ref_path.resolve()}", + f"-ref {ref_path.resolve()}", + f"-applyisoxfm {resolution}", + f"-out {flirt_out_path.resolve()}", + ] + # Call flirt + flirt_cmd_str = " ".join(flirt_cmd) + logger.info(f"flirt command to be executed: {flirt_cmd_str}") + flirt_process = subprocess.run( + flirt_cmd_str, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, # needed for respecting $PATH + check=False, + ) + if flirt_process.returncode == 0: + logger.info( + "flirt succeeded with the following output: " + f"{flirt_process.stdout}" + ) + else: + raise_error( + msg="flirt failed with the following error: " + f"{flirt_process.stdout}", + klass=RuntimeError, + ) + + # TODO(synchon): Modify reference or not? + + # Create a tempfile for warped output + applywarp_out_path = tempdir / "input_warped.nii.gz" # Set applywarp command applywarp_cmd = [ "applywarp", "--interp=spline", - f"-i {input_path.resolve()}", - f"-r {ref_path.resolve()}", + f"-i {input_data['path'].resolve()}", + f"-r {flirt_out_path.resolve()}", # use resampled reference f"-w {warp_path.resolve()}", f"-o {applywarp_out_path.resolve()}", ] @@ -141,6 +208,7 @@ class ApplyWarper(BasePreprocessor): # Load nifti output_img = nib.load(applywarp_out_path) # Delete file + flirt_out_path.unlink() applywarp_out_path.unlink() # Stupid casting @@ -160,7 +228,7 @@ class ApplyWarper(BasePreprocessor): A single input from the Junifer Data object in which to preprocess. 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 ------- @@ -176,22 +244,23 @@ class ApplyWarper(BasePreprocessor): If ``extra_input`` is None. """ + logger.debug("Warping via FSL using ApplyWarper") # 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." ) - # Retrieve BOLD data - bold = input - # Retrieve T1w data - t1w = extra_input["T1w"] + # Retrieve data type info to warp + to_warp_input = input + # Retrieve data type info to use as reference + ref_input = extra_input[self.ref] # Retrieve Warp data warp = extra_input["Warp"] - # Replace original BOLD data with warped BOLD data + # Replace original data with warped data input["data"] = self._run_applywarp( - input_path=bold["path"], - ref_path=t1w["path"], + input_data=to_warp_input, + ref_path=ref_input["path"], warp_path=warp["path"], ) - return "BOLD", input + return self.on, input -- 2.52.0 From bbea45851250ad5a4089261b83ea062a1c158cc9 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 18:39:21 +0200 Subject: [PATCH 04/12] update: adapt ApplyWarper to keep the resampled reference file and pass down its path --- junifer/preprocess/fsl/apply_warper.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 125f4a5dd..2caad25a8 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -106,7 +106,7 @@ class ApplyWarper(BasePreprocessor): input_data: Dict, ref_path: Path, warp_path: Path, - ) -> "Nifti1Image": + ) -> Tuple["Nifti1Image", Path]: """Run ``applywarp``. Parameters @@ -121,6 +121,9 @@ class ApplyWarper(BasePreprocessor): Returns ------- Niimg-like object + The warped input image. + pathlib.Path + The path to the resampled reference image. Raises ------ @@ -207,13 +210,12 @@ class ApplyWarper(BasePreprocessor): # Load nifti output_img = nib.load(applywarp_out_path) - # Delete file + # Delete warped output file flirt_out_path.unlink() - applywarp_out_path.unlink() # Stupid casting output_img = cast("Nifti1Image", output_img) - return output_img + return output_img, flirt_out_path def preprocess( self, @@ -257,8 +259,9 @@ class ApplyWarper(BasePreprocessor): ref_input = extra_input[self.ref] # Retrieve Warp data warp = extra_input["Warp"] - # Replace original data with warped data - input["data"] = self._run_applywarp( + # Replace original data with warped data and add resampled reference + # path + input["data"], input["reference_path"] = self._run_applywarp( input_data=to_warp_input, ref_path=ref_input["path"], warp_path=warp["path"], -- 2.52.0 From 2d7dee7920f76d25ef34fa3411dfdc6604c0fd57 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 18:41:11 +0200 Subject: [PATCH 05/12] fix: remove warped output instead of resampled output in ApplyWarper --- junifer/preprocess/fsl/apply_warper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 2caad25a8..8c88c7540 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -211,7 +211,7 @@ class ApplyWarper(BasePreprocessor): # Load nifti output_img = nib.load(applywarp_out_path) # Delete warped output file - flirt_out_path.unlink() + applywarp_out_path.unlink() # Stupid casting output_img = cast("Nifti1Image", output_img) -- 2.52.0 From 333248efa3ba6d437403aeeea7392428851e950c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 18:41:52 +0200 Subject: [PATCH 06/12] update: replace input's space with reference's space in ApplyWarper after warping --- junifer/preprocess/fsl/apply_warper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 8c88c7540..46f4f8110 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -266,4 +266,6 @@ class ApplyWarper(BasePreprocessor): ref_path=ref_input["path"], warp_path=warp["path"], ) + # Use reference input's space as warped input's space + input["space"] = ref_input["space"] return self.on, input -- 2.52.0 From 6fc6e9b30fe55c16486b148dee6cc14201cca7d6 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 18:43:25 +0200 Subject: [PATCH 07/12] chore: remove preprocessor registration for ApplyWarper --- junifer/preprocess/fsl/apply_warper.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 46f4f8110..5354dd0ed 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -21,7 +21,6 @@ from typing import ( import nibabel as nib import numpy as np -from ...api.decorators import register_preprocessor from ...utils import logger, raise_error from ..base import BasePreprocessor @@ -30,7 +29,6 @@ if TYPE_CHECKING: from nibabel import Nifti1Image -@register_preprocessor class ApplyWarper(BasePreprocessor): """Class for warping NIfTI images via FSL FLIRT. -- 2.52.0 From f63bc627c1aa55ea4f96e90f5d687ea60a2add9c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 18:57:01 +0200 Subject: [PATCH 08/12] update: rename ApplyWarper to _ApplyWarper --- junifer/preprocess/__init__.py | 1 - junifer/preprocess/fsl/__init__.py | 2 -- junifer/preprocess/fsl/apply_warper.py | 2 +- junifer/preprocess/fsl/tests/test_apply_warper.py | 12 ++++++------ 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/junifer/preprocess/__init__.py b/junifer/preprocess/__init__.py index 54e5f62ec..d73020c9e 100644 --- a/junifer/preprocess/__init__.py +++ b/junifer/preprocess/__init__.py @@ -7,4 +7,3 @@ from .base import BasePreprocessor from .confounds import fMRIPrepConfoundRemover -from .fsl import ApplyWarper diff --git a/junifer/preprocess/fsl/__init__.py b/junifer/preprocess/fsl/__init__.py index c57c16c3e..01c947063 100644 --- a/junifer/preprocess/fsl/__init__.py +++ b/junifer/preprocess/fsl/__init__.py @@ -2,5 +2,3 @@ # Authors: Synchon Mandal # License: AGPL - -from .apply_warper import ApplyWarper diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 5354dd0ed..c8c160ee3 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: from nibabel import Nifti1Image -class ApplyWarper(BasePreprocessor): +class _ApplyWarper(BasePreprocessor): """Class for warping NIfTI images via FSL FLIRT. Wraps FSL FLIRT ``applywarp``. diff --git a/junifer/preprocess/fsl/tests/test_apply_warper.py b/junifer/preprocess/fsl/tests/test_apply_warper.py index 26b21f5cd..c38b559c9 100644 --- a/junifer/preprocess/fsl/tests/test_apply_warper.py +++ b/junifer/preprocess/fsl/tests/test_apply_warper.py @@ -9,12 +9,12 @@ import pytest # from junifer.datareader import DefaultDataReader # from junifer.pipeline.utils import _check_fsl -from junifer.preprocess.fsl.apply_warper import ApplyWarper +from junifer.preprocess.fsl.apply_warper import _ApplyWarper def test_ApplyWarper_init() -> None: """Test ApplyWarper init.""" - apply_warper = ApplyWarper(ref="T1w", on="BOLD") + apply_warper = _ApplyWarper(ref="T1w", on="BOLD") assert apply_warper.ref == "T1w" assert apply_warper.on == "BOLD" assert apply_warper._on == ["BOLD"] @@ -22,7 +22,7 @@ def test_ApplyWarper_init() -> None: def test_ApplyWarper_get_valid_inputs() -> None: """Test ApplyWarper get_valid_inputs.""" - apply_warper = ApplyWarper(ref="T1w", on="BOLD") + apply_warper = _ApplyWarper(ref="T1w", on="BOLD") assert apply_warper.get_valid_inputs() == ["BOLD", "T1w", "Warp"] @@ -43,7 +43,7 @@ def test_ApplyWarper_get_output_type(input_: List[str]) -> None: The input data types. """ - apply_warper = ApplyWarper(ref="T1w", on="BOLD") + apply_warper = _ApplyWarper(ref="T1w", on="BOLD") assert apply_warper.get_output_type(input_) == input_ @@ -56,7 +56,7 @@ def test_ApplyWarper__run_applywarp() -> None: # Initialize datareader # reader = DefaultDataReader() # Initialize preprocessor - # bold_warper = ApplyWarper(ref="T1w", on="BOLD") + # bold_warper = _ApplyWarper(ref="T1w", on="BOLD") # TODO(synchon): setup datagrabber and run pipeline @@ -69,5 +69,5 @@ def test_ApplyWarper_preprocess() -> None: # Initialize datareader # reader = DefaultDataReader() # Initialize preprocessor - # bold_warper = ApplyWarper(ref="T1w", on="BOLD") + # bold_warper = _ApplyWarper(ref="T1w", on="BOLD") # TODO(synchon): setup datagrabber and run pipeline -- 2.52.0 From 3f46123c0f05d0d2e6595f7f2e2bddbb96d15dd8 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 18:59:05 +0200 Subject: [PATCH 09/12] update: rename ref to reference for _ApplyWarper --- junifer/preprocess/fsl/apply_warper.py | 6 +++--- junifer/preprocess/fsl/tests/test_apply_warper.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index c8c160ee3..2b63621e5 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -36,7 +36,7 @@ class _ApplyWarper(BasePreprocessor): Parameters ---------- - ref : str + reference : str The data type to use as reference for warping. on : str The data type to use for warping. @@ -58,9 +58,9 @@ class _ApplyWarper(BasePreprocessor): }, ] - def __init__(self, ref: str, on: str) -> None: + def __init__(self, reference: str, on: str) -> None: """Initialize the class.""" - self.ref = ref + self.ref = reference # Check only single data type is passed if isinstance(on, list): raise_error("Can only work on single data type, list was passed.") diff --git a/junifer/preprocess/fsl/tests/test_apply_warper.py b/junifer/preprocess/fsl/tests/test_apply_warper.py index c38b559c9..1a20df54e 100644 --- a/junifer/preprocess/fsl/tests/test_apply_warper.py +++ b/junifer/preprocess/fsl/tests/test_apply_warper.py @@ -14,7 +14,7 @@ from junifer.preprocess.fsl.apply_warper import _ApplyWarper def test_ApplyWarper_init() -> None: """Test ApplyWarper init.""" - apply_warper = _ApplyWarper(ref="T1w", on="BOLD") + apply_warper = _ApplyWarper(reference="T1w", on="BOLD") assert apply_warper.ref == "T1w" assert apply_warper.on == "BOLD" assert apply_warper._on == ["BOLD"] @@ -22,7 +22,7 @@ def test_ApplyWarper_init() -> None: def test_ApplyWarper_get_valid_inputs() -> None: """Test ApplyWarper get_valid_inputs.""" - apply_warper = _ApplyWarper(ref="T1w", on="BOLD") + apply_warper = _ApplyWarper(reference="T1w", on="BOLD") assert apply_warper.get_valid_inputs() == ["BOLD", "T1w", "Warp"] @@ -43,7 +43,7 @@ def test_ApplyWarper_get_output_type(input_: List[str]) -> None: The input data types. """ - apply_warper = _ApplyWarper(ref="T1w", on="BOLD") + apply_warper = _ApplyWarper(reference="T1w", on="BOLD") assert apply_warper.get_output_type(input_) == input_ @@ -56,7 +56,7 @@ def test_ApplyWarper__run_applywarp() -> None: # Initialize datareader # reader = DefaultDataReader() # Initialize preprocessor - # bold_warper = _ApplyWarper(ref="T1w", on="BOLD") + # bold_warper = _ApplyWarper(reference="T1w", on="BOLD") # TODO(synchon): setup datagrabber and run pipeline @@ -69,5 +69,5 @@ def test_ApplyWarper_preprocess() -> None: # Initialize datareader # reader = DefaultDataReader() # Initialize preprocessor - # bold_warper = _ApplyWarper(ref="T1w", on="BOLD") + # bold_warper = _ApplyWarper(reference="T1w", on="BOLD") # TODO(synchon): setup datagrabber and run pipeline -- 2.52.0 From d00ced915a9b7f63a09fe742bc2c93a57f794af5 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 19:01:32 +0200 Subject: [PATCH 10/12] update: adapt _ApplyWarper and tests to BasePreprocessor changes --- junifer/preprocess/fsl/apply_warper.py | 6 ++++-- junifer/preprocess/fsl/tests/test_apply_warper.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 2b63621e5..8ad2b6b9b 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -65,7 +65,9 @@ class _ApplyWarper(BasePreprocessor): if isinstance(on, list): raise_error("Can only work on single data type, list was passed.") self.on = on # needed for the base validation to work - super().__init__(on=self.on) + super().__init__( + on=self.on, required_data_types=[self.on, self.ref, "Warp"] + ) def get_valid_inputs(self) -> List[str]: """Get valid data types for input. @@ -78,7 +80,7 @@ class _ApplyWarper(BasePreprocessor): """ # Constructed dynamically - return [self.on, self.ref, "Warp"] + return [self.on] def get_output_type(self, input: List[str]) -> List[str]: """Get output type. diff --git a/junifer/preprocess/fsl/tests/test_apply_warper.py b/junifer/preprocess/fsl/tests/test_apply_warper.py index 1a20df54e..24aee85cd 100644 --- a/junifer/preprocess/fsl/tests/test_apply_warper.py +++ b/junifer/preprocess/fsl/tests/test_apply_warper.py @@ -23,7 +23,7 @@ def test_ApplyWarper_init() -> None: def test_ApplyWarper_get_valid_inputs() -> None: """Test ApplyWarper get_valid_inputs.""" apply_warper = _ApplyWarper(reference="T1w", on="BOLD") - assert apply_warper.get_valid_inputs() == ["BOLD", "T1w", "Warp"] + assert apply_warper.get_valid_inputs() == ["BOLD"] @pytest.mark.parametrize( -- 2.52.0 From da6d121437f59b30958e255151f2ace0d9df9cb7 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 19:04:01 +0200 Subject: [PATCH 11/12] update: use WorkDirManager in _ApplyWarper --- junifer/preprocess/fsl/apply_warper.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py index 8ad2b6b9b..f70bd0d06 100644 --- a/junifer/preprocess/fsl/apply_warper.py +++ b/junifer/preprocess/fsl/apply_warper.py @@ -4,7 +4,6 @@ # License: AGPL import subprocess -import tempfile from pathlib import Path from typing import ( TYPE_CHECKING, @@ -21,6 +20,7 @@ from typing import ( import nibabel as nib import numpy as np +from ...pipeline import WorkDirManager from ...utils import logger, raise_error from ..base import BasePreprocessor @@ -135,8 +135,8 @@ class _ApplyWarper(BasePreprocessor): # resolution resolution = np.min(input_data["data"].header.get_zooms()[:3]) - # Create tempdir - tempdir = Path(tempfile.mkdtemp()) + # Create element-specific tempdir for storing post-warping assets + tempdir = WorkDirManager().get_element_tempdir(prefix="applywarp") # Create a tempfile for resampled reference output flirt_out_path = tempdir / "reference_resampled.nii.gz" @@ -210,8 +210,6 @@ class _ApplyWarper(BasePreprocessor): # Load nifti output_img = nib.load(applywarp_out_path) - # Delete warped output file - applywarp_out_path.unlink() # Stupid casting output_img = cast("Nifti1Image", output_img) -- 2.52.0 From 3d91e848f7bd6729acb3de1104067e7010d9b901 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 17 Oct 2023 19:13:17 +0200 Subject: [PATCH 12/12] chore: add changelog 266.feature --- docs/changes/newsfragments/266.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/266.feature diff --git a/docs/changes/newsfragments/266.feature b/docs/changes/newsfragments/266.feature new file mode 100644 index 000000000..a207b8a9d --- /dev/null +++ b/docs/changes/newsfragments/266.feature @@ -0,0 +1 @@ +Introduce ``junifer.preprocess.fsl.apply_warper._ApplyWarper`` to wrap FSL's ``applywarp`` by `Synchon Mandal`_ -- 2.52.0