junifer 0.0.7.dev332__py3-none-any.whl → 0.0.8__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 (293) hide show
  1. junifer/_version.py +9 -19
  2. junifer/api/__init__.pyi +17 -2
  3. junifer/api/functions.py +222 -39
  4. junifer/api/queue_context/__init__.pyi +11 -3
  5. junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
  6. junifer/api/queue_context/htcondor_adapter.py +118 -181
  7. junifer/api/queue_context/queue_context_adapter.py +51 -7
  8. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
  9. junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
  10. junifer/api/res/fsl/run_fsl_docker.sh +4 -0
  11. junifer/api/tests/test_functions.py +863 -36
  12. junifer/cli/__init__.py +1 -7
  13. junifer/cli/cli.py +41 -22
  14. junifer/cli/parser.py +23 -147
  15. junifer/cli/tests/data/gmd_mean.yaml +6 -6
  16. junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
  17. junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
  18. junifer/cli/tests/test_cli.py +120 -4
  19. junifer/cli/tests/test_cli_utils.py +23 -6
  20. junifer/cli/utils.py +14 -9
  21. junifer/configs/__init__.py +5 -0
  22. junifer/configs/__init__.pyi +0 -0
  23. junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
  24. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
  25. junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
  26. junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
  27. junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
  28. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
  29. junifer/configs/juseless/datagrabbers/ucla.py +111 -104
  30. junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
  31. junifer/configs/py.typed +0 -0
  32. junifer/conftest.py +63 -24
  33. junifer/data/_dispatch.py +9 -19
  34. junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
  35. junifer/data/coordinates/_coordinates.py +51 -6
  36. junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
  37. junifer/data/coordinates/tests/test_coordinates.py +15 -3
  38. junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
  39. junifer/data/maps/_ants_maps_warper.py +18 -12
  40. junifer/data/maps/_fsl_maps_warper.py +7 -3
  41. junifer/data/maps/_maps.py +12 -8
  42. junifer/data/maps/tests/test_maps.py +13 -21
  43. junifer/data/masks/_ants_mask_warper.py +18 -12
  44. junifer/data/masks/_fsl_mask_warper.py +7 -3
  45. junifer/data/masks/_masks.py +15 -12
  46. junifer/data/masks/tests/test_masks.py +20 -17
  47. junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
  48. junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
  49. junifer/data/parcellations/_parcellations.py +125 -91
  50. junifer/data/parcellations/tests/test_parcellations.py +122 -1
  51. junifer/data/template_spaces.py +117 -50
  52. junifer/data/tests/test_template_spaces.py +10 -7
  53. junifer/data/utils.py +10 -8
  54. junifer/datagrabber/__init__.pyi +38 -5
  55. junifer/datagrabber/aomic/__init__.pyi +8 -1
  56. junifer/datagrabber/aomic/_types.py +22 -0
  57. junifer/datagrabber/aomic/id1000.py +193 -183
  58. junifer/datagrabber/aomic/piop1.py +215 -259
  59. junifer/datagrabber/aomic/piop2.py +211 -244
  60. junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
  61. junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
  62. junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
  63. junifer/datagrabber/base.py +253 -79
  64. junifer/datagrabber/datalad_base.py +127 -76
  65. junifer/datagrabber/dmcc13_benchmark.py +204 -289
  66. junifer/datagrabber/hcp1200/__init__.pyi +7 -2
  67. junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
  68. junifer/datagrabber/hcp1200/hcp1200.py +151 -164
  69. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
  70. junifer/datagrabber/multiple.py +183 -63
  71. junifer/datagrabber/pattern.py +358 -227
  72. junifer/datagrabber/pattern_datalad.py +84 -124
  73. junifer/datagrabber/pattern_validation_mixin.py +22 -50
  74. junifer/datagrabber/tests/test_base.py +17 -4
  75. junifer/datagrabber/tests/test_datalad_base.py +20 -27
  76. junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
  77. junifer/datagrabber/tests/test_multiple.py +293 -31
  78. junifer/datagrabber/tests/test_pattern.py +577 -18
  79. junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
  80. junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
  81. junifer/datareader/default.py +8 -5
  82. junifer/datareader/tests/test_default_reader.py +1 -1
  83. junifer/external/BrainPrint/.codecov.yml +2 -0
  84. junifer/external/BrainPrint/.codespellignore +2 -0
  85. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
  86. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
  87. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
  88. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
  89. junifer/external/BrainPrint/.github/dependabot.yml +6 -0
  90. junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
  91. junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
  92. junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
  93. junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
  94. junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
  95. junifer/external/BrainPrint/.gitignore +123 -0
  96. junifer/external/BrainPrint/LICENSE +21 -0
  97. junifer/external/BrainPrint/README.md +108 -0
  98. junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
  99. junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
  100. junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
  101. junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
  102. junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
  103. junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
  104. junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
  105. junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
  106. junifer/external/BrainPrint/doc/Makefile +20 -0
  107. junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
  108. junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
  109. junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
  110. junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
  111. junifer/external/BrainPrint/doc/api/index.rst +11 -0
  112. junifer/external/BrainPrint/doc/conf.py +272 -0
  113. junifer/external/BrainPrint/doc/index.rst +140 -0
  114. junifer/external/BrainPrint/doc/links.inc +36 -0
  115. junifer/external/BrainPrint/doc/make.bat +35 -0
  116. junifer/external/BrainPrint/doc/references.bib +23 -0
  117. junifer/external/BrainPrint/pyproject.toml +170 -0
  118. junifer/external/BrainPrint/setup.py +3 -0
  119. junifer/external/__init__.py +5 -0
  120. junifer/external/__init__.pyi +0 -0
  121. junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
  122. junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
  123. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
  124. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
  125. junifer/external/py.typed +0 -0
  126. junifer/markers/__init__.pyi +6 -3
  127. junifer/markers/base.py +126 -76
  128. junifer/markers/brainprint.py +30 -39
  129. junifer/markers/complexity/__init__.py +20 -3
  130. junifer/markers/complexity/complexity_base.py +40 -41
  131. junifer/markers/complexity/hurst_exponent.py +22 -35
  132. junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
  133. junifer/markers/complexity/perm_entropy.py +22 -36
  134. junifer/markers/complexity/range_entropy.py +22 -35
  135. junifer/markers/complexity/range_entropy_auc.py +27 -36
  136. junifer/markers/complexity/sample_entropy.py +22 -36
  137. junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
  138. junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
  139. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
  140. junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
  141. junifer/markers/complexity/tests/test_range_entropy.py +47 -36
  142. junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
  143. junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
  144. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
  145. junifer/markers/complexity/weighted_perm_entropy.py +22 -36
  146. junifer/markers/ets_rss.py +35 -31
  147. junifer/markers/falff/__init__.pyi +7 -1
  148. junifer/markers/falff/_afni_falff.py +22 -9
  149. junifer/markers/falff/_junifer_falff.py +47 -31
  150. junifer/markers/falff/falff_base.py +80 -60
  151. junifer/markers/falff/falff_maps.py +17 -41
  152. junifer/markers/falff/falff_parcels.py +26 -51
  153. junifer/markers/falff/falff_spheres.py +34 -61
  154. junifer/markers/falff/tests/test_falff_maps.py +20 -23
  155. junifer/markers/falff/tests/test_falff_mask.py +99 -0
  156. junifer/markers/falff/tests/test_falff_parcels.py +14 -10
  157. junifer/markers/falff/tests/test_falff_spheres.py +14 -10
  158. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
  159. junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
  160. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
  161. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
  162. junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
  163. junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
  164. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
  165. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
  166. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
  167. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
  168. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
  169. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
  170. junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
  171. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
  172. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
  173. junifer/markers/maps_aggregation.py +77 -55
  174. junifer/markers/parcel_aggregation.py +91 -70
  175. junifer/markers/reho/__init__.pyi +7 -1
  176. junifer/markers/reho/_afni_reho.py +23 -14
  177. junifer/markers/reho/_junifer_reho.py +112 -123
  178. junifer/markers/reho/reho_base.py +115 -37
  179. junifer/markers/reho/reho_maps.py +18 -32
  180. junifer/markers/reho/reho_parcels.py +26 -41
  181. junifer/markers/reho/reho_spheres.py +33 -54
  182. junifer/markers/reho/tests/test_junifer_reho.py +178 -0
  183. junifer/markers/reho/tests/test_reho_maps.py +27 -25
  184. junifer/markers/reho/tests/test_reho_parcels.py +28 -22
  185. junifer/markers/reho/tests/test_reho_spheres.py +16 -12
  186. junifer/markers/sphere_aggregation.py +88 -72
  187. junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
  188. junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
  189. junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
  190. junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
  191. junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
  192. junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
  193. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
  194. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
  195. junifer/markers/tests/test_brainprint.py +10 -9
  196. junifer/markers/tests/test_ets_rss.py +7 -3
  197. junifer/markers/tests/test_lost_regions.py +279 -0
  198. junifer/markers/tests/test_maps_aggregation.py +67 -114
  199. junifer/markers/tests/test_marker_utils.py +40 -1
  200. junifer/markers/tests/test_markers_base.py +275 -6
  201. junifer/markers/tests/test_parcel_aggregation.py +91 -53
  202. junifer/markers/tests/test_sphere_aggregation.py +53 -47
  203. junifer/markers/utils.py +267 -5
  204. junifer/onthefly/__init__.py +2 -3
  205. junifer/onthefly/__init__.pyi +4 -0
  206. junifer/onthefly/_brainprint.py +3 -5
  207. junifer/onthefly/py.typed +0 -0
  208. junifer/onthefly/read_transform.py +11 -9
  209. junifer/onthefly/tests/test_read_transform.py +7 -7
  210. junifer/pipeline/__init__.pyi +4 -2
  211. junifer/pipeline/marker_collection.py +14 -10
  212. junifer/pipeline/pipeline_component_registry.py +25 -6
  213. junifer/pipeline/pipeline_step_mixin.py +5 -12
  214. junifer/pipeline/tests/test_data_object_dumper.py +10 -10
  215. junifer/pipeline/tests/test_marker_collection.py +145 -168
  216. junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
  217. junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
  218. junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
  219. junifer/pipeline/tests/test_workdir_manager.py +39 -0
  220. junifer/pipeline/update_meta_mixin.py +13 -10
  221. junifer/pipeline/utils.py +22 -17
  222. junifer/pipeline/workdir_manager.py +41 -32
  223. junifer/preprocess/__init__.pyi +9 -4
  224. junifer/preprocess/_temporal_filter.py +34 -33
  225. junifer/preprocess/_temporal_slicer.py +17 -30
  226. junifer/preprocess/base.py +99 -55
  227. junifer/preprocess/confounds/__init__.pyi +10 -2
  228. junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
  229. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
  230. junifer/preprocess/smoothing/__init__.pyi +2 -2
  231. junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
  232. junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
  233. junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
  234. junifer/preprocess/smoothing/smoothing.py +59 -40
  235. junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
  236. junifer/preprocess/tests/test_preprocess_base.py +17 -9
  237. junifer/preprocess/tests/test_temporal_filter.py +37 -16
  238. junifer/preprocess/tests/test_temporal_slicer.py +12 -12
  239. junifer/preprocess/warping/__init__.pyi +2 -2
  240. junifer/preprocess/warping/_ants_warper.py +56 -40
  241. junifer/preprocess/warping/_fsl_warper.py +49 -36
  242. junifer/preprocess/warping/_utils.py +106 -0
  243. junifer/preprocess/warping/space_warper.py +99 -98
  244. junifer/preprocess/warping/tests/test_space_warper.py +46 -41
  245. junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
  246. junifer/stats.py +46 -34
  247. junifer/storage/__init__.pyi +8 -4
  248. junifer/storage/base.py +129 -64
  249. junifer/storage/hdf5.py +307 -199
  250. junifer/storage/pandas_base.py +46 -24
  251. junifer/storage/sqlite.py +99 -62
  252. junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
  253. junifer/storage/tests/test_hdf5.py +147 -12
  254. junifer/storage/tests/test_repeated_storage.py +293 -0
  255. junifer/storage/tests/test_sqlite.py +9 -20
  256. junifer/storage/tests/test_storage_base.py +18 -21
  257. junifer/storage/tests/test_utils.py +83 -22
  258. junifer/storage/utils.py +58 -38
  259. junifer/testing/__init__.pyi +2 -2
  260. junifer/testing/datagrabbers.py +480 -44
  261. junifer/testing/registry.py +12 -0
  262. junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
  263. junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
  264. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
  265. junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
  266. junifer/testing/tests/test_testing_registry.py +31 -0
  267. junifer/testing/tests/test_testing_utils.py +35 -0
  268. junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
  269. junifer/testing/utils.py +33 -1
  270. junifer/tests/test_stats.py +1 -5
  271. junifer/typing/__init__.pyi +2 -0
  272. junifer/typing/_typing.py +34 -19
  273. junifer/typing/py.typed +0 -0
  274. junifer/utils/__init__.pyi +10 -1
  275. junifer/utils/_config.py +1 -2
  276. junifer/utils/helpers.py +68 -2
  277. junifer/utils/logging.py +31 -7
  278. junifer/utils/tests/test_helpers.py +36 -1
  279. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
  280. junifer-0.0.8.dist-info/RECORD +402 -0
  281. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
  282. junifer/cli/tests/test_parser.py +0 -280
  283. junifer/external/h5io/h5io/__init__.py +0 -15
  284. junifer/external/h5io/h5io/_h5io.py +0 -767
  285. junifer/external/h5io/h5io/_version.py +0 -1
  286. junifer/external/h5io/h5io/chunked_array.py +0 -94
  287. junifer/external/h5io/h5io/chunked_list.py +0 -65
  288. junifer/markers/complexity/__init__.pyi +0 -17
  289. junifer-0.0.7.dev332.dist-info/RECORD +0 -353
  290. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
  291. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
  292. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
  293. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,6 @@
