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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. junifer/_version.py +9 -19
  2. junifer/api/__init__.pyi +17 -2
  3. junifer/api/functions.py +222 -39
  4. junifer/api/queue_context/__init__.pyi +11 -3
  5. junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
  6. junifer/api/queue_context/htcondor_adapter.py +118 -181
  7. junifer/api/queue_context/queue_context_adapter.py +51 -7
  8. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
  9. junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
  10. junifer/api/res/fsl/run_fsl_docker.sh +4 -0
  11. junifer/api/tests/test_functions.py +863 -36
  12. junifer/cli/__init__.py +1 -7
  13. junifer/cli/cli.py +41 -22
  14. junifer/cli/parser.py +23 -147
  15. junifer/cli/tests/data/gmd_mean.yaml +6 -6
  16. junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
  17. junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
  18. junifer/cli/tests/test_cli.py +120 -4
  19. junifer/cli/tests/test_cli_utils.py +23 -6
  20. junifer/cli/utils.py +14 -9
  21. junifer/configs/__init__.py +5 -0
  22. junifer/configs/__init__.pyi +0 -0
  23. junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
  24. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
  25. junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
  26. junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
  27. junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
  28. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
  29. junifer/configs/juseless/datagrabbers/ucla.py +111 -104
  30. junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
  31. junifer/configs/py.typed +0 -0
  32. junifer/conftest.py +63 -24
  33. junifer/data/_dispatch.py +9 -19
  34. junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
  35. junifer/data/coordinates/_coordinates.py +51 -6
  36. junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
  37. junifer/data/coordinates/tests/test_coordinates.py +15 -3
  38. junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
  39. junifer/data/maps/_ants_maps_warper.py +18 -12
  40. junifer/data/maps/_fsl_maps_warper.py +7 -3
  41. junifer/data/maps/_maps.py +12 -8
  42. junifer/data/maps/tests/test_maps.py +13 -21
  43. junifer/data/masks/_ants_mask_warper.py +18 -12
  44. junifer/data/masks/_fsl_mask_warper.py +7 -3
  45. junifer/data/masks/_masks.py +15 -12
  46. junifer/data/masks/tests/test_masks.py +20 -17
  47. junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
  48. junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
  49. junifer/data/parcellations/_parcellations.py +125 -91
  50. junifer/data/parcellations/tests/test_parcellations.py +122 -1
  51. junifer/data/template_spaces.py +117 -50
  52. junifer/data/tests/test_template_spaces.py +10 -7
  53. junifer/data/utils.py +10 -8
  54. junifer/datagrabber/__init__.pyi +38 -5
  55. junifer/datagrabber/aomic/__init__.pyi +8 -1
  56. junifer/datagrabber/aomic/_types.py +22 -0
  57. junifer/datagrabber/aomic/id1000.py +193 -183
  58. junifer/datagrabber/aomic/piop1.py +215 -259
  59. junifer/datagrabber/aomic/piop2.py +211 -244
  60. junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
  61. junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
  62. junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
  63. junifer/datagrabber/base.py +253 -79
  64. junifer/datagrabber/datalad_base.py +127 -76
  65. junifer/datagrabber/dmcc13_benchmark.py +204 -289
  66. junifer/datagrabber/hcp1200/__init__.pyi +7 -2
  67. junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
  68. junifer/datagrabber/hcp1200/hcp1200.py +151 -164
  69. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
  70. junifer/datagrabber/multiple.py +183 -63
  71. junifer/datagrabber/pattern.py +358 -227
  72. junifer/datagrabber/pattern_datalad.py +84 -124
  73. junifer/datagrabber/pattern_validation_mixin.py +22 -50
  74. junifer/datagrabber/tests/test_base.py +17 -4
  75. junifer/datagrabber/tests/test_datalad_base.py +20 -27
  76. junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
  77. junifer/datagrabber/tests/test_multiple.py +293 -31
  78. junifer/datagrabber/tests/test_pattern.py +577 -18
  79. junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
  80. junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
  81. junifer/datareader/default.py +8 -5
  82. junifer/datareader/tests/test_default_reader.py +1 -1
  83. junifer/external/BrainPrint/.codecov.yml +2 -0
  84. junifer/external/BrainPrint/.codespellignore +2 -0
  85. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
  86. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
  87. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
  88. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
  89. junifer/external/BrainPrint/.github/dependabot.yml +6 -0
  90. junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
  91. junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
  92. junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
  93. junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
  94. junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
  95. junifer/external/BrainPrint/.gitignore +123 -0
  96. junifer/external/BrainPrint/LICENSE +21 -0
  97. junifer/external/BrainPrint/README.md +108 -0
  98. junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
  99. junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
  100. junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
  101. junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
  102. junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
  103. junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
  104. junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
  105. junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
  106. junifer/external/BrainPrint/doc/Makefile +20 -0
  107. junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
  108. junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
  109. junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
  110. junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
  111. junifer/external/BrainPrint/doc/api/index.rst +11 -0
  112. junifer/external/BrainPrint/doc/conf.py +272 -0
  113. junifer/external/BrainPrint/doc/index.rst +140 -0
  114. junifer/external/BrainPrint/doc/links.inc +36 -0
  115. junifer/external/BrainPrint/doc/make.bat +35 -0
  116. junifer/external/BrainPrint/doc/references.bib +23 -0
  117. junifer/external/BrainPrint/pyproject.toml +170 -0
  118. junifer/external/BrainPrint/setup.py +3 -0
  119. junifer/external/__init__.py +5 -0
  120. junifer/external/__init__.pyi +0 -0
  121. junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
  122. junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
  123. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
  124. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
  125. junifer/external/py.typed +0 -0
  126. junifer/markers/__init__.pyi +6 -3
  127. junifer/markers/base.py +126 -76
  128. junifer/markers/brainprint.py +30 -39
  129. junifer/markers/complexity/__init__.py +20 -3
  130. junifer/markers/complexity/complexity_base.py +40 -41
  131. junifer/markers/complexity/hurst_exponent.py +22 -35
  132. junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
  133. junifer/markers/complexity/perm_entropy.py +22 -36
  134. junifer/markers/complexity/range_entropy.py +22 -35
  135. junifer/markers/complexity/range_entropy_auc.py +27 -36
  136. junifer/markers/complexity/sample_entropy.py +22 -36
  137. junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
  138. junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
  139. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
  140. junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
  141. junifer/markers/complexity/tests/test_range_entropy.py +47 -36
  142. junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
  143. junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
  144. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
  145. junifer/markers/complexity/weighted_perm_entropy.py +22 -36
  146. junifer/markers/ets_rss.py +35 -31
  147. junifer/markers/falff/__init__.pyi +7 -1
  148. junifer/markers/falff/_afni_falff.py +22 -9
  149. junifer/markers/falff/_junifer_falff.py +47 -31
  150. junifer/markers/falff/falff_base.py +80 -60
  151. junifer/markers/falff/falff_maps.py +17 -41
  152. junifer/markers/falff/falff_parcels.py +26 -51
  153. junifer/markers/falff/falff_spheres.py +34 -61
  154. junifer/markers/falff/tests/test_falff_maps.py +20 -23
  155. junifer/markers/falff/tests/test_falff_mask.py +99 -0
  156. junifer/markers/falff/tests/test_falff_parcels.py +14 -10
  157. junifer/markers/falff/tests/test_falff_spheres.py +14 -10
  158. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
  159. junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
  160. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
  161. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
  162. junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
  163. junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
  164. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
  165. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
  166. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
  167. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
  168. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
  169. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
  170. junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
  171. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
  172. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
  173. junifer/markers/maps_aggregation.py +77 -55
  174. junifer/markers/parcel_aggregation.py +91 -70
  175. junifer/markers/reho/__init__.pyi +7 -1
  176. junifer/markers/reho/_afni_reho.py +23 -14
  177. junifer/markers/reho/_junifer_reho.py +112 -123
  178. junifer/markers/reho/reho_base.py +115 -37
  179. junifer/markers/reho/reho_maps.py +18 -32
  180. junifer/markers/reho/reho_parcels.py +26 -41
  181. junifer/markers/reho/reho_spheres.py +33 -54
  182. junifer/markers/reho/tests/test_junifer_reho.py +178 -0
  183. junifer/markers/reho/tests/test_reho_maps.py +27 -25
  184. junifer/markers/reho/tests/test_reho_parcels.py +28 -22
  185. junifer/markers/reho/tests/test_reho_spheres.py +16 -12
  186. junifer/markers/sphere_aggregation.py +88 -72
  187. junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
  188. junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
  189. junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
  190. junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
  191. junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
  192. junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
  193. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
  194. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
  195. junifer/markers/tests/test_brainprint.py +10 -9
  196. junifer/markers/tests/test_ets_rss.py +7 -3
  197. junifer/markers/tests/test_lost_regions.py +279 -0
  198. junifer/markers/tests/test_maps_aggregation.py +67 -114
  199. junifer/markers/tests/test_marker_utils.py +40 -1
  200. junifer/markers/tests/test_markers_base.py +275 -6
  201. junifer/markers/tests/test_parcel_aggregation.py +91 -53
  202. junifer/markers/tests/test_sphere_aggregation.py +53 -47
  203. junifer/markers/utils.py +267 -5
  204. junifer/onthefly/__init__.py +2 -3
  205. junifer/onthefly/__init__.pyi +4 -0
  206. junifer/onthefly/_brainprint.py +3 -5
  207. junifer/onthefly/py.typed +0 -0
  208. junifer/onthefly/read_transform.py +11 -9
  209. junifer/onthefly/tests/test_read_transform.py +7 -7
  210. junifer/pipeline/__init__.pyi +4 -2
  211. junifer/pipeline/marker_collection.py +14 -10
  212. junifer/pipeline/pipeline_component_registry.py +25 -6
  213. junifer/pipeline/pipeline_step_mixin.py +5 -12
  214. junifer/pipeline/tests/test_data_object_dumper.py +10 -10
  215. junifer/pipeline/tests/test_marker_collection.py +145 -168
  216. junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
  217. junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
  218. junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
  219. junifer/pipeline/tests/test_workdir_manager.py +39 -0
  220. junifer/pipeline/update_meta_mixin.py +13 -10
  221. junifer/pipeline/utils.py +22 -17
  222. junifer/pipeline/workdir_manager.py +41 -32
  223. junifer/preprocess/__init__.pyi +9 -4
  224. junifer/preprocess/_temporal_filter.py +34 -33
  225. junifer/preprocess/_temporal_slicer.py +17 -30
  226. junifer/preprocess/base.py +99 -55
  227. junifer/preprocess/confounds/__init__.pyi +10 -2
  228. junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
  229. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
  230. junifer/preprocess/smoothing/__init__.pyi +2 -2
  231. junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
  232. junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
  233. junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
  234. junifer/preprocess/smoothing/smoothing.py +59 -40
  235. junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
  236. junifer/preprocess/tests/test_preprocess_base.py +17 -9
  237. junifer/preprocess/tests/test_temporal_filter.py +37 -16
  238. junifer/preprocess/tests/test_temporal_slicer.py +12 -12
  239. junifer/preprocess/warping/__init__.pyi +2 -2
  240. junifer/preprocess/warping/_ants_warper.py +56 -40
  241. junifer/preprocess/warping/_fsl_warper.py +49 -36
  242. junifer/preprocess/warping/_utils.py +106 -0
  243. junifer/preprocess/warping/space_warper.py +99 -98
  244. junifer/preprocess/warping/tests/test_space_warper.py +46 -41
  245. junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
  246. junifer/stats.py +46 -34
  247. junifer/storage/__init__.pyi +8 -4
  248. junifer/storage/base.py +129 -64
  249. junifer/storage/hdf5.py +307 -199
  250. junifer/storage/pandas_base.py +46 -24
  251. junifer/storage/sqlite.py +99 -62
  252. junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
  253. junifer/storage/tests/test_hdf5.py +147 -12
  254. junifer/storage/tests/test_repeated_storage.py +293 -0
  255. junifer/storage/tests/test_sqlite.py +9 -20
  256. junifer/storage/tests/test_storage_base.py +18 -21
  257. junifer/storage/tests/test_utils.py +83 -22
  258. junifer/storage/utils.py +58 -38
  259. junifer/testing/__init__.pyi +2 -2
  260. junifer/testing/datagrabbers.py +480 -44
  261. junifer/testing/registry.py +12 -0
  262. junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
  263. junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
  264. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
  265. junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
  266. junifer/testing/tests/test_testing_registry.py +31 -0
  267. junifer/testing/tests/test_testing_utils.py +35 -0
  268. junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
  269. junifer/testing/utils.py +33 -1
  270. junifer/tests/test_stats.py +1 -5
  271. junifer/typing/__init__.pyi +2 -0
  272. junifer/typing/_typing.py +34 -19
  273. junifer/typing/py.typed +0 -0
  274. junifer/utils/__init__.pyi +10 -1
  275. junifer/utils/_config.py +1 -2
  276. junifer/utils/helpers.py +68 -2
  277. junifer/utils/logging.py +31 -7
  278. junifer/utils/tests/test_helpers.py +36 -1
  279. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
  280. junifer-0.0.8.dist-info/RECORD +402 -0
  281. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
  282. junifer/cli/tests/test_parser.py +0 -280
  283. junifer/external/h5io/h5io/__init__.py +0 -15
  284. junifer/external/h5io/h5io/_h5io.py +0 -767
  285. junifer/external/h5io/h5io/_version.py +0 -1
  286. junifer/external/h5io/h5io/chunked_array.py +0 -94
  287. junifer/external/h5io/h5io/chunked_list.py +0 -65
  288. junifer/markers/complexity/__init__.pyi +0 -17
  289. junifer-0.0.7.dev332.dist-info/RECORD +0 -353
  290. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
  291. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
  292. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
  293. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,19 @@
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ import nibabel as nib
7
+ import numpy as np
8
+ import pandas as pd
9
+ import pytest
10
+ from numpy.testing import assert_array_equal
11
+ from pandas.testing import assert_frame_equal
12
+
6
13
  from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
