Marker/edge centric fc #169
|
|
@ -182,6 +182,16 @@ Available
|
||||||
- Calculate (f)ALFF and aggregate using spheres placed on coordinates
|
- Calculate (f)ALFF and aggregate using spheres placed on coordinates
|
||||||
|
From the class' docstrings:
From the class' docstrings:
```
Jo et al. (2021)
Subject identification using edge-centric functional connectivity
doi: https://doi.org/10.1016/j.neuroimage.2021.118204
```
Can you add this to the docs? Can you add this to the docs?
Like you mean in the Like you mean in the `builtin.rst`?
we can also add this one as it was published a bit earlier (by the same group): https://www.nature.com/articles/s41593-020-00719-y but the 2021 one was the one i used as a reference for implementation we can also add this one as it was published a bit earlier (by the same group): https://www.nature.com/articles/s41593-020-00719-y but the 2021 one was the one i used as a reference for implementation
yes The idea is that people looking at markers list can know what they are. > Like you mean in the `builtin.rst`?
yes
The idea is that people looking at markers list can know what they are.
this paper is also on topic but by a different group and more of a reaction to the eFC method, but may be an interesting read for users: https://www.nature.com/articles/s41467-022-29775-7 this paper is also on topic but by a different group and more of a reaction to the eFC method, but may be an interesting read for users: https://www.nature.com/articles/s41467-022-29775-7
Which column should it go in then? > > Like you mean in the `builtin.rst`?
>
> yes
>
> The idea is that people looking at markers list can know what they are.
Which column should it go in then?
In the description. In the description.
I have added the reference. I have added the reference.
|
|||||||
- Done
|
- Done
|
||||||
- 0.0.1
|
- 0.0.1
|
||||||
|
* - :class:`junifer.markers.EdgeCentricFCParcels`
|
||||||
|
- Calculate edge-centric functional connectivity over parcellation, as found in
|
||||||
|
`Jo et al. (2021) <https://doi.org/10.1016/j.neuroimage.2021.118204>`_
|
||||||
|
- Done
|
||||||
|
- 0.0.2
|
||||||
|
* - :class:`junifer.markers.EdgeCentricFCSpheres`
|
||||||
|
- Calculate edge-centric functional connectivity over spheres placed on coordinates,
|
||||||
|
as found in `Jo et al. (2021) <https://doi.org/10.1016/j.neuroimage.2021.118204>`_
|
||||||
|
- Done
|
||||||
|
- 0.0.2
|
||||||
|
|
||||||
Planned
|
Planned
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
@ -199,10 +209,6 @@ Planned
|
||||||
* - Permutation entropy, Range entropy, Multiscale entropy and Hurst exponent
|
* - Permutation entropy, Range entropy, Multiscale entropy and Hurst exponent
|
||||||
- Calculate Permutation entropy, Range entropy, Multiscale entropy and Hurst exponent
|
- Calculate Permutation entropy, Range entropy, Multiscale entropy and Hurst exponent
|
||||||
- :gh:`61`
|
- :gh:`61`
|
||||||
* - EdgeCentricFC
|
|
||||||
- Calculate edge-centric functional connectivity
|
|
||||||
- :gh:`64`
|
|
||||||
|
|
||||||
|
|
||||||
Parcellations
|
Parcellations
|
||||||
-------------
|
-------------
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ Enhancements
|
||||||
- Organize functional connectivity markers in ``junifer.markers.functional_connectivity``
|
- Organize functional connectivity markers in ``junifer.markers.functional_connectivity``
|
||||||
(:gh:`107` by `Synchon Mandal`_).
|
(:gh:`107` by `Synchon Mandal`_).
|
||||||
|
|
||||||
|
- Add :class:`junifer.markers.EdgeCentricFCParcels` and :class:`junifer.markers.EdgeCentricFCSpheres`
|
||||||
|
(:gh:`64` by `Leonard Sasse`_).
|
||||||
|
|
||||||
Bugs
|
Bugs
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ from .functional_connectivity import (
|
||||||
FunctionalConnectivityParcels,
|
FunctionalConnectivityParcels,
|
||||||
FunctionalConnectivitySpheres,
|
FunctionalConnectivitySpheres,
|
||||||
CrossParcellationFC,
|
CrossParcellationFC,
|
||||||
|
EdgeCentricFCParcels,
|
||||||
|
EdgeCentricFCSpheres,
|
||||||
)
|
)
|
||||||
from .reho import ReHoParcels, ReHoSpheres
|
from .reho import ReHoParcels, ReHoSpheres
|
||||||
from .falff import (
|
from .falff import (
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,5 @@
|
||||||
from .functional_connectivity_parcels import FunctionalConnectivityParcels
|
from .functional_connectivity_parcels import FunctionalConnectivityParcels
|
||||||
from .functional_connectivity_spheres import FunctionalConnectivitySpheres
|
from .functional_connectivity_spheres import FunctionalConnectivitySpheres
|
||||||
from .crossparcellation_functional_connectivity import CrossParcellationFC
|
from .crossparcellation_functional_connectivity import CrossParcellationFC
|
||||||
|
from .edge_functional_connectivity_parcels import EdgeCentricFCParcels
|
||||||
|
from .edge_functional_connectivity_spheres import EdgeCentricFCSpheres
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
"""Provide class for edge-centric functional connectivity using parcels."""
|
||||||
|
|
||||||
|
# Authors: Leonard Sasse <l.sasse@fz-juelich.de>
|
||||||
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
from ...api.decorators import register_marker
|
||||||
|
from ..parcel_aggregation import ParcelAggregation
|
||||||
|
from ..utils import _ets
|
||||||
|
from .functional_connectivity_base import FunctionalConnectivityBase
|
||||||
|
|
||||||
|
|
||||||
|
@register_marker
|
||||||
|
class EdgeCentricFCParcels(FunctionalConnectivityBase):
|
||||||
|
"""Class for edge-centric FC using parcellations.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parcellation : str or list of str
|
||||||
|
The name(s) of the parcellation(s). Check valid options by calling
|
||||||
|
:func:`junifer.data.parcellations.list_parcellations`.
|
||||||
|
agg_method : str, optional
|
||||||
|
The method to perform aggregation of BOLD time series.
|
||||||
|
Check valid options in :func:`junifer.stats.get_aggfunc_by_name`
|
||||||
|
(default "mean").
|
||||||
|
agg_method_params : dict, optional
|
||||||
|
Parameters to pass to the aggregation function. Check valid options in
|
||||||
|
:func:`junifer.stats.get_aggfunc_by_name` (default None).
|
||||||
|
cor_method : str, optional
|
||||||
|
The method to perform correlation. Check valid options in
|
||||||
|
:class:`nilearn.connectome.ConnectivityMeasure`
|
||||||
|
(default "covariance").
|
||||||
|
cor_method_params : dict, optional
|
||||||
|
Parameters to pass to the correlation function. Check valid options in
|
||||||
|
:class:`nilearn.connectome.ConnectivityMeasure` (default None).
|
||||||
|
mask : str, optional
|
||||||
|
The name of the mask to apply to regions before extracting signals.
|
||||||
|
Check valid options by calling :func:`junifer.data.masks.list_masks`
|
||||||
|
(default None).
|
||||||
|
name : str, optional
|
||||||
|
The name of the marker. If None, will use the class name (default
|
||||||
|
None).
|
||||||
|
|
||||||
|
References
|
||||||
|
----------
|
||||||
|
.. [1] Jo et al. (2021)
|
||||||
|
Subject identification using
|
||||||
|
edge-centric functional connectivity
|
||||||
|
doi: https://doi.org/10.1016/j.neuroimage.2021.118204
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parcellation: Union[str, List[str]],
|
||||||
|
agg_method: str = "mean",
|
||||||
|
agg_method_params: Optional[Dict] = None,
|
||||||
|
cor_method: str = "covariance",
|
||||||
|
cor_method_params: Optional[Dict] = None,
|
||||||
|
mask: Optional[str] = None,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
self.parcellation = parcellation
|
||||||
|
super().__init__(
|
||||||
|
agg_method=agg_method,
|
||||||
|
agg_method_params=agg_method_params,
|
||||||
|
cor_method=cor_method,
|
||||||
|
cor_method_params=cor_method_params,
|
||||||
|
mask=mask,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def aggregate(self, input: Dict[str, Any]) -> Dict:
|
||||||
|
"""Perform parcel aggregation."""
|
||||||
|
parcel_aggregation = ParcelAggregation(
|
||||||
|
parcellation=self.parcellation,
|
||||||
|
method=self.agg_method,
|
||||||
|
method_params=self.agg_method_params,
|
||||||
|
mask=self.mask,
|
||||||
|
on="BOLD",
|
||||||
|
)
|
||||||
|
|
||||||
|
bold_aggregated = parcel_aggregation.compute(input)
|
||||||
|
ets, edge_names = _ets(
|
||||||
|
bold_aggregated["data"], bold_aggregated["columns"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return dict(data=ets, columns=edge_names)
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
"""Provide class for edge-centric functional connectivity using spheres."""
|
||||||
|
|
||||||
|
# Authors: Leonard Sasse <l.sasse@fz-juelich.de>
|
||||||
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from ...api.decorators import register_marker
|
||||||
|
from ..sphere_aggregation import SphereAggregation
|
||||||
|
from ..utils import _ets, raise_error
|
||||||
|
from .functional_connectivity_base import FunctionalConnectivityBase
|
||||||
|
|
||||||
|
|
||||||
|
@register_marker
|
||||||
|
class EdgeCentricFCSpheres(FunctionalConnectivityBase):
|
||||||
|
"""Class for edge-centric FC using coordinates (spheres).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
coords : str
|
||||||
|
The name of the coordinates list to use. See
|
||||||
|
:func:`junifer.data.coordinates.list_coordinates` for options.
|
||||||
|
radius : float, optional
|
||||||
|
The radius of the sphere in mm. If None, the signal will be extracted
|
||||||
|
from a single voxel. See :class:`nilearn.maskers.NiftiSpheresMasker`
|
||||||
|
for more information (default None).
|
||||||
|
agg_method : str, optional
|
||||||
|
The aggregation method to use.
|
||||||
|
See :func:`junifer.stats.get_aggfunc_by_name` for more information
|
||||||
|
(default None).
|
||||||
|
agg_method_params : dict, optional
|
||||||
|
The parameters to pass to the aggregation method (default None).
|
||||||
|
cor_method : str, optional
|
||||||
|
The method to perform correlation using. Check valid options in
|
||||||
|
:class:`nilearn.connectome.ConnectivityMeasure` (default "covariance").
|
||||||
|
cor_method_params : dict, optional
|
||||||
|
Parameters to pass to the correlation function. Check valid options in
|
||||||
|
:class:`nilearn.connectome.ConnectivityMeasure` (default None).
|
||||||
|
mask : str, optional
|
||||||
|
The name of the mask to apply to regions before extracting signals.
|
||||||
|
Check valid options by calling :func:`junifer.data.masks.list_masks`
|
||||||
|
(default None).
|
||||||
|
name : str, optional
|
||||||
|
The name of the marker. By default, it will use
|
||||||
|
KIND_EdgeCentricFCSpheres where KIND is the kind of data it
|
||||||
|
was applied to (default None).
|
||||||
|
|
||||||
|
References
|
||||||
|
----------
|
||||||
|
.. [1] Jo et al. (2021)
|
||||||
|
Subject identification using
|
||||||
|
edge-centric functional connectivity
|
||||||
|
doi: https://doi.org/10.1016/j.neuroimage.2021.118204
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coords: str,
|
||||||
|
radius: Optional[float] = None,
|
||||||
|
agg_method: str = "mean",
|
||||||
|
agg_method_params: Optional[Dict] = None,
|
||||||
|
cor_method: str = "covariance",
|
||||||
|
cor_method_params: Optional[Dict] = None,
|
||||||
|
mask: Optional[str] = None,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
self.coords = coords
|
||||||
|
self.radius = radius
|
||||||
|
if radius is None or radius <= 0:
|
||||||
|
Do we really need > 0? In nilearn terms (same as junifer), radius=0 means "single voxel". Do we really need > 0? In nilearn terms (same as junifer), radius=0 means "single voxel".
From nilearn's docs, From nilearn's docs, `radius = None` extracts signal from single voxel, not sure about 0 though. I think the condition can be improved in the sense that, `radius is None` is valid so it can be removed. This was copied from `FunctionalConnectivitySpheres`, so need to update that as well. I can open a new PR to address this for both.
Yes please. The SphereAggregator takes care of that error. There's no reason to check it in other markers with a different condition (unless it invalidates the marker) Yes please. The SphereAggregator takes care of that error. There's no reason to check it in other markers with a different condition (unless it invalidates the marker)
|
|||||||
|
raise_error(f"radius should be > 0: provided {radius}")
|
||||||
|
super().__init__(
|
||||||
|
agg_method=agg_method,
|
||||||
|
agg_method_params=agg_method_params,
|
||||||
|
cor_method=cor_method,
|
||||||
|
cor_method_params=cor_method_params,
|
||||||
|
mask=mask,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def aggregate(self, input: Dict[str, Any]) -> Dict:
|
||||||
|
"""Perform sphere aggregation."""
|
||||||
|
sphere_aggregation = SphereAggregation(
|
||||||
|
coords=self.coords,
|
||||||
|
radius=self.radius,
|
||||||
|
method=self.agg_method,
|
||||||
|
method_params=self.agg_method_params,
|
||||||
|
mask=self.mask,
|
||||||
|
on="BOLD",
|
||||||
|
)
|
||||||
|
bold_aggregated = sphere_aggregation.compute(input)
|
||||||
|
ets, edge_names = _ets(
|
||||||
|
bold_aggregated["data"], bold_aggregated["columns"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return dict(data=ets, columns=edge_names)
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
"""Provide tests for edge-centric functional connectivity using parcels."""
|
||||||
|
|
||||||
|
# Authors: Leonard Sasse <l.sasse@fz-juelich.de>
|
||||||
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from nilearn import datasets, image
|
||||||
|
|
||||||
|
from junifer.markers.functional_connectivity import EdgeCentricFCParcels
|
||||||
|
from junifer.storage import SQLiteFeatureStorage
|
||||||
|
|
||||||
|
|
||||||
|
def test_EdgeCentricFCParcels(tmp_path: Path) -> None:
|
||||||
|
"""Test EdgeCentricFCParcels.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tmp_path : pathlib.Path
|
||||||
|
The path to the test directory.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# get a dataset
|
||||||
|
ni_data = datasets.fetch_spm_auditory(subject_id="sub001")
|
||||||
|
fmri_img = image.concat_imgs(ni_data.func) # type: ignore
|
||||||
|
|
||||||
|
# Check empirical correlation method parameters
|
||||||
|
efc = EdgeCentricFCParcels(
|
||||||
|
parcellation="TianxS1x3TxMNInonlinear2009cAsym",
|
||||||
|
cor_method_params={"empirical": True},
|
||||||
|
)
|
||||||
|
all_out = efc.fit_transform({"BOLD": {"data": fmri_img, "meta": {}}})
|
||||||
|
|
||||||
|
out = all_out["BOLD"]
|
||||||
|
|
||||||
|
# for 16 ROIs we should get (16 * (16 -1) / 2) edges in the ETS
|
||||||
|
n_edges = int(16 * (16 - 1) / 2)
|
||||||
|
assert "data" in out
|
||||||
|
assert "row_names" in out
|
||||||
|
assert "col_names" in out
|
||||||
|
assert out["data"].shape[0] == n_edges
|
||||||
|
assert out["data"].shape[1] == n_edges
|
||||||
|
assert len(set(out["row_names"])) == n_edges
|
||||||
|
assert len(set(out["col_names"])) == n_edges
|
||||||
|
|
||||||
|
# check correct output
|
||||||
|
assert efc.get_output_type("BOLD") == "matrix"
|
||||||
|
|
||||||
|
uri = tmp_path / "test_fc_parcellation.sqlite"
|
||||||
|
# Single storage, must be the uri
|
||||||
|
storage = SQLiteFeatureStorage(uri=uri, upsert="ignore")
|
||||||
|
meta = {"element": {"subject": "test"}, "dependencies": {"numpy"}}
|
||||||
|
input = {"BOLD": {"data": fmri_img, "meta": meta}}
|
||||||
|
all_out = efc.fit_transform(input, storage=storage)
|
||||||
|
|
||||||
|
features = storage.list_features()
|
||||||
|
assert any(
|
||||||
|
x["name"] == "BOLD_EdgeCentricFCParcels" for x in features.values()
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
"""Provide tests for edge-centric functional connectivity using spheres."""
|
||||||
|
|
||||||
|
# Authors: Leonard Sasse <l.sasse@fz-juelich.de>
|
||||||
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from nilearn import datasets, image
|
||||||
|
|
||||||
|
from junifer.markers.functional_connectivity import EdgeCentricFCSpheres
|
||||||
|
from junifer.storage import SQLiteFeatureStorage
|
||||||
|
|
||||||
|
|
||||||
|
def test_EdgeCentricFCSpheres(tmp_path: Path) -> None:
|
||||||
|
"""Test EdgeCentricFCSpheres.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tmp_path : pathlib.Path
|
||||||
|
The path to the test directory.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# get a dataset
|
||||||
|
ni_data = datasets.fetch_spm_auditory(subject_id="sub001")
|
||||||
|
fmri_img = image.concat_imgs(ni_data.func) # type: ignore
|
||||||
|
|
||||||
|
efc = EdgeCentricFCSpheres(
|
||||||
|
coords="DMNBuckner", radius=5.0, cor_method="correlation"
|
||||||
|
)
|
||||||
|
all_out = efc.fit_transform({"BOLD": {"data": fmri_img, "meta": {}}})
|
||||||
|
|
||||||
|
out = all_out["BOLD"]
|
||||||
|
|
||||||
|
# There are six DMNBuckner coordinates, so
|
||||||
|
# for 6 ROIs we should get (6 * (6 -1) / 2) edges in the ETS
|
||||||
|
n_edges = int(6 * (6 - 1) / 2)
|
||||||
|
assert "data" in out
|
||||||
|
assert "row_names" in out
|
||||||
|
assert "col_names" in out
|
||||||
|
assert out["data"].shape[0] == n_edges
|
||||||
|
assert out["data"].shape[1] == n_edges
|
||||||
|
assert len(set(out["row_names"])) == n_edges
|
||||||
|
assert len(set(out["col_names"])) == n_edges
|
||||||
|
|
||||||
|
# check correct output
|
||||||
|
assert efc.get_output_type("BOLD") == "matrix"
|
||||||
|
|
||||||
|
# Check empirical correlation method parameters
|
||||||
|
efc = EdgeCentricFCSpheres(
|
||||||
|
coords="DMNBuckner",
|
||||||
|
radius=5.0,
|
||||||
|
cor_method="correlation",
|
||||||
|
cor_method_params={"empirical": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
"element": {"subject": "sub001"},
|
||||||
|
"dependencies": {"nilearn"},
|
||||||
|
}
|
||||||
|
all_out = efc.fit_transform({"BOLD": {"data": fmri_img, "meta": meta}})
|
||||||
|
|
||||||
|
uri = tmp_path / "test_fc_parcellation.sqlite"
|
||||||
|
# Single storage, must be the uri
|
||||||
|
storage = SQLiteFeatureStorage(uri=uri, upsert="ignore")
|
||||||
|
meta = {"element": {"subject": "test"}, "dependencies": {"numpy"}}
|
||||||
|
input = {"BOLD": {"data": fmri_img, "meta": meta}}
|
||||||
|
all_out = efc.fit_transform(input, storage=storage)
|
||||||
|
|
||||||
|
features = storage.list_features()
|
||||||
|
assert any(
|
||||||
|
x["name"] == "BOLD_EdgeCentricFCSpheres" for x in features.values()
|
||||||
|
)
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
# License: AGPL
|
# License: AGPL
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
from junifer.markers.utils import _ets
|
from junifer.markers.utils import _ets
|
||||||
|
|
||||||
|
|
@ -25,5 +26,24 @@ def test_ets() -> None:
|
||||||
n_time, n_rois = bold_ts.shape
|
n_time, n_rois = bold_ts.shape
|
||||||
n_edges = int(n_rois * (n_rois - 1) / 2)
|
n_edges = int(n_rois * (n_rois - 1) / 2)
|
||||||
|
|
||||||
|
# test without labels
|
||||||
edge_ts = _ets(bold_ts)
|
edge_ts = _ets(bold_ts)
|
||||||
assert edge_ts.shape == (n_time, n_edges)
|
assert edge_ts.shape == (n_time, n_edges)
|
||||||
|
|
||||||
|
# test with labels
|
||||||
|
roi_labels = [f"Label_{x}" for x in range(n_rois)]
|
||||||
|
edge_ts, edge_labels = _ets(bold_ts, roi_labels)
|
||||||
|
assert edge_ts.shape == (n_time, n_edges)
|
||||||
|
assert len(edge_labels) == n_edges
|
||||||
|
|
||||||
|
|
||||||
|
def test_ets_incorrect_label_list() -> None:
|
||||||
|
"""Test edge-wise timeseries computing function with incorrect labels."""
|
||||||
|
bold_ts = np.arange(30).reshape(10, 3)
|
||||||
|
# one label is missing, the bold time series suggests three ROIs
|
||||||
|
roi_labels = ["label 1", "label 2"]
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError, match="List of roi names does not correspond"
|
||||||
|
):
|
||||||
|
_ets(bold_ts, roi_labels)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
# Federico Raimondo <f.raimondo@fz-juelich.de>
|
# Federico Raimondo <f.raimondo@fz-juelich.de>
|
||||||
# License: AGPL
|
# License: AGPL
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, Type, Union
|
from typing import Any, Callable, Dict, List, Type, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
@ -55,7 +55,9 @@ def singleton(cls: Type) -> Type:
|
||||||
return get_instance
|
return get_instance
|
||||||
|
|
||||||
|
|
||||||
def _ets(bold_ts: np.ndarray) -> np.ndarray:
|
def _ets(
|
||||||
|
bold_ts: np.ndarray, roi_names: Union[None, List[str]] = None
|
||||||
|
) -> np.ndarray:
|
||||||
"""Compute the edge-wise time series based on BOLD time series.
|
"""Compute the edge-wise time series based on BOLD time series.
|
||||||
|
|
||||||
Take a timeseries of brain areas, and calculate timeseries for each
|
Take a timeseries of brain areas, and calculate timeseries for each
|
||||||
|
|
@ -66,12 +68,21 @@ def _ets(bold_ts: np.ndarray) -> np.ndarray:
|
||||||
----------
|
----------
|
||||||
bold_ts : np.ndarray
|
bold_ts : np.ndarray
|
||||||
BOLD time series (time x ROIs)
|
BOLD time series (time x ROIs)
|
||||||
|
roi_names : List[str] or None
|
||||||
|
List containing the names of the ROIs.
|
||||||
|
Order of the ROI names should correspond to order of the columns
|
||||||
|
in bold_ts. If None (default), only the edge-wise time series are
|
||||||
|
returned, without corresponding edge labels.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
np.ndarray
|
ets : np.ndarray
|
||||||
edge-wise time series, i.e. estimate of functional connectivity at each
|
edge-wise time series, i.e. estimate of functional connectivity at each
|
||||||
time point.
|
time point.
|
||||||
|
edge_names : List[str]
|
||||||
|
List of edge names corresponding to columns in the
|
||||||
|
edge-wise time series. This is only returned if the roi_names
|
||||||
|
are specified.
|
||||||
|
|
||||||
References
|
References
|
||||||
----------
|
----------
|
||||||
|
|
@ -88,7 +99,21 @@ def _ets(bold_ts: np.ndarray) -> np.ndarray:
|
||||||
# indices of unique edges (lower triangle)
|
# indices of unique edges (lower triangle)
|
||||||
u, v = np.tril_indices(n_roi, k=-1)
|
u, v = np.tril_indices(n_roi, k=-1)
|
||||||
# Compute the ETS
|
# Compute the ETS
|
||||||
return timeseries[:, u] * timeseries[:, v]
|
ets = timeseries[:, u] * timeseries[:, v]
|
||||||
|
# Obtain the corresponding edge labels if specified else return
|
||||||
|
if roi_names is None:
|
||||||
|
return ets
|
||||||
|
else:
|
||||||
|
if len(roi_names) != n_roi:
|
||||||
|
raise_error(
|
||||||
|
"List of roi names does not correspond "
|
||||||
|
"to the number of ROIs in the timeseries!"
|
||||||
|
)
|
||||||
|
roi_names = np.array(roi_names)
|
||||||
|
edge_names = [
|
||||||
|
"~".join([x, y]) for x, y in zip(roi_names[u], roi_names[v])
|
||||||
|
]
|
||||||
|
return ets, list(edge_names)
|
||||||
|
|
||||||
|
|
||||||
def _correlate_dataframes(
|
def _correlate_dataframes(
|
||||||
|
|
|
||||||
@LeSasse Is there any related publication explaining the method?