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
@@ -6,20 +6,25 @@
6
6
 
7
7
  from pathlib import Path
8
8
 
9
+ import numpy as np
9
10
  import pytest
10
11
  from nilearn.maskers import NiftiSpheresMasker
11
12
  from numpy.testing import assert_array_equal
12
13
 
13
14
  from junifer.data import CoordinatesRegistry, MaskRegistry
15
+ from junifer.datagrabber import DataType
14
16
  from junifer.datareader import DefaultDataReader
15
17
  from junifer.markers.sphere_aggregation import SphereAggregation
16
- from junifer.storage import SQLiteFeatureStorage
18
+ from junifer.storage import HDF5FeatureStorage, StorageType
17
19
  from junifer.testing.datagrabbers import (
18
- OasisVBMTestingDataGrabber,
19
20
  SPMAuditoryTestingDataGrabber,
21
+ UCLACNPVBMTestingDataGrabber,
20
22
  )
21
23
 
22
24
 
25
+ pytestmark = pytest.mark.external
26
+
27
+
23
28
  # Define common variables
24
29
  COORDS = "DMNBuckner"
25
30
  RADIUS = 8
@@ -29,70 +34,70 @@ RADIUS = 8
29
34
  "input_type, storage_type",
30
35
  [
31
36
  (
32
- "T1w",
33
- "vector",
37
+ DataType.T1w,
38
+ StorageType.Vector,
34
39
  ),
35
40
  (
36
- "T2w",
37
- "vector",
41
+ DataType.T2w,
42
+ StorageType.Vector,
38
43
  ),
39
44
  (
40
- "BOLD",
41
- "timeseries",
45
+ DataType.BOLD,
46
+ StorageType.Timeseries,
42
47
  ),
43
48
  (
44
- "VBM_GM",
45
- "vector",
49
+ DataType.VBM_GM,
50
+ StorageType.Vector,
46
51
  ),
47
52
  (
48
- "VBM_WM",
49
- "vector",
53
+ DataType.VBM_WM,
54
+ StorageType.Vector,
50
55
  ),
51
56
  (
52
- "VBM_CSF",
53
- "vector",
57
+ DataType.VBM_CSF,
58
+ StorageType.Vector,
54
59
  ),
55
60
  (
56
- "fALFF",
57
- "vector",
61
+ DataType.FALFF,
62
+ StorageType.Vector,
58
63
  ),
59
64
  (
60
- "GCOR",
61
- "vector",
65
+ DataType.GCOR,
66
+ StorageType.Vector,
62
67
  ),
63
68
  (
64
- "LCOR",
65
- "vector",
69
+ DataType.LCOR,
70
+ StorageType.Vector,
66
71
  ),
67
72
  ],
68
73
  )
69
74
  def test_SphereAggregation_input_output(
70
- input_type: str, storage_type: str
75
+ input_type: DataType, storage_type: StorageType
71
76
  ) -> None:
72
77
  """Test SphereAggregation input and output types.
73
78
 
74
79
  Parameters
75
80
  ----------
76
- input_type : str
81
+ input_type : DataType
77
82
  The parametrized input type.
78
- storage_type : str
83
+ storage_type : StorageType
79
84
  The parametrized storage type.
80
85
 
81
86
  """
82
87
  assert storage_type == SphereAggregation(
83
88
  coords="DMNBuckner",
84
89
  method="mean",
85
- on=input_type,
90
+ on=[input_type],
86
91
  ).storage_type(input_type=input_type, output_feature="aggregation")
87
92
 
88
93
 
89
94
  def test_SphereAggregation_3D() -> None:
90
95
  """Test SphereAggregation object on 3D images."""
91
- with OasisVBMTestingDataGrabber() as dg:
92
- element_data = DefaultDataReader().fit_transform(dg["sub-01"])
96
+ with UCLACNPVBMTestingDataGrabber() as dg:
97
+ element_data = DefaultDataReader().fit_transform(dg["sub-10206"])
93
98
  # Create SphereAggregation object
94
99
  marker = SphereAggregation(
95
- coords=COORDS, method="mean", radius=RADIUS, on="VBM_GM"
100
+ coords=COORDS, method="mean", radius=RADIUS, on=[DataType.VBM_GM]
96
101
  )
