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
junifer/cli/__init__.py CHANGED
@@ -3,17 +3,11 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- import sys
6
+ from importlib.metadata import entry_points
7
7
 
8
8
  import lazy_loader as lazy
9
9
 
10
10
 
11
- if sys.version_info < (3, 11): # pragma: no cover
12
- from importlib_metadata import entry_points
13
- else:
14
- from importlib.metadata import entry_points
15
-
16
-
17
11
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
18
12
 
19
13
 
junifer/cli/cli.py CHANGED
@@ -8,7 +8,6 @@ import pathlib
8
8
  import subprocess
9
9
  import sys
10
10
  from pathlib import Path
11
- from typing import Optional, Union
12
11
 
13
12
  import click
14
13
 
@@ -18,7 +17,7 @@ from ..utils import (
18
17
  raise_error,
19
18
  yaml,
20
19
  )
21
- from .parser import parse_elements, parse_yaml
20
+ from .parser import parse_elements
22
21
  from .utils import (
23
22
  _get_dependency_information,
24
23
  _get_environment_information,
@@ -45,8 +44,30 @@ __all__ = [
45
44
  ]
46
45
 
47
46
 
47
+ def _parse_yaml(filepath: click.Path) -> dict:
48
+ """Parse the YAML file and set up the working directory.
49
+
50
+ The working directory is set up before anything else, as all the
51
+ components assume it is available.
52
+
53
+ Parameters
54
+ ----------
55
+ filepath : click.Path
56
+ The filepath to the configuration file.
57
+
58
+ Returns
59
+ -------
60
+ dict
61
+ The configuration.
62
+
63
+ """
64
+ config = cli_func.parse_yaml(filepath) # type: ignore
65
+ cli_func._setup_workdir(config.get("workdir"))
66
+ return config
67
+
68
+
48
69
  def _validate_optional_verbose(
49
- ctx: click.Context, param: str, value: Optional[str]
70
+ ctx: click.Context, param: str, value: str | None
50
71
  ):
51
72
  """Validate optional verbose option.
52
73
 
@@ -71,9 +92,7 @@ def _validate_optional_verbose(
71
92
  return _validate_verbose(ctx, param, value)
72
93
 
73
94
 
74
- def _validate_verbose(
75
- ctx: click.Context, param: str, value: str
76
- ) -> Union[str, int]:
95
+ def _validate_verbose(ctx: click.Context, param: str, value: str) -> str | int:
77
96
  """Validate verbose option.
78
97
 
79
98
  Parameters
@@ -142,8 +161,8 @@ def cli() -> None: # pragma: no cover
142
161
  def run(
143
162
  filepath: click.Path,
144
163
  element: tuple[str],
145
- verbose: Union[str, int],
146
- verbose_datalad: Optional[Union[str, int]],
164
+ verbose: str | int,
165
+ verbose_datalad: str | int | None,
147
166
  ) -> None:
148
167
  """Run feature extraction.
149
168
 
@@ -165,7 +184,7 @@ def run(
165
184
  configure_logging(level=verbose, level_datalad=verbose_datalad)
166
185
  # TODO(synchon): add validation
167
186
  # Parse YAML
168
- config = parse_yaml(filepath)
187
+ config = _parse_yaml(filepath)
169
188
  # Retrieve working directory
170
189
  workdir = config["workdir"]
171
190
  # Fetch datagrabber
@@ -214,8 +233,8 @@ def run(
214
233
  )
215
234
  def collect(
216
235
  filepath: click.Path,
217
- verbose: Union[str, int],
218
- verbose_datalad: Union[str, int, None],
236
+ verbose: str | int,
237
+ verbose_datalad: str | int | None,
219
238
  ) -> None:
220
239
  """Collect extracted features.
221
240
 
@@ -235,7 +254,7 @@ def collect(
235
254
  configure_logging(level=verbose, level_datalad=verbose_datalad)
236
255
  # TODO: add validation
237
256
  # Parse YAML
238
- config = parse_yaml(filepath)
257
+ config = _parse_yaml(filepath)
239
258
  # Fetch storage
240
259
  storage = config["storage"]
241
260
  # Perform operation
@@ -270,8 +289,8 @@ def queue(
270
289
  element: tuple[str],
271
290
  overwrite: bool,
272
291
  submit: bool,
273
- verbose: Union[str, int],
274
- verbose_datalad: Union[str, int, None],
292
+ verbose: str | int,
293
+ verbose_datalad: str | int | None,
275
294
  ) -> None:
276
295
  """Queue feature extraction.
277
296
 
@@ -302,7 +321,7 @@ def queue(
302
321
  configure_logging(level=verbose, level_datalad=verbose_datalad)
303
322
  # TODO: add validation
304
323
  # Parse YAML
305
- config = parse_yaml(filepath) # type: ignore
324
+ config = _parse_yaml(filepath)
306
325
  # Check queue section
307
326
  if "queue" not in config:
308
327
  raise_error(f"No queue configuration found in {filepath}.")
@@ -433,8 +452,8 @@ def selftest(subpkg: str) -> None:
433
452
  )
434
453
  def reset(
435
454
  filepath: click.Path,
436
- verbose: Union[str, int],
437
- verbose_datalad: Union[str, int, None],
455
+ verbose: str | int,
456
+ verbose_datalad: str | int | None,
438
457
  ) -> None:
439
458
  """Reset generated assets.
440
459
 
@@ -453,7 +472,7 @@ def reset(
453
472
  # Setup logging
454
473
  configure_logging(level=verbose, level_datalad=verbose_datalad)
455
474
  # Parse YAML
456
- config = parse_yaml(filepath)
475
+ config = _parse_yaml(filepath)
457
476
  # Perform operation
458
477
  cli_func.reset(config)
459
478
 
@@ -487,9 +506,9 @@ def reset(
487
506
  def list_elements(
488
507
  filepath: click.Path,
489
508
  element: tuple[str],
490
- output_file: Optional[click.Path],
491
- verbose: Union[str, int],
492
- verbose_datalad: Union[str, int, None],
509
+ output_file: click.Path | None,
510
+ verbose: str | int,
511
+ verbose_datalad: str | int | None,
493
512
  ) -> None:
494
513
  """List elements of a dataset.
495
514
 
@@ -513,7 +532,7 @@ def list_elements(
513
532
  # Setup logging
514
533
  configure_logging(level=verbose, level_datalad=verbose_datalad)
515
534
  # Parse YAML
516
- config = parse_yaml(filepath)
535
+ config = _parse_yaml(filepath)
517
536
  # Fetch datagrabber
518
537
  datagrabber = config["datagrabber"]
519
538
  # Parse elements
junifer/cli/parser.py CHANGED
@@ -4,147 +4,19 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- import importlib
8
- import importlib.util
9
- import sys
10
7
  from pathlib import Path
11
- from typing import Union
12
8
 
13
9
  import pandas as pd
14
10
 
15
11
  from ..typing import Elements
16
- from ..utils import logger, raise_error, warn_with_log, yaml
12
+ from ..utils import logger, raise_error, warn_with_log
17
13
 
18
14
 
19
- __all__ = ["parse_elements", "parse_yaml"]
15
+ __all__ = ["parse_elements"]
20
16
 
21
17
 
22
- def parse_yaml(filepath: Union[str, Path]) -> dict: # noqa: C901
23
- """Parse YAML.
24
-
25
- Parameters
26
- ----------
27
- filepath : str or pathlib.Path
28
- The filepath to read from.
29
-
30
- Returns
31
- -------
32
- dict
33
- The contents represented as dictionary.
34
-
35
- """
36
- # Convert str to Path
37
- if not isinstance(filepath, Path):
38
- filepath = Path(filepath)
39
-
40
- logger.info(f"Parsing yaml file: {filepath.absolute()!s}")
41
- # Filepath existence check
42
- if not filepath.exists():
43
- raise_error(f"File does not exist: {filepath.absolute()!s}")
44
- # Filepath reading
45
- contents = yaml.load(filepath)
46
- if "elements" in contents:
47
- if contents["elements"] is None:
48
- raise_error(
49
- "The elements key was defined but its content is empty. "
50
- "Please define the elements to operate on or remove the key."
51
- )
52
- # load modules
53
- if "with" in contents:
54
- to_load = contents["with"]
55
- # Convert load modules to list
56
- if not isinstance(to_load, list):
57
- to_load = [to_load]
58
- # Initialize list to have absolute paths for custom modules
59
- final_to_load = []
60
- for t_module in to_load:
61
- if t_module.endswith(".py"):
62
- logger.debug(f"Importing file: {t_module}")
63
- # This resolves both absolute and relative paths
64
- file_path = filepath.parent / t_module
65
- if not file_path.exists():
66
- raise_error(
67
- f"File in 'with' section does not exist: {file_path}"
68
- )
69
- # Add the parent directory to the sys.path so that the
70
- # any imports from this module work correctly
71
- t_path = str(file_path.parent)
72
- if t_path not in sys.path:
73
- sys.path.append(str(file_path.parent))
74
-
75
- spec = importlib.util.spec_from_file_location(
76
- t_module, file_path
77
- )
78
- module = importlib.util.module_from_spec(spec) # type: ignore
79
- sys.modules[t_module] = module
80
- spec.loader.exec_module(module) # type: ignore
81
-
82
- # Add absolute path to final list
83
- final_to_load.append(str(file_path.resolve()))
84
-
85
- # Check if the module has junifer_module_deps function
86
- if hasattr(module, "junifer_module_deps"):
87
- logger.debug(
88
- f"Module {t_module} has junifer_module_deps function"
89
- )
90
- # Get the dependencies
91
- deps = module.junifer_module_deps()
92
- # Add the dependencies to the final list
93
- for dep in deps:
94
- if dep not in final_to_load:
95
- final_to_load.append(
96
- str((file_path.parent / dep).resolve())
97
- )
98
- else:
99
- logger.info(f"Importing module: {t_module}")
100
- importlib.import_module(t_module)
101
- # Add module to final list
102
- final_to_load.append(t_module)
103
-
104
- # Replace modules to be loaded so that custom modules will take the
105
- # absolute path. This was not the case as found in #224. Similar thing
106
- # is done with the storage URI below.
107
- contents["with"] = final_to_load
108
-
109
- # Compute path for the URI parameter in storage files that are relative
110
- # This is a tricky thing that appeared in #127. The problem is that
111
- # the path in the URI parameter is relative to YAML file, not to the
112
- # current working directory. If we leave it as is in the contents
113
- # dict, then it will be used later in the ``build`` function as is,
114
- # which will be computed relative to the current working directory.
115
- # The solution is to compute the absolute path and replace the
116
- # relative path in the contents dict with the absolute path.
117
-
118
- # Check if the storage file is defined
119
- if "storage" in contents:
120
- if "uri" in contents["storage"]:
121
- # Check if the storage file is relative
122
- uri_path = Path(contents["storage"]["uri"])
123
- if not uri_path.is_absolute():
124
- # Compute the absolute path
125
- contents["storage"]["uri"] = str(
126
- (filepath.parent / uri_path).resolve()
127
- )
128
-
129
- # Allow relative path if queue env kind is venv; same motivation as above
130
- if "queue" in contents:
131
- if "env" in contents["queue"]:
132
- if "venv" == contents["queue"]["env"]["kind"]:
133
- # Check if the env name is relative
134
- venv_path = Path(contents["queue"]["env"]["name"])
135
- if not venv_path.is_absolute():
136
- # Compute the absolute path
137
- contents["queue"]["env"]["name"] = str(
138
- (filepath.parent / venv_path).resolve()
139
- )
140
-
141
- return contents
142
-
143
-
144
- def parse_elements(
145
- element: tuple[str, ...], config: dict
146
- ) -> Union[Elements, None]:
147
- """Parse elements from cli.
18
+ def parse_elements(element: tuple[str, ...], config: dict) -> Elements | None:
19
+ """Parse elements from cli, or from the configuration if not in the cli.
148
20
 
149
21
  Parameters
150
22
  ----------
@@ -156,13 +28,13 @@ def parse_elements(
156
28
  Returns
157
29
  -------
158
30
  list or None
159
- The element(s) as list or None.
31
+ The element(s) as list, or None (all the elements) if they are not in
32
+ the command-line options nor in the configuration.
160
33
 
161
34
  Raises
162
35
  ------
163
36
  ValueError
164
- If no element is found either in the command-line options or
165
- the configuration file.
37
+ If the ``elements`` key of the configuration has no value.
166
38
 
167
39
  Warns
168
40
  -----
@@ -172,9 +44,22 @@ def parse_elements(
172
44
 
173
45
  """
174
46
  logger.debug(f"Parsing elements: {element}")
175
- # Early return None to continue with all elements
47
+ # Use the elements of the configuration if not in the command line
176
48
  if not element:
177
- return None
49
+ if "elements" not in config:
50
+ # None to continue with all elements
51
+ return None
52
+ elements = config["elements"]
53
+ if elements is None:
54
+ raise_error(
55
+ "The `elements` key is set in the configuration, but its value"
56
+ " is `None`. It is likely that there is an empty `elements` "
57
+ "section in the yaml configuration file."
58
+ )
59
+ # A single element, and the elements with multiple values as tuples
60
+ if not isinstance(elements, list):
61
+ elements = [elements]
62
+ return [tuple(x) if isinstance(x, list) else x for x in elements]
178
63
  # Check if the element is a file for single element;
179
64
  # if yes, then parse elements from it
180
65
  if len(element) == 1 and Path(element[0]).resolve().is_file():
@@ -183,7 +68,7 @@ def parse_elements(
183
68
  # Process multi-keyed elements
184
69
  elements = [tuple(x.split(",")) if "," in x else x for x in element]
185
70
  logger.debug(f"Parsed elements: {elements}")
186
- if elements is not None and "elements" in config:
71
+ if "elements" in config:
187
72
  warn_with_log(
188
73
  "One or more elements have been specified in both the command "
189
74
  "line and in the config file. The command line has precedence "
@@ -192,15 +77,6 @@ def parse_elements(
192
77
  "the configuration file will be ignored. To remove this warning, "
193
78
  "please remove the `elements` item from the configuration file."
194
79
  )
195
- elif elements is None:
196
- # Check in config
197
- elements = config.get("elements", None)
198
- if elements is None:
199
- raise_error(
200
- "The `elements` key is set in the configuration, but its value"
201
- " is `None`. It is likely that there is an empty `elements` "
202
- "section in the yaml configuration file."
203
- )
204
80
  return elements
205
81
 
206
82
 
@@ -2,13 +2,13 @@ with: junifer.testing.registry
2
2
  workdir: /tmp
3
3
 
4
4
  datagrabber:
5
- kind: OasisVBMTestingDataGrabber
6
- elements: [1, 2]
5
+ kind: UCLACNPVBMTestingDataGrabber
6
+ elements: [sub-10206, sub-10339]
7
7
  markers:
8
- - name: Schaefer1000x7_Mean
8
+ - name: TianxS2_Mean
9
9
  kind: ParcelAggregation
10
- parcellation: Schaefer1000x7
10
+ parcellation: TianxS2x3TxMNInonlinear2009cAsym
11
11
  method: mean
12
12
  storage:
13
- kind: SQLiteFeatureStorage
14
- uri: /Users/fraimondo/dev/tbox/junifer/scratch/db/test.sqlite
13
+ kind: HDF5FeatureStorage
14
+ uri: /Users/fraimondo/dev/tbox/junifer/scratch/db/test.hdf5
@@ -2,19 +2,20 @@ with: junifer.testing.registry
2
2
  workdir: /tmp
3
3
 
4
4
  datagrabber:
5
- kind: OasisVBMTestingDataGrabber
5
+ kind: UCLACNPVBMTestingDataGrabber
6
6
  markers:
7
- - name: Schaefer1000x7_Mean
7
+ - name: TianxS2_Mean
8
8
  kind: ParcelAggregation
9
- parcellation: Schaefer1000x7
9
+ parcellation: TianxS2x3TxMNInonlinear2009cAsym
10
10
  method: mean
11
11
  storage:
12
- kind: SQLiteFeatureStorage
13
- uri: /Users/fraimondo/dev/tbox/junifer/scratch/db/test.sqlite
12
+ kind: HDF5FeatureStorage
13
+ uri: /Users/fraimondo/dev/tbox/junifer/scratch/db/test.hdf5
14
14
  queue:
15
15
  jobname: TestHTCondorQueue
16
16
  kind: HTCondor
17
17
  env:
18
18
  kind: conda
19
19
  name: junifer
20
+ shell: bash
20
21
  mem: 8G
@@ -4,6 +4,7 @@ with: junifer.testing.registry
4
4
 
5
5
  datagrabber:
6
6
  kind: PartlyCloudyTestingDataGrabber
7
+ n_subjects: 4
7
8
 
8
9
  markers:
9
10
  - kind: ParcelAggregation
@@ -4,14 +4,16 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
+ import tempfile
8
+ from collections.abc import Callable
7
9
  from pathlib import Path
8
- from typing import Callable
9
10
 
10
11
  import pytest
11
12
  from click.testing import CliRunner
12
13
  from ruamel.yaml import YAML
13
14
 
14
15
  from junifer.cli.cli import (
16
+ _parse_yaml,
15
17
  collect,
16
18
  list_elements,
17
19
  queue,
@@ -20,7 +22,11 @@ from junifer.cli.cli import (
20
22
  selftest,
21
23
  wtf,
22
24
  )
23
- from junifer.cli.parser import _parse_elements_file
25
+ from junifer.cli.parser import _parse_elements_file, parse_elements
26
+ from junifer.pipeline import WorkDirManager
27
+
28
+
29
+ pytestmark = pytest.mark.external
24
30
 
25
31
 
26
32
  # Configure YAML class
@@ -199,6 +205,47 @@ def test_multi_element_access(
199
205
  assert read_elements == expected_list
200
206
 
201
207
 
208
+ @pytest.mark.parametrize(
209
+ "element, config, expected",
210
+ [
211
+ ((), {}, None),
212
+ ((), {"elements": ["sub-01", "sub-02"]}, ["sub-01", "sub-02"]),
213
+ ((), {"elements": "sub-01"}, ["sub-01"]),
214
+ ((), {"elements": [["sub-01", "ses-01"]]}, [("sub-01", "ses-01")]),
215
+ (("sub-01", "sub-02,ses-01"), {}, ["sub-01", ("sub-02", "ses-01")]),
216
+ ],
217
+ )
218
+ def test_parse_elements(
219
+ element: tuple[str, ...], config: dict, expected: list | None
220
+ ) -> None:
221
+ """Test parsing the elements from the command line or the configuration.
222
+
223
+ Parameters
224
+ ----------
225
+ element : tuple of str
226
+ The parametrized elements of the command line.
227
+ config : dict
228
+ The parametrized configuration.
229
+ expected : list or None
230
+ The parametrized elements to expect.
231
+
232
+ """
233
+ assert parse_elements(element, config) == expected
234
+
235
+
236
+ def test_parse_elements_command_line_precedence() -> None:
237
+ """Test the elements of the command line have precedence."""
238
+ with pytest.warns(RuntimeWarning, match="command line has precedence"):
239
+ elements = parse_elements(("sub-01",), {"elements": ["sub-02"]})
240
+ assert elements == ["sub-01"]
241
+
242
+
243
+ def test_parse_elements_empty_config() -> None:
244
+ """Test an empty elements key in the configuration."""
245
+ with pytest.raises(ValueError, match="its value is `None`"):
246
+ parse_elements((), {"elements": None})
247
+
248
+
202
249
  def test_queue(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
203
250
  """Test queue command.
204
251
 
@@ -219,7 +266,7 @@ def test_queue(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
219
266
  # Working directory
220
267
  contents["workdir"] = str(tmp_path.resolve())
221
268
  # Storage
222
- contents["storage"]["uri"] = str((tmp_path / "out.sqlite").resolve())
269
+ contents["storage"]["uri"] = str((tmp_path / "out.hdf5").resolve())
223
270
  # Write new test config
224
271
  outfile = tmp_path / "in.yaml"
225
272
  yaml.dump(contents, stream=outfile)
@@ -271,7 +318,7 @@ def test_reset(
271
318
  # Working directory
272
319
  contents["workdir"] = str(tmp_path.resolve())
273
320
  # Storage
274
- contents["storage"]["uri"] = str((tmp_path / "out.sqlite").resolve())
321
+ contents["storage"]["uri"] = str((tmp_path / "out.hdf5").resolve())
275
322
  # Write new test config
276
323
  outfile = tmp_path / "in.yaml"
277
324
  yaml.dump(contents, stream=outfile)
@@ -380,6 +427,75 @@ def test_list_elements_output_file(
380
427
  assert f"{elements[0]}\n{elements[1]}" == f.read()
381
428
 
382
429
 
430
+ @pytest.mark.parametrize(
431
+ "workdir, expected",
432
+ [
433
+ ("workdir", "workdir"),
434
+ ({"path": "workdir", "cleanup": False}, "workdir"),
435
+ (None, "junifer"),
436
+ ],
437
+ ids=["path", "dict", "default"],
438
+ )
439
+ def test_parse_yaml_workdir(
440
+ tmp_path: Path,
441
+ monkeypatch: pytest.MonkeyPatch,
442
+ workdir: str | dict | None,
443
+ expected: str,
444
+ ) -> None:
445
+ """Test the working directory is set up when parsing the YAML.
446
+
447
+ Parameters
448
+ ----------
449
+ tmp_path : pathlib.Path
450
+ The path to the test directory.
451
+ monkeypatch : pytest.MonkeyPatch
452
+ The pytest.MonkeyPatch object.
453
+ workdir : str or dict or None
454
+ The parametrized working directory in the YAML. If None, the YAML
455
+ has no working directory.
456
+ expected : str
457
+ The parametrized working directory set up, relative to ``tmp_path``.
458
+
459
+ """
460
+ # Use the test directory for relative paths and as temporary directory,
461
+ # where the default working directory is
462
+ monkeypatch.chdir(tmp_path)
463
+ monkeypatch.setattr(tempfile, "tempdir", str(tmp_path))
464
+ contents = {"datagrabber": {"kind": "PartlyCloudyTestingDataGrabber"}}
465
+ if workdir is not None:
466
+ contents["workdir"] = workdir
467
+ yaml.dump(contents, stream=tmp_path / "in.yaml")
468
+ # The configuration is not changed
469
+ assert _parse_yaml(tmp_path / "in.yaml") == contents # type: ignore
470
+ assert WorkDirManager().workdir.resolve() == tmp_path / expected
471
+ assert (tmp_path / expected).is_dir()
472
+
473
+
474
+ def test_list_elements_workdir(tmp_path: Path) -> None:
475
+ """Test elements listing in the working directory of the YAML.
476
+
477
+ Parameters
478
+ ----------
479
+ tmp_path : pathlib.Path
480
+ The path to the test directory.
481
+
482
+ """
483
+ # Get test config
484
+ infile = Path(__file__).parent / "data" / "partly_cloudy_agg_mean_tian.yml"
485
+ contents = yaml.load(infile)
486
+ # Working directory
487
+ workdir = tmp_path / "workdir"
488
+ contents["workdir"] = str(workdir)
489
+ # Write new test config
490
+ outfile = tmp_path / "in.yaml"
491
+ yaml.dump(contents, stream=outfile)
492
+ # Invoke list elements command
493
+ list_elements_result = runner.invoke(list_elements, [str(outfile)])
494
+ # Check
495
+ assert list_elements_result.exit_code == 0
496
+ assert WorkDirManager().workdir == workdir
497
+
498
+
383
499
  def test_wtf_short() -> None:
384
500
  """Test short version of wtf command."""
385
501
  # Invoke wtf command