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
@@ -3,12 +3,15 @@
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 Annotated, Any
7
7
 
8
8
  import numpy as np
9
+ from pydantic import BeforeValidator
9
10
 
10
11
  from ...api.decorators import register_marker
11
- from ...utils import logger
12
+ from ...datagrabber import DataType
13
+ from ...utils import ensure_list
14
+ from ..base import logger
12
15
  from ..parcel_aggregation import ParcelAggregation
13
16
  from .reho_base import ReHoBase
14
17
 
@@ -25,15 +28,10 @@ class ReHoParcels(ReHoBase):
25
28
  parcellation : str or list of str
26
29
  The name(s) of the parcellation(s) to use.
27
30
  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
31
+ using : :enum:`.ReHoImpl`
32
+ reho_params : dict or None, optional
35
33
  Extra parameters for computing ReHo map as a dictionary (default None).
36
- If ``using="afni"``, then the valid keys are:
34
+ If ``using=ReHoImpl.afni``, then the valid keys are:
37
35
 
38
36
  * ``nneigh`` : {7, 19, 27}, optional (default 27)
39
37
  Number of voxels in the neighbourhood, inclusive. Can be:
@@ -63,7 +61,7 @@ class ReHoParcels(ReHoBase):
63
61
  The number of voxels for +/- z-axis of cuboidal volumes
64
62
  (default None).
65
63
 
66
- else if ``using="junifer"``, then the valid keys are:
64
+ else if ``using=ReHoImpl.junifer``, then the valid keys are:
67
65
 
68
66
  * ``nneigh`` : {7, 19, 27, 125}, optional (default 27)
69
67
  Number of voxels in the neighbourhood, inclusive. Can be:
@@ -74,43 +72,28 @@ class ReHoParcels(ReHoBase):
74
72
  * 125 : for 5x5 cuboidal volume
75
73
 
76
74
  agg_method : str, optional
77
- The method to perform aggregation using. Check valid options in
78
- :func:`.get_aggfunc_by_name` (default "mean").
79
- agg_method_params : dict, optional
80
- Parameters to pass to the aggregation function. Check valid options in
81
- :func:`.get_aggfunc_by_name` (default None).
82
- masks : str, dict or list of dict or str, optional
75
+ The aggregation function to use.
76
+ See :func:`.get_aggfunc_by_name` for options
77
+ (default "mean").
78
+ agg_method_params : dict or None, optional
79
+ The parameters to pass to the aggregation function.
80
+ See :func:`.get_aggfunc_by_name` for options (default None).
81
+ masks : str, dict, list of them or None, optional
83
82
  The specification of the masks to apply to regions before extracting
84
83
  signals. Check :ref:`Using Masks <using_masks>` for more details.
85
84
  If None, will not apply any mask (default None).
86
- name : str, optional
87
- The name of the marker. If None, it will use the class name
88
- (default None).
85
+ name : str or None, optional
86
+ The name of the marker.
87
+ If None, it will use the class name (default None).
89
88
 
90
89
  """
91
90
 
92
- def __init__(
93
- self,
94
- parcellation: Union[str, list[str]],
95
- using: str,
96
- reho_params: Optional[dict] = None,
97
- agg_method: str = "mean",
98
- agg_method_params: Optional[dict] = None,
99
- masks: Union[str, dict, list[Union[dict, str]], None] = None,
100
- name: Optional[str] = None,
101
- ) -> None:
102
- # Superclass init first to validate `using` parameter
103
- super().__init__(using=using, name=name)
104
- self.parcellation = parcellation
105
- self.reho_params = reho_params
106
- self.agg_method = agg_method
107
- self.agg_method_params = agg_method_params
108
- self.masks = masks
91
+ parcellation: Annotated[str | list[str], BeforeValidator(ensure_list)]
109
92
 
110
93
  def compute(
111
94
  self,
112
95
  input: dict[str, Any],
113
- extra_input: Optional[dict[str, Any]] = None,
96
+ extra_input: dict[str, Any] | None = None,
114
97
  ) -> dict[str, Any]:
115
98
  """Compute.
116
99
 
@@ -142,10 +125,12 @@ class ReHoParcels(ReHoBase):
142
125
  # transformation to native space.
