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
@@ -0,0 +1,293 @@
1
+ """Provide tests for storing the same data more than once."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ import pytest
11
+
12
+ from junifer.storage import HDF5FeatureStorage, SQLiteFeatureStorage
13
+ from junifer.storage.base import BaseFeatureStorage
14
+
15
+
16
+ # The storage types supported by each storage
17
+ _CASES = [
18
+ (klass, kind, single_output)
19
+ for klass, kinds in (
20
+ (
21
+ HDF5FeatureStorage,
22
+ [
23
+ "vector",
24
+ "matrix",
25
+ "timeseries",
26
+ "scalar_table",
27
+ "timeseries_2d",
28
+ ],
29
+ ),
30
+ (SQLiteFeatureStorage, ["vector", "matrix", "timeseries"]),
31
+ )
32
+ for kind in kinds
33
+ for single_output in (True, False)
34
+ ]
35
+
36
+
37
+ def _data(kind: str, value: float) -> dict:
38
+ """Get data of a storage type with a value.
39
+
40
+ Parameters
41
+ ----------
42
+ kind : str
43
+ The storage type.
44
+ value : float
45
+ The value of the data.
46
+
47
+ Returns
48
+ -------
49
+ dict
50
+ The data and the other parameters to store it.
51
+
52
+ """
53
+ names = {"col_names": ["a", "b"]}
54
+ if kind == "vector":
55
+ return {"data": np.full((1, 2), value), **names}
56
+ if kind == "matrix":
57
+ return {
58
+ "data": np.full((2, 2), value),
59
+ "row_names": ["r1", "r2"],
60
+ "matrix_kind": "full",
61
+ **names,
62
+ }
63
+ if kind == "timeseries":
64
+ return {"data": np.full((3, 2), value), **names}
65
+ if kind == "scalar_table":
66
+ return {
67
+ "data": np.full((2, 2), value),
68
+ "row_names": ["r1", "r2"],
69
+ "row_header_col_name": "feature",
70
+ **names,
71
+ }
72
+ return {
73
+ "data": np.full((3, 2, 2), value),
74
+ "row_names": ["r1", "r2"],
75
+ **names,
76
+ }
77
+
78
+
79
+ def _store(
80
+ storage: BaseFeatureStorage,
81
+ kind: str,
82
+ element: dict,
83
+ value: float,
84
+ element_keys: list[str] | None = None,
85
+ ) -> None:
86
+ """Store data of an element being processed, as a marker does.
87
+
88
+ Parameters
89
+ ----------
90
+ storage : BaseFeatureStorage
91
+ The storage.
92
+ kind : str
93
+ The storage type.
94
+ element : dict
95
+ The element being processed.
96
+ value : float
97
+ The value of the data.
98
+ element_keys : list of str or None, optional
99
+ The keys of the element that the data depends on. If None, all the
100
+ keys (default None).
101
+
102
+ """
103
+ meta = {
104
+ "element": element,
105
+ "dependencies": set(),
106
+ "marker": {"name": "test"},
107
+ "type": "BOLD",
108
+ }
109
+ if element_keys is not None:
110
+ meta["_element_keys"] = element_keys
111
+ storage.store(kind=kind, meta=meta, **_data(kind, value))
112
+
113
+
114
+ def _read(
115
+ storage: BaseFeatureStorage, collect: bool = True
116
+ ) -> tuple[pd.DataFrame, list[tuple]]:
117
+ """Read the stored feature, collecting it first if needed.
118
+
119
+ Parameters
120
+ ----------
121
+ storage : BaseFeatureStorage
122
+ The storage.
123
+ collect : bool, optional
124
+ Whether to collect the files of the elements first (default True).
125
+
126
+ Returns
127
+ -------
128
+ pandas.DataFrame
129
+ The feature.
130
+ list of tuple
131
+ The elements of the feature, without duplicates.
132
+
133
+ """
134
+ if not storage.single_output and collect:
135
+ storage.collect()
136
+ reader = type(storage)(uri=storage.uri)
137
+ (md5,) = reader.list_features()
138
+ df = reader.read_df(feature_md5=md5)
139
+ # No repeated rows (the index has the element and, for some storage
140
+ # types, the rows of the data of an element)
141
+ assert not df.index.duplicated().any()
142
+ keys = [k for k in ("subject", "task") if k in df.index.names]
143
+ levels = [df.index.get_level_values(k) for k in keys]
144
+ elements = sorted(set(zip(*levels, strict=True)))
145
+ return df, elements
146
+
147
+
148
+ @pytest.mark.parametrize("klass, kind, single_output", _CASES)
149
+ def test_store_again(
150
+ tmp_path: Path, klass: type, kind: str, single_output: bool
151
+ ) -> None:
152
+ """Test storing the same element again does not change the storage.
153
+
154
+ Parameters
155
+ ----------
156
+ tmp_path : pathlib.Path
157
+ The path to the test directory.
158
+ klass : type
159
+ The parametrized storage class.
160
+ kind : str
161
+ The parametrized storage type.
162
+ single_output : bool
163
+ The parametrized storage in a single file.
164
+
165
+ """
166
+ storage = klass(uri=tmp_path / "out.db", single_output=single_output)
167
+ element = {"subject": "sub-01", "task": "rest"}
168
+ _store(storage, kind, element, 1)
169
+ # Already stored: skipped, even with other values
170
+ _store(storage, kind, element, 2)
171
+ df, elements = _read(storage)
172
+ assert elements == [("sub-01", "rest")]
173
+ assert (df.to_numpy() == 1).all()
174
+
175
+
176
+ @pytest.mark.parametrize("klass, kind, single_output", _CASES)
177
+ def test_store_element_of_data(
178
+ tmp_path: Path, klass: type, kind: str, single_output: bool
179
+ ) -> None:
180
+ """Test storing data that does not depend on all the element keys.
181
+
182
+ Parameters
183
+ ----------
184
+ tmp_path : pathlib.Path
185
+ The path to the test directory.
186
+ klass : type
187
+ The parametrized storage class.
188
+ kind : str
189
+ The parametrized storage type.
190
+ single_output : bool
191
+ The parametrized storage in a single file.
192
+
193
+ """
194
+ storage = klass(uri=tmp_path / "out.db", single_output=single_output)
195
+ # The data of a subject, the same when processing each of its tasks
196
+ for task in ("rest", "movie"):
197
+ element = {"subject": "sub-01", "task": task}
198
+ _store(storage, kind, element, 1, element_keys=["subject"])
199
+ if not single_output:
200
+ # One file for each element being processed
201
+ files = sorted(x.name for x in tmp_path.glob("*_out.db"))
202
+ assert files == [
203
+ "element_sub-01_movie_out.db",
204
+ "element_sub-01_rest_out.db",
205
+ ]
206
+ df, elements = _read(storage)
207
+ assert elements == [("sub-01",)]
208
+ assert (df.to_numpy() == 1).all()
209
+
210
+
211
+ @pytest.mark.parametrize(
212
+ "klass, kind, single_output",
213
+ [case for case in _CASES if not case[2]],
214
+ )
215
+ def test_collect_again(
216
+ tmp_path: Path, klass: type, kind: str, single_output: bool
217
+ ) -> None:
218
+ """Test collecting again does not change the storage.
219
+
220
+ Parameters
221
+ ----------
222
+ tmp_path : pathlib.Path
223
+ The path to the test directory.
224
+ klass : type
225
+ The parametrized storage class.
226
+ kind : str
227
+ The parametrized storage type.
228
+ single_output : bool
229
+ The parametrized storage in a single file.
230
+
231
+ """
232
+ storage = klass(uri=tmp_path / "out.db", single_output=single_output)
233
+ for subject in ("sub-01", "sub-02"):
234
+ _store(storage, kind, {"subject": subject, "task": "rest"}, 1)
235
+ storage.collect()
236
+ _, elements = _read(storage)
237
+ assert elements == [("sub-01", "rest"), ("sub-02", "rest")]
238
+
239
+
240
+ @pytest.mark.parametrize("klass, kind, single_output", _CASES)
241
+ def test_store_trial_then_all(
242
+ tmp_path: Path, klass: type, kind: str, single_output: bool
243
+ ) -> None:
244
+ """Test storing some elements and then all the elements.
245
+
246
+ For example, running a few elements to test a configuration, and then
247
+ queueing all the elements with the same configuration.
248
+
249
+ Parameters
250
+ ----------
251
+ tmp_path : pathlib.Path
252
+ The path to the test directory.
253
+ klass : type
254
+ The parametrized storage class.
255
+ kind : str
256
+ The parametrized storage type.
257
+ single_output : bool
258
+ The parametrized storage in a single file.
259
+
260
+ """
261
+ storage = klass(uri=tmp_path / "out.db", single_output=single_output)
262
+ # Trial
263
+ _store(storage, kind, {"subject": "sub-01", "task": "rest"}, 1)
264
+ _read(storage)
265
+ # All the elements
266
+ for subject in ("sub-01", "sub-02"):
267
+ _store(storage, kind, {"subject": subject, "task": "rest"}, 2)
268
+ df, elements = _read(storage)
269
+ assert elements == [("sub-01", "rest"), ("sub-02", "rest")]
270
+ # The trial element keeps its values
271
+ sub01 = df.xs("sub-01", level="subject")
272
+ assert (sub01.to_numpy() == 1).all()
273
+
274
+
275
+ @pytest.mark.parametrize("klass", [HDF5FeatureStorage, SQLiteFeatureStorage])
276
+ def test_collect_several_elements(tmp_path: Path, klass: type) -> None:
277
+ """Test collecting a file with more than one element raises an error.
278
+
279
+ Parameters
280
+ ----------
281
+ tmp_path : pathlib.Path
282
+ The path to the test directory.
283
+ klass : type
284
+ The parametrized storage class.
285
+
286
+ """
287
+ # A file with two elements, named as the file of an element
288
+ several = klass(uri=tmp_path / "element_all_out.db")
289
+ for subject in ("sub-01", "sub-02"):
290
+ _store(several, "timeseries", {"subject": subject}, 1)
291
+ storage = klass(uri=tmp_path / "out.db", single_output=False)
292
+ with pytest.raises(RuntimeError, match="has 2 elements for the feature"):
293
+ storage.collect()
@@ -5,7 +5,6 @@
5
5
  # License: AGPL
6
6
 
7
7
  from pathlib import Path
8
- from typing import Union
9
8
 
10
9
  import numpy as np
11
10
  import pandas as pd
@@ -14,7 +13,7 @@ from numpy.testing import assert_array_equal
14
13
  from pandas.testing import assert_frame_equal
15
14
  from sqlalchemy import create_engine
16
15
 
17
- from junifer.storage.sqlite import SQLiteFeatureStorage
16
+ from junifer.storage import MatrixKind, SQLiteFeatureStorage, Upsert
18
17
  from junifer.storage.utils import element_to_prefix, process_meta
19
18
 
20
19
 
@@ -56,7 +55,7 @@ df_ignore = pd.DataFrame(
56
55
 
57
56
 
58
57
  def _read_sql(
59
- table_name: str, uri: str, index_col: Union[str, list[str]]
58
+ table_name: str, uri: str, index_col: str | list[str]
60
59
  ) -> pd.DataFrame:
61
60
  """Read database table into a pandas DataFrame.
