junifer 0.0.3.dev186__py3-none-any.whl → 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- junifer/_version.py +14 -2
- junifer/api/cli.py +162 -17
- junifer/api/functions.py +87 -419
- junifer/api/parser.py +24 -0
- junifer/api/queue_context/__init__.py +8 -0
- junifer/api/queue_context/gnu_parallel_local_adapter.py +258 -0
- junifer/api/queue_context/htcondor_adapter.py +365 -0
- junifer/api/queue_context/queue_context_adapter.py +60 -0
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +192 -0
- junifer/api/queue_context/tests/test_htcondor_adapter.py +257 -0
- junifer/api/res/afni/run_afni_docker.sh +6 -6
- junifer/api/res/ants/ResampleImage +3 -0
- junifer/api/res/ants/antsApplyTransforms +3 -0
- junifer/api/res/ants/antsApplyTransformsToPoints +3 -0
- junifer/api/res/ants/run_ants_docker.sh +39 -0
- junifer/api/res/fsl/applywarp +3 -0
- junifer/api/res/fsl/flirt +3 -0
- junifer/api/res/fsl/img2imgcoord +3 -0
- junifer/api/res/fsl/run_fsl_docker.sh +39 -0
- junifer/api/res/fsl/std2imgcoord +3 -0
- junifer/api/res/run_conda.sh +4 -4
- junifer/api/res/run_venv.sh +22 -0
- junifer/api/tests/data/partly_cloudy_agg_mean_tian.yml +16 -0
- junifer/api/tests/test_api_utils.py +21 -3
- junifer/api/tests/test_cli.py +232 -9
- junifer/api/tests/test_functions.py +211 -439
- junifer/api/tests/test_parser.py +1 -1
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +8 -8
- junifer/configs/juseless/datagrabbers/ucla.py +44 -26
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +6 -1
- junifer/data/VOIs/meta/AutobiographicalMemory_VOIs.txt +23 -0
- junifer/data/VOIs/meta/Power2013_MNI_VOIs.tsv +264 -0
- junifer/data/__init__.py +4 -0
- junifer/data/coordinates.py +298 -31
- junifer/data/masks.py +360 -28
- junifer/data/parcellations.py +621 -188
- junifer/data/template_spaces.py +190 -0
- junifer/data/tests/test_coordinates.py +34 -3
- junifer/data/tests/test_data_utils.py +1 -0
- junifer/data/tests/test_masks.py +202 -86
- junifer/data/tests/test_parcellations.py +266 -55
- junifer/data/tests/test_template_spaces.py +104 -0
- junifer/data/utils.py +4 -2
- junifer/datagrabber/__init__.py +1 -0
- junifer/datagrabber/aomic/id1000.py +111 -70
- junifer/datagrabber/aomic/piop1.py +116 -53
- junifer/datagrabber/aomic/piop2.py +116 -53
- junifer/datagrabber/aomic/tests/test_id1000.py +27 -27
- junifer/datagrabber/aomic/tests/test_piop1.py +27 -27
- junifer/datagrabber/aomic/tests/test_piop2.py +27 -27
- junifer/datagrabber/base.py +62 -10
- junifer/datagrabber/datalad_base.py +0 -2
- junifer/datagrabber/dmcc13_benchmark.py +372 -0
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +5 -0
- junifer/datagrabber/hcp1200/hcp1200.py +30 -13
- junifer/datagrabber/pattern.py +133 -27
- junifer/datagrabber/pattern_datalad.py +111 -13
- junifer/datagrabber/tests/test_base.py +57 -6
- junifer/datagrabber/tests/test_datagrabber_utils.py +204 -76
- junifer/datagrabber/tests/test_datalad_base.py +0 -6
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +256 -0
- junifer/datagrabber/tests/test_multiple.py +43 -10
- junifer/datagrabber/tests/test_pattern.py +125 -178
- junifer/datagrabber/tests/test_pattern_datalad.py +44 -25
- junifer/datagrabber/utils.py +151 -16
- junifer/datareader/default.py +36 -10
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +6 -0
- junifer/markers/base.py +25 -16
- junifer/markers/collection.py +35 -16
- junifer/markers/complexity/__init__.py +27 -0
- junifer/markers/complexity/complexity_base.py +149 -0
- junifer/markers/complexity/hurst_exponent.py +136 -0
- junifer/markers/complexity/multiscale_entropy_auc.py +140 -0
- junifer/markers/complexity/perm_entropy.py +132 -0
- junifer/markers/complexity/range_entropy.py +136 -0
- junifer/markers/complexity/range_entropy_auc.py +145 -0
- junifer/markers/complexity/sample_entropy.py +134 -0
- junifer/markers/complexity/tests/test_complexity_base.py +19 -0
- junifer/markers/complexity/tests/test_hurst_exponent.py +69 -0
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +68 -0
- junifer/markers/complexity/tests/test_perm_entropy.py +68 -0
- junifer/markers/complexity/tests/test_range_entropy.py +69 -0
- junifer/markers/complexity/tests/test_range_entropy_auc.py +69 -0
- junifer/markers/complexity/tests/test_sample_entropy.py +68 -0
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +68 -0
- junifer/markers/complexity/weighted_perm_entropy.py +133 -0
- junifer/markers/falff/_afni_falff.py +153 -0
- junifer/markers/falff/_junifer_falff.py +142 -0
- junifer/markers/falff/falff_base.py +91 -84
- junifer/markers/falff/falff_parcels.py +61 -45
- junifer/markers/falff/falff_spheres.py +64 -48
- junifer/markers/falff/tests/test_falff_parcels.py +89 -121
- junifer/markers/falff/tests/test_falff_spheres.py +92 -127
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +1 -0
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +1 -0
- junifer/markers/functional_connectivity/functional_connectivity_base.py +1 -0
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +46 -44
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +34 -39
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +40 -52
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +62 -70
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +99 -85
- junifer/markers/parcel_aggregation.py +60 -38
- junifer/markers/reho/_afni_reho.py +192 -0
- junifer/markers/reho/_junifer_reho.py +281 -0
- junifer/markers/reho/reho_base.py +69 -34
- junifer/markers/reho/reho_parcels.py +26 -16
- junifer/markers/reho/reho_spheres.py +23 -9
- junifer/markers/reho/tests/test_reho_parcels.py +93 -92
- junifer/markers/reho/tests/test_reho_spheres.py +88 -86
- junifer/markers/sphere_aggregation.py +54 -9
- junifer/markers/temporal_snr/temporal_snr_base.py +1 -0
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +38 -37
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +34 -38
- junifer/markers/tests/test_collection.py +43 -42
- junifer/markers/tests/test_ets_rss.py +29 -37
- junifer/markers/tests/test_parcel_aggregation.py +587 -468
- junifer/markers/tests/test_sphere_aggregation.py +209 -157
- junifer/markers/utils.py +2 -40
- junifer/onthefly/read_transform.py +13 -6
- junifer/pipeline/__init__.py +1 -0
- junifer/pipeline/pipeline_step_mixin.py +105 -41
- junifer/pipeline/registry.py +17 -0
- junifer/pipeline/singleton.py +45 -0
- junifer/pipeline/tests/test_pipeline_step_mixin.py +139 -51
- junifer/pipeline/tests/test_update_meta_mixin.py +1 -0
- junifer/pipeline/tests/test_workdir_manager.py +104 -0
- junifer/pipeline/update_meta_mixin.py +8 -2
- junifer/pipeline/utils.py +154 -15
- junifer/pipeline/workdir_manager.py +246 -0
- junifer/preprocess/__init__.py +3 -0
- junifer/preprocess/ants/__init__.py +4 -0
- junifer/preprocess/ants/ants_apply_transforms_warper.py +185 -0
- junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +56 -0
- junifer/preprocess/base.py +96 -69
- junifer/preprocess/bold_warper.py +265 -0
- junifer/preprocess/confounds/fmriprep_confound_remover.py +91 -134
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +106 -111
- junifer/preprocess/fsl/__init__.py +4 -0
- junifer/preprocess/fsl/apply_warper.py +179 -0
- junifer/preprocess/fsl/tests/test_apply_warper.py +45 -0
- junifer/preprocess/tests/test_bold_warper.py +159 -0
- junifer/preprocess/tests/test_preprocess_base.py +6 -6
- junifer/preprocess/warping/__init__.py +6 -0
- junifer/preprocess/warping/_ants_warper.py +167 -0
- junifer/preprocess/warping/_fsl_warper.py +109 -0
- junifer/preprocess/warping/space_warper.py +213 -0
- junifer/preprocess/warping/tests/test_space_warper.py +198 -0
- junifer/stats.py +18 -4
- junifer/storage/base.py +9 -1
- junifer/storage/hdf5.py +8 -3
- junifer/storage/pandas_base.py +2 -1
- junifer/storage/sqlite.py +1 -0
- junifer/storage/tests/test_hdf5.py +2 -1
- junifer/storage/tests/test_sqlite.py +8 -8
- junifer/storage/tests/test_utils.py +6 -6
- junifer/storage/utils.py +1 -0
- junifer/testing/datagrabbers.py +11 -7
- junifer/testing/utils.py +1 -0
- junifer/tests/test_stats.py +2 -0
- junifer/utils/__init__.py +1 -0
- junifer/utils/helpers.py +53 -0
- junifer/utils/logging.py +14 -3
- junifer/utils/tests/test_helpers.py +35 -0
- {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/METADATA +59 -28
- junifer-0.0.4.dist-info/RECORD +257 -0
- {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/WHEEL +1 -1
- junifer/markers/falff/falff_estimator.py +0 -334
- junifer/markers/falff/tests/test_falff_estimator.py +0 -238
- junifer/markers/reho/reho_estimator.py +0 -515
- junifer/markers/reho/tests/test_reho_estimator.py +0 -260
- junifer-0.0.3.dev186.dist-info/RECORD +0 -199
- {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
"""Provide test for sample entropy."""
|
2
|
+
|
3
|
+
# Authors: Amir Omidvarnia <a.omidvarnia@fz-juelich.de>
|
4
|
+
# Synchon Mandal <s.mandal@fz-juelich.de>
|
5
|
+
# License: AGPL
|
6
|
+
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
import pytest
|
10
|
+
|
11
|
+
|
12
|
+
pytest.importorskip("neurokit2")
|
13
|
+
|
14
|
+
from junifer.datareader import DefaultDataReader # noqa: E402
|
15
|
+
from junifer.markers.complexity import SampleEntropy # noqa: E402
|
16
|
+
from junifer.storage import SQLiteFeatureStorage # noqa: E402
|
17
|
+
from junifer.testing.datagrabbers import ( # noqa: E402
|
18
|
+
SPMAuditoryTestingDataGrabber,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
# Set parcellation
|
23
|
+
PARCELLATION = "Schaefer100x17"
|
24
|
+
|
25
|
+
|
26
|
+
def test_compute() -> None:
|
27
|
+
"""Test SampleEntropy compute()."""
|
28
|
+
with SPMAuditoryTestingDataGrabber() as dg:
|
29
|
+
# Fetch element
|
30
|
+
element = dg["sub001"]
|
31
|
+
# Fetch element data
|
32
|
+
element_data = DefaultDataReader().fit_transform(element)
|
33
|
+
# Initialize the marker
|
34
|
+
marker = SampleEntropy(parcellation=PARCELLATION)
|
35
|
+
# Compute the marker
|
36
|
+
feature_map = marker.fit_transform(element_data)
|
37
|
+
# Assert the dimension of timeseries
|
38
|
+
assert feature_map["BOLD"]["data"].ndim == 2
|
39
|
+
|
40
|
+
|
41
|
+
def test_get_output_type() -> None:
|
42
|
+
"""Test SampleEntropy get_output_type()."""
|
43
|
+
marker = SampleEntropy(parcellation=PARCELLATION)
|
44
|
+
assert marker.get_output_type("BOLD") == "vector"
|
45
|
+
|
46
|
+
|
47
|
+
def test_store(tmp_path: Path) -> None:
|
48
|
+
"""Test SampleEntropy store().
|
49
|
+
|
50
|
+
Parameters
|
51
|
+
----------
|
52
|
+
tmp_path : pathlib.Path
|
53
|
+
The path to the test directory.
|
54
|
+
|
55
|
+
"""
|
56
|
+
with SPMAuditoryTestingDataGrabber() as dg:
|
57
|
+
# Fetch element
|
58
|
+
element = dg["sub001"]
|
59
|
+
# Fetch element data
|
60
|
+
element_data = DefaultDataReader().fit_transform(element)
|
61
|
+
# Initialize the marker
|
62
|
+
marker = SampleEntropy(parcellation=PARCELLATION)
|
63
|
+
# Create storage
|
64
|
+
storage = SQLiteFeatureStorage(
|
65
|
+
uri=tmp_path / "test_sample_entropy.sqlite"
|
66
|
+
)
|
67
|
+
# Compute the marker and store
|
68
|
+
marker.fit_transform(input=element_data, storage=storage)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"""Provide test for weighted permutation entropy."""
|
2
|
+
|
3
|
+
# Authors: Amir Omidvarnia <a.omidvarnia@fz-juelich.de>
|
4
|
+
# Synchon Mandal <s.mandal@fz-juelich.de>
|
5
|
+
# License: AGPL
|
6
|
+
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
import pytest
|
10
|
+
|
11
|
+
|
12
|
+
pytest.importorskip("neurokit2")
|
13
|
+
|
14
|
+
from junifer.datareader import DefaultDataReader # noqa: E402
|
15
|
+
from junifer.markers.complexity import WeightedPermEntropy # noqa: E402
|
16
|
+
from junifer.storage import SQLiteFeatureStorage # noqa: E402
|
17
|
+
from junifer.testing.datagrabbers import ( # noqa: E402
|
18
|
+
SPMAuditoryTestingDataGrabber,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
# Set parcellation
|
23
|
+
PARCELLATION = "Schaefer100x17"
|
24
|
+
|
25
|
+
|
26
|
+
def test_compute() -> None:
|
27
|
+
"""Test WeightedPermEntropy compute()."""
|
28
|
+
with SPMAuditoryTestingDataGrabber() as dg:
|
29
|
+
# Fetch element
|
30
|
+
element = dg["sub001"]
|
31
|
+
# Fetch element data
|
32
|
+
element_data = DefaultDataReader().fit_transform(element)
|
33
|
+
# Initialize the marker
|
34
|
+
marker = WeightedPermEntropy(parcellation=PARCELLATION)
|
35
|
+
# Compute the marker
|
36
|
+
feature_map = marker.fit_transform(element_data)
|
37
|
+
# Assert the dimension of timeseries
|
38
|
+
assert feature_map["BOLD"]["data"].ndim == 2
|
39
|
+
|
40
|
+
|
41
|
+
def test_get_output_type() -> None:
|
42
|
+
"""Test WeightedPermEntropy get_output_type()."""
|
43
|
+
marker = WeightedPermEntropy(parcellation=PARCELLATION)
|
44
|
+
assert marker.get_output_type("BOLD") == "vector"
|
45
|
+
|
46
|
+
|
47
|
+
def test_store(tmp_path: Path) -> None:
|
48
|
+
"""Test WeightedPermEntropy store().
|
49
|
+
|
50
|
+
Parameters
|
51
|
+
----------
|
52
|
+
tmp_path : pathlib.Path
|
53
|
+
The path to the test directory.
|
54
|
+
|
55
|
+
"""
|
56
|
+
with SPMAuditoryTestingDataGrabber() as dg:
|
57
|
+
# Fetch element
|
58
|
+
element = dg["sub001"]
|
59
|
+
# Fetch element data
|
60
|
+
element_data = DefaultDataReader().fit_transform(element)
|
61
|
+
# Initialize the marker
|
62
|
+
marker = WeightedPermEntropy(parcellation=PARCELLATION)
|
63
|
+
# Create storage
|
64
|
+
storage = SQLiteFeatureStorage(
|
65
|
+
uri=tmp_path / "test_weighted_perm_entropy.sqlite"
|
66
|
+
)
|
67
|
+
# Compute the marker and store
|
68
|
+
marker.fit_transform(input=element_data, storage=storage)
|
@@ -0,0 +1,133 @@
|
|
1
|
+
"""Provide class for weighted permutation entropy of a time series."""
|
2
|
+
|
3
|
+
# Authors: Amir Omidvarnia <a.omidvarnia@fz-juelich.de>
|
4
|
+
# Leonard Sasse <l.sasse@fz-juelich.de>
|
5
|
+
# License: AGPL
|
6
|
+
|
7
|
+
from typing import Dict, List, Optional, Union
|
8
|
+
|
9
|
+
import neurokit2 as nk
|
10
|
+
import numpy as np
|
11
|
+
|
12
|
+
from ...api.decorators import register_marker
|
13
|
+
from ...utils import logger, warn_with_log
|
14
|
+
from .complexity_base import ComplexityBase
|
15
|
+
|
16
|
+
|
17
|
+
@register_marker
|
18
|
+
class WeightedPermEntropy(ComplexityBase):
|
19
|
+
"""Class for weighted permutation entropy of a time series.
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
----------
|
23
|
+
parcellation : str or list of str
|
24
|
+
The name(s) of the parcellation(s). Check valid options by calling
|
25
|
+
:func:`junifer.data.parcellations.list_parcellations`.
|
26
|
+
agg_method : str, optional
|
27
|
+
The method to perform aggregation using. Check valid options in
|
28
|
+
:func:`junifer.stats.get_aggfunc_by_name` (default "mean").
|
29
|
+
agg_method_params : dict, optional
|
30
|
+
Parameters to pass to the aggregation function. Check valid options in
|
31
|
+
:func:`junifer.stats.get_aggfunc_by_name` (default None).
|
32
|
+
masks : str, dict or list of dict or str, optional
|
33
|
+
The specification of the masks to apply to regions before extracting
|
34
|
+
signals. Check :ref:`Using Masks <using_masks>` for more details.
|
35
|
+
If None, will not apply any mask (default None).
|
36
|
+
params : dict, optional
|
37
|
+
Parameters to pass to the weighted permutation entropy calculation
|
38
|
+
function.
|
39
|
+
For more information, check out
|
40
|
+
``junifer.markers.utils._weighted_perm_entropy``. If None, value
|
41
|
+
is set to {"m": 2, "delay": 1} (default None).
|
42
|
+
name : str, optional
|
43
|
+
The name of the marker. If None, it will use the class name
|
44
|
+
(default None).
|
45
|
+
|
46
|
+
Warnings
|
47
|
+
--------
|
48
|
+
This class is not automatically imported by junifer and requires you to
|
49
|
+
import it explicitly. You can do it programmatically by
|
50
|
+
``from junifer.markers.complexity import WeightedPermEntropy`` or in the
|
51
|
+
YAML by ``with: junifer.markers.complexity``.
|
52
|
+
|
53
|
+
"""
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self,
|
57
|
+
parcellation: Union[str, List[str]],
|
58
|
+
agg_method: str = "mean",
|
59
|
+
agg_method_params: Optional[Dict] = None,
|
60
|
+
masks: Union[str, Dict, List[Union[Dict, str]], None] = None,
|
61
|
+
params: Optional[Dict] = None,
|
62
|
+
name: Optional[str] = None,
|
63
|
+
) -> None:
|
64
|
+
super().__init__(
|
65
|
+
parcellation=parcellation,
|
66
|
+
agg_method=agg_method,
|
67
|
+
agg_method_params=agg_method_params,
|
68
|
+
masks=masks,
|
69
|
+
name=name,
|
70
|
+
)
|
71
|
+
if params is None:
|
72
|
+
self.params = {"m": 4, "delay": 1}
|
73
|
+
else:
|
74
|
+
self.params = params
|
75
|
+
|
76
|
+
def compute_complexity(
|
77
|
+
self,
|
78
|
+
extracted_bold_values: np.ndarray,
|
79
|
+
) -> np.ndarray:
|
80
|
+
"""Compute complexity measure.
|
81
|
+
|
82
|
+
Take a timeseries of brain areas, and calculate weighted permutation
|
83
|
+
entropy according to the method outlined in [1].
|
84
|
+
|
85
|
+
Parameters
|
86
|
+
----------
|
87
|
+
extracted_bold_values : numpy.ndarray
|
88
|
+
The BOLD values extracted via parcel aggregation.
|
89
|
+
|
90
|
+
Returns
|
91
|
+
-------
|
92
|
+
numpy.ndarray
|
93
|
+
The values after computing complexity measure.
|
94
|
+
|
95
|
+
References
|
96
|
+
----------
|
97
|
+
.. [1] Fadlallah, B., Chen, B., Keil, A., & Principe, J. (2013)
|
98
|
+
Weighted-permutation entropy: A complexity measure for
|
99
|
+
time series incorporating amplitude information.
|
100
|
+
Physical Review E, 87(2), 022911.
|
101
|
+
|
102
|
+
See Also
|
103
|
+
--------
|
104
|
+
neurokit2.entropy_permutation
|
105
|
+
|
106
|
+
"""
|
107
|
+
logger.info("Calculating weighted permutation entropy.")
|
108
|
+
|
109
|
+
emb_dim = self.params["m"]
|
110
|
+
delay = self.params["delay"]
|
111
|
+
|
112
|
+
assert isinstance(emb_dim, int), "Embedding dimension must be integer."
|
113
|
+
assert isinstance(delay, int), "Delay must be integer."
|
114
|
+
|
115
|
+
_, n_roi = extracted_bold_values.shape
|
116
|
+
wperm_en_roi = np.zeros((n_roi, 1))
|
117
|
+
|
118
|
+
for idx_roi in range(n_roi):
|
119
|
+
sig = extracted_bold_values[:, idx_roi]
|
120
|
+
tmp = nk.entropy_permutation(
|
121
|
+
sig,
|
122
|
+
dimension=emb_dim,
|
123
|
+
delay=delay,
|
124
|
+
weighted=True, # Weighted PE
|
125
|
+
corrected=True, # Normalized PE
|
126
|
+
)
|
127
|
+
|
128
|
+
wperm_en_roi[idx_roi] = tmp[0]
|
129
|
+
|
130
|
+
if np.isnan(np.sum(wperm_en_roi)):
|
131
|
+
warn_with_log("There is NaN in the entropy values!")
|
132
|
+
|
133
|
+
return wperm_en_roi.T # 1 X n_roi
|
@@ -0,0 +1,153 @@
|
|
1
|
+
"""Provide class for computing ALFF using AFNI."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from functools import lru_cache
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import (
|
9
|
+
TYPE_CHECKING,
|
10
|
+
ClassVar,
|
11
|
+
Dict,
|
12
|
+
List,
|
13
|
+
Optional,
|
14
|
+
Tuple,
|
15
|
+
Union,
|
16
|
+
)
|
17
|
+
|
18
|
+
import nibabel as nib
|
19
|
+
|
20
|
+
from ...pipeline import WorkDirManager
|
21
|
+
from ...pipeline.singleton import singleton
|
22
|
+
from ...utils import logger, run_ext_cmd
|
23
|
+
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from nibabel import Nifti1Image
|
27
|
+
|
28
|
+
|
29
|
+
@singleton
|
30
|
+
class AFNIALFF:
|
31
|
+
"""Class for computing ALFF using AFNI.
|
32
|
+
|
33
|
+
This class uses AFNI's 3dRSFC to compute ALFF. It's designed as a singleton
|
34
|
+
with caching for efficient computation.
|
35
|
+
|
36
|
+
"""
|
37
|
+
|
38
|
+
_EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
|
39
|
+
{
|
40
|
+
"name": "afni",
|
41
|
+
"commands": ["3dRSFC", "3dAFNItoNIFTI"],
|
42
|
+
},
|
43
|
+
]
|
44
|
+
|
45
|
+
def __del__(self) -> None:
|
46
|
+
"""Terminate the class."""
|
47
|
+
# Clear the computation cache
|
48
|
+
logger.debug("Clearing cache for ALFF computation via AFNI")
|
49
|
+
self.compute.cache_clear()
|
50
|
+
|
51
|
+
@lru_cache(maxsize=None, typed=True)
|
52
|
+
def compute(
|
53
|
+
self,
|
54
|
+
data: "Nifti1Image",
|
55
|
+
highpass: float,
|
56
|
+
lowpass: float,
|
57
|
+
tr: Optional[float],
|
58
|
+
) -> Tuple["Nifti1Image", "Nifti1Image", Path, Path]:
|
59
|
+
"""Compute ALFF + fALFF map.
|
60
|
+
|
61
|
+
Parameters
|
62
|
+
----------
|
63
|
+
data : 4D Niimg-like object
|
64
|
+
Images to process.
|
65
|
+
highpass : positive float
|
66
|
+
Highpass cutoff frequency.
|
67
|
+
lowpass : positive float
|
68
|
+
Lowpass cutoff frequency.
|
69
|
+
tr : positive float, optional
|
70
|
+
The Repetition Time of the BOLD data.
|
71
|
+
|
72
|
+
Returns
|
73
|
+
-------
|
74
|
+
Niimg-like object
|
75
|
+
ALFF map.
|
76
|
+
Niimg-like object
|
77
|
+
fALFF map.
|
78
|
+
pathlib.Path
|
79
|
+
The path to the ALFF map as NIfTI.
|
80
|
+
pathlib.Path
|
81
|
+
The path to the fALFF map as NIfTI.
|
82
|
+
|
83
|
+
"""
|
84
|
+
logger.debug("Creating cache for ALFF computation via AFNI")
|
85
|
+
|
86
|
+
# Create component-scoped tempdir
|
87
|
+
tempdir = WorkDirManager().get_tempdir(prefix="afni_alff+falff")
|
88
|
+
|
89
|
+
# Save target data to a component-scoped tempfile
|
90
|
+
nifti_in_file_path = tempdir / "input.nii" # needs to be .nii
|
91
|
+
nib.save(data, nifti_in_file_path)
|
92
|
+
|
93
|
+
# Set 3dRSFC command
|
94
|
+
alff_falff_out_path_prefix = tempdir / "alff_falff"
|
95
|
+
bp_cmd = [
|
96
|
+
"3dRSFC",
|
97
|
+
f"-prefix {alff_falff_out_path_prefix.resolve()}",
|
98
|
+
f"-input {nifti_in_file_path.resolve()}",
|
99
|
+
f"-band {highpass} {lowpass}",
|
100
|
+
"-no_rsfa -nosat -nodetrend",
|
101
|
+
]
|
102
|
+
# Check tr
|
103
|
+
if tr is not None:
|
104
|
+
bp_cmd.append(f"-dt {tr}")
|
105
|
+
# Call 3dRSFC
|
106
|
+
run_ext_cmd(name="3dRSFC", cmd=bp_cmd)
|
107
|
+
|
108
|
+
# Create element-scoped tempdir so that the ALFF and fALFF maps are
|
109
|
+
# available later as nibabel stores file path reference for
|
110
|
+
# loading on computation
|
111
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
112
|
+
prefix="afni_alff_falff"
|
113
|
+
)
|
114
|
+
|
115
|
+
params_suffix = f"_{highpass}_{lowpass}_{tr}"
|
116
|
+
|
117
|
+
# Convert alff afni to nifti
|
118
|
+
alff_afni_to_nifti_out_path = (
|
119
|
+
element_tempdir / f"alff{params_suffix}_output.nii"
|
120
|
+
) # needs to be .nii
|
121
|
+
convert_alff_cmd = [
|
122
|
+
"3dAFNItoNIFTI",
|
123
|
+
f"-prefix {alff_afni_to_nifti_out_path.resolve()}",
|
124
|
+
f"{alff_falff_out_path_prefix}_ALFF+tlrc.BRIK",
|
125
|
+
]
|
126
|
+
# Call 3dAFNItoNIFTI
|
127
|
+
run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_alff_cmd)
|
128
|
+
|
129
|
+
# Convert falff afni to nifti
|
130
|
+
falff_afni_to_nifti_out_path = (
|
131
|
+
element_tempdir / f"falff{params_suffix}_output.nii"
|
132
|
+
) # needs to be .nii
|
133
|
+
convert_falff_cmd = [
|
134
|
+
"3dAFNItoNIFTI",
|
135
|
+
f"-prefix {falff_afni_to_nifti_out_path.resolve()}",
|
136
|
+
f"{alff_falff_out_path_prefix}_fALFF+tlrc.BRIK",
|
137
|
+
]
|
138
|
+
# Call 3dAFNItoNIFTI
|
139
|
+
run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_falff_cmd)
|
140
|
+
|
141
|
+
# Load nifti
|
142
|
+
alff_data = nib.load(alff_afni_to_nifti_out_path)
|
143
|
+
falff_data = nib.load(falff_afni_to_nifti_out_path)
|
144
|
+
|
145
|
+
# Delete tempdir
|
146
|
+
WorkDirManager().delete_tempdir(tempdir)
|
147
|
+
|
148
|
+
return (
|
149
|
+
alff_data,
|
150
|
+
falff_data,
|
151
|
+
alff_afni_to_nifti_out_path,
|
152
|
+
falff_afni_to_nifti_out_path,
|
153
|
+
) # type: ignore
|
@@ -0,0 +1,142 @@
|
|
1
|
+
"""Provide class for computing ALFF using junifer."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from functools import lru_cache
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import (
|
9
|
+
TYPE_CHECKING,
|
10
|
+
ClassVar,
|
11
|
+
Optional,
|
12
|
+
Set,
|
13
|
+
Tuple,
|
14
|
+
)
|
15
|
+
|
16
|
+
import nibabel as nib
|
17
|
+
import numpy as np
|
18
|
+
import scipy as sp
|
19
|
+
from nilearn import image as nimg
|
20
|
+
|
21
|
+
from ...pipeline import WorkDirManager
|
22
|
+
from ...pipeline.singleton import singleton
|
23
|
+
from ...utils import logger
|
24
|
+
|
25
|
+
|
26
|
+
if TYPE_CHECKING:
|
27
|
+
from nibabel import Nifti1Image
|
28
|
+
|
29
|
+
|
30
|
+
@singleton
|
31
|
+
class JuniferALFF:
|
32
|
+
"""Class for computing ALFF using junifer.
|
33
|
+
|
34
|
+
It's designed as a singleton with caching for efficient computation.
|
35
|
+
|
36
|
+
"""
|
37
|
+
|
38
|
+
_DEPENDENCIES: ClassVar[Set[str]] = {"numpy", "nilearn", "scipy"}
|
39
|
+
|
40
|
+
def __del__(self) -> None:
|
41
|
+
"""Terminate the class."""
|
42
|
+
# Clear the computation cache
|
43
|
+
logger.debug("Clearing cache for ALFF computation via junifer")
|
44
|
+
self.compute.cache_clear()
|
45
|
+
|
46
|
+
@lru_cache(maxsize=None, typed=True)
|
47
|
+
def compute(
|
48
|
+
self,
|
49
|
+
data: "Nifti1Image",
|
50
|
+
highpass: float,
|
51
|
+
lowpass: float,
|
52
|
+
tr: Optional[float],
|
53
|
+
) -> Tuple["Nifti1Image", "Nifti1Image", Path, Path]:
|
54
|
+
"""Compute ALFF + fALFF map.
|
55
|
+
|
56
|
+
Parameters
|
57
|
+
----------
|
58
|
+
data : 4D Niimg-like object
|
59
|
+
Images to process.
|
60
|
+
highpass : positive float
|
61
|
+
Highpass cutoff frequency.
|
62
|
+
lowpass : positive float
|
63
|
+
Lowpass cutoff frequency.
|
64
|
+
tr : positive float, optional
|
65
|
+
The Repetition Time of the BOLD data.
|
66
|
+
|
67
|
+
Returns
|
68
|
+
-------
|
69
|
+
Niimg-like object
|
70
|
+
ALFF map.
|
71
|
+
Niimg-like object
|
72
|
+
fALFF map.
|
73
|
+
pathlib.Path
|
74
|
+
The path to the ALFF map as NIfTI.
|
75
|
+
pathlib.Path
|
76
|
+
The path to the fALFF map as NIfTI.
|
77
|
+
|
78
|
+
"""
|
79
|
+
logger.debug("Creating cache for ALFF computation via junifer")
|
80
|
+
|
81
|
+
# Get scan data
|
82
|
+
niimg_data = data.get_fdata().copy()
|
83
|
+
if tr is None:
|
84
|
+
tr = float(data.header["pixdim"][4]) # type: ignore
|
85
|
+
logger.info(f"`tr` not provided, using `tr` from header: {tr}")
|
86
|
+
|
87
|
+
# Bandpass the data within the lowpass and highpass cutoff freqs
|
88
|
+
fft_data = sp.fft.fft(niimg_data, axis=-1)
|
89
|
+
fft_freqs = np.abs(sp.fft.fftfreq(niimg_data.shape[-1], tr))
|
90
|
+
# Frequency difference
|
91
|
+
fft_freqs_diff = fft_freqs[1] - fft_freqs[0]
|
92
|
+
# Nyquist frequency
|
93
|
+
nyquist = np.max(fft_freqs)
|
94
|
+
# FFT sample frequency count
|
95
|
+
n_fft = len(fft_freqs)
|
96
|
+
logger.info(
|
97
|
+
f"FFT: nfft = {n_fft}, dFreq = {fft_freqs_diff}, "
|
98
|
+
f"nyquist = {nyquist}"
|
99
|
+
)
|
100
|
+
|
101
|
+
# Compute the denominator on the broadband signal
|
102
|
+
all_freq_mask = fft_freqs > 0
|
103
|
+
denominator = np.sum(np.abs(fft_data[..., all_freq_mask]), axis=-1)
|
104
|
+
|
105
|
+
# Compute the numerator on the bandpassed signal
|
106
|
+
freq_mask = np.logical_and(fft_freqs > highpass, fft_freqs < lowpass)
|
107
|
+
|
108
|
+
# Compute ALFF
|
109
|
+
numerator = np.sum(np.abs(fft_data[..., freq_mask]), axis=-1)
|
110
|
+
|
111
|
+
# Compute fALFF, but avoid division by zero
|
112
|
+
denom_mask = denominator <= 0.000001
|
113
|
+
denominator[denom_mask] = 1 # set to 1 to avoid division by zero
|
114
|
+
# Calculate fALFF
|
115
|
+
falff = np.divide(numerator, denominator)
|
116
|
+
# Set the values where denominator is zero to zero
|
117
|
+
falff[denom_mask] = 0
|
118
|
+
|
119
|
+
# Calculate ALFF
|
120
|
+
alff = numerator / np.sqrt(niimg_data.shape[-1])
|
121
|
+
alff_data = nimg.new_img_like(
|
122
|
+
ref_niimg=data,
|
123
|
+
data=alff,
|
124
|
+
)
|
125
|
+
falff_data = nimg.new_img_like(
|
126
|
+
ref_niimg=data,
|
127
|
+
data=falff,
|
128
|
+
)
|
129
|
+
|
130
|
+
# Create element-scoped tempdir so that the ALFF and fALFF maps are
|
131
|
+
# available later as nibabel stores file path reference for
|
132
|
+
# loading on computation
|
133
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
134
|
+
prefix="junifer_alff+falff"
|
135
|
+
)
|
136
|
+
output_alff_path = element_tempdir / "output_alff.nii.gz"
|
137
|
+
output_falff_path = element_tempdir / "output_falff.nii.gz"
|
138
|
+
# Save computed data to file
|
139
|
+
nib.save(alff_data, output_alff_path)
|
140
|
+
nib.save(falff_data, output_falff_path)
|
141
|
+
|
142
|
+
return alff_data, falff_data, output_alff_path, output_falff_path # type: ignore
|