7
14
 
8
15
 
16
+ pytestmark = pytest.mark.external
17
+
18
+
9
19
  def test_PartlyCloudyTestingDataGrabber() -> None:
10
20
  """Test PartlyCloudyTestingDataGrabber."""
11
21
  expected_elements = [
@@ -38,3 +48,45 @@ def test_PartlyCloudyTestingDataGrabber() -> None:
38
48
  out = dg["sub-01"]
39
49
  assert "format" in out["BOLD"]["confounds"]
40
50
  assert "fmriprep" == out["BOLD"]["confounds"]["format"]
51
+
52
+
53
+ @pytest.mark.parametrize("reduce_confounds", [True, False])
54
+ def test_PartlyCloudyTestingDataGrabber_n_timepoints(
55
+ reduce_confounds: bool,
56
+ ) -> None:
57
+ """Test PartlyCloudyTestingDataGrabber with fewer timepoints.
58
+
59
+ Parameters
60
+ ----------
61
+ reduce_confounds : bool
62
+ The parametrized confounds reduction.
63
+
64
+ """
65
+ with PartlyCloudyTestingDataGrabber(
66
+ reduce_confounds=reduce_confounds
67
+ ) as dg:
68
+ full = dg["sub-01"]["BOLD"]
69
+ with PartlyCloudyTestingDataGrabber(
70
+ reduce_confounds=reduce_confounds, n_timepoints=50
71
+ ) as dg:
72
+ out = dg["sub-01"]["BOLD"]
73
+
74
+ full_img = nib.load(full["path"])
75
+ out_img = nib.load(out["path"])
76
+ assert out_img.shape == (*full_img.shape[:3], 50)
77
+ assert_array_equal(
78
+ np.asanyarray(out_img.dataobj),
79
+ np.asanyarray(full_img.dataobj[..., :50]),
80
+ )
81
+
82
+ full_confounds = pd.read_csv(full["confounds"]["path"], sep="\t")
83
+ out_confounds = pd.read_csv(out["confounds"]["path"], sep="\t")
84
+ assert out_confounds.shape == (50, full_confounds.shape[1])
85
+ assert_frame_equal(out_confounds, full_confounds.iloc[:50])
86
+
87
+
88
+ def test_PartlyCloudyTestingDataGrabber_n_subjects() -> None:
89
+ """Test PartlyCloudyTestingDataGrabber with fewer subjects."""
90
+ with PartlyCloudyTestingDataGrabber(n_subjects=3) as dg:
91
+ assert dg.get_elements() == ["sub-01", "sub-02", "sub-03"]
92
+ assert dg["sub-03"]["BOLD"]["path"].exists()
@@ -3,9 +3,15 @@
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ import nibabel as nib
7
+ import pytest
8
+
6
9
  from junifer.testing.datagrabbers import SPMAuditoryTestingDataGrabber
7
10
 
8
11
 
12
+ pytestmark = pytest.mark.external
13
+
14
+
9
15
  def test_SPMAuditoryTestingDataGrabber() -> None:
10
16
  """Test SPMAuditoryTestingDataGrabber."""
11
17
  expected_elements = [
@@ -31,3 +37,13 @@ def test_SPMAuditoryTestingDataGrabber() -> None:
31
37
  assert "T1w" in out
32
38
  assert out["T1w"]["path"].exists()
33
39
  assert out["T1w"]["path"].is_file()
40
+
41
+ assert nib.load(out["BOLD"]["path"]).shape == (64, 64, 64, 84)
42
+ assert nib.load(out["T1w"]["path"]).shape == (256, 256, 54)
43
+
44
+
45
+ def test_SPMAuditoryTestingDataGrabber_n_timepoints() -> None:
46
+ """Test SPMAuditoryTestingDataGrabber with fewer timepoints."""
47
+ with SPMAuditoryTestingDataGrabber(n_timepoints=10) as dg:
48
+ out = dg["sub001"]
49
+ assert nib.load(out["BOLD"]["path"]).shape == (64, 64, 64, 10)
@@ -5,12 +5,43 @@
5
5
  # License: AGPL
6
6
 
7
7
  from junifer.pipeline import PipelineComponentRegistry
8
+ from junifer.testing.datagrabbers import (
9
+ ADHDTestingDataGrabber,
10
+ OasisVBMTestingDataGrabber,
11
+ PartlyCloudyTestingDataGrabber,
12
+ SPMAuditoryTestingDataGrabber,
13
+ UCLACNPVBMTestingDataGrabber,
14
+ )
8
15
 
9
16
 
10
17
  def test_testing_registry() -> None:
11
18
  """Test testing registry."""
19
+ for dg in [
20
+ ADHDTestingDataGrabber,
21
+ OasisVBMTestingDataGrabber,
22
+ SPMAuditoryTestingDataGrabber,
23
+ PartlyCloudyTestingDataGrabber,
24
+ UCLACNPVBMTestingDataGrabber,
25
+ ]:
26
+ PipelineComponentRegistry().register(
27
+ step="datagrabber",
28
+ klass=dg,
29
+ )
12
30
  assert {
31
+ "ADHDTestingDataGrabber",
13
32
  "OasisVBMTestingDataGrabber",
14
33
  "SPMAuditoryTestingDataGrabber",
15
34
  "PartlyCloudyTestingDataGrabber",
35
+ "UCLACNPVBMTestingDataGrabber",
16
36
  }.issubset(set(PipelineComponentRegistry().step_components("datagrabber")))
37
+ for dg in [
38
+ ADHDTestingDataGrabber,
39
+ OasisVBMTestingDataGrabber,
40
+ SPMAuditoryTestingDataGrabber,
41
+ PartlyCloudyTestingDataGrabber,
42
+ UCLACNPVBMTestingDataGrabber,
43
+ ]:
44
+ PipelineComponentRegistry().deregister(
45
+ step="datagrabber",
46
+ klass=dg,
47
+ )
@@ -0,0 +1,35 @@
1
+ """Provide tests for testing utils."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import pytest
7
+
8
+ from junifer.testing import config_override
9
+ from junifer.utils import config
10
+
11
+
12
+ def test_config_override_unset() -> None:
13
+ """Test config_override deletes a previously unset key."""
14
+ assert config.get("testing.override") is None
15
+ with config_override("testing.override", True):
16
+ assert config.get("testing.override") is True
17
+ assert "testing.override" not in config._config
18
+
19
+
20
+ def test_config_override_restore() -> None:
21
+ """Test config_override restores a previously set value."""
22
+ config.set("testing.override", 1)
23
+ try:
24
+ with config_override("testing.override", 2):
25
+ assert config.get("testing.override") == 2
26
+ assert config.get("testing.override") == 1
27
+ finally:
28
+ config.delete("testing.override")
29
+
30
+
31
+ def test_config_override_error() -> None:
32
+ """Test config_override restores the state on error."""
33
+ with pytest.raises(RuntimeError), config_override("testing.override", 3):
34
+ raise RuntimeError()
35
+ assert "testing.override" not in config._config
@@ -0,0 +1,81 @@
1
+ """Provide tests for UCLACNPVBMTestingDataGrabber."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import nibabel as nib
7
+ import numpy as np
8
+ import pytest
9
+ from numpy.testing import assert_array_almost_equal
10
+ from templateflow import api as tflow
11
+
12
+ from junifer.testing.datagrabbers import UCLACNPVBMTestingDataGrabber
13
+
14
+
15
+ pytestmark = pytest.mark.external
16
+
17
+
18
+ def test_UCLACNPVBMTestingDataGrabber() -> None:
19
+ """Test UCLACNPVBMTestingDataGrabber."""
20
+ with UCLACNPVBMTestingDataGrabber() as dg:
21
+ elements = dg.get_elements()
22
+ assert len(elements) == 10
23
+ out = dg[elements[0]]
24
+ assert "VBM_GM" in out
25
+ assert out["VBM_GM"]["path"].is_file()
26
+ assert out["VBM_GM"]["space"] == "MNI152NLin2009cAsym"
27
+
28
+ # Resampled to the templateflow grid, so templates and masks do not
29
+ # need resampling
30
+ img = nib.load(out["VBM_GM"]["path"])
31
+ template = nib.load(
32
+ tflow.get(
33
+ "MNI152NLin2009cAsym",
34
+ resolution=2,
35
+ suffix="T1w",
36
+ desc=None,
37
+ extension="nii.gz",
38
+ )
39
+ )
40
+ assert img.shape == template.shape[:3]
41
+ assert_array_almost_equal(img.affine, template.affine)
42
+
43
+ participants = dg.get_participants()
44
+ assert list(participants.index) == elements
45
+ assert {"age", "gender"} <= set(participants.columns)
46
+
47
+
48
+ def test_UCLACNPVBMTestingDataGrabber_native() -> None:
49
+ """Test UCLACNPVBMTestingDataGrabber at the native resolution."""
50
+ with UCLACNPVBMTestingDataGrabber(resolution=1) as dg:
51
+ out = dg[dg.get_elements()[0]]
52
+ # No resampling at the native resolution
53
+ assert out["VBM_GM"]["path"].name.endswith("_probtissue.nii.gz")
54
+ assert nib.load(out["VBM_GM"]["path"]).shape == (193, 229, 193)
55
+
56
+
57
+ def test_UCLACNPVBMTestingDataGrabber_resample() -> None:
58
+ """Test UCLACNPVBMTestingDataGrabber resampling to a new resolution."""
59
+ with UCLACNPVBMTestingDataGrabber(resolution=4) as dg:
60
+ element = dg.get_elements()[0]
61
+ # Remove the cached map so it is resampled again (the CI image only
62
+ # has the default resolution)
63
+ native = dg._paths[element]
64
+ cached = native.with_name(
65
+ native.name.replace(".nii.gz", "_res-4mm.nii")
66
+ )
67
+ cached.unlink(missing_ok=True)
68
+
69
+ out = dg[element]
70
+ assert out["VBM_GM"]["path"] == cached
71
+ img = nib.load(cached)
72
+ assert img.shape == (49, 58, 49)
73
+ assert img.get_data_dtype() == np.float32
74
+ expected_affine = np.diag([4.0, 4.0, 4.0, 1.0])
75
+ expected_affine[:3, 3] = [-97.5, -133.5, -79.5]
76
+ assert_array_almost_equal(img.affine, expected_affine)
77
+
78
+ # Cached, so it is not resampled again
79
+ mtime = cached.stat().st_mtime_ns
80
+ assert dg[element]["VBM_GM"]["path"] == cached
81
+ assert cached.stat().st_mtime_ns == mtime
junifer/testing/utils.py CHANGED
@@ -3,10 +3,15 @@
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ from collections.abc import Generator
7
+ from contextlib import contextmanager
6
8
  from pathlib import Path
7
9
 
10
+ from ..utils import config
11
+ from ..utils._config import ConfigVal
8
12
 
9
- __all__ = ["get_testing_data"]
13
+
14
+ __all__ = ["config_override", "get_testing_data"]
10
15
 
11
16
 
12
17
  def get_testing_data(fname: str) -> Path:
@@ -27,3 +32,30 @@ def get_testing_data(fname: str) -> Path:
27
32
  if not t_path.exists():
28
33
  raise FileNotFoundError(f"File {fname} not found in testing data")
29
34
  return t_path.resolve()
35
+
36
+
37
+ @contextmanager
38
+ def config_override(key: str, val: ConfigVal) -> Generator[None, None, None]:
39
+ """Temporarily set a configuration parameter.
40
+
41
+ The previous state is restored on exit: the previous value if the key
42
+ was set, otherwise the key is deleted.
43
+
44
+ Parameters
45
+ ----------
46
+ key : str
47
+ The configuration key to set.
48
+ val : bool or int or float
49
+ The value to set ``key`` to.
50
+
51
+ """
52
+ missing = object()
53
+ prev = config.get(key, missing)
54
+ config.set(key, val)
55
+ try:
56
+ yield
57
+ finally:
58
+ if prev is missing:
59
+ config.delete(key)
60
+ else:
61
+ config.set(key, prev)
@@ -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 pytest
10
8
  from numpy.testing import assert_array_equal
@@ -25,7 +23,7 @@ from junifer.stats import count, get_aggfunc_by_name, select, winsorized_mean
25
23
  ("mode", {"keepdims": True}),
26
24
  ],
27
25
  )