62
61
 
@@ -215,7 +214,7 @@ def test_upsert_update(tmp_path: Path) -> None:
215
214
 
216
215
  """
217
216
  uri = tmp_path / "test_upsert_delete.sqlite"
218
- storage = SQLiteFeatureStorage(uri=uri)
217
+ storage = SQLiteFeatureStorage(uri=uri, upsert=Upsert.Update)
219
218
  # Save to database
220
219
  storage.store_df(
221
220
  meta_md5="table_name", element={"subject": "test"}, df=df1
@@ -503,16 +502,6 @@ def test_store_matrix(tmp_path: Path) -> None:
503
502
  read_df = storage.read_df(feature_md5=feature_md5)
504
503
  assert list(read_df.columns) == stored_names
505
504
 
506
- with pytest.raises(ValueError, match="Invalid kind"):
507
- storage.store_matrix(
508
- meta_md5=meta_md5,
509
- element=element_to_store,
510
- data=data,
511
- row_names=row_names,
512
- col_names=col_names,
513
- matrix_kind="wrong",
514
- )
515
-
516
505
  with pytest.raises(ValueError, match="non-square"):
517
506
  storage.store_matrix(
518
507
  meta_md5=meta_md5,
@@ -520,7 +509,7 @@ def test_store_matrix(tmp_path: Path) -> None:
520
509
  data=data,
521
510
  row_names=row_names,
522
511
  col_names=col_names,
523
- matrix_kind="triu",
512
+ matrix_kind=MatrixKind.UpperTriangle,
524
513
  )
525
514
 
526
515
  with pytest.raises(ValueError, match="cannot be False"):
@@ -530,7 +519,7 @@ def test_store_matrix(tmp_path: Path) -> None:
530
519
  data=data,
531
520
  row_names=row_names,
532
521
  col_names=col_names,
533
- matrix_kind="full",
522
+ matrix_kind=MatrixKind.Full,
534
523
  diagonal=False,
535
524
  )
536
525
 
@@ -550,7 +539,7 @@ def test_store_matrix(tmp_path: Path) -> None:
550
539
  data=data,
551
540
  row_names=row_names,
552
541
  col_names=col_names,
553
- matrix_kind="triu",
542
+ matrix_kind=MatrixKind.UpperTriangle,
554
543
  )
555
544
 
556
545
  stored_names = [
@@ -584,7 +573,7 @@ def test_store_matrix(tmp_path: Path) -> None:
584
573
  data=data,
585
574
  row_names=row_names,
586
575
  col_names=col_names,
587
- matrix_kind="triu",
576
+ matrix_kind=MatrixKind.UpperTriangle,
588
577
  diagonal=False,
589
578
  )
590
579
 
@@ -619,7 +608,7 @@ def test_store_matrix(tmp_path: Path) -> None:
619
608
  data=data,
620
609
  row_names=row_names,
621
610
  col_names=col_names,
622
- matrix_kind="tril",
611
+ matrix_kind=MatrixKind.LowerTriangle,
623
612
  )
624
613
 
625
614
  stored_names = [
@@ -653,7 +642,7 @@ def test_store_matrix(tmp_path: Path) -> None:
653
642
  data=data,
654
643
  row_names=row_names,
655
644
  col_names=col_names,
656
- matrix_kind="tril",
645
+ matrix_kind=MatrixKind.LowerTriangle,
657
646
  diagonal=False,
658
647
  )
659
648
  stored_names = [
@@ -5,17 +5,18 @@
5
5
  # License: AGPL
6
6
 
7
7
  from collections.abc import Sequence
8
+ from pathlib import Path
8
9
  from typing import ClassVar
9
10
 
10
11
  import pytest
11
12
 
12
- from junifer.storage.base import BaseFeatureStorage
13
+ from junifer.storage import BaseFeatureStorage, StorageType
13
14
 
14
15
 
15
16
  def test_BaseFeatureStorage_abstractness() -> None:
16
17
  """Test BaseFeatureStorage is abstract base class."""
17
18
  with pytest.raises(TypeError, match=r"abstract"):
18
- BaseFeatureStorage(uri="/tmp")
19
+ BaseFeatureStorage(uri=Path("."))
19
20
 
20
21
 
21
22
  def test_BaseFeatureStorage() -> None:
@@ -25,19 +26,13 @@ def test_BaseFeatureStorage() -> None:
25
26
  class MyFeatureStorage(BaseFeatureStorage):
26
27
  """Implement concrete class."""
27
28
 
28
- _STORAGE_TYPES: ClassVar[Sequence[str]] = [
29
- "matrix",
30
- "vector",
31
- "timeseries",
32
- "timeseries_2d",
29
+ _STORAGE_TYPES: ClassVar[Sequence[StorageType]] = [
30
+ StorageType.Matrix,
31
+ StorageType.Vector,
32
+ StorageType.Timeseries,
33
+ StorageType.Timeseries2D,
33
34
  ]
34
35
 
35
- def __init__(self, uri, single_output=True):
36
- super().__init__(
37
- uri=uri,
38
- single_output=single_output,
39
- )
40
-
41
36
  def list_features(self):
42
37
  super().list_features()
43
38
 
@@ -53,24 +48,26 @@ def test_BaseFeatureStorage() -> None:
53
48
  feature_md5=feature_md5,
54
49
  )
55
50
 
56
- def store_metadata(self, meta_md5, meta, element):
57
- super().store_metadata(meta_md5, meta, element)
51
+ def store_metadata(
52
+ self, meta_md5, element, meta, processed_element=None
53
+ ):
54
+ super().store_metadata(meta_md5, meta, element, processed_element)
58
55
 
59
56
  def collect(self):
60
- return super().collect()
57
+ super().collect()
61
58
 
62
59
  # Check single_output is False
63
- st = MyFeatureStorage(uri="/tmp", single_output=False)
60
+ st = MyFeatureStorage(uri=Path("."), single_output=False)
64
61
  assert st.single_output is False
65
62
  # Check single_output is True
66
- st = MyFeatureStorage(uri="/tmp", single_output=True)
63
+ st = MyFeatureStorage(uri=Path("."), single_output=True)
67
64
  assert st.single_output is True
68
65
 
69
66
  # Check validate with valid argument
70
- st.validate(input_=["matrix"])
67
+ st.validate_input(input_=["matrix"])
71
68
  # Check validate with invalid argument
72
69
  with pytest.raises(ValueError):
73
- st.validate(input_=["duck"])
70
+ st.validate_input(input_=["duck"])
74
71
 
75
72
  with pytest.raises(NotImplementedError):
76
73
  st.list_features()
@@ -109,4 +106,4 @@ def test_BaseFeatureStorage() -> None:
109
106
  with pytest.raises(ValueError):
110
107
  st.store(kind="lego", meta=meta)
111
108
 
112
- assert str(st.uri) == "/tmp"
109
+ assert str(st.uri) == "."
@@ -5,7 +5,6 @@
5
5
  # License: AGPL
6
6
 
7
7
  from collections.abc import Iterable
8
- from typing import Union
9
8
 
10
9
  import numpy as np
11
10
  import pytest
@@ -24,14 +23,19 @@ from junifer.storage.utils import (
24
23
  @pytest.mark.parametrize(
25
24
  "dependency, max_version",
26
25
  [
27
- ("click", "8.2"),
28
- ("numpy", "1.27"),
29
- ("datalad", "0.20"),
30
- ("pandas", "2.2"),
31
- ("nibabel", "5.11"),
32
- ("nilearn", "0.11.0"),
33
- ("sqlalchemy", "2.1.0"),
34
- ("ruamel.yaml", "0.18.0"),
26
+ ("click", "8.6"),
27
+ ("numpy", "2.5.0"),
28
+ ("scipy", "1.18.0"),
29
+ ("datalad", "1.8.0"),
30
+ ("pandas", "3.1.0"),
31
+ ("nibabel", "5.5.0"),
32
+ ("nilearn", "0.15.0"),
33
+ ("sqlalchemy", "2.2.0"),
34
+ ("ruamel.yaml", "0.20.0"),
35
+ ("tqdm", "4.71.0"),
36
+ ("templateflow", "26.0.0"),
37
+ ("lapy", "2.0.0"),
38
+ ("structlog", "27.0.0"),
35
39
  ],
36
40
  )
37
41
  def test_get_dependency_version(dependency: str, max_version: str) -> None:
@@ -47,7 +51,7 @@ def test_get_dependency_version(dependency: str, max_version: str) -> None:
47
51
  """