4
4
  # License: AGPL
5
5
 
6
6
  from functools import lru_cache
7
- from itertools import product
8
7
  from pathlib import Path
9
8
  from typing import (
10
9
  TYPE_CHECKING,
@@ -15,12 +14,12 @@ import nibabel as nib
15
14
  import numpy as np
16
15
  import scipy as sp
17
16
  from nilearn import image as nimg
18
- from nilearn import masking as nmask
19
17
 
20
18
  from ...pipeline import WorkDirManager
21
19
  from ...typing import Dependencies
22
- from ...utils import logger, raise_error
20
+ from ...utils import raise_error
23
21
  from ...utils.singleton import Singleton
22
+ from ..base import logger
24
23
 
25
24
 
26
25
  if TYPE_CHECKING:
@@ -50,6 +49,7 @@ class JuniferReHo(metaclass=Singleton):
50
49
  self,
51
50
  input_path: Path,
52
51
  nneigh: int = 27,
52
+ mask_path: Path | None = None,
53
53
  ) -> tuple["Nifti1Image", Path]:
54
54
  """Compute ReHo map.
55
55
 
@@ -66,6 +66,11 @@ class JuniferReHo(metaclass=Singleton):
66
66
  * 125 : for 5x5 cuboidal volume
67
67
 
68
68
  (default 27).
69
+ mask_path : pathlib.Path or None, optional
70
+ Path to the mask to restrict the computation to. Voxels outside
71
+ the mask are excluded from every neighbourhood and set to 0 in
72
+ the output. If None, voxels with non-zero time series are used,
73
+ same as AFNI's ``3dReHo`` without ``-mask`` (default None).
69
74
 
70
75
  Returns
71
76
  -------
@@ -90,44 +95,21 @@ class JuniferReHo(metaclass=Singleton):
90
95
 
91
96
  # Get scan data
92
97
  niimg = nib.load(input_path)
93
- niimg_data = niimg.get_fdata().copy()
98
+ niimg_data = niimg.get_fdata(caching="unchanged")
94
99
  # Get scan dimensions
95
- n_x, n_y, n_z, _ = niimg_data.shape
96
-
97
- # Get rank of every voxel across time series
98
- ranks_niimg_data = sp.stats.rankdata(niimg_data, axis=-1)
99
-
100
- # Initialize 3D array to store tied rank correction for every voxel
101
- tied_rank_corrections = np.zeros((n_x, n_y, n_z), dtype=np.float64)
102
- # Calculate tied rank correction for every voxel
103
- for i_x, i_y, i_z in product(range(n_x), range(n_y), range(n_z)):
104
- # Calculate tied rank count for every voxel across time series
105
- _, tie_count = np.unique(
106
- ranks_niimg_data[i_x, i_y, i_z, :],
107
- return_counts=True,
108
- )
109
- # Calculate and store tied rank correction for every voxel across
110
- # timeseries
111
- tied_rank_corrections[i_x, i_y, i_z] = np.sum(
112
- tie_count**3 - tie_count
113
- )
100
+ n_x, n_y, n_z, n_t = niimg_data.shape
114
101
 
115
- # Initialize 3D array to store reho map
116
- reho_map = np.ones((n_x, n_y, n_z), dtype=np.float32)
117
-
118
- # TODO(synchon): this will give incorrect results if
119
- # template doesn't match, hence needs to be changed
120
- # after #299 is merged
121
- # Calculate whole brain mask
122
- mni152_whole_brain_mask = nmask.compute_brain_mask(
123
- target_img=niimg,
124
- threshold=0.5,
125
- mask_type="whole-brain",
126
- )
127
- # Convert 0 / 1 array to bool
128
- logical_mni152_whole_brain_mask = (
129
- mni152_whole_brain_mask.get_fdata().astype(bool)
130
- )
102
+ # Get mask as bool array
103
+ if mask_path is not None:
104
+ logical_mask = np.squeeze(nib.load(mask_path).get_fdata()) != 0
105
+ else:
106
+ # Same as AFNI's 3dReHo without -mask
107
+ logical_mask = np.sum(np.abs(niimg_data), axis=-1) > 1e-6
108
+
109
+ # Get rank of every voxel across time series and tied rank correction
110
+ # for every voxel
111
+ ranks_niimg_data, tied_rank_corrections = _rank_with_ties(niimg_data)
112
+ del niimg_data
131
113
 
132
114
  # Create mask cluster and set start and end indices
133
115
  if nneigh in (7, 19, 27):
@@ -165,66 +147,49 @@ class JuniferReHo(metaclass=Singleton):
165
147
  mask_cluster[2, 0, 2] = 0
166
148
  mask_cluster[2, 2, 2] = 0
167
149
 
168
- start_idx = 1
169
- end_idx = 2
170
-
171
150
  elif nneigh == 125:
172
151
  mask_cluster = np.ones((5, 5, 5))
173
- start_idx = 2
174
- end_idx = 3
175
-
176
- # Convert 0 / 1 array to bool
177
- logical_mask_cluster = mask_cluster.astype(bool)
178
-
179
- for i, j, k in product(
180
- range(start_idx, n_x - (end_idx - 1)),
181
- range(start_idx, n_y - (end_idx - 1)),
182
- range(start_idx, n_z - (end_idx - 1)),
183
- ):
184
- # Get mask only for neighbourhood
185
- logical_neighbourhood_mni152_whole_brain_mask = (
186
- logical_mni152_whole_brain_mask[
187
- i - start_idx : i + end_idx,
188
- j - start_idx : j + end_idx,
189
- k - start_idx : k + end_idx,
190
- ]
191
- )
192
- # Perform logical AND to get neighbourhood mask;
193
- # done to take care of brain boundaries
194
- neighbourhood_mask = (
195
- logical_mask_cluster
196
- & logical_neighbourhood_mni152_whole_brain_mask
197
- )
198
- # Continue if voxel is restricted by mask
199
- if neighbourhood_mask[1, 1, 1] == 0:
200
- continue
201
-
202
- # Get ranks for the neighbourhood
203
- neighbourhood_ranks = ranks_niimg_data[
204
- i - start_idx : i + end_idx,
205
- j - start_idx : j + end_idx,
206
- k - start_idx : k + end_idx,
207
- :,
208
- ]
209
- # Get tied ranks corrections for the neighbourhood
210
- neighbourhood_tied_ranks_corrections = tied_rank_corrections[
211
- i - start_idx : i + end_idx,
212
- j - start_idx : j + end_idx,
213
- k - start_idx : k + end_idx,
214
- ]
215
- # Mask neighbourhood ranks
216
- masked_neighbourhood_ranks = neighbourhood_ranks[
217
- logical_mask_cluster, :
218
- ]
219
- # Mask tied ranks corrections for the neighbourhood
220
- masked_tied_rank_corrections = (
221
- neighbourhood_tied_ranks_corrections[logical_mask_cluster]
222
- )
223
- # Calculate KCC
224
- reho_map[i, j, k] = _kendall_w_reho(
225
- timeseries_ranks=masked_neighbourhood_ranks,
226
- tied_rank_corrections=masked_tied_rank_corrections,
227
- )
152
+
153
+ # Exclude voxels outside the mask from every neighbourhood
154
+ ranks_niimg_data[~logical_mask] = 0
155
+ tied_rank_corrections[~logical_mask] = 0
156
+ # Sum over the neighbourhood of every voxel: number of voxels in the
157
+ # mask, ranks per timepoint and tied rank corrections; at the edges
158
+ # of the volume, only the neighbours within it are used
159
+ n_neighbours = sp.ndimage.correlate(
160
+ logical_mask.astype(np.float64),
161
+ mask_cluster,
162
+ mode="constant",
163
+ )
164
+ neighbourhood_rank_sums = sp.ndimage.correlate(
165
+ ranks_niimg_data,
166
+ mask_cluster[..., np.newaxis],
167
+ mode="constant",
168
+ )
169
+ del ranks_niimg_data
170
+ neighbourhood_tied_rank_corrections = sp.ndimage.correlate(
171
+ tied_rank_corrections,
172
+ mask_cluster,
173
+ mode="constant",
174
+ )
175
+ # Calculate Kendall's coefficient of concordance (KCC)
176
+ np.square(neighbourhood_rank_sums, out=neighbourhood_rank_sums)
177
+ numerator = (12 * np.sum(neighbourhood_rank_sums, axis=-1)) - (
178
+ 3 * n_neighbours**2 * n_t * (n_t + 1) ** 2
179
+ )
180
+ del neighbourhood_rank_sums
181
+ denominator = (n_neighbours**2 * n_t * (n_t**2 - 1)) - (
182
+ n_neighbours * neighbourhood_tied_rank_corrections
183
+ )
184
+ # If all the time series in the neighbourhood are fully tied, the
185
+ # denominator is 0 and KCC is set to 0, same as AFNI's 3dReHo
186
+ with np.errstate(divide="ignore", invalid="ignore"):
187
+ kcc = np.where(denominator == 0, 0.0, numerator / denominator)
188
+
189
+ # Initialize 3D array to store reho map; voxels outside the mask stay
190
+ # at 0, same as AFNI's 3dReHo
191
+ reho_map = np.zeros((n_x, n_y, n_z), dtype=np.float32)
192
+ reho_map[logical_mask] = kcc[logical_mask]
228
193
 
229
194
  # Create new image like target image
230
195
  output_data = nimg.new_img_like(
@@ -239,45 +204,69 @@ class JuniferReHo(metaclass=Singleton):
239
204
  element_tempdir = WorkDirManager().get_element_tempdir(
240
205
  prefix="junifer_reho"
241
206
  )
242
- output_path = element_tempdir / "output.nii.gz"
207
+ output_path = element_tempdir / "output.nii"
243
208
  # Save computed data to file
244
209
  nib.save(output_data, output_path)
245
210
 
246
211
  return output_data, output_path # type: ignore
247
212
 
248
213
 
249
- def _kendall_w_reho(
250
- timeseries_ranks: np.ndarray, tied_rank_corrections: np.ndarray
251
- ) -> float:
252
- """Calculate Kendall's coefficient of concordance (KCC) for ReHo map.
214
+ def _rank_with_ties(data: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
215
+ """Rank data along the last axis and compute tied rank corrections.
253
216
 
254
- ..note:: This function should only be used to calculate KCC for a ReHo map.
255
- For general use, check out ``junifer.stats.kendall_w``.
217
+ Equivalent to ``scipy.stats.rankdata(data, axis=-1)`` (tied values get
218
+ their average rank and NaN propagates to the whole time series), but
219
+ sorting only once.
256
220
 
257
221
  Parameters
258
222
  ----------
259
- timeseries_ranks : 2D numpy.ndarray
260
- A matrix of ranks of a subset subject's brain voxels.
261
- tied_rank_corrections : 3D numpy.ndarray
262
- A 3D array consisting of the tied rank corrections for the ranks
263
- of a subset subject's brain voxels.
223
+ data : numpy.ndarray
224
+ The data to rank along the last axis.
264
225
 
265
226
  Returns
266
227
  -------
267
- float
268
- Kendall's W (KCC) of the given timeseries matrix.
228
+ numpy.ndarray
229
+ The ranks of ``data`` along the last axis.
230
+ numpy.ndarray
231
+ The tied rank correction, the sum of ``t^3 - t`` over the groups of
232
+ ``t`` tied values, for every element along the last axis.
269
233
 
270
234
  """
271
- m, n = timeseries_ranks.shape # annotators X items
272
-
273
- numerator = (12 * np.sum(np.square(np.sum(timeseries_ranks, axis=0)))) - (
274
- 3 * m**2 * n * (n + 1) ** 2
235
+ n = data.shape[-1]
236
+ sort_idx = np.argsort(data, axis=-1, kind="stable")
237
+ sorted_data = np.take_along_axis(data, sort_idx, axis=-1)
238
+ positions = np.arange(1, n + 1)
239
+ # Mark the first and last sample of every tie group in sorted order
240
+ is_first = np.ones(data.shape, dtype=bool)
241
+ is_first[..., 1:] = sorted_data[..., 1:] != sorted_data[..., :-1]
242
+ del sorted_data
243
+ is_last = np.ones(data.shape, dtype=bool)
244
+ is_last[..., :-1] = is_first[..., 1:]
245
+ # Min and max rank of the tie group of every sample in sorted order
246
+ min_ranks = np.maximum.accumulate(
247
+ np.where(is_first, positions, 0), axis=-1
275
248
  )
276
- denominator = (m**2 * n * (n**2 - 1)) - (m * np.sum(tied_rank_corrections))
277
-
278
- if denominator == 0:
279
- kcc = 1.0
280
- else:
281
- kcc = numerator / denominator
282
-
283
- return kcc
249
+ del is_first
250
+ max_ranks = np.flip(
251
+ np.minimum.accumulate(
252
+ np.flip(np.where(is_last, positions, n + 1), axis=-1), axis=-1
253
+ ),
254
+ axis=-1,
255
+ )
256
+ del is_last
257
+ # Every sample belongs to a tie group of size t = max - min + 1, and
258
+ # summing t^2 - 1 over the samples is the same as summing t^3 - t over
259
+ # the tie groups
260
+ tie_sizes = max_ranks - min_ranks + 1
261
+ tied_rank_corrections = np.sum(tie_sizes**2 - 1, axis=-1).astype(
262
+ np.float64
263
+ )
264
+ del tie_sizes
265
+ # Tied values get their average rank; put back in the original order
266
+ ranks = np.empty(data.shape, dtype=np.float64)
267
+ np.put_along_axis(ranks, sort_idx, (min_ranks + max_ranks) / 2, axis=-1)
268
+ # Propagate NaN to the whole time series, same as scipy.stats.rankdata
269
+ has_nan = np.isnan(data).any(axis=-1)
270
+ ranks[has_nan] = np.nan
271
+ tied_rank_corrections[has_nan] = np.nan
272
+ return ranks, tied_rank_corrections
@@ -3,17 +3,24 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ from enum import StrEnum
6
7
  from pathlib import Path
7
8
  from typing import (
8
9
  TYPE_CHECKING,
10
+ Annotated,
9
11
  Any,
10
12
  ClassVar,
11
- Optional,
12
13
  )
13
14
 
15
+ from pydantic import BeforeValidator
16
+
17
+ from ...data import get_data
18
+ from ...datagrabber import DataType
19
+ from ...storage import StorageType
14
20
  from ...typing import ConditionalDependencies, MarkerInOutMappings
15
- from ...utils import logger, raise_error
16
- from ..base import BaseMarker
21
+ from ...utils import ensure_list_or_none
22
+ from ..base import BaseMarker, logger
23
+ from ..utils import _save_mask
17
24
  from ._afni_reho import AFNIReHo
18
25
  from ._junifer_reho import JuniferReHo
19
26
 
@@ -21,7 +28,20 @@ from ._junifer_reho import JuniferReHo
21
28
  if TYPE_CHECKING:
22
29
  from nibabel.nifti1 import Nifti1Image
23
30
 
24
- __all__ = ["ReHoBase"]
31
+
32
+ __all__ = ["ReHoBase", "ReHoImpl"]
33
+
34
+
35
+ class ReHoImpl(StrEnum):
36
+ """Accepted ReHo implementations.
37
+
38
+ * ``junifer`` : ``junifer``'s ReHo
39
+ * ``afni`` : AFNI's ``3dReHo``
40
+
41
+ """
42
+
43
+ junifer = "junifer"
44
+ afni = "afni"
25
45
 
26
46
 
27
47
  class ReHoBase(BaseMarker):
@@ -29,57 +49,98 @@ class ReHoBase(BaseMarker):
29
49
 
30
50
  Parameters
31
51
  ----------
32
- using : {"junifer", "afni"}
33
- Implementation to use for computing ReHo:
34
-
35
- * "junifer" : Use ``junifer``'s own ReHo implementation
36
- * "afni" : Use AFNI's ``3dReHo``
37
-
38
- name : str, optional
39
- The name of the marker. If None, it will use the class name
40
- (default None).
41
-
42
- Raises
43
- ------
44
- ValueError
45
- If ``using`` is invalid.
52
+ using : :enum:`.ReHoImpl`
53
+ reho_params : dict or None, optional
54
+ Extra parameters for computing ReHo map as a dictionary (default None).
55
+ If ``using=ReHoImpl.afni``, then the valid keys are:
56
+
57
+ * ``nneigh`` : {7, 19, 27}, optional (default 27)
58
+ Number of voxels in the neighbourhood, inclusive. Can be:
59
+
60
+ - 7 : for facewise neighbours only
61
+ - 19 : for face- and edge-wise neighbours
62
+ - 27 : for face-, edge-, and node-wise neighbors
63
+
64
+ * ``neigh_rad`` : positive float, optional
65
+ The radius of a desired neighbourhood (default None).
66
+ * ``neigh_x`` : positive float, optional
67
+ The semi-radius for x-axis of ellipsoidal volumes (default None).
68
+ * ``neigh_y`` : positive float, optional
69
+ The semi-radius for y-axis of ellipsoidal volumes (default None).
70
+ * ``neigh_z`` : positive float, optional
71
+ The semi-radius for z-axis of ellipsoidal volumes (default None).
72
+ * ``box_rad`` : positive int, optional
73
+ The number of voxels outward in a given cardinal direction for a
74
+ cubic box centered on a given voxel (default None).
75
+ * ``box_x`` : positive int, optional
76
+ The number of voxels for +/- x-axis of cuboidal volumes
77
+ (default None).
78
+ * ``box_y`` : positive int, optional
79
+ The number of voxels for +/- y-axis of cuboidal volumes
80
+ (default None).
81
+ * ``box_z`` : positive int, optional
82
+ The number of voxels for +/- z-axis of cuboidal volumes
83
+ (default None).
84
+
85
+ else if ``using=ReHoImpl.junifer``, then the valid keys are:
86
+
87
+ * ``nneigh`` : {7, 19, 27, 125}, optional (default 27)
88
+ Number of voxels in the neighbourhood, inclusive. Can be:
89
+
90
+ * 7 : for facewise neighbours only
91
+ * 19 : for face- and edge-wise neighbours
92
+ * 27 : for face-, edge-, and node-wise neighbors
93
+ * 125 : for 5x5 cuboidal volume
94
+
95
+ agg_method : str, optional
96
+ The aggregation function to use.
97
+ See :func:`.get_aggfunc_by_name` for options
98
+ (default "mean").
99
+ agg_method_params : dict or None, optional
100
+ The parameters to pass to the aggregation function.
101
+ See :func:`.get_aggfunc_by_name` for options (default None).
102
+ masks : str, dict, list of them or None, optional
103
+ The specification of the masks to apply. The ReHo computation is
104
+ restricted to the voxels in the mask, and the mask is also applied
105
+ to regions before extracting signals. Check
106
+ :ref:`Using Masks <using_masks>` for more details.
107
+ If None, will not apply any mask (default None).
108
+ name : str or None, optional
109
+ The name of the marker.
110
+ If None, it will use the class name (default None).
46
111
 
47
112
  """
48
113
 
49
114
  _CONDITIONAL_DEPENDENCIES: ClassVar[ConditionalDependencies] = [
50
115
  {
51
- "using": "afni",
52
- "depends_on": AFNIReHo,
116
+ "using": ReHoImpl.afni,
117
+ "depends_on": [AFNIReHo],
53
118
  },
54
119
  {
55
- "using": "junifer",
56
- "depends_on": JuniferReHo,
120
+ "using": ReHoImpl.junifer,
121
+ "depends_on": [JuniferReHo],
57
122
  },
58
123
  ]
59
124
 
60
125
  _MARKER_INOUT_MAPPINGS: ClassVar[MarkerInOutMappings] = {
61
- "BOLD": {
62
- "reho": "vector",
126
+ DataType.BOLD: {
127
+ "reho": StorageType.Vector,
63
128
  },
64
129
  }
65
130
 
66
- def __init__(
67
- self,
68
- using: str,
69
- name: Optional[str] = None,
70
- ) -> None:
71
- # Validate `using` parameter
72
- valid_using = [dep["using"] for dep in self._CONDITIONAL_DEPENDENCIES]
73
- if using not in valid_using:
74
- raise_error(
75
- f"Invalid value for `using`, should be one of: {valid_using}"
76
- )
77
- self.using = using
78
- super().__init__(on="BOLD", name=name)
131
+ using: ReHoImpl
132
+ reho_params: dict | None = None
133
+ agg_method: str = "mean"
134
+ agg_method_params: dict | None = None
135
+ masks: Annotated[
136
+ dict | str | list[dict | str] | None,
137
+ BeforeValidator(ensure_list_or_none),
138
+ ] = None
79
139
 
80
140
  def _compute(
81
141
  self,
82
142
  input_data: dict[str, Any],
143
+ extra_input: dict[str, Any] | None = None,
83
144
  **reho_params: Any,
84
145
  ) -> tuple["Nifti1Image", Path]:
85
146
  """Compute voxel-wise ReHo.
@@ -95,6 +156,9 @@ class ReHoBase(BaseMarker):
95
156
  ----------
96
157
  input_data : dict
97
158
  The BOLD data as dictionary.
159
+ extra_input : dict, optional
160
+ The other fields in the pipeline data object. Used to compute
161
+ the mask in the space of ``input_data`` (default None).
98
162
  **reho_params : dict
99
163
  Extra keyword arguments for ReHo.
100
164
 
@@ -122,9 +186,23 @@ class ReHoBase(BaseMarker):
122
186
  estimator = AFNIReHo()
123
187
  elif self.using == "junifer":
124
188
  estimator = JuniferReHo()
189
+ # Get mask in the space of the input data
190
+ mask_path = None
191
+ if self.masks is not None:
192
+ logger.debug(f"Masking with {self.masks}")
193
+ mask_path = _save_mask(
194
+ get_data(
195
+ kind="mask",
196
+ names=self.masks,
197
+ target_data=input_data,
198
+ extra_input=extra_input,
199
+ ),
200
+ prefix="reho_mask",
201
+ )
125
202
  # Compute reho
126
203
  reho_map, reho_map_path = estimator.compute( # type: ignore
127
204
  input_path=input_data["path"],
205
+ mask_path=mask_path,
128
206
  **reho_params,
129
207
  )
130
208
 
@@ -3,12 +3,13 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import Any, Optional, Union
6
+ from typing import Any
7
7
 
8
8
  import numpy as np
9
9
 
10
10
  from ...api.decorators import register_marker
11
- from ...utils import logger
11
+ from ...datagrabber import DataType
12
+ from ..base import logger
12
13
  from ..maps_aggregation import MapsAggregation
13
14
  from .reho_base import ReHoBase
14
15
 
@@ -25,15 +26,10 @@ class ReHoMaps(ReHoBase):
25
26
  maps : str
26
27
  The name of the map(s) to use.
27
28
  See :func:`.list_data` for options.
28
- using : {"junifer", "afni"}
29
- Implementation to use for computing ReHo:
30
-
31
- * "junifer" : Use ``junifer``'s own ReHo implementation
32
- * "afni" : Use AFNI's ``3dReHo``
33
-
34
- reho_params : dict, optional
29
+ using : :enum:`.ReHoImpl`
30
+ reho_params : dict or None, optional
35
31
  Extra parameters for computing ReHo map as a dictionary (default None).
36
- If ``using="afni"``, then the valid keys are:
32
+ If ``using=ReHoImpl.afni``, then the valid keys are:
37
33
 
38
34
  * ``nneigh`` : {7, 19, 27}, optional (default 27)
39
35
  Number of voxels in the neighbourhood, inclusive. Can be:
@@ -63,7 +59,7 @@ class ReHoMaps(ReHoBase):
63
59
  The number of voxels for +/- z-axis of cuboidal volumes
64
60
  (default None).
65
61
 
66
- else if ``using="junifer"``, then the valid keys are:
62
+ else if ``using=ReHoImpl.junifer``, then the valid keys are:
67
63
 
68
64
  * ``nneigh`` : {7, 19, 27, 125}, optional (default 27)
69
65
  Number of voxels in the neighbourhood, inclusive. Can be:
@@ -73,34 +69,22 @@ class ReHoMaps(ReHoBase):
73
69
  * 27 : for face-, edge-, and node-wise neighbors
74
70
  * 125 : for 5x5 cuboidal volume
75
71
 
76
- masks : str, dict or list of dict or str, optional
72
+ masks : str, dict, list of them or None, optional
77
73
  The specification of the masks to apply to regions before extracting
78
74
  signals. Check :ref:`Using Masks <using_masks>` for more details.
79
75
  If None, will not apply any mask (default None).
80
- name : str, optional
81
- The name of the marker. If None, it will use the class name
82
- (default None).
76
+ name : str or None, optional
77
+ The name of the marker.
78
+ If None, it will use the class name (default None).
83
79
 
84
80
  """
85
81
 
86
- def __init__(
87
- self,
88
- maps: str,
89
- using: str,
90
- reho_params: Optional[dict] = None,
91
- masks: Union[str, dict, list[Union[dict, str]], None] = None,
92
- name: Optional[str] = None,
93
- ) -> None:
94
- # Superclass init first to validate `using` parameter
95
- super().__init__(using=using, name=name)
96
- self.maps = maps
97
- self.reho_params = reho_params
98
- self.masks = masks
82
+ maps: str
99
83
 
100
84
  def compute(
101
85
  self,
102
86
  input: dict[str, Any],
103
- extra_input: Optional[dict[str, Any]] = None,
87
+ extra_input: dict[str, Any] | None = None,
104
88
  ) -> dict[str, Any]:
105
89
  """Compute.
106
90
 
@@ -132,10 +116,12 @@ class ReHoMaps(ReHoBase):
132
116
  # transformation to native space.
133
117
  if self.reho_params is not None:
134
118
  reho_map, reho_file_path = self._compute(
135
- input_data=input, **self.reho_params
119
+ input_data=input, extra_input=extra_input, **self.reho_params
136
120
  )
137
121
  else:
138
- reho_map, reho_file_path = self._compute(input_data=input)
122
+ reho_map, reho_file_path = self._compute(
123
+ input_data=input, extra_input=extra_input
124
+ )
139
125
 
140
126
  # Perform aggregation on reho map
141
127
  aggregation_input = dict(input.items())
@@ -144,7 +130,7 @@ class ReHoMaps(ReHoBase):
144
130
  maps_aggregation = MapsAggregation(
145
131
  maps=self.maps,
146
132
  masks=self.masks,
147
- on="BOLD",
133
+ on=DataType.BOLD,
148
134
  ).compute(
149
135
  input=aggregation_input,
150
136
  extra_input=extra_input,