From 2ad216b408563a73ebd853d9336ebfeda1de6759 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 29 Apr 2024 15:13:51 +0200 Subject: [PATCH 1/5] chore: remove Power usage deprecation from load_coordinates() --- junifer/data/coordinates.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/junifer/data/coordinates.py b/junifer/data/coordinates.py index 3d6c0076c..2cefb1ab7 100644 --- a/junifer/data/coordinates.py +++ b/junifer/data/coordinates.py @@ -13,7 +13,7 @@ import pandas as pd from numpy.typing import ArrayLike from ..pipeline import WorkDirManager -from ..utils import logger, raise_error, run_ext_cmd, warn_with_log +from ..utils import logger, raise_error, run_ext_cmd # Path to the VOIs @@ -377,11 +377,6 @@ def load_coordinates(name: str) -> Tuple[ArrayLike, List[str], str]: ValueError If ``name`` is invalid. - Warns - ----- - DeprecationWarning - If ``Power`` is provided as the ``name``. - """ # Check for valid coordinates name if name not in _available_coordinates: @@ -389,18 +384,6 @@ def load_coordinates(name: str) -> Tuple[ArrayLike, List[str], str]: f"Coordinates {name} not found. " f"Valid options are: {list_coordinates()}" ) - - # Put up deprecation notice - if name == "Power": - warn_with_log( - msg=( - "`Power` has been replaced with `Power2011` and will be " - "removed in the next release. For now, it's available for " - "backward compatibility." - ), - category=DeprecationWarning, - ) - # Load coordinates t_coord = _available_coordinates[name] if isinstance(t_coord.get("path"), Path): -- 2.52.0 From a21aa164b520b80b8e3ddff9584a05734479e1bd Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 29 Apr 2024 15:14:40 +0200 Subject: [PATCH 2/5] chore: remove fetch_icbm152_brain_gm_mask and its usage --- docs/builtin.rst | 8 ---- junifer/data/masks.py | 67 +------------------------------- junifer/data/tests/test_masks.py | 8 ---- 3 files changed, 2 insertions(+), 81 deletions(-) diff --git a/docs/builtin.rst b/docs/builtin.rst index cc8d8eb40..637b863c6 100644 --- a/docs/builtin.rst +++ b/docs/builtin.rst @@ -729,14 +729,6 @@ Available - | Compute a brain mask for the images by guessing the value of the | background from the border of the image. | See :func:`nilearn.masking.compute_background_mask` - * - ``nilearn``'s ICBM152 template gray-matter mask - - | ``fetch_icbm152_brain_gm_mask`` - - ``MNI152NLin2009aAsym`` - - 0.0.2 - - | Compute a gray-matter mask from the asymmetrical ICBM152 2009 template, - | release a. - | See :func:`nilearn.datasets.fetch_icbm152_brain_gm_mask` - .. Planned diff --git a/junifer/data/masks.py b/junifer/data/masks.py index e059f538f..9e893a45b 100644 --- a/junifer/data/masks.py +++ b/junifer/data/masks.py @@ -19,7 +19,6 @@ from typing import ( import nibabel as nib import numpy as np -from nilearn.datasets import fetch_icbm152_brain_gm_mask from nilearn.image import get_data, new_img_like, resample_to_img from nilearn.masking import ( compute_background_mask, @@ -28,7 +27,7 @@ from nilearn.masking import ( ) from ..pipeline import WorkDirManager -from ..utils import logger, raise_error, run_ext_cmd, warn_with_log +from ..utils import logger, raise_error, run_ext_cmd from .template_spaces import get_template, get_xfm from .utils import closest_resolution @@ -121,46 +120,6 @@ def compute_brain_mask( return new_img_like(target_img, mask) # type: ignore -def _fetch_icbm152_brain_gm_mask( - target_img: "Nifti1Image", - **kwargs, -) -> "Nifti1Image": - """Fetch ICBM152 brain mask and resample. - - Parameters - ---------- - target_img : nibabel.Nifti1Image - The image to which the mask will be resampled. - **kwargs : dict - Keyword arguments to be passed to - :func:`nilearn.datasets.fetch_icbm152_brain_gm_mask`. - - Returns - ------- - nibabel.Nifti1Image - The resampled mask. - - Warns - ----- - DeprecationWarning - If this function is used. - - """ - warn_with_log( - msg=( - "It is recommended to use ``compute_brain_mask`` with " - "``mask_type='gm'``. This function will be removed in the next " - "release. For now, it's available for backward compatibility." - ), - category=DeprecationWarning, - ) - mask = fetch_icbm152_brain_gm_mask(**kwargs) - mask = resample_to_img( - mask, target_img, interpolation="nearest", copy=True - ) - return mask - - # A dictionary containing all supported masks and their respective file or # data. @@ -194,11 +153,6 @@ _available_masks: Dict[str, Dict[str, Any]] = { "func": compute_epi_mask, "space": "inherit", }, - "fetch_icbm152_brain_gm_mask": { - "family": "Callable", - "func": _fetch_icbm152_brain_gm_mask, - "space": "MNI152NLin2009aAsym", - }, } @@ -292,9 +246,7 @@ def get_mask( # noqa: C901 Raises ------ RuntimeError - If warp / transformation file extension is not ".mat" or ".h5" or - if fetch_icbm152_brain_gm_mask is used and requires warping to - other template space. + If warp / transformation file extension is not ".mat" or ".h5". ValueError If extra key is provided in addition to mask name in ``masks`` or if no mask is provided or @@ -402,21 +354,6 @@ def get_mask( # noqa: C901 mask_img = extra_input[inherited_mask_item]["data"] # Starting with new mask else: - # Restrict fetch_icbm152_brain_gm_mask if target std space doesn't - # match - if ( - mask_name == "fetch_icbm152_brain_gm_mask" - and target_std_space != "MNI152NLin2009aAsym" - ): - raise_error( - ( - "``fetch_icbm152_brain_gm_mask`` is deprecated and " - "space transformation to any other template space is " - "prohibited as it will lead to unforeseen errors. " - "``compute_brain_mask`` is a better alternative." - ), - klass=RuntimeError, - ) # Load mask mask_object, _, mask_space = load_mask( mask_name, path_only=False, resolution=resolution diff --git a/junifer/data/tests/test_masks.py b/junifer/data/tests/test_masks.py index 187f26ef3..c8da1298e 100644 --- a/junifer/data/tests/test_masks.py +++ b/junifer/data/tests/test_masks.py @@ -386,14 +386,6 @@ def test_get_mask_errors() -> None: masks="inherit", target_data=vbm_gm, extra_input=extra_input ) - # Block fetch_icbm152_brain_gm_mask space transformation - with pytest.raises(RuntimeError, match="prohibited"): - get_mask( - masks="fetch_icbm152_brain_gm_mask", - target_data=vbm_gm, - extra_input=extra_input, - ) - @pytest.mark.parametrize( "mask_name,function,params,resample", -- 2.52.0 From 19089aa751ab0417e6af23a6fa3a1f0f62ae9dc3 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 29 Apr 2024 15:26:48 +0200 Subject: [PATCH 3/5] chore: remove BOLDWarper and co. --- junifer/preprocess/__init__.py | 1 - junifer/preprocess/ants/__init__.py | 4 - .../ants/ants_apply_transforms_warper.py | 185 ------------ .../test_ants_apply_transforms_warper.py | 56 ---- junifer/preprocess/bold_warper.py | 265 ------------------ junifer/preprocess/fsl/__init__.py | 4 - junifer/preprocess/fsl/apply_warper.py | 179 ------------ .../preprocess/fsl/tests/test_apply_warper.py | 45 --- junifer/preprocess/tests/test_bold_warper.py | 159 ----------- 9 files changed, 898 deletions(-) delete mode 100644 junifer/preprocess/ants/__init__.py delete mode 100644 junifer/preprocess/ants/ants_apply_transforms_warper.py delete mode 100644 junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py delete mode 100644 junifer/preprocess/bold_warper.py delete mode 100644 junifer/preprocess/fsl/__init__.py delete mode 100644 junifer/preprocess/fsl/apply_warper.py delete mode 100644 junifer/preprocess/fsl/tests/test_apply_warper.py delete mode 100644 junifer/preprocess/tests/test_bold_warper.py diff --git a/junifer/preprocess/__init__.py b/junifer/preprocess/__init__.py index 8c83b9aab..32b944353 100644 --- a/junifer/preprocess/__init__.py +++ b/junifer/preprocess/__init__.py @@ -7,6 +7,5 @@ from .base import BasePreprocessor from .confounds import fMRIPrepConfoundRemover -from .bold_warper import BOLDWarper from .warping import SpaceWarper from .smoothing import Smoothing diff --git a/junifer/preprocess/ants/__init__.py b/junifer/preprocess/ants/__init__.py deleted file mode 100644 index 4ffb8cbc3..000000000 --- a/junifer/preprocess/ants/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Provide imports for ants sub-package.""" - -# Authors: Synchon Mandal -# License: AGPL diff --git a/junifer/preprocess/ants/ants_apply_transforms_warper.py b/junifer/preprocess/ants/ants_apply_transforms_warper.py deleted file mode 100644 index 9bfa0f379..000000000 --- a/junifer/preprocess/ants/ants_apply_transforms_warper.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Provide class for warping via ANTs antsApplyTransforms.""" - -# Authors: Synchon Mandal -# License: AGPL - -from pathlib import Path -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - List, - Optional, - Tuple, - Union, -) - -import nibabel as nib -import numpy as np - -from ...pipeline import WorkDirManager -from ...utils import logger, raise_error, run_ext_cmd - - -if TYPE_CHECKING: - from nibabel import Nifti1Image - - -class _AntsApplyTransformsWarper: - """Class for warping NIfTI images via ANTs antsApplyTransforms. - - Warps ANTs ``antsApplyTransforms``. - - Parameters - ---------- - reference : 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[List[Dict[str, Union[str, List[str]]]]] = [ - { - "name": "ants", - "commands": ["ResampleImage", "antsApplyTransforms"], - }, - ] - - def __init__(self, reference: str, on: str) -> None: - """Initialize the class.""" - 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.") - self.on = on - - def _run_apply_transforms( - self, - input_data: Dict, - ref_path: Path, - warp_path: Path, - ) -> Tuple["Nifti1Image", Path]: - """Run ``antsApplyTransforms``. - - Parameters - ---------- - input_data : dict - The input data. - ref_path : pathlib.Path - The path to the reference file. - warp_path : pathlib.Path - The path to the warp file. - - Returns - ------- - Niimg-like object - The warped input image. - pathlib.Path - The path to the resampled reference image. - - """ - # 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 element-specific tempdir for storing post-warping assets - tempdir = WorkDirManager().get_element_tempdir( - prefix="applytransforms" - ) - - # Create a tempfile for resampled reference output - resample_image_out_path = tempdir / "reference_resampled.nii.gz" - # Set ResampleImage command - resample_image_cmd = [ - "ResampleImage", - "3", # image dimension - f"{ref_path.resolve()}", - f"{resample_image_out_path.resolve()}", - f"{resolution}x{resolution}x{resolution}", - "0", # option for spacing and not size - "3 3", # Lanczos windowed sinc - ] - # Call ResampleImage - run_ext_cmd(name="ResampleImage", cmd=resample_image_cmd) - - # Create a tempfile for warped output - apply_transforms_out_path = tempdir / "input_warped.nii.gz" - # Set antsApplyTransforms command - apply_transforms_cmd = [ - "antsApplyTransforms", - "-d 3", - "-e 3", - "-n LanczosWindowedSinc", - f"-i {input_data['path'].resolve()}", - # use resampled reference - f"-r {resample_image_out_path.resolve()}", - f"-t {warp_path.resolve()}", - f"-o {apply_transforms_out_path.resolve()}", - ] - # Call antsApplyTransforms - run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd) - - # Load nifti - output_img = nib.load(apply_transforms_out_path) - - return output_img, resample_image_out_path # type: ignore - - 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 - ``Warp`` and ``ref`` value's 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 via ANTs using antsApplyTransforms") - # Check for extra inputs - if extra_input is None: - raise_error( - f"No extra input provided, requires `Warp` and `{self.ref}` " - "data types in particular." - ) - # 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 data with warped data and add resampled reference - # path - input["data"], input["reference_path"] = self._run_apply_transforms( - input_data=to_warp_input, - 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 diff --git a/junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py b/junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py deleted file mode 100644 index bc693bcb3..000000000 --- a/junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Provide tests for AntsApplyTransformsWarper.""" - -# Authors: Synchon Mandal -# License: AGPL - -import socket - -import pytest - -from junifer.datagrabber import DMCC13Benchmark -from junifer.datareader import DefaultDataReader -from junifer.pipeline.utils import _check_ants -from junifer.preprocess.ants.ants_apply_transforms_warper import ( - _AntsApplyTransformsWarper, -) - - -def test_AntsApplyTransformsWarper_init() -> None: - """Test AntsApplyTransformsWarper init.""" - ants_apply_transforms_warper = _AntsApplyTransformsWarper( - reference="T1w", on="BOLD" - ) - assert ants_apply_transforms_warper.ref == "T1w" - assert ants_apply_transforms_warper.on == "BOLD" - - -@pytest.mark.skipif( - _check_ants() is False, reason="requires ANTs to be in PATH" -) -@pytest.mark.skipif( - socket.gethostname() != "juseless", - reason="only for juseless", -) -def test_AntsApplyTransformsWarper_preprocess() -> None: - """Test AntsApplyTransformsWarper preprocess.""" - with DMCC13Benchmark( - types=["BOLD", "T1w", "Warp"], - sessions=["ses-wave1bas"], - tasks=["Rest"], - phase_encodings=["AP"], - runs=["1"], - native_t1w=True, - ) as dg: - # Read data - element_data = DefaultDataReader().fit_transform( - dg[("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1")] - ) - # Preprocess data - data_type, data = _AntsApplyTransformsWarper( - reference="T1w", on="BOLD" - ).preprocess( - input=element_data["BOLD"], - extra_input=element_data, - ) - assert isinstance(data_type, str) - assert isinstance(data, dict) diff --git a/junifer/preprocess/bold_warper.py b/junifer/preprocess/bold_warper.py deleted file mode 100644 index 304f59bbc..000000000 --- a/junifer/preprocess/bold_warper.py +++ /dev/null @@ -1,265 +0,0 @@ -"""Provide class for warping BOLD to other template spaces.""" - -# Authors: Synchon Mandal -# License: AGPL - -from typing import ( - Any, - ClassVar, - Dict, - List, - Optional, - Tuple, - Type, - Union, -) - -import nibabel as nib -from templateflow import api as tflow - -from ..api.decorators import register_preprocessor -from ..data import get_template, get_xfm -from ..pipeline import WorkDirManager -from ..utils import logger, raise_error, run_ext_cmd -from .ants.ants_apply_transforms_warper import _AntsApplyTransformsWarper -from .base import BasePreprocessor -from .fsl.apply_warper import _ApplyWarper - - -@register_preprocessor -class BOLDWarper(BasePreprocessor): - """Class for warping BOLD NIfTI images. - - .. deprecated:: 0.0.3 - `BOLDWarper` will be removed in v0.0.4, it is replaced by - `SpaceWarper` because the latter works also with T1w data. - - Parameters - ---------- - using : {"fsl", "ants"} - Implementation to use for warping: - - * "fsl" : Use FSL's ``applywarp`` - * "afni" : Use ANTs' ``antsApplyTransforms`` - - reference : str - The data type to use as reference for warping, can be either a data - type like "T1w" or a template space like "MNI152NLin2009cAsym". - - Raises - ------ - ValueError - If ``using`` is invalid or - if ``reference`` is invalid. - - Notes - ----- - If you are setting ``reference`` to a template space like - "MNI152NLin2009cAsym", make sure ANTs is available for the - transformation else it will fail during runtime. It is tricky to validate - this beforehand and difficult to enforce this as a requirement, hence the - heads-up. - - """ - - _CONDITIONAL_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, Type]]]] = [ - { - "using": "fsl", - "depends_on": _ApplyWarper, - }, - { - "using": "ants", - "depends_on": _AntsApplyTransformsWarper, - }, - ] - - def __init__(self, using: str, reference: str) -> None: - """Initialize the class.""" - # Validate `using` parameter - valid_using = [dep["using"] for dep in self._CONDITIONAL_DEPENDENCIES] - if using not in valid_using: - raise_error( - f"Invalid value for `using`, should be one of: {valid_using}" - ) - self.using = using - self.ref = reference - # Initialize superclass based on reference - if self.ref == "T1w": - super().__init__( - on="BOLD", required_data_types=["BOLD", self.ref, "Warp"] - ) - elif self.ref in tflow.templates(): - super().__init__(on="BOLD", required_data_types=["BOLD"]) - else: - raise_error(f"Unknown reference: {self.ref}") - - 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. - - 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], - extra_input: Optional[Dict[str, Any]] = None, - ) -> Tuple[Dict[str, Any], Optional[Dict[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 - ``Warp`` and ``ref`` value's keys if native space transformation is - needed. - - Returns - ------- - dict - The computed result as dictionary. - None - Extra "helper" data types as dictionary to add to the Junifer Data - object. - - Raises - ------ - ValueError - If ``extra_input`` is None when transforming to native space - i.e., using "T1w" as reference. - RuntimeError - If warp / transformation file extension is not ".mat" or ".h5" - when transforming to native space or - if the BOLD data is in the correct space and does not require - warping. - - """ - logger.info(f"Warping BOLD to {self.ref} space using BOLDWarper") - # Transform to native space - if self.ref == "T1w": - # Check for extra inputs - if extra_input is None: - raise_error( - "No extra input provided, requires `Warp` and " - f"`{self.ref}` data types in particular." - ) - # Check for warp file type to use correct tool - warp_file_ext = extra_input["Warp"]["path"].suffix - if warp_file_ext == ".mat": - logger.debug("Using FSL with BOLDWarper") - # Initialize ApplyWarper for computation - apply_warper = _ApplyWarper(reference=self.ref, on="BOLD") - # Replace original BOLD data with warped BOLD data - _, input = apply_warper.preprocess( - input=input, - extra_input=extra_input, - ) - elif warp_file_ext == ".h5": - logger.debug("Using ANTs with BOLDWarper") - # Initialize AntsApplyTransformsWarper for computation - ants_apply_transforms_warper = _AntsApplyTransformsWarper( - reference=self.ref, on="BOLD" - ) - # Replace original BOLD data with warped BOLD data - _, input = ants_apply_transforms_warper.preprocess( - input=input, - extra_input=extra_input, - ) - else: - raise_error( - msg=( - "Unknown warp / transformation file extension: " - f"{warp_file_ext}" - ), - klass=RuntimeError, - ) - # Transform to template space - else: - # Check pre-requirements for space manipulation - if self.ref == input["space"]: - raise_error( - ( - f"Skipped warping as the BOLD data is in {self.ref} " - "space which would mean that you can remove the " - "BOLDWarper from the preprocess step." - ), - klass=RuntimeError, - ) - else: - # Get xfm file - xfm_file_path = get_xfm(src=input["space"], dst=self.ref) - # Get template space image - template_space_img = get_template( - space=self.ref, - target_data=input, - extra_input=None, - ) - - # Create component-scoped tempdir - tempdir = WorkDirManager().get_tempdir(prefix="boldwarper") - # Create element-scoped tempdir so that warped BOLD is - # available later as nibabel stores file path reference for - # loading on computation - element_tempdir = WorkDirManager().get_element_tempdir( - prefix="boldwarper" - ) - - # Save template - template_space_img_path = tempdir / f"{self.ref}_T1w.nii.gz" - nib.save(template_space_img, template_space_img_path) - - # Create a tempfile for warped output - warped_bold_path = ( - element_tempdir - / f"bold_warped_from_{input['space']}_to_{self.ref}.nii.gz" - ) - - logger.debug( - f"Using ANTs to warp BOLD " - f"from {input['space']} to {self.ref}" - ) - # Set antsApplyTransforms command - apply_transforms_cmd = [ - "antsApplyTransforms", - "-d 3", - "-e 3", - "-n LanczosWindowedSinc", - f"-i {input['path'].resolve()}", - f"-r {template_space_img_path.resolve()}", - f"-t {xfm_file_path.resolve()}", - f"-o {warped_bold_path.resolve()}", - ] - # Call antsApplyTransforms - run_ext_cmd( - name="antsApplyTransforms", cmd=apply_transforms_cmd - ) - - # Modify target data - input["data"] = nib.load(warped_bold_path) - input["space"] = self.ref - - return input, None diff --git a/junifer/preprocess/fsl/__init__.py b/junifer/preprocess/fsl/__init__.py deleted file mode 100644 index 01c947063..000000000 --- a/junifer/preprocess/fsl/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Provide imports for fsl sub-package.""" - -# Authors: Synchon Mandal -# License: AGPL diff --git a/junifer/preprocess/fsl/apply_warper.py b/junifer/preprocess/fsl/apply_warper.py deleted file mode 100644 index 4e7cb1ba5..000000000 --- a/junifer/preprocess/fsl/apply_warper.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Provide class for warping via FSL FLIRT.""" - -# Authors: Synchon Mandal -# License: AGPL - -from pathlib import Path -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - List, - Optional, - Tuple, - Union, -) - -import nibabel as nib -import numpy as np - -from ...pipeline import WorkDirManager -from ...utils import logger, raise_error, run_ext_cmd - - -if TYPE_CHECKING: - from nibabel import Nifti1Image - - -class _ApplyWarper: - """Class for warping NIfTI images via FSL FLIRT. - - Wraps FSL FLIRT ``applywarp``. - - Parameters - ---------- - reference : 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[List[Dict[str, Union[str, List[str]]]]] = [ - { - "name": "fsl", - "commands": ["flirt", "applywarp"], - }, - ] - - def __init__(self, reference: str, on: str) -> None: - """Initialize the class.""" - 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.") - self.on = on - - def _run_applywarp( - self, - input_data: Dict, - ref_path: Path, - warp_path: Path, - ) -> Tuple["Nifti1Image", Path]: - """Run ``applywarp``. - - Parameters - ---------- - input_data : dict - The input data. - ref_path : pathlib.Path - The path to the reference file. - warp_path : pathlib.Path - The path to the warp file. - - Returns - ------- - Niimg-like object - The warped input image. - pathlib.Path - The path to the resampled reference image. - - """ - # 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 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" - # 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 - run_ext_cmd(name="flirt", cmd=flirt_cmd) - - # 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_data['path'].resolve()}", - f"-r {flirt_out_path.resolve()}", # use resampled reference - f"-w {warp_path.resolve()}", - f"-o {applywarp_out_path.resolve()}", - ] - # Call applywarp - run_ext_cmd(name="applywarp", cmd=applywarp_cmd) - - # Load nifti - output_img = nib.load(applywarp_out_path) - - return output_img, flirt_out_path # type: ignore - - 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 - ``Warp`` and ``ref`` value's 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 via FSL using ApplyWarper") - # Check for extra inputs - if extra_input is None: - raise_error( - f"No extra input provided, requires `Warp` and `{self.ref}` " - "data types in particular." - ) - # 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 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"], - ) - # Use reference input's space as warped input's space - input["space"] = ref_input["space"] - return self.on, input diff --git a/junifer/preprocess/fsl/tests/test_apply_warper.py b/junifer/preprocess/fsl/tests/test_apply_warper.py deleted file mode 100644 index e7a678a75..000000000 --- a/junifer/preprocess/fsl/tests/test_apply_warper.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Provide tests for ApplyWarper.""" - -# Authors: Synchon Mandal -# License: AGPL - -import socket - -import pytest - -from junifer.datagrabber import DataladHCP1200 -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(reference="T1w", on="BOLD") - assert apply_warper.ref == "T1w" - assert apply_warper.on == "BOLD" - - -@pytest.mark.skipif(_check_fsl() is False, reason="requires FSL to be in PATH") -@pytest.mark.skipif( - socket.gethostname() != "juseless", - reason="only for juseless", -) -def test_ApplyWarper_preprocess() -> None: - """Test ApplyWarper preprocess.""" - with DataladHCP1200( - tasks=["REST1"], - phase_encodings=["LR"], - ica_fix=True, - ) as dg: - # Read data - element_data = DefaultDataReader().fit_transform( - dg[("100206", "REST1", "LR")] - ) - # Preprocess data - data_type, data = _ApplyWarper(reference="T1w", on="BOLD").preprocess( - input=element_data["BOLD"], - extra_input=element_data, - ) - assert isinstance(data_type, str) - assert isinstance(data, dict) diff --git a/junifer/preprocess/tests/test_bold_warper.py b/junifer/preprocess/tests/test_bold_warper.py deleted file mode 100644 index 6f6367e82..000000000 --- a/junifer/preprocess/tests/test_bold_warper.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Provide tests for BOLDWarper.""" - -# Authors: Synchon Mandal -# License: AGPL - -import socket -from typing import TYPE_CHECKING, Tuple - -import pytest -from numpy.testing import assert_array_equal, assert_raises - -from junifer.datagrabber import DataladHCP1200, DMCC13Benchmark -from junifer.datareader import DefaultDataReader -from junifer.pipeline.utils import _check_ants, _check_fsl -from junifer.preprocess import BOLDWarper - - -if TYPE_CHECKING: - from junifer.datagrabber import BaseDataGrabber - - -def test_BOLDWarper_init() -> None: - """Test BOLDWarper init.""" - bold_warper = BOLDWarper(using="ants", reference="T1w") - assert bold_warper._on == ["BOLD"] - - -def test_BOLDWarper_get_valid_inputs() -> None: - """Test BOLDWarper get_valid_inputs.""" - bold_warper = BOLDWarper(using="ants", reference="T1w") - assert bold_warper.get_valid_inputs() == ["BOLD"] - - -def test_BOLDWarper_get_output_type() -> None: - """Test BOLDWarper get_output_type.""" - bold_warper = BOLDWarper(using="ants", reference="T1w") - assert bold_warper.get_output_type("BOLD") == "BOLD" - - -@pytest.mark.parametrize( - "datagrabber, element", - [ - [ - DMCC13Benchmark( - types=["BOLD", "T1w", "Warp"], - sessions=["ses-wave1bas"], - tasks=["Rest"], - phase_encodings=["AP"], - runs=["1"], - native_t1w=True, - ), - ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"), - ], - [ - DataladHCP1200( - tasks=["REST1"], - phase_encodings=["LR"], - ica_fix=True, - ), - ("100206", "REST1", "LR"), - ], - ], -) -@pytest.mark.skipif(_check_fsl() is False, reason="requires FSL to be in PATH") -@pytest.mark.skipif( - _check_ants() is False, reason="requires ANTs to be in PATH" -) -@pytest.mark.skipif( - socket.gethostname() != "juseless", - reason="only for juseless", -) -def test_BOLDWarper_preprocess_to_native( - datagrabber: "BaseDataGrabber", element: Tuple[str, ...] -) -> None: - """Test BOLDWarper preprocess. - - Parameters - ---------- - datagrabber : DataGrabber-like object - The parametrized DataGrabber objects. - element : tuple of str - The parametrized elements. - - """ - with datagrabber as dg: - # Read data - element_data = DefaultDataReader().fit_transform(dg[element]) - # Preprocess data - data, _ = BOLDWarper(reference="T1w").preprocess( - input=element_data["BOLD"], - extra_input=element_data, - ) - assert isinstance(data, dict) - - -@pytest.mark.parametrize( - "datagrabber, element, space", - [ - [ - DMCC13Benchmark( - types=["BOLD"], - sessions=["ses-wave1bas"], - tasks=["Rest"], - phase_encodings=["AP"], - runs=["1"], - native_t1w=False, - ), - ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"), - "MNI152NLin2009aAsym", - ], - [ - DMCC13Benchmark( - types=["BOLD"], - sessions=["ses-wave1bas"], - tasks=["Rest"], - phase_encodings=["AP"], - runs=["1"], - native_t1w=False, - ), - ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"), - "MNI152NLin6Asym", - ], - ], -) -@pytest.mark.skipif( - _check_ants() is False, reason="requires ANTs to be in PATH" -) -@pytest.mark.skipif( - socket.gethostname() != "juseless", - reason="only for juseless", -) -def test_BOLDWarper_preprocess_to_multi_mni( - datagrabber: "BaseDataGrabber", element: Tuple[str, ...], space: str -) -> None: - """Test BOLDWarper preprocess. - - Parameters - ---------- - datagrabber : DataGrabber-like object - The parametrized DataGrabber objects. - element : tuple of str - The parametrized elements. - space : str - The parametrized template space to transform to. - - """ - with datagrabber as dg: - # Read data - element_data = DefaultDataReader().fit_transform(dg[element]) - pre_xfm_data = element_data["BOLD"]["data"].get_fdata().copy() - # Preprocess data - data, _ = BOLDWarper(reference=space).preprocess( - input=element_data["BOLD"], - extra_input=element_data, - ) - assert isinstance(data, dict) - assert data["space"] == space - with assert_raises(AssertionError): - assert_array_equal(pre_xfm_data, data["data"]) -- 2.52.0 From c6d0931a3a0a1cda2db18fe11363c7d166f58193 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 29 Apr 2024 16:18:56 +0200 Subject: [PATCH 4/5] chore: add changelog 336.removal --- docs/changes/newsfragments/336.removal | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/336.removal diff --git a/docs/changes/newsfragments/336.removal b/docs/changes/newsfragments/336.removal new file mode 100644 index 000000000..2ad43cabd --- /dev/null +++ b/docs/changes/newsfragments/336.removal @@ -0,0 +1 @@ +Remove ``Power`` coordinates, ``fetch_icbm152_brain_gm_mask`` mask, ``BOLDWarper`` Preprocessor by `Synchon Mandal`_ -- 2.52.0 From c384786ad70f671712e86a436441abdb6f516e34 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 29 Apr 2024 16:54:00 +0200 Subject: [PATCH 5/5] chore: remove xref for BOLDWarper from whats_new.rst --- docs/whats_new.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 276dbb294..8339cdc63 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -79,7 +79,7 @@ API Changes - Add a positional argument ``using`` for Markers and Preprocessors having implementation-based variations, in particular :class:`.ReHoParcels`, :class:`.ReHoSpheres`, :class:`.ALFFParcels`, :class:`.ALFFSpheres` and - :class:`.BOLDWarper` by `Synchon Mandal`_ (:gh:`311`) + ``BOLDWarper`` by `Synchon Mandal`_ (:gh:`311`) - Change all ``probseg_`` types to ``VBM_`` types by `Fede Raimondo`_ (:gh:`320`) - Change the subject and session patterns for :class:`.DataladAOMICID1000`, @@ -120,13 +120,13 @@ Enhancements coordinates by `Synchon Mandal`_ and `Fede Raimondo`_ (:gh:`268`) - Add ``mode`` as an aggregation function option in :func:`.get_aggfunc_by_name` by `Synchon Mandal`_ (:gh:`287`) -- Adapt :class:`.BOLDWarper` to use FSL or ANTs depending on warp file +- Adapt ``BOLDWarper`` to use FSL or ANTs depending on warp file extension by `Synchon Mandal`_ (:gh:`293`) - Rewrite :func:`.compute_brain_mask` to allow variable template fetching via templateflow, according to target data by `Synchon Mandal`_ (:gh:`299`) - Replace ``requests`` with ``httpx`` for fetching parcellations by `Synchon Mandal`_ (:gh:`300`) -- Allow :class:`.BOLDWarper` to warp BOLD data to other MNI spaces by `Synchon +- Allow ``BOLDWarper`` to warp BOLD data to other MNI spaces by `Synchon Mandal`_ (:gh:`302`) - Add support for local ``junifer queue`` via GNU Parallel by `Synchon Mandal`_ (:gh:`306`) @@ -168,7 +168,7 @@ Features by `Synchon Mandal`_ (:gh:`265`) - Introduce ``junifer.preprocess.fsl.apply_warper._ApplyWarper`` to wrap FSL's ``applywarp`` by `Synchon Mandal`_ (:gh:`266`) -- Introduce :class:`.BOLDWarper` for warping BOLD data via FSL's ``applywarp`` +- Introduce ``BOLDWarper`` for warping BOLD data via FSL's ``applywarp`` by `Synchon Mandal`_ (:gh:`267`) - Introduce :class:`.DMCC13Benchmark` to access `DMCC13benchmark dataset `_ by `Synchon @@ -216,7 +216,7 @@ Miscellaneous `Synchon Mandal`_ (:gh:`279`) - Add support for accessing FSL ``img2imgcoord`` via Docker wrapper command by `Synchon Mandal`_ (:gh:`281`) -- Make :class:`.BOLDWarper` tool-agnostic by moving it from +- Make ``BOLDWarper`` tool-agnostic by moving it from ``junifer.preprocess.fsl`` to :mod:`junifer.preprocess` by `Synchon Mandal`_ (:gh:`288`) - Add support for accessing ANTs' ``ResampleImage`` via Docker wrapper by @@ -232,7 +232,7 @@ Miscellaneous Deprecations and Removals ^^^^^^^^^^^^^^^^^^^^^^^^^ -- Deprecate :class:`.BOLDWarper` and mark for removal in v0.0.4 by `Synchon +- Deprecate ``BOLDWarper`` and mark for removal in v0.0.4 by `Synchon Mandal`_ (:gh:`301`) -- 2.52.0