28
- def test_get_aggfunc_by_name(name: str, params: Optional[dict]) -> None:
26
+ def test_get_aggfunc_by_name(name: str, params: dict | None) -> None:
29
27
  """Test aggregation function retrieval by name.
30
28
 
31
29
  Parameters
@@ -41,8 +39,6 @@ def test_get_aggfunc_by_name(name: str, params: Optional[dict]) -> None:
41
39
 
42
40
  def test_get_aggfunc_by_name_errors() -> None:
43
41
  """Test aggregation function retrieval using wrong name."""
44
- with pytest.raises(ValueError, match=r"unknown. Please provide any of"):
45
- get_aggfunc_by_name(name="invalid", func_params=None)
46
42
 
47
43
  with pytest.raises(ValueError, match=r"list of limits"):
48
44
  get_aggfunc_by_name(name="winsorized_mean", func_params=None)
@@ -12,6 +12,7 @@ __all__ = [
12
12
  "MarkerInOutMappings",
13
13
  "DataGrabberPatterns",
14
14
  "ConfigVal",
15
+ "SpaceLike",
15
16
  "Element",
16
17
  "Elements",
17
18
  ]
@@ -30,6 +31,7 @@ from ._typing import (
30
31
  MarkerInOutMappings,
31
32
  DataGrabberPatterns,
32
33
  ConfigVal,
34
+ SpaceLike,
33
35
  Element,
34
36
  Elements,
35
37
  )
junifer/typing/_typing.py CHANGED
@@ -3,7 +3,16 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from collections.abc import MutableMapping, Sequence
6
+ import sys
7
+
8
+
9
+ if sys.version_info < (3, 12): # pragma: no cover
10
+ from typing_extensions import TypedDict
11
+ else:
12
+ from typing import TypedDict
13
+
14
+ from collections.abc import Sequence
15
+ from enum import Enum
7
16
  from typing import (
8
17
  TYPE_CHECKING,
9
18
  Union,
@@ -12,11 +21,12 @@ from typing import (
12
21
 
13
22
  if TYPE_CHECKING:
14
23
  from ..data import BasePipelineDataRegistry
15
- from ..datagrabber import BaseDataGrabber
24
+ from ..datagrabber import BaseDataGrabber, DataType
16
25
  from ..datareader import DefaultDataReader
17
26
  from ..markers import BaseMarker
27
+ from ..pipeline import BaseDataDumpAsset, ExtDep
18
28
  from ..preprocess import BasePreprocessor
19
- from ..storage import BaseFeatureStorage
29
+ from ..storage import BaseFeatureStorage, StorageType
20
30
 
21
31
 
22
32
  __all__ = [
@@ -34,11 +44,22 @@ __all__ = [
34
44
  "MarkerLike",
35
45
  "PipelineComponent",
36
46
  "PreprocessorLike",
47
+ "SpaceLike",
37
48
  "StorageLike",
38
49
  ]
39
50
 
40
51
 
41
- DataDumpAssetLike = type["DataDumpAssetLike"]
52
+ class ExternalDependency(TypedDict):
53
+ name: "ExtDep"
54
+ commands: list[str]
55
+
56
+
57
+ class ConditionalDependency(TypedDict):
58
+ using: object
59
+ depends_on: list[object]
60
+
61
+
62
+ DataDumpAssetLike = type["BaseDataDumpAsset"]
42
63
  DataRegistryLike = type["BasePipelineDataRegistry"]
43
64
  DataGrabberLike = type["BaseDataGrabber"]
44
65
  PreprocessorLike = type["BasePreprocessor"]
@@ -52,20 +73,14 @@ PipelineComponent = Union[
52
73
  "StorageLike",
53
74
  ]
54
75
  Dependencies = set[str]
55
- ConditionalDependencies = Sequence[
56
- MutableMapping[
57
- str,
58
- Union[
59
- str,
60
- PipelineComponent,
61
- Sequence[str],
62
- Sequence[PipelineComponent],
63
- ],
64
- ]
76
+ ConditionalDependencies = list[ConditionalDependency]
77
+ ExternalDependencies = list[ExternalDependency]
78
+ MarkerInOutMappings = dict["DataType", dict[str, "StorageType"]]
79
+ DataGrabberPatterns = dict[
80
+ str,
81
+ dict[str, dict[str, str] | str] | dict[str, str] | list[dict[str, str]],
65
82
  ]
66
- ExternalDependencies = Sequence[MutableMapping[str, Union[str, Sequence[str]]]]
67
- MarkerInOutMappings = MutableMapping[str, MutableMapping[str, str]]
68
- DataGrabberPatterns = dict[str, Union[dict[str, str], list[dict[str, str]]]]
69
- ConfigVal = Union[bool, int, float, str]
70
- Element = Union[str, tuple[str, ...]]
83
+ ConfigVal = bool | int | float | str
84
+ SpaceLike = str | Enum
85
+ Element = str | tuple[str, ...]
71
86
  Elements = Sequence[Element]
File without changes
@@ -7,6 +7,9 @@ __all__ = [
7
7
  "warn_with_log",
8
8
  "run_ext_cmd",
9
9
  "deep_update",
10
+ "ensure_list",
11
+ "ensure_list_or_none",
12
+ "check_standardize",
10
13
  "yaml",
11
14
  "ConfigManager",
12
15
  ]
@@ -14,5 +17,11 @@ __all__ = [
14
17
  from .fs import make_executable
15
18
  from .logging import configure_logging, logger, raise_error, warn_with_log
16
19
  from ._config import config, ConfigManager
17
- from .helpers import run_ext_cmd, deep_update
20
+ from .helpers import (
21
+ run_ext_cmd,
22
+ deep_update,
23
+ ensure_list,
24
+ ensure_list_or_none,
25
+ check_standardize,
26
+ )
18
27
  from ._yaml import yaml
junifer/utils/_config.py CHANGED
@@ -5,7 +5,6 @@
5
5
  # License: AGPL
6
6
 
7
7
  import os
8
- from typing import Optional
9
8
 
10
9
  from ..typing import ConfigVal
11
10
  from .logging import logger
@@ -61,7 +60,7 @@ class ConfigManager(metaclass=Singleton):
61
60
  )
62
61
  self._config[var_name] = var_value
63
62
 
64
- def get(self, key: str, default: Optional[ConfigVal] = None) -> ConfigVal:
63
+ def get(self, key: str, default: ConfigVal | None = None) -> ConfigVal:
65
64
  """Get configuration parameter.
