From a68e84c07d772277ce649cf8a12ec520eac449cd Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 11 Mar 2025 11:27:10 +0100 Subject: [PATCH 01/10] feat: add FunctionalConnectivity{Parcels,Spheres} markers --- junifer/markers/__init__.pyi | 4 + .../functional_connectivity/__init__.pyi | 8 + .../functional_connectivity_lagged_base.py | 168 ++++++++++++++++++ .../functional_connectivity_lagged_parcels.py | 102 +++++++++++ .../functional_connectivity_lagged_spheres.py | 119 +++++++++++++ .../pipeline/pipeline_component_registry.py | 6 + 6 files changed, 407 insertions(+) create mode 100644 junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py create mode 100644 junifer/markers/functional_connectivity/functional_connectivity_lagged_parcels.py create mode 100644 junifer/markers/functional_connectivity/functional_connectivity_lagged_spheres.py diff --git a/junifer/markers/__init__.pyi b/junifer/markers/__init__.pyi index 73462565fe..3462f1bf77 100644 --- a/junifer/markers/__init__.pyi +++ b/junifer/markers/__init__.pyi @@ -5,6 +5,8 @@ __all__ = [ "SphereAggregation", "FunctionalConnectivityParcels", "FunctionalConnectivitySpheres", + "FunctionalConnectivityLaggedParcels", + "FunctionalConnectivityLaggedSpheres", "CrossParcellationFC", "EdgeCentricFCParcels", "EdgeCentricFCSpheres", @@ -24,6 +26,8 @@ from .sphere_aggregation import SphereAggregation from .functional_connectivity import ( FunctionalConnectivityParcels, FunctionalConnectivitySpheres, + FunctionalConnectivityLaggedParcels, + FunctionalConnectivityLaggedSpheres, CrossParcellationFC, EdgeCentricFCParcels, EdgeCentricFCSpheres, diff --git a/junifer/markers/functional_connectivity/__init__.pyi b/junifer/markers/functional_connectivity/__init__.pyi index dd7790d35a..0d212fd972 100644 --- a/junifer/markers/functional_connectivity/__init__.pyi +++ b/junifer/markers/functional_connectivity/__init__.pyi @@ -1,6 +1,8 @@ __all__ = [ "FunctionalConnectivityParcels", "FunctionalConnectivitySpheres", + "FunctionalConnectivityLaggedParcels", + "FunctionalConnectivityLaggedSpheres", "CrossParcellationFC", "EdgeCentricFCParcels", "EdgeCentricFCSpheres", @@ -8,6 +10,12 @@ __all__ = [ from .functional_connectivity_parcels import FunctionalConnectivityParcels from .functional_connectivity_spheres import FunctionalConnectivitySpheres +from .functional_connectivity_lagged_parcels import ( + FunctionalConnectivityLaggedParcels, +) +from .functional_connectivity_lagged_spheres import ( + FunctionalConnectivityLaggedSpheres, +) from .crossparcellation_functional_connectivity import CrossParcellationFC from .edge_functional_connectivity_parcels import EdgeCentricFCParcels from .edge_functional_connectivity_spheres import EdgeCentricFCSpheres diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py new file mode 100644 index 0000000000..12e9661da3 --- /dev/null +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py @@ -0,0 +1,168 @@ +"""Provide abstract base class for lagged functional connectivity (FC).""" + +# Authors: Synchon Mandal +# License: AGPL + + +from abc import abstractmethod +from itertools import product +from typing import Any, ClassVar, Optional, Union + +import numpy as np +from scipy.signal import correlate + +from ...typing import Dependencies, MarkerInOutMappings +from ...utils import raise_error +from ..base import BaseMarker + + +__all__ = ["FunctionalConnectivityLaggedBase"] + + +class FunctionalConnectivityLaggedBase(BaseMarker): + """Abstract base class for lagged functional connectivity markers. + + Parameters + ---------- + max_lag : int + The time lag range. The lag ranges from ``-max_lag`` to ``+max_lag`` + time points. + agg_method : str, optional + The method to perform aggregation using. + Check valid options in :func:`.get_aggfunc_by_name` + (default "mean"). + agg_method_params : dict, optional + Parameters to pass to the aggregation function. + Check valid options in :func:`.get_aggfunc_by_name` + (default None). + masks : str, dict or list of dict or str, optional + The specification of the masks to apply to regions before extracting + signals. Check :ref:`Using Masks ` for more details. + If None, will not apply any mask (default None). + name : str, optional + The name of the marker. If None, will use ``BOLD_`` + (default None). + + """ + + _DEPENDENCIES: ClassVar[Dependencies] = {"numpy", "scipy"} + + _MARKER_INOUT_MAPPINGS: ClassVar[MarkerInOutMappings] = { + "BOLD": { + "functional_connectivity": "matrix", + "lag": "matrix", + }, + } + + def __init__( + self, + max_lag: int, + agg_method: str = "mean", + agg_method_params: Optional[dict] = None, + masks: Union[str, dict, list[Union[dict, str]], None] = None, + name: Optional[str] = None, + ) -> None: + self.max_lag = max_lag + self.agg_method = agg_method + self.agg_method_params = agg_method_params + self.masks = masks + super().__init__(on="BOLD", name=name) + + @abstractmethod + def aggregate( + self, + input: dict[str, Any], + extra_input: Optional[dict[str, Any]] = None, + ) -> dict[str, Any]: + """Perform aggregation.""" + raise_error( + msg="Concrete classes need to implement aggregate().", + klass=NotImplementedError, + ) + + def compute( + self, + input: dict[str, Any], + extra_input: Optional[dict] = None, + ) -> dict: + """Compute. + + Parameters + ---------- + input : dict + A single input from the pipeline data object in which to compute + the marker. + extra_input : dict, optional + The other fields in the pipeline data object. Useful for accessing + other data kind that needs to be used in the computation. For + example, the functional connectivity markers can make use of the + confounds if available (default None). + + Returns + ------- + dict + The computed result as dictionary. This will be either returned + to the user or stored in the storage by calling the store method + with this as a parameter. The dictionary has the following keys: + + * ``functional_connectivity`` : dictionary with the following keys: + + - ``data`` : functional connectivity matrix as ``numpy.ndarray`` + - ``row_names`` : ROI labels as list of str + - ``col_names`` : ROI labels as list of str + - ``matrix_kind`` : the kind of matrix (tril, triu or full) + + * ``lag`` : dictionary with the following keys: + + - ``data`` : lag matrix as ``numpy.ndarray`` + - ``row_names`` : ROI labels as list of str + - ``col_names`` : ROI labels as list of str + - ``matrix_kind`` : the kind of matrix (tril, triu or full) + + """ + # Perform necessary aggregation + aggregation = self.aggregate(input, extra_input=extra_input) + # Initialize variables + # transposed to get (n_rois, n_timepoints) + fmri_data = aggregation["aggregation"]["data"].T + n_rois = fmri_data.shape[0] + fc_matrix = np.zeros((n_rois, n_rois)) + lag_matrix = np.zeros((n_rois, n_rois)) + lags = np.arange(-self.max_lag, self.max_lag + 1) + # Compute + for i, j in product(range(n_rois), range(n_rois)): + if i != j: + # Compute cross-correlation + ccf = correlate( + fmri_data[i], fmri_data[j], mode="full", method="auto" + ) + ccf = ccf[ + len(ccf) // 2 + - self.max_lag : len(ccf) // 2 + + self.max_lag + + 1 + ] # Limit lag range + + # Find peak correlation and corresponding lag + peak_idx = np.argmax(np.abs(ccf)) # Peak correlation index + peak_corr = ccf[peak_idx] # Peak correlation value + peak_lag = lags[peak_idx] # Corresponding lag + # Store in matrices + fc_matrix[i, j] = peak_corr + lag_matrix[i, j] = peak_lag + # Create dictionary for output + roi_labels = aggregation["aggregation"]["col_names"] + return { + "functional_connectivity": { + "data": fc_matrix, + "row_names": roi_labels, + "col_names": roi_labels, + "matrix_kind": "full", + }, + "lag": { + "data": lag_matrix, + "row_names": roi_labels, + "col_names": roi_labels, + "matrix_kind": "full", + }, + } diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_parcels.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_parcels.py new file mode 100644 index 0000000000..7a16f3c661 --- /dev/null +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_parcels.py @@ -0,0 +1,102 @@ +"""Provide class for lagged functional connectivity using parcels.""" + +# Authors: Synchon Mandal +# License: AGPL + + +from typing import Any, Optional, Union + +from ...api.decorators import register_marker +from ..parcel_aggregation import ParcelAggregation +from .functional_connectivity_lagged_base import ( + FunctionalConnectivityLaggedBase, +) + + +__all__ = ["FunctionalConnectivityLaggedParcels"] + + +@register_marker +class FunctionalConnectivityLaggedParcels(FunctionalConnectivityLaggedBase): + """Class for lagged functional connectivity using parcellations. + + Parameters + ---------- + parcellation : str or list of str + The name(s) of the parcellation(s) to use. + See :func:`.list_data` for options. + max_lag : int + The time lag range. The lag ranges from ``-max_lag`` to ``+max_lag`` + time points. + agg_method : str, optional + The method to perform aggregation using. + See :func:`.get_aggfunc_by_name` for options + (default "mean"). + agg_method_params : dict, optional + Parameters to pass to the aggregation function. + See :func:`.get_aggfunc_by_name` for options + (default None). + masks : str, dict or list of dict or str, optional + The specification of the masks to apply to regions before extracting + signals. Check :ref:`Using Masks ` for more details. + If None, will not apply any mask (default None). + name : str, optional + The name of the marker. If None, will use + ``BOLD_FunctionalConnectivityLaggedParcels`` (default None). + + """ + + def __init__( + self, + parcellation: Union[str, list[str]], + max_lag: int, + agg_method: str = "mean", + agg_method_params: Optional[dict] = None, + masks: Union[str, dict, list[Union[dict, str]], None] = None, + name: Optional[str] = None, + ) -> None: + self.parcellation = parcellation + super().__init__( + max_lag=max_lag, + agg_method=agg_method, + agg_method_params=agg_method_params, + masks=masks, + name=name, + ) + + def aggregate( + self, input: dict[str, Any], extra_input: Optional[dict] = None + ) -> dict: + """Perform parcel aggregation. + + Parameters + ---------- + input : dict + A single input from the pipeline data object in which to compute + the marker. + extra_input : dict, optional + The other fields in the pipeline data object. Useful for accessing + other data kind that needs to be used in the computation. For + example, the functional connectivity markers can make use of the + confounds if available (default None). + + Returns + ------- + dict + The computed result as dictionary. This will be either returned + to the user or stored in the storage by calling the store method + with this as a parameter. The dictionary has the following keys: + + * ``aggregation`` : dictionary with the following keys: + + - ``data`` : ROI values as ``numpy.ndarray`` + - ``col_names`` : ROI labels as list of str + + """ + return ParcelAggregation( + parcellation=self.parcellation, + method=self.agg_method, + method_params=self.agg_method_params, + masks=self.masks, + on="BOLD", + ).compute(input=input, extra_input=extra_input) diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_spheres.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_spheres.py new file mode 100644 index 0000000000..19c2267a51 --- /dev/null +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_spheres.py @@ -0,0 +1,119 @@ +"""Provide class for lagged functional connectivity using spheres.""" + +# Authors: Synchon Mandal +# License: AGPL + + +from typing import Any, Optional, Union + +from ...api.decorators import register_marker +from ..sphere_aggregation import SphereAggregation +from ..utils import raise_error +from .functional_connectivity_lagged_base import ( + FunctionalConnectivityLaggedBase, +) + + +__all__ = ["FunctionalConnectivityLaggedSpheres"] + + +@register_marker +class FunctionalConnectivityLaggedSpheres(FunctionalConnectivityLaggedBase): + """Class for lagged functional connectivity using coordinates (spheres). + + Parameters + ---------- + coords : str + The name of the coordinates list to use. + See :func:`.list_data` for options. + radius : positive float, optional + The radius of the sphere around each coordinates in millimetres. + If None, the signal will be extracted from a single voxel. + See :class:`.JuniferNiftiSpheresMasker` for more information + (default None). + allow_overlap : bool, optional + Whether to allow overlapping spheres. If False, an error is raised if + the spheres overlap (default False). + max_lag : int + The time lag range. The lag ranges from ``-max_lag`` to ``+max_lag`` + time points. + agg_method : str, optional + The method to perform aggregation using. + See :func:`.get_aggfunc_by_name` for options + (default "mean"). + agg_method_params : dict, optional + Parameters to pass to the aggregation function. + See :func:`.get_aggfunc_by_name` for options + (default None). + masks : str, dict or list of dict or str, optional + The specification of the masks to apply to regions before extracting + signals. Check :ref:`Using Masks ` for more details. + If None, will not apply any mask (default None). + name : str, optional + The name of the marker. If None, will use + ``BOLD_FunctionalConnectivityLaggedSpheres`` (default None). + + """ + + def __init__( + self, + coords: str, + max_lag: int, + radius: Optional[float] = None, + allow_overlap: bool = False, + agg_method: str = "mean", + agg_method_params: Optional[dict] = None, + masks: Union[str, dict, list[Union[dict, str]], None] = None, + name: Optional[str] = None, + ) -> None: + self.coords = coords + self.radius = radius + self.allow_overlap = allow_overlap + if radius is None or radius <= 0: + raise_error(f"radius should be > 0: provided {radius}") + super().__init__( + max_lag=max_lag, + agg_method=agg_method, + agg_method_params=agg_method_params, + masks=masks, + name=name, + ) + + def aggregate( + self, input: dict[str, Any], extra_input: Optional[dict] = None + ) -> dict: + """Perform sphere aggregation. + + Parameters + ---------- + input : dict + A single input from the pipeline data object in which to compute + the marker. + extra_input : dict, optional + The other fields in the pipeline data object. Useful for accessing + other data kind that needs to be used in the computation. For + example, the functional connectivity markers can make use of the + confounds if available (default None). + + Returns + ------- + dict + The computed result as dictionary. This will be either returned + to the user or stored in the storage by calling the store method + with this as a parameter. The dictionary has the following keys: + + * ``aggregation`` : dictionary with the following keys: + + - ``data`` : ROI values as ``numpy.ndarray`` + - ``col_names`` : ROI labels as list of str + + """ + return SphereAggregation( + coords=self.coords, + radius=self.radius, + allow_overlap=self.allow_overlap, + method=self.agg_method, + method_params=self.agg_method_params, + masks=self.masks, + on="BOLD", + ).compute(input=input, extra_input=extra_input) diff --git a/junifer/pipeline/pipeline_component_registry.py b/junifer/pipeline/pipeline_component_registry.py index 55a8fa31ad..a0d1a602cd 100644 --- a/junifer/pipeline/pipeline_component_registry.py +++ b/junifer/pipeline/pipeline_component_registry.py @@ -91,6 +91,12 @@ def __init__(self) -> None: "FunctionalConnectivitySpheres": ( "FunctionalConnectivitySpheres" ), + "FunctionalConnectivityLaggedParcels": ( + "FunctionalConnectivityLaggedParcels" + ), + "FunctionalConnectivityLaggedSpheres": ( + "FunctionalConnectivityLaggedSpheres" + ), "ParcelAggregation": "ParcelAggregation", "ReHoParcels": "ReHoParcels", "ReHoSpheres": "ReHoSpheres", From ed4570eec4d586e02cc7e6f4d2b22183d442738e Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 11 Mar 2025 13:35:11 +0100 Subject: [PATCH 02/10] update: add tests for FunctionalConnectivity{Parcels,Spheres} --- ..._functional_connectivity_lagged_parcels.py | 72 ++++++++++++++++ ..._functional_connectivity_lagged_spheres.py | 83 +++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_parcels.py create mode 100644 junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_spheres.py diff --git a/junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_parcels.py b/junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_parcels.py new file mode 100644 index 0000000000..975e25a318 --- /dev/null +++ b/junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_parcels.py @@ -0,0 +1,72 @@ +"""Provide tests for lagged functional connectivity using parcels.""" + +# Authors: Synchon Mandal +# License: AGPL + +from pathlib import Path + +from junifer.datareader import DefaultDataReader +from junifer.markers.functional_connectivity import ( + FunctionalConnectivityLaggedParcels, +) +from junifer.storage import HDF5FeatureStorage +from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber + + +def test_FunctionalConnectivityLaggedParcels( + tmp_path: Path, +) -> None: + """Test FunctionalConnectivityLaggedParcels. + + Parameters + ---------- + tmp_path : pathlib.Path + The path to the test directory. + + """ + with PartlyCloudyTestingDataGrabber() as dg: + # Get element data + element_data = DefaultDataReader().fit_transform(dg["sub-01"]) + # Setup marker + marker = FunctionalConnectivityLaggedParcels( + parcellation="TianxS1x3TxMNInonlinear2009cAsym", + max_lag=10, + ) + # Check correct outputs + assert "matrix" == marker.get_output_type( + input_type="BOLD", output_feature="functional_connectivity" + ) + assert "matrix" == marker.get_output_type( + input_type="BOLD", output_feature="lag" + ) + + # Fit-transform the data and check + lagged_fc = marker.fit_transform(element_data) + + for feature in ("functional_connectivity", "lag"): + lagged_fc_bold = lagged_fc["BOLD"][feature] + + assert "data" in lagged_fc_bold + assert "row_names" in lagged_fc_bold + assert "col_names" in lagged_fc_bold + assert lagged_fc_bold["data"].shape == (16, 16) + assert len(set(lagged_fc_bold["row_names"])) == 16 + assert len(set(lagged_fc_bold["col_names"])) == 16 + + # Store + storage = HDF5FeatureStorage( + uri=tmp_path / "test_lagged_fc_parcels.hdf5", + ) + marker.fit_transform(input=element_data, storage=storage) + features = storage.list_features() + assert all( + x["name"] + in ( + ( + "BOLD_FunctionalConnectivityLaggedParcels_" + "functional_connectivity" + ), + "BOLD_FunctionalConnectivityLaggedParcels_lag", + ) + for x in features.values() + ) diff --git a/junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_spheres.py b/junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_spheres.py new file mode 100644 index 0000000000..c26463713f --- /dev/null +++ b/junifer/markers/functional_connectivity/tests/test_functional_connectivity_lagged_spheres.py @@ -0,0 +1,83 @@ +"""Provide tests for lagged functional connectivity using spheres.""" + +# Authors: Synchon Mandal +# License: AGPL + +from pathlib import Path + +import pytest + +from junifer.datareader import DefaultDataReader +from junifer.markers.functional_connectivity import ( + FunctionalConnectivityLaggedSpheres, +) +from junifer.storage import HDF5FeatureStorage +from junifer.testing.datagrabbers import SPMAuditoryTestingDataGrabber + + +def test_FunctionalConnectivitySpheres( + tmp_path: Path, +) -> None: + """Test FunctionalConnectivityLaggedSpheres. + + Parameters + ---------- + tmp_path : pathlib.Path + The path to the test directory. + + """ + with SPMAuditoryTestingDataGrabber() as dg: + # Get element data + element_data = DefaultDataReader().fit_transform(dg["sub001"]) + # Setup marker + marker = FunctionalConnectivityLaggedSpheres( + coords="DMNBuckner", + radius=5.0, + max_lag=10, + ) + # Check correct outputs + assert "matrix" == marker.get_output_type( + input_type="BOLD", output_feature="functional_connectivity" + ) + assert "matrix" == marker.get_output_type( + input_type="BOLD", output_feature="lag" + ) + + # Fit-transform the data + lagged_fc = marker.fit_transform(element_data) + + for feature in ("functional_connectivity", "lag"): + lagged_fc_bold = lagged_fc["BOLD"][feature] + + assert "data" in lagged_fc_bold + assert "row_names" in lagged_fc_bold + assert "col_names" in lagged_fc_bold + assert lagged_fc_bold["data"].shape == (6, 6) + assert len(set(lagged_fc_bold["row_names"])) == 6 + assert len(set(lagged_fc_bold["col_names"])) == 6 + + # Store + storage = HDF5FeatureStorage( + uri=tmp_path / "test_lagged_fc_spheres.hdf5", + ) + marker.fit_transform(input=element_data, storage=storage) + features = storage.list_features() + assert all( + x["name"] + in ( + ( + "BOLD_FunctionalConnectivityLaggedSpheres_" + "functional_connectivity" + ), + "BOLD_FunctionalConnectivityLaggedSpheres_lag", + ) + for x in features.values() + ) + + +def test_FunctionalConnectivityLaggedSpheres_error() -> None: + """Test FunctionalConnectivityLaggedSpheres errors.""" + with pytest.raises(ValueError, match="radius should be > 0"): + FunctionalConnectivityLaggedSpheres( + coords="DMNBuckner", radius=-0.1, max_lag=10 + ) From d08fee3993c67e04506ad2de750cf3a5cb6c5be3 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 11 Mar 2025 13:35:55 +0100 Subject: [PATCH 03/10] chore: add changelog 435.feature --- docs/changes/newsfragments/435.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/435.feature diff --git a/docs/changes/newsfragments/435.feature b/docs/changes/newsfragments/435.feature new file mode 100644 index 0000000000..f1f4c9cfca --- /dev/null +++ b/docs/changes/newsfragments/435.feature @@ -0,0 +1 @@ +Add support for :class:`.FunctionalConnectivityLaggedParcels` and :class:`.FunctionalConnectivityLaggedSpheres` markers by `Synchon Mandal`_ From 7016bc5f68a042aff343d1c8771fe34544b5c6fc Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 11 Mar 2025 13:36:12 +0100 Subject: [PATCH 04/10] docs: update builtin.rst --- docs/builtin.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/builtin.rst b/docs/builtin.rst index e802ab47b0..1dda0ee9bb 100644 --- a/docs/builtin.rst +++ b/docs/builtin.rst @@ -292,6 +292,14 @@ Available | `Richman et al. (2000) `_ - Done - 0.0.4 + * - :class:`.FunctionalConnectivityLaggedParcels` + - Compute lagged functional connectivity over parcellation + - Done + - 0.0.6 + * - :class:`.FunctionalConnectivityLaggedSpheres` + - Compute lagged functional connectivity over spheres placed on coordinates + - Done + - 0.0.6 Planned ~~~~~~~ From 718fae3e144ed652d7a05802cea36f03b46115ca Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 14 Mar 2025 11:19:49 +0100 Subject: [PATCH 05/10] chore: add notes in FunctionalConnectivityLaggedBase docstring --- .../functional_connectivity_lagged_base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py index 12e9661da3..132aabbeb0 100644 --- a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py @@ -119,6 +119,10 @@ def compute( - ``col_names`` : ROI labels as list of str - ``matrix_kind`` : the kind of matrix (tril, triu or full) + Notes + ----- + Pearson correlation is used to perform connectivity measure. + """ # Perform necessary aggregation aggregation = self.aggregate(input, extra_input=extra_input) From 130fc3b7d83863af49df110a3aca531563dc7603 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 14 Mar 2025 11:20:32 +0100 Subject: [PATCH 06/10] update: make lag_matrix dtype int in FunctionalConnectivityLaggedBase --- .../functional_connectivity_lagged_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py index 132aabbeb0..cc9982b8ba 100644 --- a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py @@ -131,7 +131,7 @@ def compute( fmri_data = aggregation["aggregation"]["data"].T n_rois = fmri_data.shape[0] fc_matrix = np.zeros((n_rois, n_rois)) - lag_matrix = np.zeros((n_rois, n_rois)) + lag_matrix = np.zeros((n_rois, n_rois), dtype=int) lags = np.arange(-self.max_lag, self.max_lag + 1) # Compute for i, j in product(range(n_rois), range(n_rois)): From 896b1e449b59bf29f4639505e9b8dad41ef191a8 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 14 Mar 2025 11:20:56 +0100 Subject: [PATCH 07/10] update: set fc_matrix to 1 initially in FunctionalConnectivityLaggedBase --- .../functional_connectivity_lagged_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py index cc9982b8ba..25016bb68e 100644 --- a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py @@ -130,7 +130,7 @@ def compute( # transposed to get (n_rois, n_timepoints) fmri_data = aggregation["aggregation"]["data"].T n_rois = fmri_data.shape[0] - fc_matrix = np.zeros((n_rois, n_rois)) + fc_matrix = np.ones((n_rois, n_rois)) lag_matrix = np.zeros((n_rois, n_rois), dtype=int) lags = np.arange(-self.max_lag, self.max_lag + 1) # Compute From f57397d9e2d4ee4cb151a0c34bb3cd711f25fdc8 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 14 Mar 2025 11:21:36 +0100 Subject: [PATCH 08/10] update: make matrix_kind tril for both features of FunctionalConnectivityLaggedBase --- .../functional_connectivity_lagged_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py index 25016bb68e..445e2996d7 100644 --- a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py @@ -161,12 +161,12 @@ def compute( "data": fc_matrix, "row_names": roi_labels, "col_names": roi_labels, - "matrix_kind": "full", + "matrix_kind": "tril", }, "lag": { "data": lag_matrix, "row_names": roi_labels, "col_names": roi_labels, - "matrix_kind": "full", + "matrix_kind": "tril", }, } From 82631abf4a30156f465dfe6c979a0f188aec7b9a Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 17 Mar 2025 18:06:22 +0100 Subject: [PATCH 09/10] update: make lag_matrix store full matrix in FunctionalConnectivtyLaggedBase --- .../functional_connectivity_lagged_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py index 445e2996d7..65873b7a63 100644 --- a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py @@ -167,6 +167,6 @@ def compute( "data": lag_matrix, "row_names": roi_labels, "col_names": roi_labels, - "matrix_kind": "tril", + "matrix_kind": "full", }, } From 6aed9164f5228d9de2cba32613e2d7ec050a32f7 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 17 Mar 2025 18:09:34 +0100 Subject: [PATCH 10/10] fix: normalise cross correlation function in FunctionalConnectivityLaggedBase --- .../functional_connectivity_lagged_base.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py index 65873b7a63..dc4cc978f9 100644 --- a/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py +++ b/junifer/markers/functional_connectivity/functional_connectivity_lagged_base.py @@ -136,17 +136,19 @@ def compute( # Compute for i, j in product(range(n_rois), range(n_rois)): if i != j: - # Compute cross-correlation - ccf = correlate( - fmri_data[i], fmri_data[j], mode="full", method="auto" - ) + x = fmri_data[i] + y = fmri_data[j] + # Compute cross-correlation function (CCF) + ccf = correlate(x, y, mode="full", method="auto") + # Normalize CCF + ccf /= np.sqrt(np.sum(np.abs(x**2)) * np.sum(np.abs(y**2))) + # Limit lag range ccf = ccf[ len(ccf) // 2 - self.max_lag : len(ccf) // 2 + self.max_lag + 1 - ] # Limit lag range - + ] # Find peak correlation and corresponding lag peak_idx = np.argmax(np.abs(ccf)) # Peak correlation index peak_corr = ccf[peak_idx] # Peak correlation value