[ENH]: Further Aggregation methods for SphereAggregation Marker #102
|
|
@ -85,6 +85,8 @@ Enhancements
|
||||||
|
|
||||||
- Refactor the :class:`junifer.datagrabber.BaseDataGrabber` class to allow for easier subclassing (:gh:`123` by `Fede Raimondo`_).
|
- Refactor the :class:`junifer.datagrabber.BaseDataGrabber` class to allow for easier subclassing (:gh:`123` by `Fede Raimondo`_).
|
||||||
|
|
||||||
|
- Allow custom aggregation method for :class:`junifer.markers.SphereAggregation` (:gh:`102` by `Synchon Mandal`_).
|
||||||
|
|
||||||
Bugs
|
Bugs
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
|
|
@ -97,7 +99,3 @@ Bugs
|
||||||
|
|
||||||
API changes
|
API changes
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
- Add new argument ``check_file_existence`` for PatternDataGrabber which allows skipping actual file existence check.
|
|
||||||
This is needed for datagrabbers which extend from PatternDataGrabber and DataladDataGrabber and have one or more
|
|
||||||
Datalad subdatasets inside the dataset, for example, DataladHCP1200.
|
|
||||||
|
|
@ -16,5 +16,6 @@ from . import (
|
||||||
stats,
|
stats,
|
||||||
storage,
|
storage,
|
||||||
utils,
|
utils,
|
||||||
|
external,
|
||||||
)
|
)
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
|
|
|
||||||
4
junifer/external/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
"""Provide imports for external sub-package."""
|
||||||
|
|
||||||
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
6
junifer/external/nilearn/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""Provide imports for custom nilearn objects sub-package."""
|
||||||
|
|
||||||
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
from .junifer_nifti_spheres_masker import JuniferNiftiSpheresMasker
|
||||||
290
junifer/external/nilearn/junifer_nifti_spheres_masker.py
vendored
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
"""Provide JuniferNiftiSpheresMasker class."""
|
||||||
|
|
||||||
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
Yeah from nilearn, okay will add the information. Yeah from nilearn, okay will add the information.
Okay two doubts: Okay two doubts:
- nilearn has a different [style](https://github.com/nilearn/nilearn/blob/c3a9390af2349e3263fd0b2991c17a927a142f6c/nilearn/maskers/nifti_spheres_masker.py#L1) of module docstrings, so it will look way different when it renders.
- According to BSD license (which nilearn uses), needs the [license](https://github.com/nilearn/nilearn/blob/main/LICENSE) to be there in anything that redistributes the code.
1) Change the docstrings
2) Keep the header of the file where it says MIT License, including the authors and stuff
https://github.com/nilearn/nilearn/blob/98a3ee060b55aa073118885d11cc6a1cecd95059/LICENSE#L4-L35
You mean make it nilearn style?
They have BSD but I get your point. > * Change the docstrings
You mean make it nilearn style?
> * Keep the header of the file where it says MIT License, including the authors and stuff
They have BSD but I get your point.
Shouldn't it be the latest license? Licensing is always tricky. > https://github.com/nilearn/nilearn/blob/98a3ee060b55aa073118885d11cc6a1cecd95059/LICENSE#L4-L35
Shouldn't it be the latest license? Licensing is always tricky.
Make them our style
No, the first is nilearn license. The rest are software incorporated in nilearn. > > * Change the docstrings
>
> You mean make it nilearn style?
Make them our style
> > https://github.com/nilearn/nilearn/blob/98a3ee060b55aa073118885d11cc6a1cecd95059/LICENSE#L4-L35
>
> Shouldn't it be the latest license? Licensing is always tricky.
No, the first is nilearn license. The rest are software incorporated in nilearn.
I meant that the link you referenced has I meant that the link you referenced has `2007-2015` and the one I referenced has `2007-2022`. So, should we go for the new one?
check the main branch: https://github.com/nilearn/nilearn/blob/main/LICENSE check the main branch: https://github.com/nilearn/nilearn/blob/main/LICENSE
|
|||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from nilearn._utils.class_inspect import get_params
|
||||||
|
from nilearn._utils.niimg import img_data_dtype
|
||||||
|
from nilearn._utils.niimg_conversions import check_niimg_4d
|
||||||
|
from nilearn.maskers import NiftiSpheresMasker
|
||||||
|
from nilearn.maskers.base_masker import _filter_and_extract
|
||||||
|
from nilearn.maskers.nifti_spheres_masker import _iter_signals_from_spheres
|
||||||
|
|
||||||
|
from ...utils import raise_error
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from nibabel import Nifti1Image, Nifti2Image
|
||||||
|
from numpy.typing import ArrayLike, DTypeLike
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
New BSD License
|
||||||
|
|
||||||
|
Copyright (c) 2007 - 2022 The nilearn developers.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
a. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
b. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
c. Neither the name of the nilearn developers nor the names of
|
||||||
|
its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class _JuniferExtractionFunctor:
|
||||||
|
"""Functor to extract signals from spheres.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
seeds_ : list of triple of coordinates in native space
|
||||||
|
Seed definitions. List of coordinates of the seeds in the same space
|
||||||
|
as the images (typically MNI or TAL).
|
||||||
|
radius : float
|
||||||
|
Indicates, in millimeters, the radius for the sphere around the seed.
|
||||||
|
mask_img : Niimg-like object
|
||||||
|
Mask to apply to regions before extracting signals.
|
||||||
|
agg_func : callable
|
||||||
|
The function to aggregate signals using.
|
||||||
|
allow_overlap : bool
|
||||||
|
If False, an error is raised if the maps overlap.
|
||||||
|
dtype : any type that can be coerced into a numpy dtype or "auto"
|
||||||
|
The dtype for the extraction. If "auto", the data will be converted to
|
||||||
|
int32 if dtype is discrete and float32 if it is continuous.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
func_name = "junifer_nifti_spheres_masker_extractor"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
seeds_: "ArrayLike",
|
||||||
|
radius: Optional[float],
|
||||||
|
mask_img: Union["Nifti1Image", "Nifti2Image", None],
|
||||||
|
agg_func: Callable,
|
||||||
|
allow_overlap: bool,
|
||||||
|
dtype: Union["DTypeLike", str, None],
|
||||||
|
) -> None:
|
||||||
|
self.seeds_ = seeds_
|
||||||
|
self.radius = radius
|
||||||
|
self.mask_img = mask_img
|
||||||
|
self.agg_func = agg_func
|
||||||
|
self.allow_overlap = allow_overlap
|
||||||
|
self.dtype = dtype
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
imgs: Union["Nifti1Image", "Nifti2Image"],
|
||||||
|
) -> Tuple["ArrayLike", None]:
|
||||||
|
"""Implement function call overloading.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
imgs : 4D Niimg-like object
|
||||||
|
If ``imgs`` is an iterable, checks if data is really 4D. Then,
|
||||||
|
considering that it is a list of ``img``, load them one by one.
|
||||||
|
If ``img`` is a string, consider it as a path to Nifti image and
|
||||||
|
call :func:`nibabel.load` on it.
|
||||||
|
If it is an object, check if the affine attribute is present and
|
||||||
|
that :func:`nilearn.image.get_data` returns a result, eval raise
|
||||||
|
TypeError.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
TypeError
|
||||||
|
If ``img`` in ``imgs`` is an object without the affine attribute.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple of numpy.ndarray and None
|
||||||
|
The numpy.ndarray returned is the array of extracted signals.
|
||||||
|
|
||||||
|
"""
|
||||||
|
n_seeds = len(self.seeds_)
|
||||||
|
imgs = check_niimg_4d(imgs, dtype=self.dtype)
|
||||||
|
|
||||||
|
signals = np.empty(
|
||||||
|
(imgs.shape[3], n_seeds), dtype=img_data_dtype(imgs)
|
||||||
|
)
|
||||||
|
for i, sphere in enumerate(
|
||||||
|
_iter_signals_from_spheres(
|
||||||
|
seeds=self.seeds_,
|
||||||
|
niimg=imgs,
|
||||||
|
radius=self.radius,
|
||||||
|
allow_overlap=self.allow_overlap,
|
||||||
|
mask_img=self.mask_img,
|
||||||
|
)
|
||||||
|
):
|
||||||
|
signals[:, i] = self.agg_func(sphere, axis=1)
|
||||||
|
return signals, None
|
||||||
|
|
||||||
|
|
||||||
|
class JuniferNiftiSpheresMasker(NiftiSpheresMasker):
|
||||||
|
"""Class for custom NiftiSpheresMasker.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
seeds : list of triplet of coordinates in native space
|
||||||
|
Seed definitions. List of coordinates of the seeds in the same space
|
||||||
|
as the images (typically MNI or TAL).
|
||||||
|
radius : float, optional
|
||||||
|
Indicates, in millimeters, the radius for the sphere around the seed.
|
||||||
|
If None, signal is extracted on a single voxel (default None).
|
||||||
|
mask_img : Niimg-like object, optional
|
||||||
|
Mask to apply to regions before extracting signals (default None).
|
||||||
|
agg_func : callable, optional
|
||||||
|
The function to aggregate signals using (default numpy.mean).
|
||||||
|
allow_overlap : bool, optional
|
||||||
|
If False, an error is raised if the maps overlap (default None).
|
||||||
|
dtype : any type that can be coerced into a numpy dtype or "auto", optional
|
||||||
|
The dtype for the extraction. If "auto", the data will be converted to
|
||||||
|
int32 if dtype is discrete and float32 if it is continuous
|
||||||
|
(default None).
|
||||||
|
**kwargs
|
||||||
|
Keyword arguments are passed to the
|
||||||
|
:func:`nilearn.maskers.NiftiSpheresMasker`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
seeds: "ArrayLike",
|
||||||
|
radius: Optional[float] = None,
|
||||||
|
mask_img: Union["Nifti1Image", "Nifti2Image", None] = None,
|
||||||
|
agg_func: Callable = np.mean,
|
||||||
|
allow_overlap: bool = False,
|
||||||
|
dtype: Union["DTypeLike", str, None] = None,
|
||||||
|
**kwargs, # TODO: to keep or not?
|
||||||
|
) -> None:
|
||||||
|
self.agg_func = agg_func
|
||||||
|
super().__init__(
|
||||||
|
seeds=seeds,
|
||||||
|
radius=radius,
|
||||||
|
mask_img=mask_img,
|
||||||
|
allow_overlap=allow_overlap,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
def transform_single_imgs(
|
||||||
|
self,
|
||||||
|
imgs: Union["Nifti1Image", "Nifti2Image"],
|
||||||
|
confounds: Union[
|
||||||
|
str, "Path", "ArrayLike", "DataFrame", List, None
|
||||||
|
] = None,
|
||||||
|
sample_mask: Union["ArrayLike", List, Tuple, None] = None,
|
||||||
|
) -> "ArrayLike":
|
||||||
|
"""Extract signals from a single 4D niimg.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
imgs : 3D/4D Niimg-like object
|
||||||
|
Images to process.
|
||||||
|
If a 3D niimg is provided, a singleton dimension will be added to
|
||||||
|
the output to represent the single scan in the niimg.
|
||||||
|
confounds : CSV file or array-like or pandas.DataFrame, optional
|
||||||
|
This parameter is passed to :func:`nilearn.signal.clean`.
|
||||||
|
Please see the related documentation for details.
|
||||||
|
shape: (number of scans, number of confounds)
|
||||||
|
sample_mask : Any type compatible with numpy-array indexing, optional
|
||||||
|
Masks the niimgs along time/fourth dimension to perform scrubbing
|
||||||
|
(remove volumes with high motion) and/or non-steady-state volumes.
|
||||||
|
This parameter is passed to :func:`nilearn.signal.clean`.
|
||||||
|
shape: (number of scans - number of volumes removed, )
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
region_signals : 2D numpy.ndarray
|
||||||
|
Signal for each sphere.
|
||||||
|
shape: (number of scans, number of spheres)
|
||||||
|
|
||||||
|
Warns
|
||||||
|
-----
|
||||||
|
DeprecationWarning
|
||||||
|
If a 3D niimg input is provided, the current behavior
|
||||||
|
(adding a singleton dimension to produce a 2D array) is deprecated.
|
||||||
|
Starting in version 0.12, a 1D array will be returned for 3D
|
||||||
|
inputs.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._check_fitted()
|
||||||
|
|
||||||
|
params = get_params(NiftiSpheresMasker, self)
|
||||||
|
|
||||||
|
signals, _ = self._cache(
|
||||||
|
_filter_and_extract, ignore=["verbose", "memory", "memory_level"]
|
||||||
|
)(
|
||||||
|
imgs,
|
||||||
|
_JuniferExtractionFunctor(
|
||||||
|
seeds_=self.seeds_,
|
||||||
|
radius=self.radius,
|
||||||
|
mask_img=self.mask_img,
|
||||||
|
agg_func=self.agg_func,
|
||||||
|
allow_overlap=self.allow_overlap,
|
||||||
|
dtype=self.dtype,
|
||||||
|
),
|
||||||
|
# Pre-processing
|
||||||
|
params,
|
||||||
|
confounds=confounds,
|
||||||
|
sample_mask=sample_mask,
|
||||||
|
dtype=self.dtype,
|
||||||
|
# Caching
|
||||||
|
memory=self.memory,
|
||||||
|
memory_level=self.memory_level,
|
||||||
|
# kwargs
|
||||||
|
verbose=self.verbose,
|
||||||
|
)
|
||||||
|
return signals
|
||||||
|
|
||||||
|
def inverse_transform(self, region_signals: "ArrayLike") -> "Nifti1Image":
|
||||||
|
"""Compute voxel signals from spheres signals.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
region_signals : 1D/2D numpy.ndarray
|
||||||
|
Signal for each region.
|
||||||
|
If a 1D array is provided, then the shape should be
|
||||||
|
(number of elements,), and a 3D img will be returned.
|
||||||
|
If a 2D array is provided, then the shape should be
|
||||||
|
(number of scans, number of elements), and a 4D img will be
|
||||||
|
returned.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
voxel_signals : nibabel.nifti1.Nifti1Image
|
||||||
|
Signal for each sphere.
|
||||||
|
shape: (mask_img, number of scans).
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise_error(
|
||||||
|
msg="As this class allows multiple methods of aggregation while "
|
||||||
|
"transforming, some of which are non-reversible, "
|
||||||
|
"inverse_transform() should not be implemented.",
|
||||||
|
klass=NotImplementedError,
|
||||||
|
)
|
||||||
334
junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py
vendored
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
"""Provide tests for JuniferNiftiSpheresMasker class."""
|
||||||
|
|
||||||
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
Same here, how much is it nilearn's code? Same here, how much is it nilearn's code?
The whole of it. I removed the tests for The whole of it. I removed the tests for `inverse_transform`.
then keep nilearn's header then. then keep nilearn's header then.
This will also need the license, I believe? This will also need the license, I believe?
Yes. Yes.
|
|||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import nibabel
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
from nilearn._utils import data_gen
|
||||||
|
from nilearn.image import get_data
|
||||||
|
from numpy.testing import assert_array_equal
|
||||||
|
|
||||||
|
from junifer.external.nilearn import JuniferNiftiSpheresMasker
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
New BSD License
|
||||||
|
|
||||||
|
Copyright (c) 2007 - 2022 The nilearn developers.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
a. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
b. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
c. Neither the name of the nilearn developers nor the names of
|
||||||
|
its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_seed_extraction() -> None:
|
||||||
|
"""Test seed extraction."""
|
||||||
|
data = np.random.RandomState(42).random_sample((3, 3, 3, 5))
|
||||||
|
img = nibabel.Nifti1Image(data, np.eye(4))
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)])
|
||||||
|
# Test the fit
|
||||||
|
masker.fit()
|
||||||
|
# Test the transform
|
||||||
|
s = masker.transform(img)
|
||||||
|
assert_array_equal(s[:, 0], data[1, 1, 1])
|
||||||
|
|
||||||
|
|
||||||
|
def test_sphere_extraction() -> None:
|
||||||
|
"""Test sphere extraction."""
|
||||||
|
data = np.random.RandomState(42).random_sample((3, 3, 3, 5))
|
||||||
|
img = nibabel.Nifti1Image(data, np.eye(4))
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)], radius=1)
|
||||||
|
|
||||||
|
# Check attributes defined at fit
|
||||||
|
assert not hasattr(masker, "seeds_")
|
||||||
|
assert not hasattr(masker, "n_elements_")
|
||||||
|
|
||||||
|
masker.fit()
|
||||||
|
|
||||||
|
# Check attributes defined at fit
|
||||||
|
assert hasattr(masker, "seeds_")
|
||||||
|
assert hasattr(masker, "n_elements_")
|
||||||
|
assert masker.n_elements_ == 1
|
||||||
|
|
||||||
|
# Test the fit
|
||||||
|
masker.fit()
|
||||||
|
|
||||||
|
# Test the transform
|
||||||
|
s = masker.transform(img)
|
||||||
|
mask = np.zeros((3, 3, 3), dtype=bool)
|
||||||
|
mask[:, 1, 1] = True
|
||||||
|
mask[1, :, 1] = True
|
||||||
|
mask[1, 1, :] = True
|
||||||
|
assert_array_equal(s[:, 0], np.mean(data[mask], axis=0))
|
||||||
|
|
||||||
|
# Now with a mask
|
||||||
|
mask_img = np.zeros((3, 3, 3))
|
||||||
|
mask_img[1, :, :] = 1
|
||||||
|
mask_img = nibabel.Nifti1Image(mask_img, np.eye(4))
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds=[(1, 1, 1)],
|
||||||
|
radius=1,
|
||||||
|
mask_img=mask_img,
|
||||||
|
)
|
||||||
|
masker.fit()
|
||||||
|
s = masker.transform(img)
|
||||||
|
assert_array_equal(
|
||||||
|
s[:, 0],
|
||||||
|
np.mean(
|
||||||
|
data[np.logical_and(mask, get_data(mask_img))],
|
||||||
|
axis=0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_anisotropic_sphere_extraction() -> None:
|
||||||
|
"""Test anisotropic sphere extraction."""
|
||||||
|
data = np.random.RandomState(42).random_sample((3, 3, 3, 5))
|
||||||
|
affine = np.eye(4)
|
||||||
|
affine[0, 0] = 2
|
||||||
|
affine[2, 2] = 2
|
||||||
|
img = nibabel.Nifti1Image(data, affine)
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=[(2, 1, 2)], radius=1)
|
||||||
|
# Test the fit
|
||||||
|
masker.fit()
|
||||||
|
# Test the transform
|
||||||
|
s = masker.transform(img)
|
||||||
|
mask = np.zeros((3, 3, 3), dtype=bool)
|
||||||
|
mask[1, :, 1] = True
|
||||||
|
assert_array_equal(s[:, 0], np.mean(data[mask], axis=0))
|
||||||
|
# Now with a mask
|
||||||
|
mask_img = np.zeros((3, 2, 3))
|
||||||
|
mask_img[1, 0, 1] = 1
|
||||||
|
affine_2 = affine.copy()
|
||||||
|
affine_2[0, 0] = 4
|
||||||
|
mask_img = nibabel.Nifti1Image(mask_img, affine=affine_2)
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds=[(2, 1, 2)],
|
||||||
|
radius=1,
|
||||||
|
mask_img=mask_img,
|
||||||
|
)
|
||||||
|
|
||||||
|
masker.fit()
|
||||||
|
s = masker.transform(img)
|
||||||
|
assert_array_equal(s[:, 0], data[1, 0, 1])
|
||||||
|
|
||||||
|
|
||||||
|
def test_errors() -> None:
|
||||||
|
"""Test errors."""
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=([1, 2]), radius=0.2)
|
||||||
|
with pytest.raises(ValueError, match="Seeds must be a list .+"):
|
||||||
|
masker.fit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_nifti_spheres_masker_overlap() -> None:
|
||||||
|
"""Test overlapping sphere extraction."""
|
||||||
|
# Test resampling in NiftiMapsMasker
|
||||||
|
affine = np.eye(4)
|
||||||
|
shape = (5, 5, 5)
|
||||||
|
|
||||||
|
data = np.random.RandomState(42).random_sample(shape + (5,))
|
||||||
|
fmri_img = nibabel.Nifti1Image(data, affine)
|
||||||
|
|
||||||
|
seeds = [(0, 0, 0), (2, 2, 2)]
|
||||||
|
|
||||||
|
overlapping_masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds,
|
||||||
|
radius=1,
|
||||||
|
allow_overlap=True,
|
||||||
|
)
|
||||||
|
overlapping_masker.fit_transform(fmri_img)
|
||||||
|
overlapping_masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds,
|
||||||
|
radius=2,
|
||||||
|
allow_overlap=True,
|
||||||
|
)
|
||||||
|
overlapping_masker.fit_transform(fmri_img)
|
||||||
|
|
||||||
|
noverlapping_masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds,
|
||||||
|
radius=1,
|
||||||
|
allow_overlap=False,
|
||||||
|
)
|
||||||
|
noverlapping_masker.fit_transform(fmri_img)
|
||||||
|
noverlapping_masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds,
|
||||||
|
radius=2,
|
||||||
|
allow_overlap=False,
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError, match="Overlap detected"):
|
||||||
|
noverlapping_masker.fit_transform(fmri_img)
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_radius() -> None:
|
||||||
|
"""Test sphere extraction with small radius."""
|
||||||
|
affine = np.eye(4)
|
||||||
|
shape = (3, 3, 3)
|
||||||
|
|
||||||
|
data = np.random.RandomState(42).random_sample(shape)
|
||||||
|
mask = np.zeros(shape)
|
||||||
|
mask[1, 1, 1] = 1
|
||||||
|
mask[2, 2, 2] = 1
|
||||||
|
affine = np.eye(4) * 1.2
|
||||||
|
seed = (1.4, 1.4, 1.4)
|
||||||
|
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds=[seed],
|
||||||
|
radius=0.1,
|
||||||
|
mask_img=nibabel.Nifti1Image(mask, affine),
|
||||||
|
)
|
||||||
|
masker.fit_transform(nibabel.Nifti1Image(data, affine))
|
||||||
|
|
||||||
|
# Test if masking is taken into account
|
||||||
|
mask[1, 1, 1] = 0
|
||||||
|
mask[1, 1, 0] = 1
|
||||||
|
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds=[seed],
|
||||||
|
radius=0.1,
|
||||||
|
mask_img=nibabel.Nifti1Image(mask, affine),
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError, match="These spheres are empty"):
|
||||||
|
masker.fit_transform(nibabel.Nifti1Image(data, affine))
|
||||||
|
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds=[seed],
|
||||||
|
radius=1.6,
|
||||||
|
mask_img=nibabel.Nifti1Image(mask, affine),
|
||||||
|
)
|
||||||
|
masker.fit_transform(nibabel.Nifti1Image(data, affine))
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_nifti_spheres_masker_give_nans() -> None:
|
||||||
|
"""Test NaN interaction with masker."""
|
||||||
|
affine = np.eye(4)
|
||||||
|
|
||||||
|
data_with_nans = np.zeros((10, 10, 10), dtype=np.float32)
|
||||||
|
data_with_nans[:, :, :] = np.nan
|
||||||
|
|
||||||
|
data_without_nans = np.random.RandomState(42).random_sample((9, 9, 9))
|
||||||
|
indices = np.nonzero(data_without_nans)
|
||||||
|
|
||||||
|
# Leaving nans outside of some data
|
||||||
|
data_with_nans[indices] = data_without_nans[indices]
|
||||||
|
img = nibabel.Nifti1Image(data_with_nans, affine)
|
||||||
|
seed = [(7, 7, 7)]
|
||||||
|
|
||||||
|
# Interaction of seed with nans
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=seed, radius=2.0)
|
||||||
|
assert not np.isnan(np.sum(masker.fit_transform(img)))
|
||||||
|
|
||||||
|
mask = np.ones((9, 9, 9))
|
||||||
|
mask_img = nibabel.Nifti1Image(mask, affine)
|
||||||
|
# When mask_img is provided, the seed interacts within the brain, so no nan
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds=seed,
|
||||||
|
radius=2.0,
|
||||||
|
mask_img=mask_img,
|
||||||
|
)
|
||||||
|
assert not np.isnan(np.sum(masker.fit_transform(img)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_standardization() -> None:
|
||||||
|
"""Test standardization."""
|
||||||
|
data = np.random.RandomState(42).random_sample((3, 3, 3, 5))
|
||||||
|
img = nibabel.Nifti1Image(data, np.eye(4))
|
||||||
|
|
||||||
|
# test zscore
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)], standardize="zscore")
|
||||||
|
# Test the fit
|
||||||
|
s = masker.fit_transform(img)
|
||||||
|
|
||||||
|
np.testing.assert_almost_equal(s.mean(), 0)
|
||||||
|
np.testing.assert_almost_equal(s.std(), 1)
|
||||||
|
|
||||||
|
# test psc
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)], standardize="psc")
|
||||||
|
# Test the fit
|
||||||
|
s = masker.fit_transform(img)
|
||||||
|
|
||||||
|
np.testing.assert_almost_equal(s.mean(), 0)
|
||||||
|
np.testing.assert_almost_equal(
|
||||||
|
s.ravel(),
|
||||||
|
data[1, 1, 1] / data[1, 1, 1].mean() * 100 - 100,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_nifti_spheres_masker_inverse_transform() -> None:
|
||||||
|
"""Test inverse transform."""
|
||||||
|
data = np.random.RandomState(42).random_sample((3, 3, 3, 5))
|
||||||
|
masker = JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)], radius=1)
|
||||||
|
with pytest.raises(
|
||||||
|
NotImplementedError,
|
||||||
|
match="some of which are non-reversible",
|
||||||
|
):
|
||||||
|
masker.inverse_transform(data[0, 0, 0, :])
|
||||||
|
|
||||||
|
|
||||||
|
def test_nifti_spheres_masker_io_shapes() -> None:
|
||||||
|
"""Ensure that masker handles 1D/2D/3D/4D data appropriately.
|
||||||
|
|
||||||
|
transform(4D image) --> 2D output, no warning
|
||||||
|
transform(3D image) --> 2D output, DeprecationWarning
|
||||||
|
|
||||||
|
"""
|
||||||
|
n_regions, n_volumes = 2, 5
|
||||||
|
shape_3d = (10, 11, 12)
|
||||||
|
shape_4d = (10, 11, 12, n_volumes)
|
||||||
|
affine = np.eye(4)
|
||||||
|
|
||||||
|
img_4d, mask_img = data_gen.generate_random_img(
|
||||||
|
shape_4d,
|
||||||
|
affine=affine,
|
||||||
|
)
|
||||||
|
img_3d, _ = data_gen.generate_random_img(shape_3d, affine=affine)
|
||||||
|
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
|
seeds=[(1, 1, 1), (4, 4, 4)], # number of tuples equal to n_regions
|
||||||
|
radius=1,
|
||||||
|
mask_img=mask_img,
|
||||||
|
)
|
||||||
|
masker.fit()
|
||||||
|
|
||||||
|
# DeprecationWarning *should* be raised for 3D inputs
|
||||||
|
with pytest.warns(DeprecationWarning, match="Starting in version 0.12"):
|
||||||
|
test_data = masker.transform(img_3d)
|
||||||
|
assert test_data.shape == (1, n_regions)
|
||||||
|
|
||||||
|
# DeprecationWarning should *not* be raised for 4D inputs
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings(
|
||||||
|
"error",
|
||||||
|
message="Starting in version 0.12",
|
||||||
|
category=DeprecationWarning,
|
||||||
|
)
|
||||||
|
test_data = masker.transform(img_4d)
|
||||||
|
assert test_data.shape == (n_volumes, n_regions)
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from nilearn.maskers import NiftiSpheresMasker
|
|
||||||
|
|
||||||
from ..api.decorators import register_marker
|
from ..api.decorators import register_marker
|
||||||
from ..data import load_coordinates
|
from ..data import load_coordinates
|
||||||
from ..utils import logger, raise_error
|
from ..external.nilearn import JuniferNiftiSpheresMasker
|
||||||
|
from ..stats import get_aggfunc_by_name
|
||||||
|
from ..utils import logger
|
||||||
from .base import BaseMarker
|
from .base import BaseMarker
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -27,21 +27,24 @@ class SphereAggregation(BaseMarker):
|
||||||
The name of the coordinates list to use. See
|
The name of the coordinates list to use. See
|
||||||
:func:`junifer.data.coordinates.list_coordinates` for options.
|
:func:`junifer.data.coordinates.list_coordinates` for options.
|
||||||
radius : float, optional
|
radius : float, optional
|
||||||
The radius of the sphere in mm. If None, the signal will be extracted
|
The radius of the sphere in millimeters. If None, the signal will be
|
||||||
from a single voxel. See :class:`nilearn.maskers.NiftiSpheresMasker`
|
extracted from a single voxel. See
|
||||||
for more information (default None).
|
:class:`nilearn.maskers.NiftiSpheresMasker` for more information
|
||||||
|
(default None).
|
||||||
method : str, optional
|
method : str, optional
|
||||||
The aggregation method to use.
|
The aggregation method to use.
|
||||||
See :func:`junifer.stats.get_aggfunc_by_name` for more information
|
See :func:`junifer.stats.get_aggfunc_by_name` for more information
|
||||||
(default "mean").
|
(default "mean").
|
||||||
method_params : dict, optional
|
method_params : dict, optional
|
||||||
The parameters to pass to the aggregation method (default None).
|
The parameters to pass to the aggregation method (default None).
|
||||||
on : list of str, optional
|
on : {"T1w", "BOLD", "VBM_GM", "VBM_WM", "fALFF", "GCOR", "LCOR"} or \
|
||||||
The kind of data to apply the marker to. By default, will work on all
|
list of the options, optional
|
||||||
|
The data types to apply the marker to. If None, will work on all
|
||||||
available data (default None).
|
available data (default None).
|
||||||
name : str, optional
|
name : str, optional
|
||||||
The name of the marker. By default, it will use KIND_SphereAggregation
|
The name of the marker. By default, it will use KIND_SphereAggregation
|
||||||
where KIND is the kind of data it was applied to (default None).
|
where KIND is the kind of data it was applied to (default None).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -49,21 +52,12 @@ class SphereAggregation(BaseMarker):
|
||||||
coords: str,
|
coords: str,
|
||||||
radius: Optional[float] = None,
|
radius: Optional[float] = None,
|
||||||
method: str = "mean",
|
method: str = "mean",
|
||||||
method_params: Optional[Dict] = None,
|
method_params: Optional[Dict[str, Any]] = None,
|
||||||
on: Optional[Union[List[str], str]] = None,
|
on: Union[List[str], str, None] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.coords = coords
|
self.coords = coords
|
||||||
self.radius = radius
|
self.radius = radius
|
||||||
|
|
||||||
if method != "mean":
|
|
||||||
raise_error(
|
|
||||||
"Only mean aggregation is supported for sphere aggregation. "
|
|
||||||
"If you need other aggregation methods, please open an issue "
|
|
||||||
"on `junifer github`_.",
|
|
||||||
NotImplementedError,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.method = method
|
self.method = method
|
||||||
self.method_params = {} if method_params is None else method_params
|
self.method_params = {} if method_params is None else method_params
|
||||||
super().__init__(on=on, name=name)
|
super().__init__(on=on, name=name)
|
||||||
|
|
@ -90,7 +84,7 @@ class SphereAggregation(BaseMarker):
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list of str
|
list of str
|
||||||
The kind of output.
|
The list of storage kinds.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
outputs = []
|
outputs = []
|
||||||
|
|
@ -159,17 +153,21 @@ class SphereAggregation(BaseMarker):
|
||||||
"""
|
"""
|
||||||
t_input = input["data"]
|
t_input = input["data"]
|
||||||
logger.debug(f"Sphere aggregation using {self.method}")
|
logger.debug(f"Sphere aggregation using {self.method}")
|
||||||
# agg_func = get_aggfunc_by_name(
|
# Get aggregation function
|
||||||
# self.method, func_params=self.method_params
|
agg_func = get_aggfunc_by_name(
|
||||||
# )
|
self.method, func_params=self.method_params
|
||||||
coords, out_labels = load_coordinates(self.coords)
|
)
|
||||||
masker = NiftiSpheresMasker(
|
# Get seeds and labels
|
||||||
|
coords, out_labels = load_coordinates(name=self.coords)
|
||||||
|
masker = JuniferNiftiSpheresMasker(
|
||||||
seeds=coords,
|
seeds=coords,
|
||||||
radius=self.radius,
|
radius=self.radius,
|
||||||
mask_img=None, # TODO: support this (needs #79)
|
mask_img=None, # TODO: support this (needs #79)
|
||||||
|
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)
|
||||||
|
# Format the output
|
||||||
out = {"data": out_values, "columns": out_labels}
|
out = {"data": out_values, "columns": out_labels}
|
||||||
if out_values.shape[0] > 1:
|
if out_values.shape[0] > 1:
|
||||||
out["row_names"] = "scan"
|
out["row_names"] = "scan"
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,15 @@ from junifer.data import load_coordinates
|
||||||
from junifer.markers.sphere_aggregation import SphereAggregation
|
from junifer.markers.sphere_aggregation import SphereAggregation
|
||||||
from junifer.storage import SQLiteFeatureStorage
|
from junifer.storage import SQLiteFeatureStorage
|
||||||
|
|
||||||
|
# Define common variables
|
||||||
|
COORDS = "DMNBuckner"
|
||||||
|
RADIUS = 8
|
||||||
|
|
||||||
|
|
||||||
def test_SphereAggregation_input_output() -> None:
|
def test_SphereAggregation_input_output() -> None:
|
||||||
"""Test SphereAggregation input and output types."""
|
"""Test SphereAggregation input and output types."""
|
||||||
marker = SphereAggregation(
|
marker = SphereAggregation(
|
||||||
coords="DMNBuckner", method="mean", radius=8, on="VBM_GM"
|
coords=COORDS, method="mean", radius=RADIUS, on="VBM_GM"
|
||||||
)
|
)
|
||||||
|
|
||||||
output = marker.get_output_kind(["VBM_GM", "BOLD"])
|
output = marker.get_output_kind(["VBM_GM", "BOLD"])
|
||||||
|
|
@ -33,9 +37,7 @@ def test_SphereAggregation_input_output() -> None:
|
||||||
def test_SphereAggregation_3D() -> None:
|
def test_SphereAggregation_3D() -> None:
|
||||||
"""Test SphereAggregation object on 3D images."""
|
"""Test SphereAggregation object on 3D images."""
|
||||||
# Get the testing coordinates (for nilearn)
|
# Get the testing coordinates (for nilearn)
|
||||||
coord_names = "DMNBuckner"
|
coordinates, labels = load_coordinates(COORDS)
|
||||||
radius = 8
|
|
||||||
coordinates, labels = load_coordinates(coord_names)
|
|
||||||
|
|
||||||
# Get the oasis VBM data
|
# Get the oasis VBM data
|
||||||
oasis_dataset = datasets.fetch_oasis_vbm(n_subjects=1)
|
oasis_dataset = datasets.fetch_oasis_vbm(n_subjects=1)
|
||||||
|
|
@ -43,12 +45,12 @@ def test_SphereAggregation_3D() -> None:
|
||||||
img = nib.load(vbm)
|
img = nib.load(vbm)
|
||||||
|
|
||||||
# Create NiftiLabelsMasker
|
# Create NiftiLabelsMasker
|
||||||
nifti_masker = NiftiSpheresMasker(seeds=coordinates, radius=radius)
|
nifti_masker = NiftiSpheresMasker(seeds=coordinates, radius=RADIUS)
|
||||||
auto4d = nifti_masker.fit_transform(img)
|
auto4d = nifti_masker.fit_transform(img)
|
||||||
|
|
||||||
# Create SphereAggregation object
|
# Create SphereAggregation object
|
||||||
marker = SphereAggregation(
|
marker = SphereAggregation(
|
||||||
coords=coord_names, method="mean", radius=radius, on="VBM_GM"
|
coords=COORDS, method="mean", radius=RADIUS, on="VBM_GM"
|
||||||
)
|
)
|
||||||
input = {"VBM_GM": {"data": img}}
|
input = {"VBM_GM": {"data": img}}
|
||||||
jun_values4d = marker.fit_transform(input)["VBM_GM"]["data"]
|
jun_values4d = marker.fit_transform(input)["VBM_GM"]["data"]
|
||||||
|
|
@ -59,37 +61,30 @@ def test_SphereAggregation_3D() -> None:
|
||||||
|
|
||||||
meta = marker.get_meta("VBM_GM")["marker"]
|
meta = marker.get_meta("VBM_GM")["marker"]
|
||||||
assert meta["method"] == "mean"
|
assert meta["method"] == "mean"
|
||||||
assert meta["coords"] == coord_names
|
assert meta["coords"] == COORDS
|
||||||
assert meta["radius"] == radius
|
assert meta["radius"] == RADIUS
|
||||||
assert meta["name"] == "VBM_GM_SphereAggregation"
|
assert meta["name"] == "VBM_GM_SphereAggregation"
|
||||||
assert meta["class"] == "SphereAggregation"
|
assert meta["class"] == "SphereAggregation"
|
||||||
assert meta["kind"] == "VBM_GM"
|
assert meta["kind"] == "VBM_GM"
|
||||||
assert meta["method_params"] == {}
|
assert meta["method_params"] == {}
|
||||||
|
|
||||||
with pytest.raises(NotImplementedError, match="mean aggregation"):
|
|
||||||
marker = SphereAggregation(
|
|
||||||
coords=coord_names, method="std", radius=radius, on="BOLD"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_SphereAggregation_4D() -> None:
|
def test_SphereAggregation_4D() -> None:
|
||||||
"""Test SphereAggregation object on 4D images."""
|
"""Test SphereAggregation object on 4D images."""
|
||||||
# Get the testing coordinates (for nilearn)
|
# Get the testing coordinates (for nilearn)
|
||||||
coord_names = "DMNBuckner"
|
coordinates, labels = load_coordinates(COORDS)
|
||||||
radius = 8
|
|
||||||
coordinates, labels = load_coordinates(coord_names)
|
|
||||||
|
|
||||||
# Get the SPM auditory data:
|
# Get the SPM auditory data
|
||||||
subject_data = datasets.fetch_spm_auditory()
|
subject_data = datasets.fetch_spm_auditory()
|
||||||
fmri_img = concat_imgs(subject_data.func) # type: ignore
|
fmri_img = concat_imgs(subject_data.func) # type: ignore
|
||||||
|
|
||||||
# Create NiftiLabelsMasker
|
# Create NiftiLabelsMasker
|
||||||
nifti_masker = NiftiSpheresMasker(seeds=coordinates, radius=radius)
|
nifti_masker = NiftiSpheresMasker(seeds=coordinates, radius=RADIUS)
|
||||||
auto4d = nifti_masker.fit_transform(fmri_img)
|
auto4d = nifti_masker.fit_transform(fmri_img)
|
||||||
|
|
||||||
# Create SphereAggregation object
|
# Create SphereAggregation object
|
||||||
marker = SphereAggregation(
|
marker = SphereAggregation(
|
||||||
coords=coord_names, method="mean", radius=radius
|
coords=COORDS, method="mean", radius=RADIUS
|
||||||
)
|
)
|
||||||
input = {"BOLD": {"data": fmri_img}}
|
input = {"BOLD": {"data": fmri_img}}
|
||||||
jun_values4d = marker.fit_transform(input)["BOLD"]["data"]
|
jun_values4d = marker.fit_transform(input)["BOLD"]["data"]
|
||||||
|
|
@ -100,8 +95,8 @@ def test_SphereAggregation_4D() -> None:
|
||||||
|
|
||||||
meta = marker.get_meta("BOLD")["marker"]
|
meta = marker.get_meta("BOLD")["marker"]
|
||||||
assert meta["method"] == "mean"
|
assert meta["method"] == "mean"
|
||||||
assert meta["coords"] == coord_names
|
assert meta["coords"] == COORDS
|
||||||
assert meta["radius"] == radius
|
assert meta["radius"] == RADIUS
|
||||||
assert meta["name"] == "BOLD_SphereAggregation"
|
assert meta["name"] == "BOLD_SphereAggregation"
|
||||||
assert meta["class"] == "SphereAggregation"
|
assert meta["class"] == "SphereAggregation"
|
||||||
assert meta["kind"] == "BOLD"
|
assert meta["kind"] == "BOLD"
|
||||||
|
|
@ -133,7 +128,7 @@ def test_SphereAggregation_storage(tmp_path: Path) -> None:
|
||||||
}
|
}
|
||||||
input = {"VBM_GM": {"data": img}, "meta": meta}
|
input = {"VBM_GM": {"data": img}, "meta": meta}
|
||||||
marker = SphereAggregation(
|
marker = SphereAggregation(
|
||||||
coords="DMNBuckner", method="mean", radius=8, on="VBM_GM"
|
coords=COORDS, method="mean", radius=RADIUS, on="VBM_GM"
|
||||||
)
|
)
|
||||||
|
|
||||||
marker.fit_transform(input, storage=storage)
|
marker.fit_transform(input, storage=storage)
|
||||||
|
|
@ -143,12 +138,12 @@ def test_SphereAggregation_storage(tmp_path: Path) -> None:
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"marker": {"name": "BOLD_fcname"},
|
"marker": {"name": "BOLD_fcname"},
|
||||||
}
|
}
|
||||||
# Get the SPM auditory data:
|
# Get the SPM auditory data
|
||||||
subject_data = datasets.fetch_spm_auditory()
|
subject_data = datasets.fetch_spm_auditory()
|
||||||
fmri_img = concat_imgs(subject_data.func) # type: ignore
|
fmri_img = concat_imgs(subject_data.func) # type: ignore
|
||||||
input = {"BOLD": {"data": fmri_img}, "meta": meta}
|
input = {"BOLD": {"data": fmri_img}, "meta": meta}
|
||||||
marker = SphereAggregation(
|
marker = SphereAggregation(
|
||||||
coords="DMNBuckner", method="mean", radius=8, on="BOLD"
|
coords=COORDS, method="mean", radius=RADIUS, on="BOLD"
|
||||||
)
|
)
|
||||||
|
|
||||||
marker.fit_transform(input, storage=storage)
|
marker.fit_transform(input, storage=storage)
|
||||||
|
|
|
||||||
Is this code from Nilearn? If so, can you adjust the license/info on this file to reflect that?