143
126
  if self.reho_params is not None:
144
127
  reho_map, reho_file_path = self._compute(
145
- input_data=input, **self.reho_params
128
+ input_data=input, extra_input=extra_input, **self.reho_params
146
129
  )
147
130
  else:
148
- reho_map, reho_file_path = self._compute(input_data=input)
131
+ reho_map, reho_file_path = self._compute(
132
+ input_data=input, extra_input=extra_input
133
+ )
149
134
 
150
135
  # Perform aggregation on reho map
151
136
  aggregation_input = dict(input.items())
@@ -156,7 +141,7 @@ class ReHoParcels(ReHoBase):
156
141
  method=self.agg_method,
157
142
  method_params=self.agg_method_params,
158
143
  masks=self.masks,
159
- on="BOLD",
144
+ on=DataType.BOLD,
160
145
  ).compute(
161
146
  input=aggregation_input,
162
147
  extra_input=extra_input,
@@ -3,12 +3,14 @@
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, Literal
7
7
 
8
8
  import numpy as np
9
+ from pydantic import PositiveFloat
9
10
 
10
11
  from ...api.decorators import register_marker
11
- from ...utils import logger
12
+ from ...datagrabber import DataType
13
+ from ..base import logger
12
14
  from ..sphere_aggregation import SphereAggregation
13
15
  from .reho_base import ReHoBase
14
16
 
@@ -25,26 +27,18 @@ class ReHoSpheres(ReHoBase):
25
27
  coords : str
26
28
  The name of the coordinates list to use.
27
29
  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
- radius : float, optional
35
- The radius of the sphere in millimeters. If None, the signal will be
36
- extracted from a single voxel. See
37
- :class:`nilearn.maskers.NiftiSpheresMasker` for more information
30
+ using : :enum:`.ReHoImpl`
31
+ radius : ``zero`` or positive float or None, optional
32
+ The radius of the sphere in millimetres.
33
+ If None, the signal will be extracted from a single voxel.
34
+ See :class:`.JuniferNiftiSpheresMasker` for more information
38
35
  (default None).
39
36
  allow_overlap : bool, optional
40
- Whether to allow overlapping spheres. If False, an error is raised if
41
- the spheres overlap (default is False).
42
- use_afni : bool, optional
43
- Whether to use AFNI for computing. If None, will use AFNI only
44
- if available (default None).
45
- reho_params : dict, optional
37
+ Whether to allow overlapping spheres.
38
+ If False, an error is raised if the spheres overlap (default False).
39
+ reho_params : dict or None, optional
46
40
  Extra parameters for computing ReHo map as a dictionary (default None).
47
- If ``using="afni"``, then the valid keys are:
41
+ If ``using=ReHoImpl.afni``, then the valid keys are:
48
42
 
49
43
  * ``nneigh`` : {7, 19, 27}, optional (default 27)
50
44
  Number of voxels in the neighbourhood, inclusive. Can be:
@@ -74,7 +68,7 @@ class ReHoSpheres(ReHoBase):
74
68
  The number of voxels for +/- z-axis of cuboidal volumes
75
69
  (default None).
76
70
 
77
- else if ``using="junifer"``, then the valid keys are:
71
+ else if ``using=ReHoImpl.junifer``, then the valid keys are:
78
72
 
79
73
  * ``nneigh`` : {7, 19, 27, 125}, optional (default 27)
80
74
  Number of voxels in the neighbourhood, inclusive. Can be:
@@ -85,47 +79,30 @@ class ReHoSpheres(ReHoBase):
85
79
  * 125 : for 5x5 cuboidal volume
86
80
 
87
81
  agg_method : str, optional
88
- The aggregation method to use.
89
- See :func:`.get_aggfunc_by_name` for more information
90
- (default None).
91
- agg_method_params : dict, optional
92
- The parameters to pass to the aggregation method (default None).
93
- masks : str, dict or list of dict or str, optional
82
+ The aggregation function to use.
83
+ See :func:`.get_aggfunc_by_name` for options
84
+ (default "mean").
85
+ agg_method_params : dict or None, optional
86
+ The parameters to pass to the aggregation function.
87
+ See :func:`.get_aggfunc_by_name` for options (default None).
88
+ masks : str, dict, list of them or None, optional
94
89
  The specification of the masks to apply to regions before extracting
95
90
  signals. Check :ref:`Using Masks <using_masks>` for more details.
96
91
  If None, will not apply any mask (default None).
97
- name : str, optional
98
- The name of the marker. If None, it will use the class name
99
- (default None).
92
+ name : str or None, optional
93
+ The name of the marker.
94
+ If None, it will use the class name (default None).
100
95
 
101
96
  """
102
97
 
103
- def __init__(
104
- self,
105
- coords: str,
106
- using: str,
107
- radius: Optional[float] = None,
108
- allow_overlap: bool = False,
109
- reho_params: Optional[dict] = None,
110
- agg_method: str = "mean",
111
- agg_method_params: Optional[dict] = None,
112
- masks: Union[str, dict, list[Union[dict, str]], None] = None,
113
- name: Optional[str] = None,
114
- ) -> None:
115
- # Superclass init first to validate `using` parameter
116
- super().__init__(using=using, name=name)
117
- self.coords = coords
118
- self.radius = radius
119
- self.allow_overlap = allow_overlap
120
- self.reho_params = reho_params
121
- self.agg_method = agg_method
122
- self.agg_method_params = agg_method_params
123
- self.masks = masks
98
+ coords: str
99
+ radius: Literal[0] | PositiveFloat | None = None
100
+ allow_overlap: bool = False
124
101
 
125
102
  def compute(
126
103
  self,
127
104
  input: dict[str, Any],
128
- extra_input: Optional[dict[str, Any]] = None,
105
+ extra_input: dict[str, Any] | None = None,
129
106
  ) -> dict[str, Any]:
130
107
  """Compute.
131
108
 
@@ -157,10 +134,12 @@ class ReHoSpheres(ReHoBase):
157
134
  # transformation to native space.
158
135
  if self.reho_params is not None:
159
136
  reho_map, reho_file_path = self._compute(
160
- input_data=input, **self.reho_params
137
+ input_data=input, extra_input=extra_input, **self.reho_params
161
138
  )
162
139
  else:
163
- reho_map, reho_file_path = self._compute(input_data=input)
140
+ reho_map, reho_file_path = self._compute(
141
+ input_data=input, extra_input=extra_input
142
+ )
164
143
 
165
144
  # Perform aggregation on reho map
166
145
  aggregation_input = dict(input.items())
@@ -173,7 +152,7 @@ class ReHoSpheres(ReHoBase):
173
152
  method=self.agg_method,
174
153
  method_params=self.agg_method_params,
175
154
  masks=self.masks,
176
- on="BOLD",
155
+ on=DataType.BOLD,
177
156
  ).compute(input=aggregation_input, extra_input=extra_input)
