junifer 0.0.7.dev332__py3-none-any.whl → 0.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. junifer/_version.py +9 -19
  2. junifer/api/__init__.pyi +17 -2
  3. junifer/api/functions.py +222 -39
  4. junifer/api/queue_context/__init__.pyi +11 -3
  5. junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
  6. junifer/api/queue_context/htcondor_adapter.py +118 -181
  7. junifer/api/queue_context/queue_context_adapter.py +51 -7
  8. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
  9. junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
  10. junifer/api/res/fsl/run_fsl_docker.sh +4 -0
  11. junifer/api/tests/test_functions.py +863 -36
  12. junifer/cli/__init__.py +1 -7
  13. junifer/cli/cli.py +41 -22
  14. junifer/cli/parser.py +23 -147
  15. junifer/cli/tests/data/gmd_mean.yaml +6 -6
  16. junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
  17. junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
  18. junifer/cli/tests/test_cli.py +120 -4
  19. junifer/cli/tests/test_cli_utils.py +23 -6
  20. junifer/cli/utils.py +14 -9
  21. junifer/configs/__init__.py +5 -0
  22. junifer/configs/__init__.pyi +0 -0
  23. junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
  24. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
  25. junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
  26. junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
  27. junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
  28. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
  29. junifer/configs/juseless/datagrabbers/ucla.py +111 -104
  30. junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
  31. junifer/configs/py.typed +0 -0
  32. junifer/conftest.py +63 -24
  33. junifer/data/_dispatch.py +9 -19
  34. junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
  35. junifer/data/coordinates/_coordinates.py +51 -6
  36. junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
  37. junifer/data/coordinates/tests/test_coordinates.py +15 -3
  38. junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
  39. junifer/data/maps/_ants_maps_warper.py +18 -12
  40. junifer/data/maps/_fsl_maps_warper.py +7 -3
  41. junifer/data/maps/_maps.py +12 -8
  42. junifer/data/maps/tests/test_maps.py +13 -21
  43. junifer/data/masks/_ants_mask_warper.py +18 -12
  44. junifer/data/masks/_fsl_mask_warper.py +7 -3
  45. junifer/data/masks/_masks.py +15 -12
  46. junifer/data/masks/tests/test_masks.py +20 -17
  47. junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
  48. junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
  49. junifer/data/parcellations/_parcellations.py +125 -91
  50. junifer/data/parcellations/tests/test_parcellations.py +122 -1
  51. junifer/data/template_spaces.py +117 -50
  52. junifer/data/tests/test_template_spaces.py +10 -7
  53. junifer/data/utils.py +10 -8
  54. junifer/datagrabber/__init__.pyi +38 -5
  55. junifer/datagrabber/aomic/__init__.pyi +8 -1
  56. junifer/datagrabber/aomic/_types.py +22 -0
  57. junifer/datagrabber/aomic/id1000.py +193 -183
  58. junifer/datagrabber/aomic/piop1.py +215 -259
  59. junifer/datagrabber/aomic/piop2.py +211 -244
  60. junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
  61. junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
  62. junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
  63. junifer/datagrabber/base.py +253 -79
  64. junifer/datagrabber/datalad_base.py +127 -76
  65. junifer/datagrabber/dmcc13_benchmark.py +204 -289
  66. junifer/datagrabber/hcp1200/__init__.pyi +7 -2
  67. junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
  68. junifer/datagrabber/hcp1200/hcp1200.py +151 -164
  69. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
  70. junifer/datagrabber/multiple.py +183 -63
  71. junifer/datagrabber/pattern.py +358 -227
  72. junifer/datagrabber/pattern_datalad.py +84 -124
  73. junifer/datagrabber/pattern_validation_mixin.py +22 -50
  74. junifer/datagrabber/tests/test_base.py +17 -4
  75. junifer/datagrabber/tests/test_datalad_base.py +20 -27
  76. junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
  77. junifer/datagrabber/tests/test_multiple.py +293 -31
  78. junifer/datagrabber/tests/test_pattern.py +577 -18
  79. junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
  80. junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
  81. junifer/datareader/default.py +8 -5
  82. junifer/datareader/tests/test_default_reader.py +1 -1
  83. junifer/external/BrainPrint/.codecov.yml +2 -0
  84. junifer/external/BrainPrint/.codespellignore +2 -0
  85. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
  86. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
  87. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
  88. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
  89. junifer/external/BrainPrint/.github/dependabot.yml +6 -0
  90. junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
  91. junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
  92. junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
  93. junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
  94. junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
  95. junifer/external/BrainPrint/.gitignore +123 -0
  96. junifer/external/BrainPrint/LICENSE +21 -0
  97. junifer/external/BrainPrint/README.md +108 -0
  98. junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
  99. junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
  100. junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
  101. junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
  102. junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
  103. junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
  104. junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
  105. junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
  106. junifer/external/BrainPrint/doc/Makefile +20 -0
  107. junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
  108. junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
  109. junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
  110. junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
  111. junifer/external/BrainPrint/doc/api/index.rst +11 -0
  112. junifer/external/BrainPrint/doc/conf.py +272 -0
  113. junifer/external/BrainPrint/doc/index.rst +140 -0
  114. junifer/external/BrainPrint/doc/links.inc +36 -0
  115. junifer/external/BrainPrint/doc/make.bat +35 -0
  116. junifer/external/BrainPrint/doc/references.bib +23 -0
  117. junifer/external/BrainPrint/pyproject.toml +170 -0
  118. junifer/external/BrainPrint/setup.py +3 -0
  119. junifer/external/__init__.py +5 -0
  120. junifer/external/__init__.pyi +0 -0
  121. junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
  122. junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
  123. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
  124. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
  125. junifer/external/py.typed +0 -0
  126. junifer/markers/__init__.pyi +6 -3
  127. junifer/markers/base.py +126 -76
  128. junifer/markers/brainprint.py +30 -39
  129. junifer/markers/complexity/__init__.py +20 -3
  130. junifer/markers/complexity/complexity_base.py +40 -41
  131. junifer/markers/complexity/hurst_exponent.py +22 -35
  132. junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
  133. junifer/markers/complexity/perm_entropy.py +22 -36
  134. junifer/markers/complexity/range_entropy.py +22 -35
  135. junifer/markers/complexity/range_entropy_auc.py +27 -36
  136. junifer/markers/complexity/sample_entropy.py +22 -36
  137. junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
  138. junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
  139. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
  140. junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
  141. junifer/markers/complexity/tests/test_range_entropy.py +47 -36
  142. junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
  143. junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
  144. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
  145. junifer/markers/complexity/weighted_perm_entropy.py +22 -36
  146. junifer/markers/ets_rss.py +35 -31
  147. junifer/markers/falff/__init__.pyi +7 -1
  148. junifer/markers/falff/_afni_falff.py +22 -9
  149. junifer/markers/falff/_junifer_falff.py +47 -31
  150. junifer/markers/falff/falff_base.py +80 -60
  151. junifer/markers/falff/falff_maps.py +17 -41
  152. junifer/markers/falff/falff_parcels.py +26 -51
  153. junifer/markers/falff/falff_spheres.py +34 -61
  154. junifer/markers/falff/tests/test_falff_maps.py +20 -23
  155. junifer/markers/falff/tests/test_falff_mask.py +99 -0
  156. junifer/markers/falff/tests/test_falff_parcels.py +14 -10
  157. junifer/markers/falff/tests/test_falff_spheres.py +14 -10
  158. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
  159. junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
  160. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
  161. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
  162. junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
  163. junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
  164. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
  165. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
  166. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
  167. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
  168. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
  169. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
  170. junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
  171. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
  172. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
  173. junifer/markers/maps_aggregation.py +77 -55
  174. junifer/markers/parcel_aggregation.py +91 -70
  175. junifer/markers/reho/__init__.pyi +7 -1
  176. junifer/markers/reho/_afni_reho.py +23 -14
  177. junifer/markers/reho/_junifer_reho.py +112 -123
  178. junifer/markers/reho/reho_base.py +115 -37
  179. junifer/markers/reho/reho_maps.py +18 -32
  180. junifer/markers/reho/reho_parcels.py +26 -41
  181. junifer/markers/reho/reho_spheres.py +33 -54
  182. junifer/markers/reho/tests/test_junifer_reho.py +178 -0
  183. junifer/markers/reho/tests/test_reho_maps.py +27 -25
  184. junifer/markers/reho/tests/test_reho_parcels.py +28 -22
  185. junifer/markers/reho/tests/test_reho_spheres.py +16 -12
  186. junifer/markers/sphere_aggregation.py +88 -72
  187. junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
  188. junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
  189. junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
  190. junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
  191. junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
  192. junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
  193. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
  194. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
  195. junifer/markers/tests/test_brainprint.py +10 -9
  196. junifer/markers/tests/test_ets_rss.py +7 -3
  197. junifer/markers/tests/test_lost_regions.py +279 -0
  198. junifer/markers/tests/test_maps_aggregation.py +67 -114
  199. junifer/markers/tests/test_marker_utils.py +40 -1
  200. junifer/markers/tests/test_markers_base.py +275 -6
  201. junifer/markers/tests/test_parcel_aggregation.py +91 -53
  202. junifer/markers/tests/test_sphere_aggregation.py +53 -47
  203. junifer/markers/utils.py +267 -5
  204. junifer/onthefly/__init__.py +2 -3
  205. junifer/onthefly/__init__.pyi +4 -0
  206. junifer/onthefly/_brainprint.py +3 -5
  207. junifer/onthefly/py.typed +0 -0
  208. junifer/onthefly/read_transform.py +11 -9
  209. junifer/onthefly/tests/test_read_transform.py +7 -7
  210. junifer/pipeline/__init__.pyi +4 -2
  211. junifer/pipeline/marker_collection.py +14 -10
  212. junifer/pipeline/pipeline_component_registry.py +25 -6
  213. junifer/pipeline/pipeline_step_mixin.py +5 -12
  214. junifer/pipeline/tests/test_data_object_dumper.py +10 -10
  215. junifer/pipeline/tests/test_marker_collection.py +145 -168
  216. junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
  217. junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
  218. junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
  219. junifer/pipeline/tests/test_workdir_manager.py +39 -0
  220. junifer/pipeline/update_meta_mixin.py +13 -10
  221. junifer/pipeline/utils.py +22 -17
  222. junifer/pipeline/workdir_manager.py +41 -32
  223. junifer/preprocess/__init__.pyi +9 -4
  224. junifer/preprocess/_temporal_filter.py +34 -33
  225. junifer/preprocess/_temporal_slicer.py +17 -30
  226. junifer/preprocess/base.py +99 -55
  227. junifer/preprocess/confounds/__init__.pyi +10 -2
  228. junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
  229. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
  230. junifer/preprocess/smoothing/__init__.pyi +2 -2
  231. junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
  232. junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
  233. junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
  234. junifer/preprocess/smoothing/smoothing.py +59 -40
  235. junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
  236. junifer/preprocess/tests/test_preprocess_base.py +17 -9
  237. junifer/preprocess/tests/test_temporal_filter.py +37 -16
  238. junifer/preprocess/tests/test_temporal_slicer.py +12 -12
  239. junifer/preprocess/warping/__init__.pyi +2 -2
  240. junifer/preprocess/warping/_ants_warper.py +56 -40
  241. junifer/preprocess/warping/_fsl_warper.py +49 -36
  242. junifer/preprocess/warping/_utils.py +106 -0
  243. junifer/preprocess/warping/space_warper.py +99 -98
  244. junifer/preprocess/warping/tests/test_space_warper.py +46 -41
  245. junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
  246. junifer/stats.py +46 -34
  247. junifer/storage/__init__.pyi +8 -4
  248. junifer/storage/base.py +129 -64
  249. junifer/storage/hdf5.py +307 -199
  250. junifer/storage/pandas_base.py +46 -24
  251. junifer/storage/sqlite.py +99 -62
  252. junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
  253. junifer/storage/tests/test_hdf5.py +147 -12
  254. junifer/storage/tests/test_repeated_storage.py +293 -0
  255. junifer/storage/tests/test_sqlite.py +9 -20
  256. junifer/storage/tests/test_storage_base.py +18 -21
  257. junifer/storage/tests/test_utils.py +83 -22
  258. junifer/storage/utils.py +58 -38
  259. junifer/testing/__init__.pyi +2 -2
  260. junifer/testing/datagrabbers.py +480 -44
  261. junifer/testing/registry.py +12 -0
  262. junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
  263. junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
  264. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
  265. junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
  266. junifer/testing/tests/test_testing_registry.py +31 -0
  267. junifer/testing/tests/test_testing_utils.py +35 -0
  268. junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
  269. junifer/testing/utils.py +33 -1
  270. junifer/tests/test_stats.py +1 -5
  271. junifer/typing/__init__.pyi +2 -0
  272. junifer/typing/_typing.py +34 -19
  273. junifer/typing/py.typed +0 -0
  274. junifer/utils/__init__.pyi +10 -1
  275. junifer/utils/_config.py +1 -2
  276. junifer/utils/helpers.py +68 -2
  277. junifer/utils/logging.py +31 -7
  278. junifer/utils/tests/test_helpers.py +36 -1
  279. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
  280. junifer-0.0.8.dist-info/RECORD +402 -0
  281. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
  282. junifer/cli/tests/test_parser.py +0 -280
  283. junifer/external/h5io/h5io/__init__.py +0 -15
  284. junifer/external/h5io/h5io/_h5io.py +0 -767
  285. junifer/external/h5io/h5io/_version.py +0 -1
  286. junifer/external/h5io/h5io/chunked_array.py +0 -94
  287. junifer/external/h5io/h5io/chunked_list.py +0 -65
  288. junifer/markers/complexity/__init__.pyi +0 -17
  289. junifer-0.0.7.dev332.dist-info/RECORD +0 -353
  290. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
  291. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
  292. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
  293. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
