[MAINT]: Remove deprecations from 0.0.3 and 0.0.4 #336

Merged
synchon merged 5 commits from chore/remove-deprecation into main 2024-04-30 12:36:25 +00:00
15 changed files with 10 additions and 1003 deletions

View file

@ -729,14 +729,6 @@ Available
- | Compute a brain mask for the images by guessing the value of the - | Compute a brain mask for the images by guessing the value of the
| background from the border of the image. | background from the border of the image.
| See :func:`nilearn.masking.compute_background_mask` | 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 Planned

View file

@ -0,0 +1 @@
Remove ``Power`` coordinates, ``fetch_icbm152_brain_gm_mask`` mask, ``BOLDWarper`` Preprocessor by `Synchon Mandal`_

View file

@ -79,7 +79,7 @@ API Changes
- Add a positional argument ``using`` for Markers and Preprocessors having - Add a positional argument ``using`` for Markers and Preprocessors having
implementation-based variations, in particular :class:`.ReHoParcels`, implementation-based variations, in particular :class:`.ReHoParcels`,
:class:`.ReHoSpheres`, :class:`.ALFFParcels`, :class:`.ALFFSpheres` and :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`_ - Change all ``probseg_`` types to ``VBM_`` types by `Fede Raimondo`_
(:gh:`320`) (:gh:`320`)
- Change the subject and session patterns for :class:`.DataladAOMICID1000`, - Change the subject and session patterns for :class:`.DataladAOMICID1000`,
@ -120,13 +120,13 @@ Enhancements
coordinates by `Synchon Mandal`_ and `Fede Raimondo`_ (:gh:`268`) coordinates by `Synchon Mandal`_ and `Fede Raimondo`_ (:gh:`268`)
- Add ``mode`` as an aggregation function option in - Add ``mode`` as an aggregation function option in
:func:`.get_aggfunc_by_name` by `Synchon Mandal`_ (:gh:`287`) :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`) extension by `Synchon Mandal`_ (:gh:`293`)
- Rewrite :func:`.compute_brain_mask` to allow variable template fetching via - Rewrite :func:`.compute_brain_mask` to allow variable template fetching via
templateflow, according to target data by `Synchon Mandal`_ (:gh:`299`) templateflow, according to target data by `Synchon Mandal`_ (:gh:`299`)
- Replace ``requests`` with ``httpx`` for fetching parcellations by `Synchon - Replace ``requests`` with ``httpx`` for fetching parcellations by `Synchon
Mandal`_ (:gh:`300`) 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`) Mandal`_ (:gh:`302`)
- Add support for local ``junifer queue`` via GNU Parallel by `Synchon Mandal`_ - Add support for local ``junifer queue`` via GNU Parallel by `Synchon Mandal`_
(:gh:`306`) (:gh:`306`)
@ -168,7 +168,7 @@ Features
by `Synchon Mandal`_ (:gh:`265`) by `Synchon Mandal`_ (:gh:`265`)
- Introduce ``junifer.preprocess.fsl.apply_warper._ApplyWarper`` to wrap FSL's - Introduce ``junifer.preprocess.fsl.apply_warper._ApplyWarper`` to wrap FSL's
``applywarp`` by `Synchon Mandal`_ (:gh:`266`) ``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`) by `Synchon Mandal`_ (:gh:`267`)
- Introduce :class:`.DMCC13Benchmark` to access `DMCC13benchmark dataset - Introduce :class:`.DMCC13Benchmark` to access `DMCC13benchmark dataset
<https://openneuro.org/datasets/ds003452/versions/1.0.1>`_ by `Synchon <https://openneuro.org/datasets/ds003452/versions/1.0.1>`_ by `Synchon
@ -216,7 +216,7 @@ Miscellaneous
`Synchon Mandal`_ (:gh:`279`) `Synchon Mandal`_ (:gh:`279`)
- Add support for accessing FSL ``img2imgcoord`` via Docker wrapper command by - Add support for accessing FSL ``img2imgcoord`` via Docker wrapper command by
`Synchon Mandal`_ (:gh:`281`) `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`_ ``junifer.preprocess.fsl`` to :mod:`junifer.preprocess` by `Synchon Mandal`_
(:gh:`288`) (:gh:`288`)
- Add support for accessing ANTs' ``ResampleImage`` via Docker wrapper by - Add support for accessing ANTs' ``ResampleImage`` via Docker wrapper by
@ -232,7 +232,7 @@ Miscellaneous
Deprecations and Removals 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`) Mandal`_ (:gh:`301`)

View file

@ -13,7 +13,7 @@ import pandas as pd
from numpy.typing import ArrayLike from numpy.typing import ArrayLike
from ..pipeline import WorkDirManager 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 # Path to the VOIs
@ -377,11 +377,6 @@ def load_coordinates(name: str) -> Tuple[ArrayLike, List[str], str]:
ValueError ValueError
If ``name`` is invalid. If ``name`` is invalid.
Warns
-----
DeprecationWarning
If ``Power`` is provided as the ``name``.
""" """
# Check for valid coordinates name # Check for valid coordinates name
if name not in _available_coordinates: 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"Coordinates {name} not found. "
f"Valid options are: {list_coordinates()}" 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 # Load coordinates
t_coord = _available_coordinates[name] t_coord = _available_coordinates[name]
if isinstance(t_coord.get("path"), Path): if isinstance(t_coord.get("path"), Path):

View file

@ -19,7 +19,6 @@ from typing import (
import nibabel as nib import nibabel as nib
import numpy as np 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.image import get_data, new_img_like, resample_to_img
from nilearn.masking import ( from nilearn.masking import (
compute_background_mask, compute_background_mask,
@ -28,7 +27,7 @@ from nilearn.masking import (
) )
from ..pipeline import WorkDirManager 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 .template_spaces import get_template, get_xfm
from .utils import closest_resolution from .utils import closest_resolution
@ -121,46 +120,6 @@ def compute_brain_mask(
return new_img_like(target_img, mask) # type: ignore 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 # A dictionary containing all supported masks and their respective file or
# data. # data.
@ -194,11 +153,6 @@ _available_masks: Dict[str, Dict[str, Any]] = {
"func": compute_epi_mask, "func": compute_epi_mask,
"space": "inherit", "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 Raises
------ ------
RuntimeError RuntimeError
If warp / transformation file extension is not ".mat" or ".h5" or If warp / transformation file extension is not ".mat" or ".h5".
if fetch_icbm152_brain_gm_mask is used and requires warping to
other template space.
ValueError ValueError
If extra key is provided in addition to mask name in ``masks`` or If extra key is provided in addition to mask name in ``masks`` or
if no mask is provided or if no mask is provided or
@ -402,21 +354,6 @@ def get_mask( # noqa: C901
mask_img = extra_input[inherited_mask_item]["data"] mask_img = extra_input[inherited_mask_item]["data"]
# Starting with new mask # Starting with new mask
else: 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 # Load mask
mask_object, _, mask_space = load_mask( mask_object, _, mask_space = load_mask(
mask_name, path_only=False, resolution=resolution mask_name, path_only=False, resolution=resolution

View file

@ -386,14 +386,6 @@ def test_get_mask_errors() -> None:
masks="inherit", target_data=vbm_gm, extra_input=extra_input 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( @pytest.mark.parametrize(
"mask_name,function,params,resample", "mask_name,function,params,resample",

View file

@ -7,6 +7,5 @@
from .base import BasePreprocessor from .base import BasePreprocessor
from .confounds import fMRIPrepConfoundRemover from .confounds import fMRIPrepConfoundRemover
from .bold_warper import BOLDWarper
from .warping import SpaceWarper from .warping import SpaceWarper
from .smoothing import Smoothing from .smoothing import Smoothing

View file

@ -1,4 +0,0 @@
"""Provide imports for ants sub-package."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# License: AGPL

View file

@ -1,185 +0,0 @@
"""Provide class for warping via ANTs antsApplyTransforms."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# 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

View file

@ -1,56 +0,0 @@
"""Provide tests for AntsApplyTransformsWarper."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# 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)

View file

@ -1,265 +0,0 @@
"""Provide class for warping BOLD to other template spaces."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# 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

View file

@ -1,4 +0,0 @@
"""Provide imports for fsl sub-package."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# License: AGPL

View file

@ -1,179 +0,0 @@
"""Provide class for warping via FSL FLIRT."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# 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

View file

@ -1,45 +0,0 @@
"""Provide tests for ApplyWarper."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# 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)

View file

@ -1,159 +0,0 @@
"""Provide tests for BOLDWarper."""
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
# 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"])