178
157
 
179
158
  return {
@@ -0,0 +1,178 @@
1
+ """Provide tests for ReHo computation using junifer."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from pathlib import Path
7
+
8
+ import nibabel as nib
9
+ import numpy as np
10
+ import pytest
11
+ import scipy as sp
12
+ from numpy.testing import assert_allclose, assert_array_equal
13
+
14
+ from junifer.markers.reho._afni_reho import AFNIReHo
15
+ from junifer.markers.reho._junifer_reho import JuniferReHo, _rank_with_ties
16
+ from junifer.pipeline import WorkDirManager
17
+ from junifer.pipeline.utils import _check_afni
18
+
19
+
20
+ def _tied_rank_corrections(ranks: np.ndarray) -> np.ndarray:
21
+ """Compute tied rank corrections one time series at a time."""
22
+ out = np.zeros(ranks.shape[:-1])
23
+ for idx in np.ndindex(ranks.shape[:-1]):
24
+ _, tie_count = np.unique(ranks[idx], return_counts=True)
25
+ out[idx] = np.sum(tie_count**3 - tie_count)
26
+ return out
27
+
28
+
29
+ @pytest.mark.parametrize(
30
+ "shape, scale",
31
+ [
32
+ ((4, 3, 2, 50), 3), # many ties
33
+ ((3, 3, 3, 30), 100), # few ties
34
+ ((2, 2, 2, 10), 0), # all values tied
35
+ ],
36
+ )
37
+ def test_rank_with_ties(shape: tuple[int, ...], scale: float) -> None:
38
+ """Test ranking with ties against scipy.
39
+
40
+ Parameters
41
+ ----------
42
+ shape : tuple of int
43
+ The shape of the data.
44
+ scale : float
45
+ The scale of the data before rounding.
46
+
47
+ """
48
+ data = np.round(np.random.default_rng(0).normal(size=shape) * scale)
49
+ ranks, tied_rank_corrections = _rank_with_ties(data)
50
+ expected_ranks = sp.stats.rankdata(data, axis=-1)
51
+ assert_array_equal(ranks, expected_ranks)
52
+ assert_array_equal(
53
+ tied_rank_corrections, _tied_rank_corrections(expected_ranks)
54
+ )
55
+
56
+
57
+ def test_rank_with_ties_nan() -> None:
58
+ """Test NaN propagates to the whole time series."""
59
+ data = np.array([[1.0, np.nan, 2.0], [1.0, 3.0, 2.0]])
60
+ ranks, tied_rank_corrections = _rank_with_ties(data)
61
+ assert np.isnan(ranks[0]).all()
62
+ assert np.isnan(tied_rank_corrections[0])
63
+ assert_array_equal(ranks[1], [1, 3, 2])
64
+ assert tied_rank_corrections[1] == 0
65
+
66
+
67
+ def _save(data: np.ndarray, path: Path) -> Path:
68
+ """Save data as NIfTI."""
69
+ nib.save(nib.Nifti1Image(data, np.eye(4)), path)
70
+ return path
71
+
72
+
73
+ def test_JuniferReHo_nan(tmp_path: Path) -> None:
74
+ """Test NaN time series do not give valid ReHo values.
75
+
76
+ Parameters
77
+ ----------
78
+ tmp_path : pathlib.Path
79
+ The path to the test directory.
80
+
81
+ """
82
+ WorkDirManager().workdir = tmp_path
83
+ data = np.random.default_rng(0).normal(size=(7, 7, 7, 20))
84
+ data[3, 3, 3, 5] = np.nan
85
+ bold_path = _save(data.astype(np.float32), tmp_path / "bold_nan.nii")
86
+ mask_path = _save(np.ones((7, 7, 7), dtype=np.uint8), tmp_path / "m.nii")
87
+
88
+ # With a mask including the NaN voxel, every neighbourhood with it is NaN
89
+ reho = JuniferReHo().compute(bold_path, mask_path=mask_path)[0]
90
+ reho = reho.get_fdata()
91
+ assert np.isnan(reho[2:5, 2:5, 2:5]).all()
92
+ reho[2:5, 2:5, 2:5] = 0
93
+ assert np.isfinite(reho).all()
94
+
95
+ # Without mask, the NaN voxel is excluded as AFNI's 3dReHo does
96
+ reho = JuniferReHo().compute(bold_path)[0].get_fdata()
97
+ assert np.isfinite(reho).all()
98
+ assert reho[3, 3, 3] == 0
99
+
100
+
101
+ def test_JuniferReHo_mask(tmp_path: Path) -> None:
102
+ """Test ReHo is restricted to the mask.
103
+
104
+ Parameters
105
+ ----------
106
+ tmp_path : pathlib.Path
107
+ The path to the test directory.
108
+
109
+ """
110
+ WorkDirManager().workdir = tmp_path
111
+ data = np.random.default_rng(0).normal(size=(7, 7, 7, 20))
112
+ bold_path = _save(data.astype(np.float32), tmp_path / "bold.nii")
113
+ mask = np.ones((7, 7, 7), dtype=np.uint8)
114
+ mask[:3] = 0
115
+ mask_path = _save(mask, tmp_path / "mask.nii")
116
+
117
+ masked = JuniferReHo().compute(bold_path, mask_path=mask_path)[0]
118
+ masked = masked.get_fdata()
119
+ unmasked = JuniferReHo().compute(bold_path)[0].get_fdata()
120
+ # Outside the mask is 0
121
+ assert (masked[:3] == 0).all()
122
+ # Next to the mask boundary, neighbours outside the mask are excluded
123
+ assert not np.allclose(masked[3, 1:-1, 1:-1], unmasked[3, 1:-1, 1:-1])
124
+ # Far from the mask boundary, nothing changes
125
+ assert_array_equal(masked[4:-1, 1:-1, 1:-1], unmasked[4:-1, 1:-1, 1:-1])
126
+
127
+
128
+ @pytest.mark.external
129
+ @pytest.mark.skipif(
130
+ _check_afni() is False, reason="requires AFNI to be in PATH"
131
+ )
132
+ @pytest.mark.parametrize("nneigh", [7, 19, 27])
133
+ @pytest.mark.parametrize("use_mask", [False, True])
134
+ def test_JuniferReHo_vs_AFNI(
135
+ tmp_path: Path, nneigh: int, use_mask: bool
136
+ ) -> None:
137
+ """Test junifer and AFNI give the same ReHo map.
138
+
139
+ The data covers the edges of the volume, fully tied neighbourhoods (the
140
+ denominator of KCC is 0), voxels with all-zero time series and ties.
141
+
142
+ Parameters
143
+ ----------
144
+ tmp_path : pathlib.Path
145
+ The path to the test directory.
146
+ nneigh : int
147
+ The number of voxels in the neighbourhood.
148
+ use_mask : bool
149
+ Whether to use a mask.
150
+
151
+ """
152
+ WorkDirManager().workdir = tmp_path
153
+ data = np.round(
154
+ np.random.default_rng(0).normal(size=(9, 8, 7, 25)) * 3
155
+ ).astype(np.float32)
156
+ # Constant time series: fully tied neighbourhoods
157
+ data[:4, :4, :4] = 5
158
+ # All-zero time series: outside AFNI's implicit mask
159
+ data[6:, 5:, 4:] = 0
160
+ bold_path = _save(data, tmp_path / "bold.nii")
161
+ mask_path = None
162
+ if use_mask:
163
+ mask = np.ones(data.shape[:3], dtype=np.uint8)
164
+ mask[:, :, 0] = 0
165
+ mask[4, 4, 3] = 0
166
+ mask_path = _save(mask, tmp_path / "mask.nii")
167
+
168
+ junifer_reho = JuniferReHo().compute(
169
+ bold_path, nneigh=nneigh, mask_path=mask_path
170
+ )[0]
171
+ afni_reho = AFNIReHo().compute(
172
+ bold_path, nneigh=nneigh, mask_path=mask_path
173
+ )[0]
174
+ assert_allclose(
175
+ junifer_reho.get_fdata(),
176
+ np.squeeze(afni_reho.get_fdata()),
177
+ atol=1e-6,
178
+ )
@@ -7,20 +7,23 @@ import logging
7
7
  from pathlib import Path
8
8
 
9
9
  import pytest
10
- import scipy as sp
10
+ from numpy.testing import assert_allclose
11
11
 
12
- from junifer.datagrabber import PatternDataladDataGrabber
12
+ from junifer.datagrabber import DataType
13
13
  from junifer.datareader import DefaultDataReader
14
- from junifer.markers import ReHoMaps
14
+ from junifer.markers import ReHoImpl, ReHoMaps
15
15
  from junifer.pipeline import WorkDirManager
16
16
  from junifer.pipeline.utils import _check_afni
17
17
  from junifer.storage import HDF5FeatureStorage
18
+ from junifer.testing.datagrabbers import ADHDTestingDataGrabber
19
+
20
+
21
+ pytestmark = pytest.mark.external
18
22
 
19
23
 
20
24
  def test_ReHoMaps(
21
25
  caplog: pytest.LogCaptureFixture,
22
26
  tmp_path: Path,
23
- maps_datagrabber: PatternDataladDataGrabber,
24
27
  ) -> None:
25
28
  """Test ReHoMaps.
26
29
 
@@ -30,26 +33,24 @@ def test_ReHoMaps(
30
33
  The pytest.LogCaptureFixture object.
31
34
  tmp_path : pathlib.Path
32
35
  The path to the test directory.
33
- maps_datagrabber : PatternDataladDataGrabber
34
- The testing PatternDataladDataGrabber, as fixture.
35
36
 
36
37
  """
37
38
  # Update workdir to current test's tmp_path
38
39
  WorkDirManager().workdir = tmp_path
39
40
 
40
41
  with caplog.at_level(logging.DEBUG):
41
- with maps_datagrabber as dg:
42
- element = dg[("sub-01", "sub-001", "rest", "1")]
42
+ with ADHDTestingDataGrabber() as dg:
43
+ element = dg["sub-01"]
43
44
  element_data = DefaultDataReader().fit_transform(element)
44
45
 
45
46
  # Initialize marker
46
47
  marker = ReHoMaps(
47
48
  maps="Smith_rsn_10",
48
- using="junifer",
49
+ using=ReHoImpl.junifer,
49
50
  )
50
51
  # Check correct output
51
52
  assert "vector" == marker.storage_type(
52
- input_type="BOLD", output_feature="reho"
53
+ input_type=DataType.BOLD, output_feature="reho"
53
54
  )
54
55
 
55
56
  # Fit transform marker on data
@@ -75,7 +76,7 @@ def test_ReHoMaps(
75
76
  # Reset log capture
76
77
  caplog.clear()
77
78
  # Initialize storage
78
- storage = HDF5FeatureStorage(tmp_path / "reho_maps.hdf5")
79
+ storage = HDF5FeatureStorage(uri=tmp_path / "reho_maps.hdf5")
79
80
  # Fit transform marker on data with storage
80
81
  marker.fit_transform(
81
82
  input=element_data,
@@ -93,43 +94,44 @@ def test_ReHoMaps(
93
94
  @pytest.mark.skipif(
94
95
  _check_afni() is False, reason="requires AFNI to be in PATH"
95
96
  )
96
- def test_ReHoMaps_comparison(
97
- tmp_path: Path, maps_datagrabber: PatternDataladDataGrabber
98
- ) -> None:
97
+ @pytest.mark.parametrize("masks", [None, "compute_brain_mask"])
98
+ def test_ReHoMaps_comparison(tmp_path: Path, masks: str | None) -> None:
99
99
  """Test ReHoMaps implementation comparison.
100
100
 
101
101
  Parameters
102
102
  ----------
103
103
  tmp_path : pathlib.Path
104
104
  The path to the test directory.
105
- maps_datagrabber : PatternDataladDataGrabber
106
- The testing PatternDataladDataGrabber, as fixture.
105
+ masks : str or None
106
+ The masks to use.
107
107
 
108
108
  """
109
109
  # Update workdir to current test's tmp_path
110
110
  WorkDirManager().workdir = tmp_path
111
111
 
112
- with maps_datagrabber as dg:
113
- element = dg[("sub-01", "sub-001", "rest", "1")]
112
+ with ADHDTestingDataGrabber() as dg:
113
+ element = dg["sub-01"]
114
114
  element_data = DefaultDataReader().fit_transform(element)
115
115
 
116
116
  # Initialize marker
117
- junifer_marker = ReHoMaps(maps="Smith_rsn_10", using="junifer")
117
+ junifer_marker = ReHoMaps(
118
+ maps="Smith_rsn_10", using=ReHoImpl.junifer, masks=masks
119
+ )
118
120
  # Fit transform marker on data
119
121
  junifer_output = junifer_marker.fit_transform(element_data)
120
122
  # Get BOLD output
121
123
  junifer_output_bold = junifer_output["BOLD"]["reho"]
122
124
 
123
125
  # Initialize marker
124
- afni_marker = ReHoMaps(maps="Smith_rsn_10", using="afni")
126
+ afni_marker = ReHoMaps(
127
+ maps="Smith_rsn_10", using=ReHoImpl.afni, masks=masks
128
+ )
125
129
  # Fit transform marker on data
126
130
  afni_output = afni_marker.fit_transform(element_data)
127
131
  # Get BOLD output
128
132
  afni_output_bold = afni_output["BOLD"]["reho"]
129
133
 
130
- # Check for Pearson correlation coefficient
131
- r, _ = sp.stats.pearsonr(
132
- junifer_output_bold["data"].flatten(),
133
- afni_output_bold["data"].flatten(),
134
+ # Both implementations should give the same results
135
+ assert_allclose(
136
+ junifer_output_bold["data"], afni_output_bold["data"], atol=1e-6
134
137
  )
135
- assert r >= 0.2 # this is very bad, but they differ...