66
65
 
67
66
  Parameters
junifer/utils/helpers.py CHANGED
@@ -6,14 +6,22 @@
6
6
  import collections.abc
7
7
  import subprocess
8
8
  import sys
9
+ from pathlib import Path
10
+ from typing import Any
9
11
 
10
12
  from .logging import logger, raise_error
11
13
 
12
14
 
13
- __all__ = ["deep_update", "run_ext_cmd"]
15
+ __all__ = [
16
+ "check_standardize",
17
+ "deep_update",
18
+ "ensure_list",
19
+ "ensure_list_or_none",
20
+ "run_ext_cmd",
21
+ ]
14
22
 
15
23
 
16
- def run_ext_cmd(name: str, cmd: list[str]) -> None:
24
+ def run_ext_cmd(name: str, cmd: list[str], cwd: Path | None = None) -> None:
17
25
  """Run external command via subprocess.
18
26
 
19
27
  Parameters
@@ -22,6 +30,10 @@ def run_ext_cmd(name: str, cmd: list[str]) -> None:
22
30
  The name of the command.
23
31
  cmd : list of str
24
32
  The command to run as list of string.
33
+ cwd : pathlib.Path or None, optional
34
+ The directory to run the command in, e.g., for commands that write
35
+ extra files in the current directory. If None, the current directory
36
+ is used (default None).
25
37
 
26
38
  Raises
27
39
  ------
@@ -40,6 +52,7 @@ def run_ext_cmd(name: str, cmd: list[str]) -> None:
40
52
  stderr=subprocess.STDOUT,
41
53
  shell=True, # needed for respecting $PATH
42
54
  check=False,
55
+ cwd=cwd,
43
56
  )
44
57
  # Check for success or failure
45
58
  if process.returncode == 0:
@@ -82,3 +95,56 @@ def deep_update(d: dict, u: dict) -> dict:
82
95
  else:
83
96
  d[k] = v
84
97
  return d
98
+
99
+
100
+ def ensure_list(value: Any) -> Any:
101
+ """Ensure list."""
102
+ if not isinstance(value, list):
103
+ return [value]
104
+ else:
105
+ return value
106
+
107
+
108
+ def ensure_list_or_none(value: Any) -> Any:
109
+ """Ensure list or None."""
110
+ if value is None:
111
+ return None
112
+ if not isinstance(value, list):
113
+ return [value]
114
+ else:
115
+ return value
116
+
117
+
118
+ def check_standardize(value: Any) -> Any:
119
+ """Check the value of a ``standardize`` parameter.
120
+
121
+ Booleans were replaced by the strategy names used by nilearn, so they
122
+ raise an error explaining the change instead of a generic validation
123
+ error.
124
+
125
+ Parameters
126
+ ----------
127
+ value : Any
128
+ The value of the parameter.
129
+
130
+ Returns
131
+ -------
132
+ Any
133
+ The value of the parameter.
134
+
135
+ Raises
136
+ ------
137
+ ValueError
138
+ If ``value`` is a boolean.
139
+
140
+ """
141
+ if isinstance(value, bool):
142
+ raise_error(
143
+ "`standardize` does not accept booleans anymore, following "
144
+ "nilearn, which is removing them: True used to z-score with the "
145
+ "population standard deviation, while nilearn now z-scores with "
146
+ "the sample standard deviation ('zscore_sample'). Use "
147
+ "'zscore_sample' instead of True and None instead of False "
148
+ f"(got {value})."
149
+ )
150
+ return value
junifer/utils/logging.py CHANGED
@@ -14,8 +14,17 @@ else:
14
14
 
15
15
  import logging
16
16
  import logging.config
17
+ import sys
17
18
  import warnings
18
- from typing import NoReturn, Optional, Union
19
+
20
+ # Before Python 3.11, packages_distributions() only uses top_level.txt and
21
+ # misses packages that do not ship it
22
+ from importlib.metadata import (
23
+ PackageNotFoundError,
24
+ packages_distributions,
25
+ version,
26
+ )
27
+ from typing import NoReturn
19
28
  from warnings import warn
20
29
 
21
30
  import datalad
@@ -111,7 +120,8 @@ structlog.configure(
111
120
  cache_logger_on_first_use=True,
112
121
  )
113
122
 
114
- logger = logging.getLogger("junifer")
123
+ _log = structlog.get_logger("junifer")
124
+ logger = _log.bind(pkg="utils")
115
125
 
116
126
  # Remove datalad logger handlers to avoid duplicate logging
117
127
  _datalad_lgr_hdlrs = datalad.log.lgr.handlers
@@ -150,6 +160,8 @@ def get_versions() -> dict:
150
160
  The module names and corresponding versions.
151
161
 
152
162
  """
