junifer 0.0.3.dev188__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.dev188.dist-info → junifer-0.0.4.dist-info}/METADATA +59 -28
- junifer-0.0.4.dist-info/RECORD +257 -0
- {junifer-0.0.3.dev188.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.dev188.dist-info/RECORD +0 -199
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/top_level.txt +0 -0
@@ -1,515 +0,0 @@
|
|
1
|
-
"""Provide estimator class for regional homogeneity (ReHo)."""
|
2
|
-
|
3
|
-
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
-
# Federico Raimondo <f.raimondo@fz-juelich.de>
|
5
|
-
# License: AGPL
|
6
|
-
|
7
|
-
|
8
|
-
import hashlib
|
9
|
-
import shutil
|
10
|
-
import subprocess
|
11
|
-
import tempfile
|
12
|
-
from functools import lru_cache
|
13
|
-
from itertools import product
|
14
|
-
from pathlib import Path
|
15
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
|
16
|
-
|
17
|
-
import nibabel as nib
|
18
|
-
import numpy as np
|
19
|
-
from nilearn import image as nimg
|
20
|
-
from nilearn import masking as nmask
|
21
|
-
from scipy.stats import rankdata
|
22
|
-
|
23
|
-
from ...utils import logger, raise_error
|
24
|
-
from ..utils import singleton
|
25
|
-
|
26
|
-
|
27
|
-
if TYPE_CHECKING:
|
28
|
-
from nibabel import Nifti1Image
|
29
|
-
|
30
|
-
|
31
|
-
@singleton
|
32
|
-
class ReHoEstimator:
|
33
|
-
"""Estimator class for regional homogeneity.
|
34
|
-
|
35
|
-
This class is a singleton and is used for efficient computation of ReHo,
|
36
|
-
by caching the ReHo map for a given set of file path and computation
|
37
|
-
parameters.
|
38
|
-
|
39
|
-
.. warning:: This class can only be used via ReHoBase() and is a deliberate
|
40
|
-
decision as it serves a specific purpose.
|
41
|
-
|
42
|
-
Attributes
|
43
|
-
----------
|
44
|
-
temp_dir_path : pathlib.Path
|
45
|
-
Path to the temporary directory for assets storage.
|
46
|
-
|
47
|
-
"""
|
48
|
-
|
49
|
-
def __init__(self) -> None:
|
50
|
-
self._file_path = None
|
51
|
-
# Create temporary directory for intermittent storage of assets during
|
52
|
-
# computation via afni's 3dReHo
|
53
|
-
self.temp_dir_path = Path(tempfile.mkdtemp())
|
54
|
-
|
55
|
-
def __del__(self) -> None:
|
56
|
-
"""Cleanup."""
|
57
|
-
# Delete temporary directory and ignore errors for read-only files
|
58
|
-
shutil.rmtree(self.temp_dir_path, ignore_errors=True)
|
59
|
-
|
60
|
-
def _compute_reho_afni(
|
61
|
-
self,
|
62
|
-
data: "Nifti1Image",
|
63
|
-
nneigh: int = 27,
|
64
|
-
neigh_rad: Optional[float] = None,
|
65
|
-
neigh_x: Optional[float] = None,
|
66
|
-
neigh_y: Optional[float] = None,
|
67
|
-
neigh_z: Optional[float] = None,
|
68
|
-
box_rad: Optional[int] = None,
|
69
|
-
box_x: Optional[int] = None,
|
70
|
-
box_y: Optional[int] = None,
|
71
|
-
box_z: Optional[int] = None,
|
72
|
-
) -> "Nifti1Image":
|
73
|
-
"""Compute ReHo map via afni's 3dReHo.
|
74
|
-
|
75
|
-
Parameters
|
76
|
-
----------
|
77
|
-
data : 4D Niimg-like object
|
78
|
-
Images to process.
|
79
|
-
nneigh : {7, 19, 27}, optional
|
80
|
-
Number of voxels in the neighbourhood, inclusive. Can be:
|
81
|
-
|
82
|
-
* 7 : for facewise neighbours only
|
83
|
-
* 19 : for face- and edge-wise nieghbours
|
84
|
-
* 27 : for face-, edge-, and node-wise neighbors
|
85
|
-
|
86
|
-
(default 27).
|
87
|
-
neigh_rad : positive float, optional
|
88
|
-
The radius of a desired neighbourhood (default None).
|
89
|
-
neigh_x : positive float, optional
|
90
|
-
The semi-radius for x-axis of ellipsoidal volumes (default None).
|
91
|
-
neigh_y : positive float, optional
|
92
|
-
The semi-radius for y-axis of ellipsoidal volumes (default None).
|
93
|
-
neigh_z : positive float, optional
|
94
|
-
The semi-radius for z-axis of ellipsoidal volumes (default None).
|
95
|
-
box_rad : positive int, optional
|
96
|
-
The number of voxels outward in a given cardinal direction for a
|
97
|
-
cubic box centered on a given voxel (default None).
|
98
|
-
box_x : positive int, optional
|
99
|
-
The number of voxels for +/- x-axis of cuboidal volumes
|
100
|
-
(default None).
|
101
|
-
box_y : positive int, optional
|
102
|
-
The number of voxels for +/- y-axis of cuboidal volumes
|
103
|
-
(default None).
|
104
|
-
box_z : positive int, optional
|
105
|
-
The number of voxels for +/- z-axis of cuboidal volumes
|
106
|
-
(default None).
|
107
|
-
|
108
|
-
Returns
|
109
|
-
-------
|
110
|
-
Niimg-like object
|
111
|
-
|
112
|
-
Raises
|
113
|
-
------
|
114
|
-
RuntimeError
|
115
|
-
If the 3dReHo command fails due to some issue.
|
116
|
-
|
117
|
-
Notes
|
118
|
-
-----
|
119
|
-
For more information on the publication, please check [1]_ , and for
|
120
|
-
3dReHo help check:
|
121
|
-
https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dReHo.html
|
122
|
-
|
123
|
-
Please note that that you cannot mix ``box_*`` and ``neigh_*``
|
124
|
-
arguments. The arguments are prioritized by their order in the function
|
125
|
-
signature.
|
126
|
-
|
127
|
-
As the process also depends on the conversion of AFNI files to NIFTI
|
128
|
-
via afni's 3dAFNItoNIFTI, the help for that can be found at:
|
129
|
-
https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dAFNItoNIFTI.html
|
130
|
-
|
131
|
-
References
|
132
|
-
----------
|
133
|
-
.. [1] Taylor, P.A., & Saad, Z.S. (2013).
|
134
|
-
FATCAT: (An Efficient) Functional And Tractographic Connectivity
|
135
|
-
Analysis Toolbox.
|
136
|
-
Brain connectivity, Volume 3(5), Pages 523-35.
|
137
|
-
https://doi.org/10.1089/brain.2013.0154
|
138
|
-
|
139
|
-
"""
|
140
|
-
# Save niimg to nii.gz
|
141
|
-
nifti_in_file_path = self.temp_dir_path / "input.nii"
|
142
|
-
nib.save(data, nifti_in_file_path)
|
143
|
-
|
144
|
-
# Set 3dReHo command
|
145
|
-
reho_afni_out_path_prefix = self.temp_dir_path / "reho"
|
146
|
-
reho_cmd: List[str] = [
|
147
|
-
"3dReHo",
|
148
|
-
f"-prefix {reho_afni_out_path_prefix.resolve()}",
|
149
|
-
f"-inset {nifti_in_file_path.resolve()}",
|
150
|
-
]
|
151
|
-
# Check ellipsoidal / cuboidal volume arguments
|
152
|
-
if neigh_rad:
|
153
|
-
reho_cmd.append(f"-neigh_RAD {neigh_rad}")
|
154
|
-
elif neigh_x and neigh_y and neigh_z:
|
155
|
-
reho_cmd.extend(
|
156
|
-
[
|
157
|
-
f"-neigh_X {neigh_x}",
|
158
|
-
f"-neigh_Y {neigh_y}",
|
159
|
-
f"-neigh_Z {neigh_z}",
|
160
|
-
]
|
161
|
-
)
|
162
|
-
elif box_rad:
|
163
|
-
reho_cmd.append(f"-box_RAD {box_rad}")
|
164
|
-
elif box_x and box_y and box_z:
|
165
|
-
reho_cmd.extend(
|
166
|
-
[f"-box_X {box_x}", f"-box_Y {box_y}", f"-box_Z {box_z}"]
|
167
|
-
)
|
168
|
-
else:
|
169
|
-
reho_cmd.append(f"-nneigh {nneigh}")
|
170
|
-
# Call 3dReHo
|
171
|
-
reho_cmd_str = " ".join(reho_cmd)
|
172
|
-
logger.info(f"3dReHo command to be executed: {reho_cmd_str}")
|
173
|
-
reho_process = subprocess.run(
|
174
|
-
reho_cmd_str, # string needed with shell=True
|
175
|
-
stdin=subprocess.DEVNULL,
|
176
|
-
stdout=subprocess.PIPE,
|
177
|
-
stderr=subprocess.STDOUT,
|
178
|
-
shell=True, # needed for respecting $PATH
|
179
|
-
check=False,
|
180
|
-
)
|
181
|
-
if reho_process.returncode == 0:
|
182
|
-
logger.info(
|
183
|
-
"3dReHo succeeded with the following output: "
|
184
|
-
f"{reho_process.stdout}"
|
185
|
-
)
|
186
|
-
else:
|
187
|
-
raise_error(
|
188
|
-
msg="3dReHo failed with the following error: "
|
189
|
-
f"{reho_process.stdout}",
|
190
|
-
klass=RuntimeError,
|
191
|
-
)
|
192
|
-
|
193
|
-
# SHA256 for bypassing memmap
|
194
|
-
sha256_params = hashlib.sha256(bytes(reho_cmd_str, "utf-8"))
|
195
|
-
# Convert afni to nifti
|
196
|
-
reho_afni_to_nifti_out_path = (
|
197
|
-
self.temp_dir_path / f"output_{sha256_params.hexdigest()}.nii"
|
198
|
-
)
|
199
|
-
convert_cmd: List[str] = [
|
200
|
-
"3dAFNItoNIFTI",
|
201
|
-
f"-prefix {reho_afni_to_nifti_out_path.resolve()}",
|
202
|
-
f"{reho_afni_out_path_prefix}+tlrc.BRIK",
|
203
|
-
]
|
204
|
-
# Call 3dAFNItoNIFTI
|
205
|
-
convert_cmd_str = " ".join(convert_cmd)
|
206
|
-
logger.info(f"3dAFNItoNIFTI command to be executed: {convert_cmd_str}")
|
207
|
-
convert_process = subprocess.run(
|
208
|
-
convert_cmd_str, # string needed with shell=True
|
209
|
-
stdin=subprocess.DEVNULL,
|
210
|
-
stdout=subprocess.PIPE,
|
211
|
-
stderr=subprocess.STDOUT,
|
212
|
-
shell=True, # needed for respecting $PATH
|
213
|
-
check=False,
|
214
|
-
)
|
215
|
-
if convert_process.returncode == 0:
|
216
|
-
logger.info(
|
217
|
-
"3dAFNItoNIFTI succeeded with the following output: "
|
218
|
-
f"{convert_process.stdout}"
|
219
|
-
)
|
220
|
-
else:
|
221
|
-
raise_error(
|
222
|
-
msg="3dAFNItoNIFTI failed with the following error: "
|
223
|
-
f"{convert_process.stdout}",
|
224
|
-
klass=RuntimeError,
|
225
|
-
)
|
226
|
-
|
227
|
-
# Cleanup intermediate files
|
228
|
-
for fname in self.temp_dir_path.glob("reho*"):
|
229
|
-
fname.unlink()
|
230
|
-
|
231
|
-
# Load nifti
|
232
|
-
output_data = nib.load(reho_afni_to_nifti_out_path)
|
233
|
-
# Stupid casting
|
234
|
-
output_data = cast("Nifti1Image", output_data)
|
235
|
-
return output_data
|
236
|
-
|
237
|
-
def _compute_reho_python(
|
238
|
-
self,
|
239
|
-
data: "Nifti1Image",
|
240
|
-
nneigh: int = 27,
|
241
|
-
) -> "Nifti1Image":
|
242
|
-
"""Compute ReHo map.
|
243
|
-
|
244
|
-
Parameters
|
245
|
-
----------
|
246
|
-
data : 4D Niimg-like object
|
247
|
-
Images to process.
|
248
|
-
nneigh : {7, 19, 27, 125}, optional
|
249
|
-
Number of voxels in the neighbourhood, inclusive. Can be:
|
250
|
-
|
251
|
-
* 7 : for facewise neighbours only
|
252
|
-
* 19 : for face- and edge-wise nieghbours
|
253
|
-
* 27 : for face-, edge-, and node-wise neighbors
|
254
|
-
* 125 : for 5x5 cuboidal volume
|
255
|
-
|
256
|
-
(default 27).
|
257
|
-
|
258
|
-
Returns
|
259
|
-
-------
|
260
|
-
Niimg-like object
|
261
|
-
|
262
|
-
Raises
|
263
|
-
------
|
264
|
-
ValueError
|
265
|
-
If ``nneigh`` is invalid.
|
266
|
-
|
267
|
-
"""
|
268
|
-
valid_nneigh = (7, 19, 27, 125)
|
269
|
-
if nneigh not in valid_nneigh:
|
270
|
-
raise_error(
|
271
|
-
f"Invalid value for `nneigh`, should be one of {valid_nneigh}."
|
272
|
-
)
|
273
|
-
|
274
|
-
logger.info(f"Computing ReHo map using {nneigh} neighbours.")
|
275
|
-
# Get scan data
|
276
|
-
niimg_data = data.get_fdata()
|
277
|
-
# Get scan dimensions
|
278
|
-
n_x, n_y, n_z, _ = niimg_data.shape
|
279
|
-
|
280
|
-
# Get rank of every voxel across time series
|
281
|
-
ranks_niimg_data = rankdata(niimg_data, axis=-1)
|
282
|
-
|
283
|
-
# Initialize 3D array to store tied rank correction for every voxel
|
284
|
-
tied_rank_corrections = np.zeros((n_x, n_y, n_z), dtype=np.float64)
|
285
|
-
# Calculate tied rank correction for every voxel
|
286
|
-
for i_x, i_y, i_z in product(range(n_x), range(n_y), range(n_z)):
|
287
|
-
# Calculate tied rank count for every voxel across time series
|
288
|
-
_, tie_count = np.unique(
|
289
|
-
ranks_niimg_data[i_x, i_y, i_z, :],
|
290
|
-
return_counts=True,
|
291
|
-
)
|
292
|
-
# Calculate and store tied rank correction for every voxel across
|
293
|
-
# timeseries
|
294
|
-
tied_rank_corrections[i_x, i_y, i_z] = np.sum(
|
295
|
-
tie_count**3 - tie_count
|
296
|
-
)
|
297
|
-
|
298
|
-
# Initialize 3D array to store reho map
|
299
|
-
reho_map = np.ones((n_x, n_y, n_z), dtype=np.float32)
|
300
|
-
|
301
|
-
# Calculate whole brain mask
|
302
|
-
mni152_whole_brain_mask = nmask.compute_brain_mask(
|
303
|
-
data, threshold=0.5, mask_type="whole-brain"
|
304
|
-
)
|
305
|
-
# Convert 0 / 1 array to bool
|
306
|
-
logical_mni152_whole_brain_mask = (
|
307
|
-
mni152_whole_brain_mask.get_fdata().astype(bool)
|
308
|
-
)
|
309
|
-
|
310
|
-
# Create mask cluster and set start and end indices
|
311
|
-
if nneigh in (7, 19, 27):
|
312
|
-
mask_cluster = np.ones((3, 3, 3))
|
313
|
-
|
314
|
-
if nneigh == 7:
|
315
|
-
mask_cluster[0, 0, 0] = 0
|
316
|
-
mask_cluster[0, 1, 0] = 0
|
317
|
-
mask_cluster[0, 2, 0] = 0
|
318
|
-
mask_cluster[0, 0, 1] = 0
|
319
|
-
mask_cluster[0, 2, 1] = 0
|
320
|
-
mask_cluster[0, 0, 2] = 0
|
321
|
-
mask_cluster[0, 1, 2] = 0
|
322
|
-
mask_cluster[0, 2, 2] = 0
|
323
|
-
mask_cluster[1, 0, 0] = 0
|
324
|
-
mask_cluster[1, 2, 0] = 0
|
325
|
-
mask_cluster[1, 0, 2] = 0
|
326
|
-
mask_cluster[1, 2, 2] = 0
|
327
|
-
mask_cluster[2, 0, 0] = 0
|
328
|
-
mask_cluster[2, 1, 0] = 0
|
329
|
-
mask_cluster[2, 2, 0] = 0
|
330
|
-
mask_cluster[2, 0, 1] = 0
|
331
|
-
mask_cluster[2, 2, 1] = 0
|
332
|
-
mask_cluster[2, 0, 2] = 0
|
333
|
-
mask_cluster[2, 1, 2] = 0
|
334
|
-
mask_cluster[2, 2, 2] = 0
|
335
|
-
|
336
|
-
elif nneigh == 19:
|
337
|
-
mask_cluster[0, 0, 0] = 0
|
338
|
-
mask_cluster[0, 2, 0] = 0
|
339
|
-
mask_cluster[2, 0, 0] = 0
|
340
|
-
mask_cluster[2, 2, 0] = 0
|
341
|
-
mask_cluster[0, 0, 2] = 0
|
342
|
-
mask_cluster[0, 2, 2] = 0
|
343
|
-
mask_cluster[2, 0, 2] = 0
|
344
|
-
mask_cluster[2, 2, 2] = 0
|
345
|
-
|
346
|
-
start_idx = 1
|
347
|
-
end_idx = 2
|
348
|
-
|
349
|
-
elif nneigh == 125:
|
350
|
-
mask_cluster = np.ones((5, 5, 5))
|
351
|
-
start_idx = 2
|
352
|
-
end_idx = 3
|
353
|
-
|
354
|
-
# Convert 0 / 1 array to bool
|
355
|
-
logical_mask_cluster = mask_cluster.astype(bool)
|
356
|
-
|
357
|
-
for i, j, k in product(
|
358
|
-
range(start_idx, n_x - (end_idx - 1)),
|
359
|
-
range(start_idx, n_y - (end_idx - 1)),
|
360
|
-
range(start_idx, n_z - (end_idx - 1)),
|
361
|
-
):
|
362
|
-
# Get mask only for neighbourhood
|
363
|
-
logical_neighbourhood_mni152_whole_brain_mask = (
|
364
|
-
logical_mni152_whole_brain_mask[
|
365
|
-
i - start_idx : i + end_idx,
|
366
|
-
j - start_idx : j + end_idx,
|
367
|
-
k - start_idx : k + end_idx,
|
368
|
-
]
|
369
|
-
)
|
370
|
-
# Perform logical AND to get neighbourhood mask;
|
371
|
-
# done to take care of brain boundaries
|
372
|
-
neighbourhood_mask = (
|
373
|
-
logical_mask_cluster
|
374
|
-
& logical_neighbourhood_mni152_whole_brain_mask
|
375
|
-
)
|
376
|
-
# Continue if voxel is restricted by mask
|
377
|
-
if neighbourhood_mask[1, 1, 1] == 0:
|
378
|
-
continue
|
379
|
-
|
380
|
-
# Get ranks for the neighbourhood
|
381
|
-
neighbourhood_ranks = ranks_niimg_data[
|
382
|
-
i - start_idx : i + end_idx,
|
383
|
-
j - start_idx : j + end_idx,
|
384
|
-
k - start_idx : k + end_idx,
|
385
|
-
:,
|
386
|
-
]
|
387
|
-
# Get tied ranks corrections for the neighbourhood
|
388
|
-
neighbourhood_tied_ranks_corrections = tied_rank_corrections[
|
389
|
-
i - start_idx : i + end_idx,
|
390
|
-
j - start_idx : j + end_idx,
|
391
|
-
k - start_idx : k + end_idx,
|
392
|
-
]
|
393
|
-
# Mask neighbourhood ranks
|
394
|
-
masked_neighbourhood_ranks = neighbourhood_ranks[
|
395
|
-
logical_mask_cluster, :
|
396
|
-
]
|
397
|
-
# Mask tied ranks corrections for the neighbourhood
|
398
|
-
masked_tied_rank_corrections = (
|
399
|
-
neighbourhood_tied_ranks_corrections[logical_mask_cluster]
|
400
|
-
)
|
401
|
-
# Calculate KCC
|
402
|
-
reho_map[i, j, k] = _kendall_w_reho(
|
403
|
-
timeseries_ranks=masked_neighbourhood_ranks,
|
404
|
-
tied_rank_corrections=masked_tied_rank_corrections,
|
405
|
-
)
|
406
|
-
|
407
|
-
output = nimg.new_img_like(data, reho_map, copy_header=False)
|
408
|
-
return output
|
409
|
-
|
410
|
-
@lru_cache(maxsize=None, typed=True)
|
411
|
-
def _compute(
|
412
|
-
self,
|
413
|
-
use_afni: bool,
|
414
|
-
data: "Nifti1Image",
|
415
|
-
**reho_params: Any,
|
416
|
-
) -> "Nifti1Image":
|
417
|
-
"""Compute the ReHo map with memoization.
|
418
|
-
|
419
|
-
Parameters
|
420
|
-
----------
|
421
|
-
use_afni : bool
|
422
|
-
Whether to use afni or not.
|
423
|
-
data : 4D Niimg-like object
|
424
|
-
Images to process.
|
425
|
-
**reho_params : dict
|
426
|
-
Extra keyword arguments for ReHo.
|
427
|
-
|
428
|
-
Returns
|
429
|
-
-------
|
430
|
-
Niimg-like object
|
431
|
-
|
432
|
-
"""
|
433
|
-
if use_afni:
|
434
|
-
output = self._compute_reho_afni(data, **reho_params)
|
435
|
-
else:
|
436
|
-
output = self._compute_reho_python(data, **reho_params)
|
437
|
-
return output
|
438
|
-
|
439
|
-
def fit_transform(
|
440
|
-
self,
|
441
|
-
use_afni: bool,
|
442
|
-
input_data: Dict[str, Any],
|
443
|
-
**reho_params: Any,
|
444
|
-
) -> "Nifti1Image":
|
445
|
-
"""Fit and transform for the estimator.
|
446
|
-
|
447
|
-
Parameters
|
448
|
-
----------
|
449
|
-
use_afni : bool
|
450
|
-
Whether to use afni or not.
|
451
|
-
input_data : dict
|
452
|
-
The BOLD data as dictionary.
|
453
|
-
**reho_params : dict
|
454
|
-
Extra keyword arguments for ReHo.
|
455
|
-
|
456
|
-
Returns
|
457
|
-
-------
|
458
|
-
Niimg-like object
|
459
|
-
|
460
|
-
"""
|
461
|
-
bold_path = input_data["path"]
|
462
|
-
bold_data = input_data["data"]
|
463
|
-
# Clear cache if file path is different from when caching was done
|
464
|
-
if self._file_path != bold_path:
|
465
|
-
logger.info(f"Removing ReHo map cache at {self._file_path}.")
|
466
|
-
# Clear the cache
|
467
|
-
self._compute.cache_clear()
|
468
|
-
# Clear temporary directory files
|
469
|
-
for file_ in self.temp_dir_path.iterdir():
|
470
|
-
file_.unlink(missing_ok=True)
|
471
|
-
# Set the new file path
|
472
|
-
self._file_path = bold_path
|
473
|
-
else:
|
474
|
-
logger.info(f"Using ReHo map cache at {self._file_path}.")
|
475
|
-
# Compute
|
476
|
-
return self._compute(use_afni, bold_data, **reho_params)
|
477
|
-
|
478
|
-
|
479
|
-
def _kendall_w_reho(
|
480
|
-
timeseries_ranks: np.ndarray, tied_rank_corrections: np.ndarray
|
481
|
-
) -> float:
|
482
|
-
"""Calculate Kendall's coefficient of concordance (KCC) for ReHo map.
|
483
|
-
|
484
|
-
..note:: This function should only be used to calculate KCC for a ReHo map.
|
485
|
-
For general use, check out ``junifer.stats.kendall_w``.
|
486
|
-
|
487
|
-
Parameters
|
488
|
-
----------
|
489
|
-
timeseries_ranks : 2D numpy.ndarray
|
490
|
-
A matrix of ranks of a subset subject's brain voxels.
|
491
|
-
tied_rank_corrections : 3D numpy.ndarray
|
492
|
-
A 3D array consisting of the tied rank corrections for the ranks
|
493
|
-
of a subset subject's brain voxels.
|
494
|
-
|
495
|
-
Returns
|
496
|
-
-------
|
497
|
-
float
|
498
|
-
Kendall's W (KCC) of the given timeseries matrix.
|
499
|
-
|
500
|
-
"""
|
501
|
-
m, n = timeseries_ranks.shape # annotators X items
|
502
|
-
|
503
|
-
numerator = (12 * np.sum(np.square(np.sum(timeseries_ranks, axis=0)))) - (
|
504
|
-
3 * m**2 * n * (n + 1) ** 2
|
505
|
-
)
|
506
|
-
denominator = (m**2 * n * (n**2 - 1)) - (
|
507
|
-
m * np.sum(tied_rank_corrections)
|
508
|
-
)
|
509
|
-
|
510
|
-
if denominator == 0:
|
511
|
-
kcc = 1.0
|
512
|
-
else:
|
513
|
-
kcc = numerator / denominator
|
514
|
-
|
515
|
-
return kcc
|