97
102
  sphere_agg_vbm_gm_data = marker.fit_transform(element_data)["VBM_GM"][
98
103
  "aggregation"
@@ -107,8 +112,9 @@ def test_SphereAggregation_3D() -> None:
107
112
  nifti_spheres_masker = NiftiSpheresMasker(
108
113
  seeds=testing_coords, radius=RADIUS
109
114
  )
110
- nifti_spheres_masked_vbm_gm = nifti_spheres_masker.fit_transform(
111
- element_data["VBM_GM"]["data"]
115
+ # nilearn returns a 1D array for 3D images
116
+ nifti_spheres_masked_vbm_gm = np.atleast_2d(
117
+ nifti_spheres_masker.fit_transform(element_data["VBM_GM"]["data"])
112
118
  )
113
119
 
114
120
  assert sphere_agg_vbm_gm_data.ndim == 2
@@ -124,7 +130,7 @@ def test_SphereAggregation_4D() -> None:
124
130
  element_data = DefaultDataReader().fit_transform(dg["sub001"])
125
131
  # Create SphereAggregation object
126
132
  marker = SphereAggregation(
127
- coords=COORDS, method="mean", radius=RADIUS, on="BOLD"
133
+ coords=COORDS, method="mean", radius=RADIUS, on=DataType.BOLD
128
134
  )
129
135
  sphere_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
130
136
  "aggregation"
@@ -160,13 +166,13 @@ def test_SphereAggregation_storage(tmp_path: Path) -> None:
160
166
 
161
167
  """
162
168
  # Store 3D
163
- with OasisVBMTestingDataGrabber() as dg:
164
- element_data = DefaultDataReader().fit_transform(dg["sub-01"])
165
- storage = SQLiteFeatureStorage(
166
- uri=tmp_path / "test_sphere_storage_3D.sqlite", upsert="ignore"
169
+ with UCLACNPVBMTestingDataGrabber() as dg:
170
+ element_data = DefaultDataReader().fit_transform(dg["sub-10206"])
171
+ storage = HDF5FeatureStorage(
172
+ uri=tmp_path / "test_sphere_storage_3D.hdf5",
167
173
  )
168
174
  marker = SphereAggregation(
169
- coords=COORDS, method="mean", radius=RADIUS, on="VBM_GM"
175
+ coords=COORDS, method="mean", radius=RADIUS, on=[DataType.VBM_GM]
170
176
  )
171
177
  marker.fit_transform(input=element_data, storage=storage)
172
178
  features = storage.list_features()
@@ -178,11 +184,11 @@ def test_SphereAggregation_storage(tmp_path: Path) -> None:
178
184
  # Store 4D
179
185
  with SPMAuditoryTestingDataGrabber() as dg:
180
186
  element_data = DefaultDataReader().fit_transform(dg["sub001"])
181
- storage = SQLiteFeatureStorage(
182
- uri=tmp_path / "test_sphere_storage_4D.sqlite", upsert="ignore"
187
+ storage = HDF5FeatureStorage(
188
+ uri=tmp_path / "test_sphere_storage_4D.hdf5",
183
189
  )
184
190
  marker = SphereAggregation(
185
- coords=COORDS, method="mean", radius=RADIUS, on="BOLD"
191
+ coords=COORDS, method="mean", radius=RADIUS, on=DataType.BOLD
186
192
  )
187
193
  marker.fit_transform(input=element_data, storage=storage)
188
194
  features = storage.list_features()
@@ -194,15 +200,15 @@ def test_SphereAggregation_storage(tmp_path: Path) -> None:
194
200
 
195
201
  def test_SphereAggregation_3D_mask() -> None:
196
202
  """Test SphereAggregation object on 3D images using mask."""
197
- with OasisVBMTestingDataGrabber() as dg:
198
- element_data = DefaultDataReader().fit_transform(dg["sub-01"])
203
+ with UCLACNPVBMTestingDataGrabber() as dg:
204
+ element_data = DefaultDataReader().fit_transform(dg["sub-10206"])
199
205
  # Create SphereAggregation object
200
206
  marker = SphereAggregation(
201
207
  coords=COORDS,
202
208
  method="mean",
203
209
  radius=RADIUS,
204
- on="VBM_GM",
205
- masks="compute_brain_mask",
210
+ on=[DataType.VBM_GM],
211
+ masks=["compute_brain_mask"],
206
212
  )
207
213
  sphere_agg_vbm_gm_data = marker.fit_transform(element_data)["VBM_GM"][
208
214
  "aggregation"
@@ -245,7 +251,7 @@ def test_SphereAggregation_4D_agg_time() -> None:
245
251
  method="mean",
246
252
  radius=RADIUS,
247
253
  time_method="mean",
248
- on="BOLD",
254
+ on=DataType.BOLD,
249
255
  )
250
256
  sphere_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
251
257
  "aggregation"
@@ -281,7 +287,7 @@ def test_SphereAggregation_4D_agg_time() -> None:
281
287
  radius=RADIUS,
282
288
  time_method="select",
283
289
  time_method_params={"pick": [0]},
284
- on="BOLD",
290
+ on=DataType.BOLD,
285
291
  )
286
292
  sphere_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
287
293
  "aggregation"
@@ -305,7 +311,7 @@ def test_SphereAggregation_errors() -> None:
305
311
  radius=RADIUS,
306
312
  time_method="pick",
307
313
  time_method_params={"pick": [0]},
308
- on="VBM_GM",
314
+ on=[DataType.VBM_GM],
309
315
  )
310
316
 
311
317
  with pytest.raises(
@@ -316,7 +322,7 @@ def test_SphereAggregation_errors() -> None:
316
322
  method="mean",
317
323
  radius=RADIUS,
318
324
  time_method_params={"pick": [0]},
319
- on="VBM_GM",
325
+ on=[DataType.VBM_GM],
320
326
  )
321
327
 
322
328
 
@@ -333,7 +339,7 @@ def test_SphereAggregation_warning() -> None:
333
339
  radius=RADIUS,
334
340
  time_method="select",
335
341
  time_method_params={"pick": [0]},
336
- on="BOLD",
342
+ on=DataType.BOLD,
337
343
  )
338
344
  element_data["BOLD"]["data"] = element_data["BOLD"]["data"].slicer[
339
345
  ..., 0:1
junifer/markers/utils.py CHANGED
@@ -8,19 +8,86 @@
8
8
  # Amir Omidvarnia <a.omidvarnia@fz-juelich.de>
9
9
  # License: AGPL
10
10
 
11
- from typing import Callable, Optional, Union
11
+ import hashlib
12
+ import zlib
13
+ from collections.abc import Callable
14
+ from pathlib import Path
15
+ from typing import Any
12
16
 
17
+ import nibabel as nib
13
18
  import numpy as np
14
19
  import pandas as pd
20
+ from nibabel.spatialimages import SpatialImage
15
21
  from scipy.stats import zscore
16
22
 
23
+ from ..pipeline import WorkDirManager
17
24
  from ..utils import raise_error
18
25
 
19
26
 
27
+ def _save_mask(mask_img: SpatialImage, prefix: str) -> Path:
28
+ """Save mask to the element directory, named by its content.
29
+
30
+ The same mask always gets the same path within an element, so that the
31
+ cached computations (e.g., ReHo or ALFF) can be reused by markers using
32
+ the same mask. The element directory is deleted after the element is
33
+ processed.
34
+
35
+ Parameters
36
+ ----------
37
+ mask_img : Niimg-like object
38
+ The mask to save.
39
+ prefix : str
40
+ The prefix of the file name.
41
+
42
+ Returns
43
+ -------
44
+ pathlib.Path
45
+ The path to the saved mask.
46
+
47
+ """
48
+ mask_data = np.squeeze(np.asarray(mask_img.dataobj)) != 0
49
+ digest = hashlib.sha256()
50
+ digest.update(str(mask_data.shape).encode())
51
+ digest.update(np.packbits(mask_data).tobytes())
52
+ digest.update(mask_img.affine.tobytes())
53
+ mask_path = (
54
+ WorkDirManager().get_elementdir()
55
+ / f"{prefix}_{digest.hexdigest()[:16]}.nii"
56
+ )
57
+ if not mask_path.exists():
58
+ nib.save(
59
+ nib.Nifti1Image(
60
+ mask_data.astype(np.uint8), mask_img.affine, mask_img.header
61
+ ),
62
+ mask_path,
63
+ )
64
+ return mask_path
65
+
66
+
67
+ def _has_data(data: np.ndarray) -> np.ndarray:
68
+ """Get the columns with data.
69
+
70
+ Regions that are lost when resampling or warping the parcellation to the
71
+ target image have no voxels, so their time series are all NaN.
72
+
73
+ Parameters
74
+ ----------
75
+ data : np.ndarray
76
+ The data (time x columns).
77
+
78
+ Returns
79
+ -------
80
+ np.ndarray
81
+ Boolean mask of the columns that are not all NaN.
82
+
83
+ """
84
+ return ~np.all(np.isnan(data), axis=0)
85
+
86
+
20
87
  def _ets(
21
88
  bold_ts: np.ndarray,
22
- roi_names: Union[None, list[str]] = None,
23
- ) -> tuple[np.ndarray, Optional[list[str]]]:
89
+ roi_names: list[str] | None = None,
90
+ ) -> tuple[np.ndarray, list[str] | None]:
24
91
  """Compute the edge-wise time series based on BOLD time series.
25
92
 
26
93
  Take a timeseries of brain areas, and calculate timeseries for each
@@ -73,7 +140,8 @@ def _ets(
73
140
  )
74
141
  _roi_names = np.array(roi_names)
75
142
  edge_names = [
76
- "~".join([x, y]) for x, y in zip(_roi_names[u], _roi_names[v])
143
+ "~".join([x, y])
144
+ for x, y in zip(_roi_names[u], _roi_names[v], strict=False)
77
145
  ]
78
146
  return ets, edge_names
79
147
 
@@ -81,7 +149,7 @@ def _ets(
81
149
  def _correlate_dataframes(
82
150
  df1: pd.DataFrame,
83
151
  df2: pd.DataFrame,
84
- method: Union[str, Callable] = "pearson",
152
+ method: str | Callable = "pearson",
85
153
  ) -> pd.DataFrame:
86
154
  """Column-wise correlations between two dataframes.
87
155
 
@@ -118,3 +186,197 @@ def _correlate_dataframes(
118
186
  .corr(method=method) # type: ignore
119
187
  .loc["df2", "df1"]
120
188
  )
189
+
190
+
191
+ def _checksum(array: np.ndarray) -> tuple:
192
+ """Get the checksum of an array.
193
+
194
+ Parameters
195
+ ----------
196
+ array : numpy.ndarray
197
+ The array.
198
+
199
+ Returns
200
+ -------
201
+ tuple
202
+ The shape, the dtype and the CRC-32 of the bytes of the array (None
203
+ for arrays of objects).
204
+
205
+ """
206
+ crc = None
207
+ if not array.dtype.hasobject:
208
+ # The transpose of an array in Fortran order (e.g., the data of a
209
+ # NIfTI image) is in C order, so it is not copied
210
+ if array.flags.f_contiguous and not array.flags.c_contiguous:
211
+ array = array.T
212
+ crc = zlib.crc32(memoryview(np.ascontiguousarray(array)).cast("B"))
213
+ return (array.shape, array.dtype.str, crc)
214
+
215
+
216
+ def _image_data(img: SpatialImage) -> tuple:
217
+ """Get the data of an image in memory.
218
+
219
+ The data read from the file of an image is not loaded, as it cannot be
220
+ changed (unless it is cached) and it would be kept in memory.
221
+
222
+ Parameters
223
+ ----------
224
+ img : nibabel.spatialimages.SpatialImage
225
+ The image.
226
+
227
+ Returns
228
+ -------
229
+ tuple
230
+ The data of the image if it is in memory (the data of an image
231
+ created from an array, or the data cached by nilearn, e.g., with
232
+ ``nilearn.image.get_data``) and the data cached by nibabel's
233
+ ``get_fdata``, or None.
234
+
235
+ """
236
+ data = img.dataobj
237
+ if not isinstance(data, np.ndarray):
238
+ data = getattr(img, "_data_cache", None)
239
+ return data, getattr(img, "_fdata_cache", None)
240
+
241
+
242
+ def _checksums(value: Any) -> tuple:
243
+ """Get the checksums of an array, data frame, image or set.
244
+
245
+ Parameters
246
+ ----------
247
+ value : Any
248
+ The value.
249
+
250
+ Returns
251
+ -------
252
+ tuple
253
+ The checksums, empty for other values. For data frames and series:
254
+ the values and index, and the column labels or name. For images: the
255
+ affine and the data in memory (see :func:`_image_data`, None if not in
256
+ memory). For sets: their contents.
257
+
258
+ """
259
+ if isinstance(value, np.ndarray):
260
+ return (_checksum(value),)
261
+ if isinstance(value, pd.DataFrame | pd.Series):
262
+ labels = (
263
+ tuple(value.columns)
264
+ if isinstance(value, pd.DataFrame)
265
+ else value.name
266
+ )
267
+ return (
268
+ _checksum(pd.util.hash_pandas_object(value).to_numpy()),
269
+ labels,
270
+ )
271
+ if isinstance(value, SpatialImage):
272
+ return (
273
+ _checksum(value.affine),
274
+ *(
275
+ None if data is None else _checksum(data)
276
+ for data in _image_data(value)
277
+ ),
278
+ )
279
+ if isinstance(value, set | frozenset):
280
+ return (frozenset(value),)
281
+ return ()
282
+
283
+
284
+ def _fingerprint(data: Any, path: tuple = ()) -> dict[tuple, tuple]:
285
+ """Get a fingerprint of the data, to check whether it is changed.
286
+
287
+ Parameters
288
+ ----------
289
+ data : Any
290
+ The data.
291
+ path : tuple, optional
292
+ The keys (or indices) leading to ``data`` (default ()).
293
+
294
+ Returns
295
+ -------
296
+ dict
297
+ For each path, the value (to check it is not replaced) and its
298
+ checksums (to check it is not changed in place).
299
+
300
+ """
301
+ out = {path: (data, _checksums(data))}
302
+ if isinstance(data, dict):
303
+ items = data.items()
304
+ elif isinstance(data, list | tuple):
305
+ items = enumerate(data)
306
+ else:
307
+ items = ()
308
+ for key, value in items:
309
+ out.update(_fingerprint(value, (*path, key)))
310
+ return out
311
+
312
+
313
+ def _is_changed(old: tuple, new: tuple) -> bool:
314
+ """Check whether a value is changed.
315
+
316
+ Parameters
317
+ ----------
318
+ old : tuple
319
+ The value and its checksums before.
320
+ new : tuple
321
+ The value and its checksums after.
322
+
323
+ Returns
324
+ -------
325
+ bool
326
+ Whether the value was replaced or changed in place.
327
+
328
+ """
329
+ (old_value, old_sums), (new_value, new_sums) = old, new
330
+ if new_value is not old_value:
331
+ return True
332
+ if isinstance(new_value, SpatialImage):
333
+ # The affine must be the same
334
+ if old_sums[0] != new_sums[0]:
335
+ return True
336
+ for old_data, new_data, data in zip(
337
+ old_sums[1:], new_sums[1:], _image_data(new_value), strict=True
338
+ ):
339
+ if new_data is None:
340
+ # Not in memory (or no longer cached)
341
+ continue
342
+ if old_data is not None and old_data[1] == new_data[1]:
343
+ # Same dtype: the data in memory must be the same
344
+ if old_data != new_data:
345
+ return True
346
+ else:
347
+ # The data was cached during the computation, possibly with
348
+ # another dtype: compare it with the data read from the file
349
+ expected = np.asanyarray(new_value.dataobj, dtype=data.dtype)
350
+ if not np.array_equal(data, expected, equal_nan=True):
351
+ return True
352
+ return False
353
+ return old_sums != new_sums
354
+
355
+
356
+ def _changed_paths(
357
+ before: dict[tuple, tuple], after: dict[tuple, tuple]
358
+ ) -> list[str]:
359
+ """Get the paths of the data that are changed.
360
+
361
+ Parameters
362
+ ----------
363
+ before : dict
364
+ The fingerprint of the data before (see :func:`_fingerprint`).
365
+ after : dict
366
+ The fingerprint of the data after.
367
+
368
+ Returns
369
+ -------
370
+ list of str
371
+ The paths (keys joined by ".") that were added, removed, replaced or
372
+ changed in place.
373
+
374
+ """
375
+ changed = [
376
+ path
377
+ for path in sorted(before.keys() | after.keys(), key=str)
378
+ if path not in before
379
+ or path not in after
380
+ or _is_changed(before[path], after[path])
381
+ ]
382
+ return [".".join(str(key) for key in path) for path in changed]
@@ -3,8 +3,7 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from .read_transform import read_transform
7
- from . import _brainprint as brainprint
6
+ import lazy_loader as lazy
8
7
 
9
8
 
10
- __all__ = ["read_transform", "brainprint"]
9
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,4 @@
1
+ __all__ = ["read_transform", "brainprint"]
2
+
3
+ from .read_transform import read_transform
4
+ from . import _brainprint as brainprint
@@ -3,8 +3,6 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import Optional
7
-
8
6
  import numpy as np
9
7
  import pandas as pd
10
8
 
@@ -17,7 +15,7 @@ __all__ = ["normalize", "reweight"]
17
15
 
18
16
  def normalize(
19
17
  storage: StorageLike,
20
- features: dict[str, dict[str, Optional[str]]],
18
+ features: dict[str, dict[str, str | None]],
21
19
  kind: str,
22
20
  ) -> pd.DataFrame: # pragma: no cover
23
21
  """Read stored brainprint data and normalize either surfaces or volumes.
@@ -87,8 +85,8 @@ def normalize(
87
85
 
88
86
  def reweight(
89
87
  storage: StorageLike,
90
- feature_name: Optional[str] = None,
91
- feature_md5: Optional[str] = None,
88
+ feature_name: str | None = None,
89
+ feature_md5: str | None = None,
92
90
  ) -> pd.DataFrame: # pragma: no cover
93
91
  """Read stored brainprint data and reweight eigenvalues.
94
92
 
File without changes
@@ -3,33 +3,35 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import Optional
7
-
8
6
  import numpy as np
9
7
  import pandas as pd
8
+ import structlog
10
9
 
11
10
  from ..typing import StorageLike
12
- from ..utils import logger, raise_error, warn_with_log
11
+ from ..utils import raise_error, warn_with_log
13
12
 
14
13
 
15
14
  __all__ = ["read_transform"]
16
15
 
16
+ _log = structlog.get_logger("junifer")
17
+ logger = _log.bind(pkg="onthefly")
18
+
17
19
 
18
20
  def read_transform(
19
21
  storage: StorageLike,
20
22
  transform: str,
21
- feature_name: Optional[str] = None,
22
- feature_md5: Optional[str] = None,
23
- nan_policy: Optional[str] = "bypass",
24
- transform_args: Optional[tuple] = None,
25
- transform_kw_args: Optional[dict] = None,
23
+ feature_name: str | None = None,
24
+ feature_md5: str | None = None,
25
+ nan_policy: str | None = "bypass",
26
+ transform_args: tuple | None = None,
27
+ transform_kw_args: dict | None = None,
26
28
  ) -> pd.DataFrame:
27
29
  """Read stored feature and transform to specific statistical output.
28
30
 
29
31
  Parameters
30
32
  ----------
31
33
  storage : storage-like
32
- The storage class, for example, SQLiteFeatureStorage.
34
+ The storage class, for example, HDF5FeatureStorage.
33
35
  transform : str
34
36
  The kind of transform formatted as ``<package>_<function>``,
35
37
  for example, ``bctpy_degrees_und``.
@@ -10,7 +10,7 @@ import numpy as np
10
10
  import pytest
11
11
 
12
12
  from junifer.onthefly import read_transform
13
- from junifer.storage.hdf5 import HDF5FeatureStorage
13
+ from junifer.storage import HDF5FeatureStorage, StorageType
14
14
 
15
15
 
16
16
  @pytest.fixture
@@ -23,9 +23,9 @@ def vector_storage(tmp_path: Path) -> HDF5FeatureStorage:
23
23
  The path to the test directory.
24
24
 
25
25
  """
26
- storage = HDF5FeatureStorage(tmp_path / "vector_store.hdf5")
26
+ storage = HDF5FeatureStorage(uri=tmp_path / "vector_store.hdf5")
27
27
  storage.store(
28
- kind="vector",
28
+ kind=StorageType.Vector,
29
29
  meta={
30
30
  "element": {"subject": "test"},
31
31
  "dependencies": [],
@@ -48,9 +48,9 @@ def matrix_storage(tmp_path: Path) -> HDF5FeatureStorage:
48
48
  The path to the test directory.
49
49
 
50
50
  """
51
- storage = HDF5FeatureStorage(tmp_path / "matrix_store.hdf5")
51
+ storage = HDF5FeatureStorage(uri=tmp_path / "matrix_store.hdf5")
52
52
  storage.store(
53
- kind="matrix",
53
+ kind=StorageType.Matrix,
54
54
  meta={
55
55
  "element": {"subject": "test"},
56
56
  "dependencies": [],
@@ -74,13 +74,13 @@ def matrix_storage_with_nan(tmp_path: Path) -> HDF5FeatureStorage:
74
74
  The path to the test directory.
75
75
 
76
76
  """
77
- storage = HDF5FeatureStorage(tmp_path / "matrix_store_nan.hdf5")
77
+ storage = HDF5FeatureStorage(uri=tmp_path / "matrix_store_nan.hdf5")
78
78
  data = np.arange(36).reshape(3, 3, 4).astype(float)
79
79
  data[1, 1, 2] = np.nan
80
80
  data[1, 2, 2] = np.nan
81
81
  for i in range(4):
82
82
  storage.store(
83
- kind="matrix",
83
+ kind=StorageType.Matrix,
84
84
  meta={
85
85
  "element": {"subject": f"test{i + 1}"},
86
86
  "dependencies": [],