163
+ # Map top-level modules to their distributions
164
+ module_distributions = packages_distributions()
153
165
  # Setup dictionary to track versions of modules
154
166
  module_versions = {}
155
167
  for name, module in sys.modules.copy().items():
@@ -157,8 +169,20 @@ def get_versions() -> dict:
157
169
  # allowing ruamel.yaml
158
170
  if "." in name and name != "ruamel.yaml":
159
171
  continue
172
+ # Get version from distribution metadata, as some packages
173
+ # deprecated the module attribute (e.g. click, markupsafe)
174
+ vstring = None
175
+ for dist_name in module_distributions.get(name, []):
176
+ try:
177
+ vstring = version(dist_name)
178
+ break
179
+ except PackageNotFoundError:
180
+ continue
181
+ # Fallback to module attribute
182
+ if vstring is None:
183
+ vstring = getattr(module, "__version__", None)
160
184
  # Get version or None as string
161
- vstring = str(getattr(module, "__version__", None))
185
+ vstring = str(vstring)
162
186
  # Get module version
163
187
  module_version = getattr(LooseVersion(vstring), "vstring", None)
164
188
  module_versions[name] = module_version
@@ -210,8 +234,8 @@ def log_versions() -> None:
210
234
 
211
235
 
212
236
  def configure_logging(
213
- level: Union[int, str] = "WARNING",
214
- level_datalad: Union[int, str, None] = None,
237
+ level: int | str = "WARNING",
238
+ level_datalad: int | str | None = None,
215
239
  ) -> None:
216
240
  """Configure the logging functionality.
217
241
 
@@ -246,7 +270,7 @@ def configure_logging(
246
270
  def raise_error(
247
271
  msg: str,
248
272
  klass: type[Exception] = ValueError,
249
- exception: Optional[Exception] = None,
273
+ exception: Exception | None = None,
250
274
  ) -> NoReturn:
251
275
  """Raise error, but first log it.
252
276
 
@@ -268,7 +292,7 @@ def raise_error(
268
292
 
269
293
 
270
294
  def warn_with_log(
271
- msg: str, category: Optional[type[Warning]] = RuntimeWarning
295
+ msg: str, category: type[Warning] | None = RuntimeWarning
272
296
  ) -> None:
273
297
  """Warn, but first log it.
274
298