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.
Files changed (178) hide show
  1. junifer/_version.py +14 -2
  2. junifer/api/cli.py +162 -17
  3. junifer/api/functions.py +87 -419
  4. junifer/api/parser.py +24 -0
  5. junifer/api/queue_context/__init__.py +8 -0
  6. junifer/api/queue_context/gnu_parallel_local_adapter.py +258 -0
  7. junifer/api/queue_context/htcondor_adapter.py +365 -0
  8. junifer/api/queue_context/queue_context_adapter.py +60 -0
  9. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +192 -0
  10. junifer/api/queue_context/tests/test_htcondor_adapter.py +257 -0
  11. junifer/api/res/afni/run_afni_docker.sh +6 -6
  12. junifer/api/res/ants/ResampleImage +3 -0
  13. junifer/api/res/ants/antsApplyTransforms +3 -0
  14. junifer/api/res/ants/antsApplyTransformsToPoints +3 -0
  15. junifer/api/res/ants/run_ants_docker.sh +39 -0
  16. junifer/api/res/fsl/applywarp +3 -0
  17. junifer/api/res/fsl/flirt +3 -0
  18. junifer/api/res/fsl/img2imgcoord +3 -0
  19. junifer/api/res/fsl/run_fsl_docker.sh +39 -0
  20. junifer/api/res/fsl/std2imgcoord +3 -0
  21. junifer/api/res/run_conda.sh +4 -4
  22. junifer/api/res/run_venv.sh +22 -0
  23. junifer/api/tests/data/partly_cloudy_agg_mean_tian.yml +16 -0
  24. junifer/api/tests/test_api_utils.py +21 -3
  25. junifer/api/tests/test_cli.py +232 -9
  26. junifer/api/tests/test_functions.py +211 -439
  27. junifer/api/tests/test_parser.py +1 -1
  28. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +6 -1
  29. junifer/configs/juseless/datagrabbers/camcan_vbm.py +6 -1
  30. junifer/configs/juseless/datagrabbers/ixi_vbm.py +6 -1
  31. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +8 -8
  32. junifer/configs/juseless/datagrabbers/ucla.py +44 -26
  33. junifer/configs/juseless/datagrabbers/ukb_vbm.py +6 -1
  34. junifer/data/VOIs/meta/AutobiographicalMemory_VOIs.txt +23 -0
  35. junifer/data/VOIs/meta/Power2013_MNI_VOIs.tsv +264 -0
  36. junifer/data/__init__.py +4 -0
  37. junifer/data/coordinates.py +298 -31
  38. junifer/data/masks.py +360 -28
  39. junifer/data/parcellations.py +621 -188
  40. junifer/data/template_spaces.py +190 -0
  41. junifer/data/tests/test_coordinates.py +34 -3
  42. junifer/data/tests/test_data_utils.py +1 -0
  43. junifer/data/tests/test_masks.py +202 -86
  44. junifer/data/tests/test_parcellations.py +266 -55
  45. junifer/data/tests/test_template_spaces.py +104 -0
  46. junifer/data/utils.py +4 -2
  47. junifer/datagrabber/__init__.py +1 -0
  48. junifer/datagrabber/aomic/id1000.py +111 -70
  49. junifer/datagrabber/aomic/piop1.py +116 -53
  50. junifer/datagrabber/aomic/piop2.py +116 -53
  51. junifer/datagrabber/aomic/tests/test_id1000.py +27 -27
  52. junifer/datagrabber/aomic/tests/test_piop1.py +27 -27
  53. junifer/datagrabber/aomic/tests/test_piop2.py +27 -27
  54. junifer/datagrabber/base.py +62 -10
  55. junifer/datagrabber/datalad_base.py +0 -2
  56. junifer/datagrabber/dmcc13_benchmark.py +372 -0
  57. junifer/datagrabber/hcp1200/datalad_hcp1200.py +5 -0
  58. junifer/datagrabber/hcp1200/hcp1200.py +30 -13
  59. junifer/datagrabber/pattern.py +133 -27
  60. junifer/datagrabber/pattern_datalad.py +111 -13
  61. junifer/datagrabber/tests/test_base.py +57 -6
  62. junifer/datagrabber/tests/test_datagrabber_utils.py +204 -76
  63. junifer/datagrabber/tests/test_datalad_base.py +0 -6
  64. junifer/datagrabber/tests/test_dmcc13_benchmark.py +256 -0
  65. junifer/datagrabber/tests/test_multiple.py +43 -10
  66. junifer/datagrabber/tests/test_pattern.py +125 -178
  67. junifer/datagrabber/tests/test_pattern_datalad.py +44 -25
  68. junifer/datagrabber/utils.py +151 -16
  69. junifer/datareader/default.py +36 -10
  70. junifer/external/nilearn/junifer_nifti_spheres_masker.py +6 -0
  71. junifer/markers/base.py +25 -16
  72. junifer/markers/collection.py +35 -16
  73. junifer/markers/complexity/__init__.py +27 -0
  74. junifer/markers/complexity/complexity_base.py +149 -0
  75. junifer/markers/complexity/hurst_exponent.py +136 -0
  76. junifer/markers/complexity/multiscale_entropy_auc.py +140 -0
  77. junifer/markers/complexity/perm_entropy.py +132 -0
  78. junifer/markers/complexity/range_entropy.py +136 -0
  79. junifer/markers/complexity/range_entropy_auc.py +145 -0
  80. junifer/markers/complexity/sample_entropy.py +134 -0
  81. junifer/markers/complexity/tests/test_complexity_base.py +19 -0
  82. junifer/markers/complexity/tests/test_hurst_exponent.py +69 -0
  83. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +68 -0
  84. junifer/markers/complexity/tests/test_perm_entropy.py +68 -0
  85. junifer/markers/complexity/tests/test_range_entropy.py +69 -0
  86. junifer/markers/complexity/tests/test_range_entropy_auc.py +69 -0
  87. junifer/markers/complexity/tests/test_sample_entropy.py +68 -0
  88. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +68 -0
  89. junifer/markers/complexity/weighted_perm_entropy.py +133 -0
  90. junifer/markers/falff/_afni_falff.py +153 -0
  91. junifer/markers/falff/_junifer_falff.py +142 -0
  92. junifer/markers/falff/falff_base.py +91 -84
  93. junifer/markers/falff/falff_parcels.py +61 -45
  94. junifer/markers/falff/falff_spheres.py +64 -48
  95. junifer/markers/falff/tests/test_falff_parcels.py +89 -121
  96. junifer/markers/falff/tests/test_falff_spheres.py +92 -127
  97. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +1 -0
  98. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +1 -0
  99. junifer/markers/functional_connectivity/functional_connectivity_base.py +1 -0
  100. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +46 -44
  101. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +34 -39
  102. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +40 -52
  103. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +62 -70
  104. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +99 -85
  105. junifer/markers/parcel_aggregation.py +60 -38
  106. junifer/markers/reho/_afni_reho.py +192 -0
  107. junifer/markers/reho/_junifer_reho.py +281 -0
  108. junifer/markers/reho/reho_base.py +69 -34
  109. junifer/markers/reho/reho_parcels.py +26 -16
  110. junifer/markers/reho/reho_spheres.py +23 -9
  111. junifer/markers/reho/tests/test_reho_parcels.py +93 -92
  112. junifer/markers/reho/tests/test_reho_spheres.py +88 -86
  113. junifer/markers/sphere_aggregation.py +54 -9
  114. junifer/markers/temporal_snr/temporal_snr_base.py +1 -0
  115. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +38 -37
  116. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +34 -38
  117. junifer/markers/tests/test_collection.py +43 -42
  118. junifer/markers/tests/test_ets_rss.py +29 -37
  119. junifer/markers/tests/test_parcel_aggregation.py +587 -468
  120. junifer/markers/tests/test_sphere_aggregation.py +209 -157
  121. junifer/markers/utils.py +2 -40
  122. junifer/onthefly/read_transform.py +13 -6
  123. junifer/pipeline/__init__.py +1 -0
  124. junifer/pipeline/pipeline_step_mixin.py +105 -41
  125. junifer/pipeline/registry.py +17 -0
  126. junifer/pipeline/singleton.py +45 -0
  127. junifer/pipeline/tests/test_pipeline_step_mixin.py +139 -51
  128. junifer/pipeline/tests/test_update_meta_mixin.py +1 -0
  129. junifer/pipeline/tests/test_workdir_manager.py +104 -0
  130. junifer/pipeline/update_meta_mixin.py +8 -2
  131. junifer/pipeline/utils.py +154 -15
  132. junifer/pipeline/workdir_manager.py +246 -0
  133. junifer/preprocess/__init__.py +3 -0
  134. junifer/preprocess/ants/__init__.py +4 -0
  135. junifer/preprocess/ants/ants_apply_transforms_warper.py +185 -0
  136. junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +56 -0
  137. junifer/preprocess/base.py +96 -69
  138. junifer/preprocess/bold_warper.py +265 -0
  139. junifer/preprocess/confounds/fmriprep_confound_remover.py +91 -134
  140. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +106 -111
  141. junifer/preprocess/fsl/__init__.py +4 -0
  142. junifer/preprocess/fsl/apply_warper.py +179 -0
  143. junifer/preprocess/fsl/tests/test_apply_warper.py +45 -0
  144. junifer/preprocess/tests/test_bold_warper.py +159 -0
  145. junifer/preprocess/tests/test_preprocess_base.py +6 -6
  146. junifer/preprocess/warping/__init__.py +6 -0
  147. junifer/preprocess/warping/_ants_warper.py +167 -0
  148. junifer/preprocess/warping/_fsl_warper.py +109 -0
  149. junifer/preprocess/warping/space_warper.py +213 -0
  150. junifer/preprocess/warping/tests/test_space_warper.py +198 -0
  151. junifer/stats.py +18 -4
  152. junifer/storage/base.py +9 -1
  153. junifer/storage/hdf5.py +8 -3
  154. junifer/storage/pandas_base.py +2 -1
  155. junifer/storage/sqlite.py +1 -0
  156. junifer/storage/tests/test_hdf5.py +2 -1
  157. junifer/storage/tests/test_sqlite.py +8 -8
  158. junifer/storage/tests/test_utils.py +6 -6
  159. junifer/storage/utils.py +1 -0
  160. junifer/testing/datagrabbers.py +11 -7
  161. junifer/testing/utils.py +1 -0
  162. junifer/tests/test_stats.py +2 -0
  163. junifer/utils/__init__.py +1 -0
  164. junifer/utils/helpers.py +53 -0
  165. junifer/utils/logging.py +14 -3
  166. junifer/utils/tests/test_helpers.py +35 -0
  167. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/METADATA +59 -28
  168. junifer-0.0.4.dist-info/RECORD +257 -0
  169. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/WHEEL +1 -1
  170. junifer/markers/falff/falff_estimator.py +0 -334
  171. junifer/markers/falff/tests/test_falff_estimator.py +0 -238
  172. junifer/markers/reho/reho_estimator.py +0 -515
  173. junifer/markers/reho/tests/test_reho_estimator.py +0 -260
  174. junifer-0.0.3.dev186.dist-info/RECORD +0 -199
  175. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/AUTHORS.rst +0 -0
  176. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/LICENSE.md +0 -0
  177. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/entry_points.txt +0 -0
  178. {junifer-0.0.3.dev186.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