48
52
  version = get_dependency_version(dependency)
49
53
  if len(version.split(".")) == 3: # semver
50
- assert int(version.split(".")[1]) <= int(max_version.split(".")[1])
54
+ assert int(version.split(".")[0]) <= int(max_version.split(".")[0])
51
55
  else:
52
56
  assert version < max_version
53
57
 
@@ -150,6 +154,73 @@ def test_process_meta_hash() -> None:
150
154
  assert hash4 != hash6
151
155
 
152
156
 
157
+ def test_process_meta_element_keys() -> None:
158
+ """Test metadata processing with the element keys of the data."""
159
+ meta = {
160
+ "element": {"subject": "sub-01", "task": "rest"},
161
+ "dependencies": ["numpy"],
162
+ "marker": {"name": "fc"},
163
+ "type": "VBM_GM",
164
+ }
165
+ # All the keys of the element by default
166
+ hash_all, meta_all, element_all = process_meta(meta)
167
+ assert element_all == {"subject": "sub-01", "task": "rest"}
168
+ assert meta_all["_element_keys"] == ["subject", "task"]
169
+ # Only the keys of the element that the data depends on, which are the
170
+ # same for all the tasks
171
+ hashes = set()
172
+ for task in ("rest", "movie"):
173
+ t_meta = {
174
+ **meta,
175
+ "element": {"subject": "sub-01", "task": task},
176
+ "_element_keys": ["subject"],
177
+ }
178
+ t_hash, t_processed, t_element = process_meta(t_meta)
179
+ assert t_element == {"subject": "sub-01"}
180
+ assert t_processed["_element_keys"] == ["subject"]
181
+ hashes.add(t_hash)
182
+ assert len(hashes) == 1
183
+ assert hash_all not in hashes
184
+ # Keys that are not in the element
185
+ with pytest.raises(ValueError, match=r"keys \['session'\]"):
186
+ process_meta({**meta, "_element_keys": ["subject", "session"]})
187
+
188
+
189
+ def test_process_meta_preprocessors() -> None:
190
+ """Test the metadata hash depends on all the preprocessors."""
191
+ hashes = []
192
+ for first in (1, 2):
193
+ meta = {
194
+ "element": {"subject": "sub-01"},
195
+ "dependencies": ["numpy"],
196
+ "preprocess": [
197
+ {"class": "A", "parameter": first},
198
+ {"class": "B", "parameter": 1},
199
+ ],
200
+ "marker": {"name": "fc"},
201
+ "type": "BOLD",
202
+ }
203
+ t_hash, _, _ = process_meta(meta)
204
+ hashes.append(t_hash)
205
+ # Only the first preprocessor is different
206
+ assert hashes[0] != hashes[1]
207
+
208
+
209
+ def test_process_meta_junifer_version() -> None:
210
+ """Test the metadata hash depends on the version of junifer."""
211
+ meta = {
212
+ "element": {"subject": "sub-01"},
213
+ "dependencies": ["numpy"],
214
+ "marker": {"name": "fc"},
215
+ "type": "BOLD",
216
+ }
217
+ _, processed, _ = process_meta(meta)
218
+ assert processed["dependencies"] == {
219
+ "numpy": get_dependency_version("numpy"),
220
+ "junifer": get_dependency_version("junifer"),
221
+ }
222
+
223
+
153
224
  def test_process_meta_invalid_metadata_key() -> None:
154
225
  """Test invalid metadata key check for metadata hash processing."""
155
226
  meta = {}
@@ -259,16 +330,6 @@ def test_element_to_prefix_invalid_type() -> None:
259
330
  @pytest.mark.parametrize(
260
331
  "params, err_msg",
261
332
  [
262
- (
263
- {
264
- "matrix_kind": "half",
265
- "diagonal": True,
266
- "data_shape": (1, 1),
267
- "row_names_len": 1,
268
- "col_names_len": 1,
269
- },
270
- "Invalid kind",
271
- ),
272
333
  (
273
334
  {
274
335
  "matrix_kind": "full",
@@ -322,7 +383,7 @@ def test_element_to_prefix_invalid_type() -> None:
322
383
  ],
323
384
  )
324
385
  def test_store_matrix_checks(
325
- params: dict[str, Union[str, bool, tuple[int, int], int]], err_msg: str
386
+ params: dict[str, str | bool | tuple[int, int] | int], err_msg: str
326
387
  ) -> None:
327
388
  """Test matrix storing parameter checks.
328
389
 
@@ -403,7 +464,7 @@ def test_store_matrix_checks(
403
464
  ],
404
465
  )
405
466
  def test_matrix_to_vector(
406
- params: dict[str, Union[np.ndarray, Iterable[str], str, bool]],
467
+ params: dict[str, np.ndarray | Iterable[str] | str | bool],
407
468
  expected_data: np.ndarray,
408
469
  expected_columns: list[str],
409
470
  ) -> None: