[ENH]: Support for multiple computed masks #175
17 changed files with 375 additions and 45 deletions
|
|
@ -515,7 +515,7 @@ Available
|
|||
* - Name
|
||||
- Keys
|
||||
- Version added
|
||||
- Publication
|
||||
- Description - Publication
|
||||
* - Vickery-Patil (Gray Matter)
|
||||
- | ``GM_prob0.2``
|
||||
- 0.0.1
|
||||
|
|
@ -528,6 +528,29 @@ Available
|
|||
- | Vickery, Sam, & Patil, Kaustubh. (2022).
|
||||
| Chimpanzee and Human Gray Matter Masks [Data set]. Zenodo.
|
||||
| https://doi.org/10.5281/zenodo.6463123
|
||||
* - Nilearn's MNI152 1mm-resolution mask
|
||||
- | ``compute_brain_mask``
|
||||
- 0.0.2
|
||||
- | Compute the whole-brain mask. This mask is calculated using MNI152 1mm-resolution template mask onto the
|
||||
| target image. See :func:`nilearn.masking.compute_brain_mask`
|
||||
* - Nilearn's mask computed from FMRI data
|
||||
- | ``compute_epi_mask``
|
||||
- 0.0.2
|
||||
- | Compute a brain mask from fMRI data. This is based on an heuristic proposed by T.Nichols: find the least
|
||||
| dense point of the histogram, between fractions ``lower_cutoff`` and ``upper_cutoff`` of the total image
|
||||
| histogram. See :func:`nilearn.masking.compute_epi_mask`
|
||||
* - Nilearn's background mask
|
||||
- | ``compute_background_mask``
|
||||
- 0.0.2
|
||||
- | 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``
|
||||
- 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
|
||||
~~~~~~~
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ Enhancements
|
|||
|
||||
- Change HCP datagrabber tests to decrease CI running time (:gh:`155` by `Fede Raimondo`_).
|
||||
|
||||
- Add support for nilearn computed masks (``compute_epi_mask``, ``compute_brain_mask``, ``compute_background_mask``,
|
||||
``fetch_icbm152_brain_gm_mask``) (:gh:`175` by `Fede Raimondo`_).
|
||||
|
||||
Bugs
|
||||
~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from .masks import (
|
|||
list_masks,
|
||||
load_mask,
|
||||
register_mask,
|
||||
get_mask,
|
||||
)
|
||||
|
||||
from . import utils
|
||||
|
|
|
|||
|
|
@ -4,9 +4,27 @@
|
|||
# License: AGPL
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
Callable,
|
||||
)
|
||||
|
||||
import numpy as np
|
||||
|
||||
import nibabel as nib
|
||||
from nilearn.masking import (
|
||||
compute_brain_mask,
|
||||
compute_background_mask,
|
||||
compute_epi_mask,
|
||||
)
|
||||
from nilearn.datasets import fetch_icbm152_brain_gm_mask
|
||||
from nilearn.image import resample_to_img
|
||||
|
||||
from ..utils.logging import logger, raise_error
|
||||
from .utils import closest_resolution
|
||||
|
|
@ -18,6 +36,30 @@ if TYPE_CHECKING:
|
|||
# Path to the VOIs
|
||||
|
|
||||
_masks_path = Path(__file__).parent / "masks"
|
||||
|
||||
|
||||
def _fetch_icbm152_brain_gm_mask(target_img: "Nifti1Image", **kwargs):
|
||||
"""Fetch ICBM152 brain mask and resample.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
target_img : nibabel.Nifti1Image
|
||||
|
I think it would be better to have the actual function name from nilearn (i.e. "compute_brain_mask") rather than an abbreviation of it. I think it would be better to have the actual function name from nilearn (i.e. "compute_brain_mask") rather than an abbreviation of it.
|
||||
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.
|
||||
"""
|
||||
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.
|
||||
|
|
@ -28,6 +70,22 @@ data/masks directory. The user can also register their own masks.
|
|||
_available_masks: Dict[str, Dict[str, Any]] = {
|
||||
"GM_prob0.2": {"family": "Vickery-Patil"},
|
||||
"GM_prob0.2_cortex": {"family": "Vickery-Patil"},
|
||||
"compute_brain_mask": {
|
||||
"family": "Callable",
|
||||
"func": compute_brain_mask,
|
||||
},
|
||||
"compute_background_mask": {
|
||||
"family": "Callable",
|
||||
"func": compute_background_mask,
|
||||
},
|
||||
"compute_epi_mask": {
|
||||
"family": "Callable",
|
||||
"func": compute_epi_mask,
|
||||
},
|
||||
"fetch_icbm152_brain_gm_mask": {
|
||||
"family": "Callable",
|
||||
"func": _fetch_icbm152_brain_gm_mask,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -88,11 +146,67 @@ def list_masks() -> List[str]:
|
|||
return sorted(_available_masks.keys())
|
||||
|
Would prefer to have the type as Would prefer to have the type as `dict` and the key-value pair information in the description below.
Missing coverage here. Missing coverage here.
|
||||
|
||||
|
||||
def get_mask(
|
||||
mask: Union[str, Dict],
|
||||
target_data: Dict[str, Any],
|
||||
) -> "Nifti1Image":
|
||||
"""Get mask, tailored for the target image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
masks : str or dict
|
||||
The name of the mask, or the name of a callable mask and the parameters
|
||||
of the mask.
|
||||
target_data : dict
|
||||
The corresponding item of the data object to which the mask will be
|
||||
applied.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Nifti1Image
|
||||
The mask image.
|
||||
"""
|
||||
# Get the min of the voxels sizes and use it as the resolution
|
||||
target_img = target_data["data"]
|
||||
resolution = np.min(target_img.header.get_zooms()[:3])
|
||||
|
||||
if isinstance(mask, dict):
|
||||
if len(mask) != 1:
|
||||
raise_error(
|
||||
"The mask dictionary must have only one key, "
|
||||
"the name of the mask."
|
||||
)
|
||||
mask_name = list(mask.keys())[0]
|
||||
mask_params = mask[mask_name]
|
||||
else:
|
||||
mask_name = mask
|
||||
mask_params = None
|
||||
|
||||
mask_object, _ = load_mask(
|
||||
mask_name, path_only=False, resolution=resolution
|
||||
)
|
||||
if callable(mask_object):
|
||||
if mask_params is None:
|
||||
mask_params = {}
|
||||
mask_img = mask_object(target_img, **mask_params)
|
||||
else: # Mask is a Nifti1Image
|
||||
if mask_params is not None:
|
||||
raise_error("Cannot pass callable params to a non-callable mask.")
|
||||
mask_img = resample_to_img(
|
||||
mask_object,
|
||||
target_img,
|
||||
interpolation="nearest",
|
||||
copy=True,
|
||||
)
|
||||
|
||||
return mask_img
|
||||
|
||||
|
||||
def load_mask(
|
||||
name: str,
|
||||
resolution: Optional[float] = None,
|
||||
path_only: bool = False,
|
||||
) -> Tuple[Optional["Nifti1Image"], Path]:
|
||||
) -> Tuple[Optional[Union["Nifti1Image", Callable]], Optional[Path]]:
|
||||
"""Load mask.
|
||||
|
||||
Parameters
|
||||
|
|
@ -109,11 +223,12 @@ def load_mask(
|
|||
|
||||
Returns
|
||||
-------
|
||||
Nifti1Image or None
|
||||
Nifti1Image, Callable or None
|
||||
Loaded mask image.
|
||||
pathlib.Path
|
||||
pathlib.Path or None
|
||||
File path to the mask image.
|
||||
"""
|
||||
mask_img = None
|
||||
if name not in _available_masks:
|
||||
raise_error(
|
||||
f"Mask {name} not found. " f"Valid options are: {list_masks()}"
|
||||
|
|
@ -126,14 +241,16 @@ def load_mask(
|
|||
mask_fname = Path(mask_definition["path"])
|
||||
elif t_family == "Vickery-Patil":
|
||||
mask_fname = _load_vickery_patil_mask(name, resolution)
|
||||
elif t_family == "Callable":
|
||||
mask_img = mask_definition["func"]
|
||||
|
Needs a newline after this line. Needs a newline after this line.
|
||||
mask_fname = None
|
||||
else:
|
||||
raise_error(f"I don't know about the {t_family} mask family.")
|
||||
|
||||
logger.info(f"Loading mask {mask_fname.absolute()}")
|
||||
|
||||
mask_img = None
|
||||
if path_only is False:
|
||||
mask_img = nib.load(mask_fname)
|
||||
if mask_fname is not None:
|
||||
logger.info(f"Loading mask {mask_fname.absolute()}")
|
||||
if path_only is False:
|
||||
mask_img = nib.load(mask_fname)
|
||||
|
||||
return mask_img, mask_fname
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,37 @@
|
|||
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||
# License: AGPL
|
||||
|
||||
from typing import Callable, Dict, Union
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
from numpy.testing import (
|
||||
assert_array_almost_equal,
|
||||
assert_array_equal,
|
||||
)
|
||||
|
||||
from nilearn.image import resample_to_img
|
||||
from nilearn.masking import (
|
||||
compute_brain_mask,
|
||||
compute_background_mask,
|
||||
compute_epi_mask,
|
||||
)
|
||||
from nilearn.datasets import fetch_icbm152_brain_gm_mask
|
||||
|
||||
from junifer.data.masks import (
|
||||
_load_vickery_patil_mask,
|
||||
list_masks,
|
||||
load_mask,
|
||||
register_mask,
|
||||
get_mask,
|
||||
_available_masks,
|
||||
)
|
||||
|
||||
from junifer.datareader import DefaultDataReader
|
||||
from junifer.testing.datagrabbers import (
|
||||
OasisVBMTestingDatagrabber,
|
||||
SPMAuditoryTestingDatagrabber,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -151,3 +172,124 @@ def test_vickery_patil() -> None:
|
|||
|
||||
with pytest.raises(ValueError, match=r"find a Vickery-Patil mask "):
|
||||
_load_vickery_patil_mask("wrong", resolution=2)
|
||||
|
||||
|
||||
def test_get_mask() -> None:
|
||||
"""Test the get_mask function."""
|
||||
reader = DefaultDataReader()
|
||||
with OasisVBMTestingDatagrabber() as dg:
|
||||
input = dg["sub-01"]
|
||||
input = reader.fit_transform(input)
|
||||
vbm_gm = input["VBM_GM"]
|
||||
vbm_gm_img = vbm_gm["data"]
|
||||
mask = get_mask(mask="GM_prob0.2", target_data=vbm_gm)
|
||||
|
||||
assert mask.shape == vbm_gm_img.shape
|
||||
assert_array_equal(mask.affine, vbm_gm_img.affine)
|
||||
|
||||
raw_mask_img, _ = load_mask("GM_prob0.2", resolution=1.5)
|
||||
res_mask_img = resample_to_img(
|
||||
raw_mask_img,
|
||||
vbm_gm_img,
|
||||
interpolation="nearest",
|
||||
copy=True,
|
||||
)
|
||||
assert_array_equal(mask.get_fdata(), res_mask_img.get_fdata())
|
||||
|
||||
|
||||
def test_mask_callable() -> None:
|
||||
"""Test using a callable mask."""
|
||||
|
||||
def ident(x):
|
||||
return x
|
||||
|
||||
_available_masks["identity"] = {"family": "Callable", "func": ident}
|
||||
reader = DefaultDataReader()
|
||||
with OasisVBMTestingDatagrabber() as dg:
|
||||
input = dg["sub-01"]
|
||||
input = reader.fit_transform(input)
|
||||
vbm_gm = input["VBM_GM"]
|
||||
vbm_gm_img = vbm_gm["data"]
|
||||
mask = get_mask(mask="identity", target_data=vbm_gm)
|
||||
|
||||
assert_array_equal(mask.get_fdata(), vbm_gm_img.get_fdata())
|
||||
|
||||
del _available_masks["identity"]
|
||||
|
||||
|
||||
def test_get_mask_errors() -> None:
|
||||
"""Test passing wrong parameters to get_mask."""
|
||||
reader = DefaultDataReader()
|
||||
with OasisVBMTestingDatagrabber() as dg:
|
||||
input = dg["sub-01"]
|
||||
input = reader.fit_transform(input)
|
||||
vbm_gm = input["VBM_GM"]
|
||||
with pytest.raises(ValueError, match=r"only one key"):
|
||||
get_mask(mask={"GM_prob0.2": {}, "Other": {}}, target_data=vbm_gm)
|
||||
|
||||
with pytest.raises(ValueError, match=r"callable params"):
|
||||
get_mask(mask={"GM_prob0.2": {"param": 1}}, target_data=vbm_gm)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mask_name,function,params,resample",
|
||||
[
|
||||
("compute_brain_mask", compute_brain_mask, {"threshold": 0.2}, False),
|
||||
("compute_background_mask", compute_background_mask, None, False),
|
||||
("compute_epi_mask", compute_epi_mask, None, False),
|
||||
(
|
||||
"fetch_icbm152_brain_gm_mask",
|
||||
fetch_icbm152_brain_gm_mask,
|
||||
None,
|
||||
True,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_nilearn_compute_masks(
|
||||
mask_name: str,
|
||||
function: Callable,
|
||||
params: Union[Dict, None],
|
||||
resample: bool,
|
||||
) -> None:
|
||||
"""Test using nilearn compute mask functions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mask_name : str
|
||||
Name of the mask.
|
||||
function : callable
|
||||
Function to call.
|
||||
params : dict, optional
|
||||
Parameters to pass to the function.
|
||||
resample : bool
|
||||
Whether to resample the mask to the target data.
|
||||
"""
|
||||
reader = DefaultDataReader()
|
||||
with SPMAuditoryTestingDatagrabber() as dg:
|
||||
input = dg["sub001"]
|
||||
input = reader.fit_transform(input)
|
||||
bold = input["BOLD"]
|
||||
bold_img = bold["data"]
|
||||
|
||||
if params is None:
|
||||
params = {}
|
||||
mask_spec = mask_name
|
||||
else:
|
||||
mask_spec = {mask_name: params}
|
||||
|
||||
mask = get_mask(mask=mask_spec, target_data=bold)
|
||||
|
||||
assert_array_equal(mask.affine, bold_img.affine)
|
||||
|
||||
if resample is False:
|
||||
ni_mask = function(bold_img, **params)
|
||||
else:
|
||||
ni_mask = function(**params)
|
||||
# Mask needs resample
|
||||
ni_mask = resample_to_img(
|
||||
ni_mask,
|
||||
bold_img,
|
||||
interpolation="nearest",
|
||||
copy=True,
|
||||
)
|
||||
assert_array_equal(mask.get_fdata(), ni_mask.get_fdata())
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class RSSETSMarker(BaseMarker):
|
|||
parcellation: Union[str, List[str]],
|
||||
agg_method: str = "mean",
|
||||
agg_method_params: Optional[Dict] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.parcellation = parcellation
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class AmplitudeLowFrequencyFluctuationParcels(
|
|||
lowpass: float = 0.1,
|
||||
tr: Optional[float] = None,
|
||||
use_afni: Optional[bool] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
method: str = "mean",
|
||||
method_params: Optional[Dict] = None,
|
||||
name: Optional[str] = None,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# Kaustubh R. Patil <k.patil@fz-juelich.de>
|
||||
# License: AGPL
|
||||
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, Optional, Union
|
||||
|
||||
from ...api.decorators import register_marker
|
||||
from .. import SphereAggregation
|
||||
|
|
@ -75,7 +75,7 @@ class AmplitudeLowFrequencyFluctuationSpheres(
|
|||
lowpass: float = 0.1,
|
||||
tr: Optional[float] = None,
|
||||
use_afni: Optional[bool] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
method: str = "mean",
|
||||
method_params: Optional[Dict] = None,
|
||||
name: Optional[str] = None,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
# Kaustubh R. Patil <k.patil@fz-juelich.de>
|
||||
# License: AGPL
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ class CrossParcellationFC(BaseMarker):
|
|||
parcellation_two: str,
|
||||
aggregation_method: str = "mean",
|
||||
correlation_method: str = "pearson",
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
if parcellation_one == parcellation_two:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from nilearn.connectome import ConnectivityMeasure
|
||||
from sklearn.covariance import EmpiricalCovariance
|
||||
|
|
@ -50,7 +50,7 @@ class FunctionalConnectivityBase(BaseMarker):
|
|||
agg_method_params: Optional[Dict] = None,
|
||||
cor_method: str = "covariance",
|
||||
cor_method_params: Optional[Dict] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.agg_method = agg_method
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class FunctionalConnectivityParcels(FunctionalConnectivityBase):
|
|||
agg_method_params: Optional[Dict] = None,
|
||||
cor_method: str = "covariance",
|
||||
cor_method_params: Optional[Dict] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.parcellation = parcellation
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||
# License: AGPL
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from ...api.decorators import register_marker
|
||||
from ..sphere_aggregation import SphereAggregation
|
||||
|
|
@ -57,7 +57,7 @@ class FunctionalConnectivitySpheres(FunctionalConnectivityBase):
|
|||
agg_method_params: Optional[Dict] = None,
|
||||
cor_method: str = "covariance",
|
||||
cor_method_params: Optional[Dict] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.coords = coords
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from nilearn.image import math_img, new_img_like, resample_to_img
|
|||
from nilearn.maskers import NiftiMasker
|
||||
|
same here: in the ParcelAggregation init method should masks not be this should be str or list of (str or dict) now? same here: in the ParcelAggregation init method should masks not be this should be str or list of (str or dict) now?
same as before. This is for computing masks. same as before. This is for computing masks.
i see makes sense! i see makes sense!
|
||||
|
||||
from ..api.decorators import register_marker
|
||||
from ..data import load_mask, load_parcellation
|
||||
from ..data import get_mask, load_parcellation
|
||||
from ..stats import get_aggfunc_by_name
|
||||
from ..utils import logger
|
||||
from .base import BaseMarker
|
||||
|
|
@ -52,7 +52,7 @@ class ParcelAggregation(BaseMarker):
|
|||
parcellation: Union[str, List[str]],
|
||||
method: str,
|
||||
method_params: Optional[Dict[str, Any]] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
on: Union[List[str], str, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
|
|
@ -126,14 +126,14 @@ class ParcelAggregation(BaseMarker):
|
|||
* ``columns`` : the column labels for the computed values as a list
|
||||
|
||||
"""
|
||||
t_input = input["data"]
|
||||
t_input_img = input["data"]
|
||||
logger.debug(f"Parcel aggregation using {self.method}")
|
||||
agg_func = get_aggfunc_by_name(
|
||||
name=self.method,
|
||||
func_params=self.method_params,
|
||||
)
|
||||
# Get the min of the voxels sizes and use it as the resolution
|
||||
resolution = np.min(t_input.header.get_zooms()[:3])
|
||||
resolution = np.min(t_input_img.header.get_zooms()[:3])
|
||||
|
||||
# Load the parcellations
|
||||
all_parcelations = []
|
||||
|
|
@ -146,7 +146,7 @@ class ParcelAggregation(BaseMarker):
|
|||
# Resample all of them to the image
|
||||
t_parcellation_img_res = resample_to_img(
|
||||
t_parcellation,
|
||||
t_input,
|
||||
t_input_img,
|
||||
interpolation="nearest",
|
||||
copy=True,
|
||||
)
|
||||
|
|
@ -186,13 +186,8 @@ class ParcelAggregation(BaseMarker):
|
|||
|
||||
if self.mask is not None:
|
||||
logger.debug(f"Masking with {self.mask}")
|
||||
mask_img, _ = load_mask(name=self.mask, resolution=resolution)
|
||||
mask_img = resample_to_img(
|
||||
mask_img,
|
||||
t_input,
|
||||
interpolation="nearest",
|
||||
copy=True,
|
||||
)
|
||||
mask_img = get_mask(mask=self.mask, target_data=input)
|
||||
|
||||
parcellation_bin = math_img(
|
||||
"np.logical_and(img, mask)",
|
||||
img=parcellation_bin,
|
||||
|
|
@ -201,11 +196,11 @@ class ParcelAggregation(BaseMarker):
|
|||
|
||||
logger.debug("Masking")
|
||||
masker = NiftiMasker(
|
||||
parcellation_bin, target_affine=t_input.affine
|
||||
parcellation_bin, target_affine=t_input_img.affine
|
||||
) # type: ignore
|
||||
|
||||
# Mask the input data and the parcellation
|
||||
data = masker.fit_transform(t_input)
|
||||
data = masker.fit_transform(t_input_img)
|
||||
parcellation_values = masker.transform(parcellation_img_res)
|
||||
parcellation_values = np.squeeze(parcellation_values).astype(int)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
# License: AGPL
|
||||
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ class ReHoParcels(ReHoBase):
|
|||
reho_params: Optional[Dict] = None,
|
||||
agg_method: str = "mean",
|
||||
agg_method_params: Optional[Dict] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.parcellation = parcellation
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
# License: AGPL
|
||||
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ class ReHoSpheres(ReHoBase):
|
|||
reho_params: Optional[Dict] = None,
|
||||
agg_method: str = "mean",
|
||||
agg_method_params: Optional[Dict] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.coords = coords
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from ..api.decorators import register_marker
|
||||
from ..data import load_coordinates, load_mask
|
||||
from ..data import load_coordinates, get_mask
|
||||
from ..external.nilearn import JuniferNiftiSpheresMasker
|
||||
from ..stats import get_aggfunc_by_name
|
||||
from ..utils import logger
|
||||
|
|
@ -56,7 +56,7 @@ class SphereAggregation(BaseMarker):
|
|||
radius: Optional[float] = None,
|
||||
method: str = "mean",
|
||||
method_params: Optional[Dict[str, Any]] = None,
|
||||
mask: Optional[str] = None,
|
||||
mask: Union[str, Dict, None] = None,
|
||||
on: Union[List[str], str, None] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> None:
|
||||
|
|
@ -129,7 +129,7 @@ class SphereAggregation(BaseMarker):
|
|||
* ``columns`` : the column labels for the computed values as a list
|
||||
|
||||
"""
|
||||
t_input = input["data"]
|
||||
t_input_img = input["data"]
|
||||
logger.debug(f"Sphere aggregation using {self.method}")
|
||||
# Get aggregation function
|
||||
agg_func = get_aggfunc_by_name(
|
||||
|
|
@ -139,7 +139,7 @@ class SphereAggregation(BaseMarker):
|
|||
mask_img = None
|
||||
if self.mask is not None:
|
||||
logger.debug(f"Masking with {self.mask}")
|
||||
mask_img, _ = load_mask(self.mask)
|
||||
mask_img = get_mask(mask=self.mask, target_data=input)
|
||||
# Get seeds and labels
|
||||
coords, out_labels = load_coordinates(name=self.coords)
|
||||
masker = JuniferNiftiSpheresMasker(
|
||||
|
|
@ -149,7 +149,7 @@ class SphereAggregation(BaseMarker):
|
|||
agg_func=agg_func,
|
||||
)
|
||||
# Fit and transform the marker on the data
|
||||
out_values = masker.fit_transform(t_input)
|
||||
out_values = masker.fit_transform(t_input_img)
|
||||
# Format the output
|
||||
out = {"data": out_values, "columns": out_labels}
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import pytest
|
|||
from nilearn import datasets
|
||||
from nilearn.image import concat_imgs, math_img, new_img_like, resample_to_img
|
||||
from nilearn.maskers import NiftiLabelsMasker, NiftiMasker
|
||||
from nilearn.masking import compute_brain_mask
|
||||
from numpy.testing import assert_array_almost_equal, assert_array_equal
|
||||
from scipy.stats import trim_mean
|
||||
|
||||
|
|
@ -240,6 +241,54 @@ def test_ParcelAggregation_3D_mask() -> None:
|
|||
assert_array_almost_equal(auto, jun_values3d_mean)
|
||||
|
||||
|
||||
def test_ParcelAggregation_3D_mask_computed() -> None:
|
||||
"""Test ParcelAggregation object on 3D images with computed masks."""
|
||||
|
||||
# Get the testing parcellation (for nilearn)
|
||||
parcellation = datasets.fetch_atlas_schaefer_2018(n_rois=100)
|
||||
|
||||
# Get the oasis VBM data
|
||||
oasis_dataset = datasets.fetch_oasis_vbm(n_subjects=1)
|
||||
vbm = oasis_dataset.gray_matter_maps[0]
|
||||
img = nib.load(vbm)
|
||||
|
||||
# Get one mask
|
||||
mask_img = compute_brain_mask(img, threshold=0.2)
|
||||
|
||||
# Create NiftiLabelsMasker
|
||||
nifti_masker = NiftiLabelsMasker(
|
||||
labels_img=parcellation.maps, mask_img=mask_img
|
||||
)
|
||||
auto = nifti_masker.fit_transform(img)
|
||||
|
||||
# Get one mask
|
||||
mask_img = compute_brain_mask(img, threshold=0.5)
|
||||
|
||||
# Create NiftiLabelsMasker
|
||||
nifti_masker = NiftiLabelsMasker(
|
||||
labels_img=parcellation.maps, mask_img=mask_img
|
||||
)
|
||||
auto_bad = nifti_masker.fit_transform(img)
|
||||
|
||||
# Use the ParcelAggregation object
|
||||
marker = ParcelAggregation(
|
||||
parcellation="Schaefer100x7",
|
||||
method="mean",
|
||||
mask={"compute_brain_mask": {"threshold": 0.2}},
|
||||
name="gmd_schaefer100x7_mean",
|
||||
on="VBM_GM",
|
||||
) # Test passing "on" as a keyword argument
|
||||
input = {"VBM_GM": {"data": img, "meta": {}}}
|
||||
jun_values3d_mean = marker.fit_transform(input)["VBM_GM"]["data"]
|
||||
|
||||
assert jun_values3d_mean.ndim == 2
|
||||
assert jun_values3d_mean.shape[0] == 1
|
||||
assert_array_almost_equal(auto, jun_values3d_mean)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
assert_array_almost_equal(jun_values3d_mean, auto_bad)
|
||||
|
||||
|
||||
def test_ParcelAggregation_3D_multiple_non_overlapping(tmp_path: Path) -> None:
|
||||
"""Test ParcelAggregation with multiple non-overlapping parcellations.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue
Missing type annotations.
**kwargs : dict