@@ -4,9 +4,18 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+
10
+ import nibabel as nib
11
+ import numpy as np
12
+ import pandas as pd
7
13
  import pytest
14
+ from nilearn.image import get_data
8
15
 
9
- from junifer.markers.base import BaseMarker
16
+ from junifer.datagrabber import DataType
17
+ from junifer.markers import BaseMarker
18
+ from junifer.storage import StorageType
10
19
 
11
20
 
12
21
  def test_base_marker_abstractness() -> None:
@@ -21,14 +30,12 @@ def test_base_marker_subclassing() -> None:
21
30
  # Create concrete class
22
31
  class MyBaseMarker(BaseMarker):
23
32
  _MARKER_INOUT_MAPPINGS = { # noqa: RUF012
24
- "BOLD": {
25
- "feat_1": "timeseries",
33
+ DataType.BOLD: {
34
+ "feat_1": StorageType.Timeseries,
26
35
  },
27
36
  }
28
37
 
29
- def __init__(self, on, name=None) -> None:
30
- self.parameter = 1
31
- super().__init__(on, name)
38
+ parameter: int = 1
32
39
 
33
40
  def compute(self, input, extra_input):
34
41
  return {
@@ -105,3 +112,265 @@ def test_base_marker_subclassing() -> None:
105
112
  # Check output
106
113
  assert "BOLD" in output
107
114
  assert "T2" not in output
115
+
116
+
117
+ def _fit(change: Callable, tmp_path: Path) -> dict:
118
+ """Fit a marker that applies a function to its input.
119
+
120
+ Parameters
121
+ ----------
122
+ change : callable
123
+ The function, called with the input and the extra input.
124
+ tmp_path : pathlib.Path
125
+ The path to the test directory.
126
+
127
+ Returns
128
+ -------
129
+ dict
130
+ The output of the marker.
131
+
132
+ """
133
+
134
+ class Marker(BaseMarker):
135
+ _MARKER_INOUT_MAPPINGS = { # noqa: RUF012
136
+ DataType.BOLD: {"feat": StorageType.Vector},
137
+ }
138
+
139
+ def compute(self, input, extra_input=None):
140
+ change(input, extra_input)
141
+ return {"feat": {"data": np.zeros((1, 1)), "col_names": ["x"]}}
142
+
143
+ marker = Marker(on=["BOLD"], name="marker")
144
+ return marker.fit_transform(input=_input(tmp_path))
145
+
146
+
147
+ def _input(tmp_path: Path) -> dict:
148
+ """Get input data with the types of values the markers check.
149
+
150
+ Parameters
151
+ ----------
152
+ tmp_path : pathlib.Path
153
+ The path to the test directory.
154
+
155
+ Returns
156
+ -------
157
+ dict
158
+ The input data.
159
+
160
+ """
161
+ # Non-integer values, which change when read as float32
162
+ values = np.full((2, 2, 2), 0.1)
163
+ # File-backed image, with its data cached
164
+ t1w_path = tmp_path / "t1w.nii.gz"
165
+ nib.save(nib.Nifti1Image(values, np.eye(4)), t1w_path)
166
+ t1w = nib.load(t1w_path)
167
+ t1w.get_fdata()
168
+ # File-backed image, without its data loaded
169
+ mask_path = tmp_path / "mask.nii.gz"
170
+ nib.save(nib.Nifti1Image(values, np.eye(4)), mask_path)
171
+ meta = {
172
+ "datagrabber": "dg",
173
+ "element": {"subject": "sub-01"},
174
+ "dependencies": {"numpy"},
175
+ }
176
+ return {
177
+ "BOLD": {
178
+ "path": tmp_path / "bold.nii.gz",
179
+ # In-memory image
180
+ "data": nib.Nifti1Image(np.full((2, 2, 2, 3), 0.1), np.eye(4)),
181
+ "array": np.arange(4.0),
182
+ "mask": nib.load(mask_path),
183
+ "confounds": {"data": pd.DataFrame({"a": [1.0, 2.0, 3.0]})},
184
+ "meta": meta,
185
+ },
186
+ "T1w": {"path": t1w_path, "data": t1w, "meta": meta.copy()},
187
+ "Warp": [{"src": "MNI", "dst": "native"}],
188
+ }
189
+
190
+
191
+ def replace_value(input: dict, extra_input: dict) -> None:
192
+ """Replace a value."""
193
+ input["data"] = "new"
194
+
195
+
196
+ def add_nested_value(input: dict, extra_input: dict) -> None:
197
+ """Add a value to a nested dictionary."""
198
+ input["meta"]["new"] = 1
199
+
200
+
201
+ def remove_nested_value(input: dict, extra_input: dict) -> None:
202
+ """Remove the values of a nested dictionary."""
203
+ input["meta"]["element"].clear()
204
+
205
+
206
+ def remove_value(input: dict, extra_input: dict) -> None:
207
+ """Remove a value."""
208
+ del input["path"]
209
+
210
+
211
+ def change_array(input: dict, extra_input: dict) -> None:
212
+ """Change an array in place."""
213
+ input["array"][0] = 9.0
214
+
215
+
216
+ def reshape_array(input: dict, extra_input: dict) -> None:
217
+ """Change the shape of an array in place."""
218
+ input["array"].shape = (2, 2)
219
+
220
+
221
+ def change_array_dtype(input: dict, extra_input: dict) -> None:
222
+ """Change the dtype of an array in place."""
223
+ input["array"].dtype = np.int64
224
+
225
+
226
+ def change_image_data(input: dict, extra_input: dict) -> None:
227
+ """Change the data of an in-memory image in place."""
228
+ np.asarray(input["data"].dataobj)[0, 0, 0, 0] = 9.0
229
+
230
+
231
+ def change_image_affine(input: dict, extra_input: dict) -> None:
232
+ """Change the affine of an image in place."""
233
+ input["data"].affine[0, 3] = 9.0
234
+
235
+
236
+ def change_dataframe(input: dict, extra_input: dict) -> None:
237
+ """Change a column of a data frame in place."""
238
+ input["confounds"]["data"]["a"] = 0.0
239
+
240
+
241
+ def rename_dataframe_column(input: dict, extra_input: dict) -> None:
242
+ """Rename a column of a data frame in place."""
243
+ input["confounds"]["data"].rename(columns={"a": "b"}, inplace=True)
244
+
245
+
246
+ def change_list_item(input: dict, extra_input: dict) -> None:
247
+ """Change an item of a list."""
248
+ extra_input["Warp"][0]["dst"] = "MNI"
249
+
250
+
251
+ def append_to_list(input: dict, extra_input: dict) -> None:
252
+ """Append an item to a list."""
253
+ extra_input["Warp"].append({})
254
+
255
+
256
+ def clear_set(input: dict, extra_input: dict) -> None:
257
+ """Remove the items of a set."""
258
+ input["meta"]["dependencies"].clear()
259
+
260
+
261
+ def change_cached_image_data(input: dict, extra_input: dict) -> None:
262
+ """Change the cached data of an image of the extra input."""
263
+ extra_input["T1w"]["data"].get_fdata()[0, 0, 0] = 9.0
264
+
265
+
266
+ def change_image_data_read_with_nilearn(
267
+ input: dict, extra_input: dict
268
+ ) -> None:
269
+ """Change the data of an image, after loading it with nilearn."""
270
+ get_data(input["mask"])[0, 0, 0] = 9.0
271
+
272
+
273
+ def change_image_data_read_with_nibabel(
274
+ input: dict, extra_input: dict
275
+ ) -> None:
276
+ """Change the data of an image, after loading it with nibabel."""
277
+ input["mask"].get_fdata()[0, 0, 0] = 9.0
278
+
279
+
280
+ # The changes of the input and the paths of the data they change
281
+ _CHANGES = {
282
+ replace_value: "BOLD.data",
283
+ add_nested_value: "BOLD.meta.new",
284
+ remove_nested_value: "BOLD.meta.element.subject",
285
+ remove_value: "BOLD.path",
286
+ change_array: "BOLD.array",
287
+ reshape_array: "BOLD.array",
288
+ change_array_dtype: "BOLD.array",
289
+ change_image_data: "BOLD.data",
290
+ change_image_affine: "BOLD.data",
291
+ change_dataframe: "BOLD.confounds.data",
292
+ rename_dataframe_column: "BOLD.confounds.data",
293
+ change_list_item: "Warp.0.dst",
294
+ append_to_list: "Warp.1",
295
+ clear_set: "BOLD.meta.dependencies",
296
+ change_cached_image_data: "T1w.data",
297
+ change_image_data_read_with_nilearn: "BOLD.mask",
298
+ change_image_data_read_with_nibabel: "BOLD.mask",
299
+ }
300
+
301
+
302
+ @pytest.mark.parametrize(
303
+ "change, changed",
304
+ _CHANGES.items(),
305
+ ids=[change.__name__ for change in _CHANGES],
306
+ )
307
+ def test_base_marker_input_changed(
308
+ tmp_path: Path, change: Callable, changed: str
309
+ ) -> None:
310
+ """Test markers cannot change their input data.
311
+
312
+ Parameters
313
+ ----------
314
+ tmp_path : pathlib.Path
315
+ The path to the test directory.
316
+ change : callable
317
+ The parametrized change of the input.
318
+ changed : str
319
+ The parametrized path of the changed data.
320
+
321
+ """
322
+ with pytest.raises(RuntimeError, match=rf"marker changed .*{changed}"):
323
+ _fit(change, tmp_path)
324
+
325
+
326
+ def test_base_marker_input_not_changed(tmp_path: Path) -> None:
327
+ """Test markers that only read their input data.
328
+
329
+ Parameters
330
+ ----------
331
+ tmp_path : pathlib.Path
332
+ The path to the test directory.
333
+
334
+ """
335
+
336
+ def read(input: dict, extra_input: dict) -> None:
337
+ input["data"].get_fdata()
338
+ float(np.sum(input["array"]))
339
+ float(input["confounds"]["data"]["a"].sum())
340
+ # Replace the cached float64 data with float32
341
+ extra_input["T1w"]["data"].get_fdata(dtype=np.float32)
342
+ # Cache float32 data
343
+ input["mask"].get_fdata(dtype=np.float32)
344
+ get_data(input["mask"])
345
+ set(input["meta"]["dependencies"])
346
+ [warp["dst"] for warp in extra_input["Warp"]]
347
+
348
+ out = _fit(read, tmp_path)
349
+ assert "feat" in out["BOLD"]
350
+
351
+
352
+ def test_base_marker_input_not_loaded(tmp_path: Path) -> None:
353
+ """Test checking the input does not load the data of the images.
354
+
355
+ Parameters
356
+ ----------
357
+ tmp_path : pathlib.Path
358
+ The path to the test directory.
359
+
360
+ """
361
+ input = _input(tmp_path)
362
+
363
+ class Marker(BaseMarker):
364
+ _MARKER_INOUT_MAPPINGS = { # noqa: RUF012
365
+ DataType.BOLD: {"feat": StorageType.Vector},
366
+ }
367
+
368
+ def compute(self, input, extra_input=None):
369
+ return {"feat": {"data": np.zeros((1, 1)), "col_names": ["x"]}}
370
+
371
+ Marker(on=["BOLD"], name="marker").fit_transform(input=input)
372
+ # The file-backed image without its data loaded is still not loaded
373
+ mask = input["BOLD"]["mask"]
374
+ assert not mask.in_memory
375
+ assert getattr(mask, "_data_cache", None) is None
376
+ assert getattr(mask, "_fdata_cache", None) is None
@@ -17,68 +17,75 @@ from numpy.testing import assert_array_almost_equal, assert_array_equal
17
17
  from scipy.stats import trim_mean
18
18
 
19
19
  from junifer.data import MaskRegistry, ParcellationRegistry
20
+ from junifer.datagrabber import DataType
20
21
  from junifer.datareader import DefaultDataReader
21
22
  from junifer.markers.parcel_aggregation import ParcelAggregation
22
- from junifer.storage import SQLiteFeatureStorage
23
- from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
23
+ from junifer.storage import HDF5FeatureStorage, StorageType
24
+ from junifer.testing.datagrabbers import (
25
+ PartlyCloudyTestingDataGrabber,
26
+ UCLACNPVBMTestingDataGrabber,
27
+ )
28
+
29
+
30
+ pytestmark = pytest.mark.external
24
31
 
25
32
 
26
33
  @pytest.mark.parametrize(
27
34
  "input_type, storage_type",
28
35
  [
29
36
  (
30
- "T1w",
31
- "vector",
37
+ DataType.T1w,
38
+ StorageType.Vector,
32
39
  ),
33
40
  (
34
- "T2w",
35
- "vector",
41
+ DataType.T2w,
42
+ StorageType.Vector,
36
43
  ),
37
44
  (
38
- "BOLD",
39
- "timeseries",
45
+ DataType.BOLD,
46
+ StorageType.Timeseries,
40
47
  ),
41
48
  (
42
- "VBM_GM",
43
- "vector",
49
+ DataType.VBM_GM,
50
+ StorageType.Vector,
44
51
  ),
45
52
  (
46
- "VBM_WM",
47
- "vector",
53
+ DataType.VBM_WM,
54
+ StorageType.Vector,
48
55
  ),
49
56
  (
50
- "VBM_CSF",
51
- "vector",
57
+ DataType.VBM_CSF,
58
+ StorageType.Vector,
52
59
  ),
53
60
  (
54
- "fALFF",
55
- "vector",
61
+ DataType.FALFF,
62
+ StorageType.Vector,
56
63
  ),
57
64
  (
58
- "GCOR",
59
- "vector",
65
+ DataType.GCOR,
66
+ StorageType.Vector,
60
67
  ),
61
68
  (
62
- "LCOR",
63
- "vector",
69
+ DataType.LCOR,
70
+ StorageType.Vector,
64
71
  ),
65
72
  ],
66
73
  )
67
74
  def test_ParcelAggregation_input_output(
68
- input_type: str, storage_type: str
75
+ input_type: DataType, storage_type: StorageType
69
76
  ) -> None:
70
77
  """Test ParcelAggregation input and output types.
71
78
 
72
79
  Parameters
73
80
  ----------
74
- input_type : str
81
+ input_type : DataType
75
82
  The parametrized input type.
76
- storage_type : str
83
+ storage_type : StorageType
77
84
  The parametrized storage type.
78
85
 
79
86
  """
80
87
  assert storage_type == ParcelAggregation(
81
- parcellation="Schaefer100x7", method="mean", on=input_type
88
+ parcellation=["Schaefer100x7"], method="mean", on=[input_type]
82
89
  ).storage_type(input_type=input_type, output_feature="aggregation")
83
90
 
84
91
 
@@ -90,7 +97,7 @@ def test_ParcelAggregation_3D() -> None:
90
97
  marker = ParcelAggregation(
91
98
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
92
99
  method="mean",
93
- on="BOLD",
100
+ on=DataType.BOLD,
94
101
  )
95
102
  element_data["BOLD"]["data"] = element_data["BOLD"]["data"].slicer[
96
103
  ..., 0:1
@@ -108,10 +115,7 @@ def test_ParcelAggregation_3D() -> None:
108
115
  img=testing_parcellation,
109
116
  )
110
117
  # Create NiftiMasker
111
- masker = NiftiMasker(
112
- testing_parcellation_bin,
113
- target_affine=element_data["BOLD"]["data"].affine,
114
- )
118
+ masker = NiftiMasker(testing_parcellation_bin)
115
119
  data = masker.fit_transform(element_data["BOLD"]["data"])
116
120
  parcellation_values = np.squeeze(
117
121
  masker.transform(testing_parcellation)
@@ -157,7 +161,7 @@ def test_ParcelAggregation_3D() -> None:
157
161
  marker = ParcelAggregation(
158
162
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
159
163
  method="std",
160
- on="BOLD",
164
+ on=DataType.BOLD,
161
165
  )
162
166
  parcel_agg_std_bold_data = marker.fit_transform(element_data)["BOLD"][
163
167
  "aggregation"
@@ -182,7 +186,7 @@ def test_ParcelAggregation_3D() -> None:
182
186
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
183
187
  method="trim_mean",
184
188
  method_params={"proportiontocut": 0.1},
185
- on="BOLD",
189
+ on=DataType.BOLD,
186
190
  )
187
191
  parcel_agg_trim_mean_bold_data = marker.fit_transform(element_data)[
188
192
  "BOLD"
@@ -192,6 +196,40 @@ def test_ParcelAggregation_3D() -> None:
192
196
  assert_array_equal(parcel_agg_trim_mean_bold_data, manual)
193
197
 
194
198
 
199
+ def test_ParcelAggregation_VBM() -> None:
200
+ """Test ParcelAggregation on VBM data in the parcellation space."""
201
+ parcellation = "TianxS1x3TxMNInonlinear2009cAsym"
202
+ with UCLACNPVBMTestingDataGrabber() as dg:
203
+ element_data = DefaultDataReader().fit_transform(dg["sub-10206"])
204
+ vbm = element_data["VBM_GM"]
205
+ marker = ParcelAggregation(
206
+ parcellation=parcellation, method="mean", on=DataType.VBM_GM
207
+ )
208
+ parcel_agg_vbm_data = marker.fit_transform(element_data)["VBM_GM"][
209
+ "aggregation"
210
+ ]["data"]
211
+
212
+ # Same space and grid, so the parcellation is used as is
213
+ tailored, _ = ParcellationRegistry().get(
214
+ parcellations=[parcellation], target_data=vbm
215
+ )
216
+ raw, _, _, _ = ParcellationRegistry().load(
217
+ name=parcellation,
218
+ target_space=vbm["space"],
219
+ resolution=2,
220
+ )
221
+ assert_array_equal(tailored.affine, raw.affine)
222
+ assert_array_equal(tailored.get_fdata(), raw.get_fdata())
223
+
224
+ # Compare with nilearn
225
+ # nilearn returns a 1D array for 3D images
226
+ nifti_labels_masked_vbm = np.atleast_2d(
227
+ NiftiLabelsMasker(labels_img=raw).fit_transform(vbm["data"])
228
+ )
229
+ assert parcel_agg_vbm_data.shape == (1, 16)
230
+ assert_array_almost_equal(nifti_labels_masked_vbm, parcel_agg_vbm_data)
231
+
232
+
195
233
  def test_ParcelAggregation_4D():
196
234
  """Test ParcelAggregation object on 4D images."""
197
235
  with PartlyCloudyTestingDataGrabber() as dg:
@@ -237,13 +275,13 @@ def test_ParcelAggregation_storage(tmp_path: Path) -> None:
237
275
  # Store 3D
238
276
  with PartlyCloudyTestingDataGrabber() as dg:
239
277
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
240
- storage = SQLiteFeatureStorage(
241
- uri=tmp_path / "test_parcel_storage_3D.sqlite", upsert="ignore"
278
+ storage = HDF5FeatureStorage(
279
+ uri=tmp_path / "test_parcel_storage_3D.hdf5",
242
280
  )
243
281
  marker = ParcelAggregation(
244
282
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
245
283
  method="mean",
246
- on="BOLD",
284
+ on=DataType.BOLD,
247
285
  )
248
286
  element_data["BOLD"]["data"] = element_data["BOLD"]["data"].slicer[
249
287
  ..., 0:1
@@ -258,13 +296,13 @@ def test_ParcelAggregation_storage(tmp_path: Path) -> None:
258
296
  # Store 4D
259
297
  with PartlyCloudyTestingDataGrabber() as dg:
260
298
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
261
- storage = SQLiteFeatureStorage(
262
- uri=tmp_path / "test_parcel_storage_4D.sqlite", upsert="ignore"
299
+ storage = HDF5FeatureStorage(
300
+ uri=tmp_path / "test_parcel_storage_4D.hdf5",
263
301
  )
264
302
  marker = ParcelAggregation(
265
303
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
266
304
  method="mean",
267
- on="BOLD",
305
+ on=DataType.BOLD,
268
306
  )
269
307
  marker.fit_transform(input=element_data, storage=storage)
270
308
  features = storage.list_features()
@@ -283,8 +321,8 @@ def test_ParcelAggregation_3D_mask() -> None:
283
321
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
284
322
  method="mean",
285
323
  name="tian_mean",
286
- on="BOLD",
287
- masks="compute_brain_mask",
324
+ on=DataType.BOLD,
325
+ masks=["compute_brain_mask"],
288
326
  )
289
327
  element_data["BOLD"]["data"] = element_data["BOLD"]["data"].slicer[
290
328
  ..., 0:1
@@ -360,9 +398,9 @@ def test_ParcelAggregation_3D_mask_computed() -> None:
360
398
  marker = ParcelAggregation(
361
399
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
362
400
  method="mean",
363
- masks={"compute_brain_mask": {"threshold": 0.2}},
401
+ masks=[{"compute_brain_mask": {"threshold": 0.2}}],
364
402
  name="tian_mean",
365
- on="BOLD",
403
+ on=DataType.BOLD,
366
404
  )
367
405
  parcel_agg_mean_bold_data = marker.fit_transform(element_data)["BOLD"][
368
406
  "aggregation"
@@ -444,7 +482,7 @@ def test_ParcelAggregation_3D_multiple_non_overlapping(tmp_path: Path) -> None:
444
482
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
445
483
  method="mean",
446
484
  name="tian_mean",
447
- on="BOLD",
485
+ on=DataType.BOLD,
448
486
  )
449
487
  orig_mean = marker_original.fit_transform(element_data)["BOLD"][
450
488
  "aggregation"
@@ -462,7 +500,7 @@ def test_ParcelAggregation_3D_multiple_non_overlapping(tmp_path: Path) -> None:
462
500
  ],
463
501
  method="mean",
464
502
  name="tian_mean",
465
- on="BOLD",
503
+ on=DataType.BOLD,
466
504
  )
467
505
 
468
506
  # No warnings should be raised
@@ -549,7 +587,7 @@ def test_ParcelAggregation_3D_multiple_overlapping(tmp_path: Path) -> None:
549
587
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
550
588
  method="mean",
551
589
  name="tian_mean",
552
- on="BOLD",
590
+ on=DataType.BOLD,
553
591
  )
554
592
  orig_mean = marker_original.fit_transform(element_data)["BOLD"][
555
593
  "aggregation"
@@ -567,7 +605,7 @@ def test_ParcelAggregation_3D_multiple_overlapping(tmp_path: Path) -> None:
567
605
  ],
568
606
  method="mean",
569
607
  name="tian_mean",
570
- on="BOLD",
608
+ on=DataType.BOLD,
571
609
  )
572
610
  # Warning should be raised
573
611
  with pytest.warns(RuntimeWarning, match="overlapping voxels"):
@@ -658,7 +696,7 @@ def test_ParcelAggregation_3D_multiple_duplicated_labels(
658
696
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
659
697
  method="mean",
660
698
  name="tian_mean",
661
- on="BOLD",
699
+ on=DataType.BOLD,
662
700
  )
663
701
  orig_mean = marker_original.fit_transform(element_data)["BOLD"][
664
702
  "aggregation"
@@ -676,7 +714,7 @@ def test_ParcelAggregation_3D_multiple_duplicated_labels(
676
714
  ],
677
715
  method="mean",
678
716
  name="tian_mean",
679
- on="BOLD",
717
+ on=DataType.BOLD,
680
718
  )
681
719
 
682
720
  # Warning should be raised
@@ -712,7 +750,7 @@ def test_ParcelAggregation_4D_agg_time():
712
750
  parcellation="TianxS1x3TxMNInonlinear2009cAsym",
713
751
  method="mean",
714
752
  time_method="mean",
715
- on="BOLD",
753
+ on=DataType.BOLD,
716
754
  )
717
755
  parcel_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
718
756
  "aggregation"
@@ -744,11 +782,11 @@ def test_ParcelAggregation_4D_agg_time():
744
782
  # Test picking first time point
745
783
  nifti_labels_masked_bold_pick_0 = nifti_labels_masked_bold[:1, :]
746
784
  marker = ParcelAggregation(
747
- parcellation="TianxS1x3TxMNInonlinear2009cAsym",
785
+ parcellation=["TianxS1x3TxMNInonlinear2009cAsym"],
748
786
  method="mean",
749
787
  time_method="select",
750
788
  time_method_params={"pick": [0]},
751
- on="BOLD",
789
+ on=DataType.BOLD,
752
790
  )
753
791
  parcel_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
754
792
  "aggregation"
@@ -771,7 +809,7 @@ def test_ParcelAggregation_errors() -> None:
771
809
  method="mean",
772
810
  time_method="select",
773
811
  time_method_params={"pick": [0]},
774
- on="VBM_GM",
812
+ on=[DataType.VBM_GM],
775
813
  )
776
814
 
777
815
  with pytest.raises(
@@ -781,7 +819,7 @@ def test_ParcelAggregation_errors() -> None:
781
819
  parcellation="Schaefer100x7",
782
820
  method="mean",
783
821
  time_method_params={"pick": [0]},
784
- on="VBM_GM",
822
+ on=[DataType.VBM_GM],
785
823
  )
786
824
 
787
825
 
@@ -797,7 +835,7 @@ def test_ParcelAggregation_warning() -> None:
797
835
  method="mean",
798
836
  time_method="select",
799
837
  time_method_params={"pick": [0]},
800
- on="BOLD",
838
+ on=DataType.BOLD,
801
839
  )
802
840
  element_data["BOLD"]["data"] = element_data["BOLD"]["data"].slicer[
803
841
  ..., 0:1