junifer 0.0.5.dev240__py3-none-any.whl → 0.0.6__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 (279) hide show
  1. junifer/__init__.py +2 -31
  2. junifer/__init__.pyi +37 -0
  3. junifer/_version.py +9 -4
  4. junifer/api/__init__.py +3 -5
  5. junifer/api/__init__.pyi +4 -0
  6. junifer/api/decorators.py +14 -19
  7. junifer/api/functions.py +165 -109
  8. junifer/api/py.typed +0 -0
  9. junifer/api/queue_context/__init__.py +2 -4
  10. junifer/api/queue_context/__init__.pyi +5 -0
  11. junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
  12. junifer/api/queue_context/htcondor_adapter.py +23 -6
  13. junifer/api/queue_context/py.typed +0 -0
  14. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
  15. junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
  16. junifer/api/tests/test_functions.py +168 -74
  17. junifer/cli/__init__.py +24 -0
  18. junifer/cli/__init__.pyi +3 -0
  19. junifer/{api → cli}/cli.py +141 -125
  20. junifer/cli/parser.py +235 -0
  21. junifer/cli/py.typed +0 -0
  22. junifer/{api → cli}/tests/test_cli.py +8 -8
  23. junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
  24. junifer/{api → cli}/tests/test_parser.py +2 -2
  25. junifer/{api → cli}/utils.py +6 -16
  26. junifer/configs/juseless/__init__.py +2 -2
  27. junifer/configs/juseless/__init__.pyi +3 -0
  28. junifer/configs/juseless/datagrabbers/__init__.py +2 -12
  29. junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
  30. junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
  31. junifer/configs/juseless/datagrabbers/py.typed +0 -0
  32. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
  33. junifer/configs/juseless/datagrabbers/ucla.py +4 -4
  34. junifer/configs/juseless/py.typed +0 -0
  35. junifer/conftest.py +25 -0
  36. junifer/data/__init__.py +2 -42
  37. junifer/data/__init__.pyi +29 -0
  38. junifer/data/_dispatch.py +248 -0
  39. junifer/data/coordinates/__init__.py +9 -0
  40. junifer/data/coordinates/__init__.pyi +5 -0
  41. junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
  42. junifer/data/coordinates/_coordinates.py +385 -0
  43. junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
  44. junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
  45. junifer/data/masks/__init__.py +9 -0
  46. junifer/data/masks/__init__.pyi +6 -0
  47. junifer/data/masks/_ants_mask_warper.py +177 -0
  48. junifer/data/masks/_fsl_mask_warper.py +106 -0
  49. junifer/data/masks/_masks.py +802 -0
  50. junifer/data/{tests → masks/tests}/test_masks.py +67 -63
  51. junifer/data/parcellations/__init__.py +9 -0
  52. junifer/data/parcellations/__init__.pyi +6 -0
  53. junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
  54. junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
  55. junifer/data/parcellations/_parcellations.py +1388 -0
  56. junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
  57. junifer/data/pipeline_data_registry_base.py +76 -0
  58. junifer/data/py.typed +0 -0
  59. junifer/data/template_spaces.py +44 -79
  60. junifer/data/tests/test_data_utils.py +1 -2
  61. junifer/data/tests/test_template_spaces.py +8 -4
  62. junifer/data/utils.py +109 -4
  63. junifer/datagrabber/__init__.py +2 -26
  64. junifer/datagrabber/__init__.pyi +27 -0
  65. junifer/datagrabber/aomic/__init__.py +2 -4
  66. junifer/datagrabber/aomic/__init__.pyi +5 -0
  67. junifer/datagrabber/aomic/id1000.py +81 -52
  68. junifer/datagrabber/aomic/piop1.py +83 -55
  69. junifer/datagrabber/aomic/piop2.py +85 -56
  70. junifer/datagrabber/aomic/py.typed +0 -0
  71. junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
  72. junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
  73. junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
  74. junifer/datagrabber/base.py +22 -18
  75. junifer/datagrabber/datalad_base.py +71 -34
  76. junifer/datagrabber/dmcc13_benchmark.py +31 -18
  77. junifer/datagrabber/hcp1200/__init__.py +2 -3
  78. junifer/datagrabber/hcp1200/__init__.pyi +4 -0
  79. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
  80. junifer/datagrabber/hcp1200/hcp1200.py +26 -15
  81. junifer/datagrabber/hcp1200/py.typed +0 -0
  82. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
  83. junifer/datagrabber/multiple.py +14 -9
  84. junifer/datagrabber/pattern.py +132 -96
  85. junifer/datagrabber/pattern_validation_mixin.py +206 -94
  86. junifer/datagrabber/py.typed +0 -0
  87. junifer/datagrabber/tests/test_datalad_base.py +27 -12
  88. junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
  89. junifer/datagrabber/tests/test_multiple.py +48 -2
  90. junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
  91. junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
  92. junifer/datareader/__init__.py +2 -2
  93. junifer/datareader/__init__.pyi +3 -0
  94. junifer/datareader/default.py +6 -6
  95. junifer/datareader/py.typed +0 -0
  96. junifer/external/nilearn/__init__.py +2 -3
  97. junifer/external/nilearn/__init__.pyi +4 -0
  98. junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
  99. junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
  100. junifer/external/nilearn/py.typed +0 -0
  101. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
  102. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
  103. junifer/markers/__init__.py +2 -38
  104. junifer/markers/__init__.pyi +37 -0
  105. junifer/markers/base.py +11 -14
  106. junifer/markers/brainprint.py +12 -14
  107. junifer/markers/complexity/__init__.py +2 -18
  108. junifer/markers/complexity/__init__.pyi +17 -0
  109. junifer/markers/complexity/complexity_base.py +9 -11
  110. junifer/markers/complexity/hurst_exponent.py +7 -7
  111. junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
  112. junifer/markers/complexity/perm_entropy.py +7 -7
  113. junifer/markers/complexity/py.typed +0 -0
  114. junifer/markers/complexity/range_entropy.py +7 -7
  115. junifer/markers/complexity/range_entropy_auc.py +7 -7
  116. junifer/markers/complexity/sample_entropy.py +7 -7
  117. junifer/markers/complexity/tests/test_complexity_base.py +1 -1
  118. junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
  119. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
  120. junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
  121. junifer/markers/complexity/tests/test_range_entropy.py +5 -5
  122. junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
  123. junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
  124. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
  125. junifer/markers/complexity/weighted_perm_entropy.py +7 -7
  126. junifer/markers/ets_rss.py +12 -11
  127. junifer/markers/falff/__init__.py +2 -3
  128. junifer/markers/falff/__init__.pyi +4 -0
  129. junifer/markers/falff/_afni_falff.py +38 -45
  130. junifer/markers/falff/_junifer_falff.py +16 -19
  131. junifer/markers/falff/falff_base.py +7 -11
  132. junifer/markers/falff/falff_parcels.py +9 -9
  133. junifer/markers/falff/falff_spheres.py +8 -8
  134. junifer/markers/falff/py.typed +0 -0
  135. junifer/markers/falff/tests/test_falff_spheres.py +3 -1
  136. junifer/markers/functional_connectivity/__init__.py +2 -12
  137. junifer/markers/functional_connectivity/__init__.pyi +13 -0
  138. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
  139. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
  140. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
  141. junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
  142. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
  143. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
  144. junifer/markers/functional_connectivity/py.typed +0 -0
  145. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
  146. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
  147. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
  148. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
  149. junifer/markers/parcel_aggregation.py +22 -17
  150. junifer/markers/py.typed +0 -0
  151. junifer/markers/reho/__init__.py +2 -3
  152. junifer/markers/reho/__init__.pyi +4 -0
  153. junifer/markers/reho/_afni_reho.py +29 -35
  154. junifer/markers/reho/_junifer_reho.py +13 -14
  155. junifer/markers/reho/py.typed +0 -0
  156. junifer/markers/reho/reho_base.py +7 -11
  157. junifer/markers/reho/reho_parcels.py +10 -10
  158. junifer/markers/reho/reho_spheres.py +9 -9
  159. junifer/markers/sphere_aggregation.py +22 -17
  160. junifer/markers/temporal_snr/__init__.py +2 -3
  161. junifer/markers/temporal_snr/__init__.pyi +4 -0
  162. junifer/markers/temporal_snr/py.typed +0 -0
  163. junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
  164. junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
  165. junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
  166. junifer/markers/tests/test_ets_rss.py +3 -3
  167. junifer/markers/tests/test_parcel_aggregation.py +24 -24
  168. junifer/markers/tests/test_sphere_aggregation.py +6 -6
  169. junifer/markers/utils.py +3 -3
  170. junifer/onthefly/__init__.py +2 -1
  171. junifer/onthefly/_brainprint.py +138 -0
  172. junifer/onthefly/read_transform.py +5 -8
  173. junifer/pipeline/__init__.py +2 -10
  174. junifer/pipeline/__init__.pyi +13 -0
  175. junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
  176. junifer/pipeline/pipeline_component_registry.py +294 -0
  177. junifer/pipeline/pipeline_step_mixin.py +15 -11
  178. junifer/pipeline/py.typed +0 -0
  179. junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
  180. junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
  181. junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
  182. junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
  183. junifer/pipeline/tests/test_workdir_manager.py +43 -0
  184. junifer/pipeline/update_meta_mixin.py +21 -17
  185. junifer/pipeline/utils.py +6 -6
  186. junifer/pipeline/workdir_manager.py +19 -5
  187. junifer/preprocess/__init__.py +2 -10
  188. junifer/preprocess/__init__.pyi +11 -0
  189. junifer/preprocess/base.py +10 -10
  190. junifer/preprocess/confounds/__init__.py +2 -2
  191. junifer/preprocess/confounds/__init__.pyi +3 -0
  192. junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
  193. junifer/preprocess/confounds/py.typed +0 -0
  194. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
  195. junifer/preprocess/py.typed +0 -0
  196. junifer/preprocess/smoothing/__init__.py +2 -2
  197. junifer/preprocess/smoothing/__init__.pyi +3 -0
  198. junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
  199. junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
  200. junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
  201. junifer/preprocess/smoothing/py.typed +0 -0
  202. junifer/preprocess/smoothing/smoothing.py +11 -13
  203. junifer/preprocess/warping/__init__.py +2 -2
  204. junifer/preprocess/warping/__init__.pyi +3 -0
  205. junifer/preprocess/warping/_ants_warper.py +136 -32
  206. junifer/preprocess/warping/_fsl_warper.py +73 -22
  207. junifer/preprocess/warping/py.typed +0 -0
  208. junifer/preprocess/warping/space_warper.py +39 -11
  209. junifer/preprocess/warping/tests/test_space_warper.py +5 -9
  210. junifer/py.typed +0 -0
  211. junifer/stats.py +5 -5
  212. junifer/storage/__init__.py +2 -10
  213. junifer/storage/__init__.pyi +11 -0
  214. junifer/storage/base.py +47 -13
  215. junifer/storage/hdf5.py +95 -33
  216. junifer/storage/pandas_base.py +12 -11
  217. junifer/storage/py.typed +0 -0
  218. junifer/storage/sqlite.py +11 -11
  219. junifer/storage/tests/test_hdf5.py +86 -4
  220. junifer/storage/tests/test_sqlite.py +2 -2
  221. junifer/storage/tests/test_storage_base.py +5 -2
  222. junifer/storage/tests/test_utils.py +33 -7
  223. junifer/storage/utils.py +95 -9
  224. junifer/testing/__init__.py +2 -3
  225. junifer/testing/__init__.pyi +4 -0
  226. junifer/testing/datagrabbers.py +10 -11
  227. junifer/testing/py.typed +0 -0
  228. junifer/testing/registry.py +4 -7
  229. junifer/testing/tests/test_testing_registry.py +9 -17
  230. junifer/tests/test_stats.py +2 -2
  231. junifer/typing/__init__.py +9 -0
  232. junifer/typing/__init__.pyi +31 -0
  233. junifer/typing/_typing.py +68 -0
  234. junifer/utils/__init__.py +2 -12
  235. junifer/utils/__init__.pyi +18 -0
  236. junifer/utils/_config.py +110 -0
  237. junifer/utils/_yaml.py +16 -0
  238. junifer/utils/helpers.py +6 -6
  239. junifer/utils/logging.py +117 -8
  240. junifer/utils/py.typed +0 -0
  241. junifer/{pipeline → utils}/singleton.py +19 -14
  242. junifer/utils/tests/test_config.py +59 -0
  243. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
  244. junifer-0.0.6.dist-info/RECORD +350 -0
  245. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
  246. junifer-0.0.6.dist-info/entry_points.txt +2 -0
  247. junifer/api/parser.py +0 -118
  248. junifer/data/coordinates.py +0 -408
  249. junifer/data/masks.py +0 -670
  250. junifer/data/parcellations.py +0 -1828
  251. junifer/pipeline/registry.py +0 -177
  252. junifer/pipeline/tests/test_registry.py +0 -150
  253. junifer-0.0.5.dev240.dist-info/RECORD +0 -275
  254. junifer-0.0.5.dev240.dist-info/entry_points.txt +0 -2
  255. /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
  256. /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
  257. /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
  258. /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
  259. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
  260. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
  261. /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
  262. /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
  263. /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
  264. /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
  265. /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
  266. /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
  267. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
  268. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
  269. /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
  270. /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
  271. /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
  272. /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
  273. /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
  274. /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
  275. /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
  276. /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
  277. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
  278. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
  279. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,200 @@
1
+ """Provide tests for PipelineComponentRegistry."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # Leonard Sasse <l.sasse@fz-juelich.de>
5
+ # Synchon Mandal <s.mandal@fz-juelich.de>
6
+ # License: AGPL
7
+
8
+ import logging
9
+ from abc import ABC
10
+
11
+ import pytest
12
+
13
+ from junifer.datagrabber.pattern import PatternDataGrabber
14
+ from junifer.pipeline.pipeline_component_registry import (
15
+ PipelineComponentRegistry,
16
+ )
17
+ from junifer.storage import SQLiteFeatureStorage
18
+
19
+
20
+ def test_pipeline_component_registry_singleton() -> None:
21
+ """Test that PipelineComponentRegistry is a singleton."""
22
+ registry_1 = PipelineComponentRegistry()
23
+ registry_2 = PipelineComponentRegistry()
24
+ assert id(registry_1) == id(registry_2)
25
+
26
+
27
+ def test_pipeline_component_registry_register_invalid_step():
28
+ """Test invalid step name during component registration."""
29
+ with pytest.raises(ValueError, match="Invalid step:"):
30
+ PipelineComponentRegistry().register(step="foo", klass=str)
31
+
32
+
33
+ def test_pipeline_component_registry_steps():
34
+ """Test steps for PipelineComponentRegistry."""
35
+ assert set(PipelineComponentRegistry().steps) == {
36
+ "datagrabber",
37
+ "datareader",
38
+ "preprocessing",
39
+ "marker",
40
+ "storage",
41
+ }
42
+
43
+
44
+ def test_pipeline_component_registry_components():
45
+ """Test components for PipelineComponentRegistry."""
46
+ assert set(PipelineComponentRegistry().steps) == {
47
+ "datagrabber",
48
+ "datareader",
49
+ "preprocessing",
50
+ "marker",
51
+ "storage",
52
+ }
53
+
54
+
55
+ @pytest.mark.parametrize(
56
+ "step, klass",
57
+ [
58
+ ("datagrabber", PatternDataGrabber),
59
+ ("storage", SQLiteFeatureStorage),
60
+ ],
61
+ )
62
+ def test_pipeline_component_registry_register(
63
+ caplog: pytest.LogCaptureFixture, step: str, klass: type
64
+ ) -> None:
65
+ """Test register for PipelineComponentRegistry.
66
+
67
+ Parameters
68
+ ----------
69
+ caplog : pytest.LogCaptureFixture
70
+ The pytest.LogCaptureFixture object.
71
+ step : str
72
+ The parametrized name of the step.
73
+ klass : str
74
+ The parametrized name of the class.
75
+
76
+ """
77
+ with caplog.at_level(logging.INFO):
78
+ # Register
79
+ PipelineComponentRegistry().register(step=step, klass=klass)
80
+ # Check logging message
81
+ assert "Registering" in caplog.text
82
+
83
+
84
+ @pytest.mark.parametrize(
85
+ "step, klass",
86
+ [
87
+ ("datagrabber", PatternDataGrabber),
88
+ ("storage", SQLiteFeatureStorage),
89
+ ],
90
+ )
91
+ def test_pipeline_component_registry_deregister(
92
+ caplog: pytest.LogCaptureFixture, step: str, klass: type
93
+ ) -> None:
94
+ """Test de-register for PipelineComponentRegistry.
95
+
96
+ Parameters
97
+ ----------
98
+ caplog : pytest.LogCaptureFixture
99
+ The pytest.LogCaptureFixture object.
100
+ step : str
101
+ The parametrized name of the step.
102
+ klass : str
103
+ The parametrized name of the class.
104
+
105
+ """
106
+ with caplog.at_level(logging.INFO):
107
+ # Register
108
+ PipelineComponentRegistry().deregister(step=step, klass=klass)
109
+ # Check logging message
110
+ assert "De-registering" in caplog.text
111
+
112
+
113
+ def test_pipeline_component_registry_step_components() -> None:
114
+ """Test step components for PipelineComponentRegistry."""
115
+ # Check absent name
116
+ assert "fizz" not in PipelineComponentRegistry().step_components(
117
+ step="datagrabber"
118
+ )
119
+
120
+ # Create new class
121
+ class fizz(str):
122
+ pass
123
+
124
+ # Register datagrabber
125
+ PipelineComponentRegistry().register(step="datagrabber", klass=fizz)
126
+ # Check registered component
127
+ assert "fizz" in PipelineComponentRegistry().step_components(
128
+ step="datagrabber"
129
+ )
130
+
131
+
132
+ def test_pipeline_component_registry_get_class_invalid_name() -> None:
133
+ """Test get class invalid name for PipelineComponentRegistry."""
134
+ with pytest.raises(ValueError, match="Invalid name:"):
135
+ PipelineComponentRegistry().get_class(step="datagrabber", name="foo")
136
+
137
+
138
+ def test_pipeline_component_registry_get_class():
139
+ """Test get class for PipelineComponentRegistry."""
140
+
141
+ # Create new class
142
+ class bar(str):
143
+ pass
144
+
145
+ # Register datagrabber
146
+ PipelineComponentRegistry().register(step="datagrabber", klass=bar)
147
+ # Get class
148
+ obj = PipelineComponentRegistry().get_class(step="datagrabber", name="bar")
149
+ assert isinstance(obj, type(bar))
150
+
151
+
152
+ def test_pipeline_component_registry_build():
153
+ """Test component instance building for PipelineComponentRegistry."""
154
+ import numpy as np
155
+
156
+ # Define abstract base class
157
+ class SuperClass(ABC):
158
+ pass
159
+
160
+ # Define concrete class
161
+ class ConcreteClass(SuperClass):
162
+ def __init__(self, value=1):
163
+ self.value = value
164
+
165
+ # Register
166
+ PipelineComponentRegistry().register(
167
+ step="datagrabber", klass=ConcreteClass
168
+ )
169
+
170
+ # Build
171
+ obj = PipelineComponentRegistry().build_component_instance(
172
+ step="datagrabber", name="ConcreteClass", baseclass=SuperClass
173
+ )
174
+ assert isinstance(obj, ConcreteClass)
175
+ assert obj.value == 1
176
+
177
+ # Build
178
+ obj = PipelineComponentRegistry().build_component_instance(
179
+ step="datagrabber",
180
+ name="ConcreteClass",
181
+ baseclass=SuperClass,
182
+ init_params={"value": 2},
183
+ )
184
+ assert isinstance(obj, ConcreteClass)
185
+ assert obj.value == 2
186
+
187
+ # Check error
188
+ with pytest.raises(ValueError, match="Must inherit"):
189
+ PipelineComponentRegistry().build_component_instance(
190
+ step="datagrabber", name="ConcreteClass", baseclass=np.ndarray
191
+ )
192
+
193
+ # Check error
194
+ with pytest.raises(RuntimeError, match="Failed to create"):
195
+ PipelineComponentRegistry().build_component_instance(
196
+ step="datagrabber",
197
+ name="ConcreteClass",
198
+ baseclass=SuperClass,
199
+ init_params={"wrong": 2},
200
+ )
@@ -5,12 +5,17 @@
5
5
  # License: AGPL
6
6
 
7
7
  import warnings
8
- from typing import ClassVar, Dict, List, Set, Type, Union
8
+ from typing import ClassVar
9
9
 
10
10
  import pytest
11
11
 
12
12
  from junifer.pipeline.pipeline_step_mixin import PipelineStepMixin
13
13
  from junifer.pipeline.utils import _check_afni
14
+ from junifer.typing import (
15
+ ConditionalDependencies,
16
+ Dependencies,
17
+ ExternalDependencies,
18
+ )
14
19
 
15
20
 
16
21
  def test_PipelineStepMixin_correct_dependencies() -> None:
@@ -19,15 +24,15 @@ def test_PipelineStepMixin_correct_dependencies() -> None:
19
24
  class CorrectMixer(PipelineStepMixin):
20
25
  """Test class for validation."""
21
26
 
22
- _DEPENDENCIES: ClassVar[Set[str]] = {"math"}
27
+ _DEPENDENCIES: ClassVar[Dependencies] = {"math"}
23
28
 
24
- def validate_input(self, input: List[str]) -> List[str]:
29
+ def validate_input(self, input: list[str]) -> list[str]:
25
30
  return input
26
31
 
27
32
  def get_output_type(self, input_type: str) -> str:
28
33
  return input_type
29
34
 
30
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
35
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
31
36
  return {"input": input}
32
37
 
33
38
  mixer = CorrectMixer()
@@ -40,15 +45,15 @@ def test_PipelineStepMixin_incorrect_dependencies() -> None:
40
45
  class IncorrectMixer(PipelineStepMixin):
41
46
  """Test class for validation."""
42
47
 
43
- _DEPENDENCIES: ClassVar[Set[str]] = {"foobar"}
48
+ _DEPENDENCIES: ClassVar[Dependencies] = {"foobar"}
44
49
 
45
- def validate_input(self, input: List[str]) -> List[str]:
50
+ def validate_input(self, input: list[str]) -> list[str]:
46
51
  return input
47
52
 
48
53
  def get_output_type(self, input_type: str) -> str:
49
54
  return input_type
50
55
 
51
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
56
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
52
57
  return {"input": input}
53
58
 
54
59
  mixer = IncorrectMixer()
@@ -65,15 +70,15 @@ def test_PipelineStepMixin_correct_ext_dependencies() -> None:
65
70
  class CorrectMixer(PipelineStepMixin):
66
71
  """Test class for validation."""
67
72
 
68
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, str]]] = [{"name": "afni"}]
73
+ _EXT_DEPENDENCIES: ClassVar[ExternalDependencies] = [{"name": "afni"}]
69
74
 
70
- def validate_input(self, input: List[str]) -> List[str]:
75
+ def validate_input(self, input: list[str]) -> list[str]:
71
76
  return input
72
77
 
73
78
  def get_output_type(self, input_type: str) -> str:
74
79
  return input_type
75
80
 
76
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
81
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
77
82
  return {"input": input}
78
83
 
79
84
  mixer = CorrectMixer()
@@ -89,17 +94,17 @@ def test_PipelineStepMixin_ext_deps_correct_commands() -> None:
89
94
  class CorrectMixer(PipelineStepMixin):
90
95
  """Test class for validation."""
91
96
 
92
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
97
+ _EXT_DEPENDENCIES: ClassVar[ExternalDependencies] = [
93
98
  {"name": "afni", "commands": ["3dReHo"]}
94
99
  ]
95
100
 
96
- def validate_input(self, input: List[str]) -> List[str]:
101
+ def validate_input(self, input: list[str]) -> list[str]:
97
102
  return input
98
103
 
99
104
  def get_output_type(self, input_type: str) -> str:
100
105
  return input_type
101
106
 
102
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
107
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
103
108
  return {"input": input}
104
109
 
105
110
  mixer = CorrectMixer()
@@ -117,17 +122,17 @@ def test_PipelineStepMixin_ext_deps_incorrect_commands() -> None:
117
122
  class CorrectMixer(PipelineStepMixin):
118
123
  """Test class for validation."""
119
124
 
120
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
125
+ _EXT_DEPENDENCIES: ClassVar[ExternalDependencies] = [
121
126
  {"name": "afni", "commands": ["3d"]}
122
127
  ]
123
128
 
124
- def validate_input(self, input: List[str]) -> List[str]:
129
+ def validate_input(self, input: list[str]) -> list[str]:
125
130
  return input
126
131
 
127
132
  def get_output_type(self, input_type: str) -> str:
128
133
  return input_type
129
134
 
130
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
135
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
131
136
  return {"input": input}
132
137
 
133
138
  mixer = CorrectMixer()
@@ -141,17 +146,17 @@ def test_PipelineStepMixin_incorrect_ext_dependencies() -> None:
141
146
  class IncorrectMixer(PipelineStepMixin):
142
147
  """Test class for validation."""
143
148
 
144
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, bool]]]] = [
149
+ _EXT_DEPENDENCIES: ClassVar[ExternalDependencies] = [
145
150
  {"name": "foobar", "optional": True}
146
151
  ]
147
152
 
148
- def validate_input(self, input: List[str]) -> List[str]:
153
+ def validate_input(self, input: list[str]) -> list[str]:
149
154
  return input
150
155
 
151
156
  def get_output_type(self, input_type: str) -> str:
152
157
  return input_type
153
158
 
154
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
159
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
155
160
  return {"input": input}
156
161
 
157
162
  mixer = IncorrectMixer()
@@ -163,14 +168,12 @@ def test_PipelineStepMixin_correct_conditional_dependencies() -> None:
163
168
  """Test fit-transform with correct conditional dependencies."""
164
169
 
165
170
  class Dependency:
166
- _DEPENDENCIES: ClassVar[Set[str]] = {"math"}
171
+ _DEPENDENCIES: ClassVar[Dependencies] = {"math"}
167
172
 
168
173
  class CorrectMixer(PipelineStepMixin):
169
174
  """Test class for validation."""
170
175
 
171
- _CONDITIONAL_DEPENDENCIES: ClassVar[
172
- List[Dict[str, Union[str, Type]]]
173
- ] = [
176
+ _CONDITIONAL_DEPENDENCIES: ClassVar[ConditionalDependencies] = [
174
177
  {
175
178
  "using": "math",
176
179
  "depends_on": Dependency,
@@ -179,13 +182,13 @@ def test_PipelineStepMixin_correct_conditional_dependencies() -> None:
179
182
 
180
183
  using = "math"
181
184
 
182
- def validate_input(self, input: List[str]) -> List[str]:
185
+ def validate_input(self, input: list[str]) -> list[str]:
183
186
  return input
184
187
 
185
188
  def get_output_type(self, input_type: str) -> str:
186
189
  return input_type
187
190
 
188
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
191
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
189
192
  return {"input": input}
190
193
 
191
194
  mixer = CorrectMixer()
@@ -196,27 +199,25 @@ def test_PipelineStepMixin_incorrect_conditional_dependencies() -> None:
196
199
  """Test fit-transform with incorrect conditional dependencies."""
197
200
 
198
201
  class Dependency:
199
- _DEPENDENCIES: ClassVar[Set[str]] = {"math"}
202
+ _DEPENDENCIES: ClassVar[Dependencies] = {"math"}
200
203
 
201
204
  class IncorrectMixer(PipelineStepMixin):
202
205
  """Test class for validation."""
203
206
 
204
- _CONDITIONAL_DEPENDENCIES: ClassVar[
205
- List[Dict[str, Union[str, Type]]]
206
- ] = [
207
+ _CONDITIONAL_DEPENDENCIES: ClassVar[ConditionalDependencies] = [
207
208
  {
208
209
  "using": "math",
209
210
  "depends_on": Dependency,
210
211
  },
211
212
  ]
212
213
 
213
- def validate_input(self, input: List[str]) -> List[str]:
214
+ def validate_input(self, input: list[str]) -> list[str]:
214
215
  return input
215
216
 
216
217
  def get_output_type(self, input_type: str) -> str:
217
218
  return input_type
218
219
 
219
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
220
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
220
221
  return {"input": input}
221
222
 
222
223
  mixer = IncorrectMixer()
@@ -231,14 +232,12 @@ def test_PipelineStepMixin_correct_conditional_ext_dependencies() -> None:
231
232
  """Test fit-transform with correct conditional external dependencies."""
232
233
 
233
234
  class ExternalDependency:
234
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, str]]] = [{"name": "afni"}]
235
+ _EXT_DEPENDENCIES: ClassVar[ExternalDependencies] = [{"name": "afni"}]
235
236
 
236
237
  class CorrectMixer(PipelineStepMixin):
237
238
  """Test class for validation."""
238
239
 
239
- _CONDITIONAL_DEPENDENCIES: ClassVar[
240
- List[Dict[str, Union[str, Type]]]
241
- ] = [
240
+ _CONDITIONAL_DEPENDENCIES: ClassVar[ConditionalDependencies] = [
242
241
  {
243
242
  "using": "afni",
244
243
  "depends_on": ExternalDependency,
@@ -247,13 +246,13 @@ def test_PipelineStepMixin_correct_conditional_ext_dependencies() -> None:
247
246
 
248
247
  using = "afni"
249
248
 
250
- def validate_input(self, input: List[str]) -> List[str]:
249
+ def validate_input(self, input: list[str]) -> list[str]:
251
250
  return input
252
251
 
253
252
  def get_output_type(self, input_type: str) -> str:
254
253
  return input_type
255
254
 
256
- def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
255
+ def _fit_transform(self, input: dict[str, dict]) -> dict[str, dict]:
257
256
  return {"input": input}
258
257
 
259
258
  mixer = CorrectMixer()
@@ -4,7 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- from typing import Dict, List, Set, Union
7
+ from typing import Union
8
8
 
9
9
  import pytest
10
10
 
@@ -21,10 +21,10 @@ from junifer.pipeline.update_meta_mixin import UpdateMetaMixin
21
21
  ],
22
22
  )
23
23
  def test_UpdateMetaMixin(
24
- input: Dict,
24
+ input: dict,
25
25
  step_name: str,
26
- dependencies: Union[Set, List, str, None],
27
- expected: Set,
26
+ dependencies: Union[set, list, str, None],
27
+ expected: set,
28
28
  ) -> None:
29
29
  """Test UpdateMetaMixin.
30
30
 
@@ -102,3 +102,46 @@ def test_workdir_manager_get_and_delete_tempdir(tmp_path: Path) -> None:
102
102
  workdir_mgr._cleanup()
103
103
  # Should remove temporary directory
104
104
  assert workdir_mgr.root_tempdir is None
105
+
106
+
107
+ def test_workdir_manager_no_cleanup(tmp_path: Path) -> None:
108
+ """Test WorkDirManager correctly bypasses cleanup.
109
+
110
+ Parameters
111
+ ----------
112
+ tmp_path : pathlib.Path
113
+ The path to the test directory.
114
+
115
+ """
116
+ workdir_mgr = WorkDirManager(cleanup=False)
117
+ workdir_mgr.workdir = tmp_path
118
+ # Check no root temporary directory
119
+ assert workdir_mgr.root_tempdir is None
120
+
121
+ tempdir = workdir_mgr.get_tempdir()
122
+ assert tempdir.exists()
123
+ # Should create a temporary directory
124
+ assert workdir_mgr.root_tempdir is not None
125
+
126
+ workdir_mgr.delete_tempdir(tempdir)
127
+ workdir_mgr._cleanup()
128
+
129
+ # Should remove temporary directory
130
+ assert workdir_mgr.root_tempdir is None
131
+ # But the temporary directory should still exist
132
+ assert tempdir.exists()
133
+
134
+ # Now the same but for the element directory
135
+
136
+ # Check no element directory
137
+ assert workdir_mgr.elementdir is None
138
+
139
+ tempdir = workdir_mgr.get_element_tempdir()
140
+ # Should create a temporary directory
141
+ assert workdir_mgr.elementdir is not None
142
+
143
+ workdir_mgr.cleanup_elementdir()
144
+ # Should remove temporary directory
145
+ assert workdir_mgr.elementdir is None
146
+ # But the temporary directory should still exist
147
+ assert tempdir.exists()
@@ -4,7 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- from typing import Dict
7
+ from typing import Union
8
8
 
9
9
 
10
10
  __all__ = ["UpdateMetaMixin"]
@@ -15,14 +15,14 @@ class UpdateMetaMixin:
15
15
 
16
16
  def update_meta(
17
17
  self,
18
- input: Dict,
18
+ input: Union[dict, list[dict]],
19
19
  step_name: str,
20
20
  ) -> None:
21
21
  """Update metadata.
22
22
 
23
23
  Parameters
24
24
  ----------
25
- input : dict
25
+ input : dict or list of dict
26
26
  The data object to update.
27
27
  step_name : str
28
28
  The name of the pipeline step.
@@ -36,17 +36,21 @@ class UpdateMetaMixin:
36
36
  for k, v in vars(self).items():
37
37
  if not k.startswith("_"):
38
38
  t_meta[k] = v
39
- # Add "meta" to the step's local context dict
40
- if "meta" not in input:
41
- input["meta"] = {}
42
- # Add step name
43
- input["meta"][step_name] = t_meta
44
- # Add step dependencies
45
- if "dependencies" not in input["meta"]:
46
- input["meta"]["dependencies"] = set()
47
- # Update step dependencies
48
- dependencies = getattr(self, "_DEPENDENCIES", set())
49
- if dependencies is not None:
50
- if not isinstance(dependencies, (set, list)):
51
- dependencies = {dependencies}
52
- input["meta"]["dependencies"].update(dependencies)
39
+ # Conditional for list dtype vals like Warp
40
+ if not isinstance(input, list):
41
+ input = [input]
42
+ for entry in input:
43
+ # Add "meta" to the step's entry's local context dict
44
+ if "meta" not in entry:
45
+ entry["meta"] = {}
46
+ # Add step name
47
+ entry["meta"][step_name] = t_meta
48
+ # Add step dependencies
49
+ if "dependencies" not in entry["meta"]:
50
+ entry["meta"]["dependencies"] = set()
51
+ # Update step dependencies
52
+ dependencies = getattr(self, "_DEPENDENCIES", set())
53
+ if dependencies is not None:
54
+ if not isinstance(dependencies, (set, list)):
55
+ dependencies = {dependencies}
56
+ entry["meta"]["dependencies"].update(dependencies)
junifer/pipeline/utils.py CHANGED
@@ -5,7 +5,7 @@
5
5
  # License: AGPL
6
6
 
7
7
  import subprocess
8
- from typing import Any, List, Optional
8
+ from typing import Any, Optional
9
9
 
10
10
  from junifer.utils.logging import raise_error, warn_with_log
11
11
 
@@ -71,7 +71,7 @@ def check_ext_dependencies(
71
71
  return found
72
72
 
73
73
 
74
- def _check_afni(commands: Optional[List[str]] = None) -> bool:
74
+ def _check_afni(commands: Optional[list[str]] = None) -> bool:
75
75
  """Check if AFNI is present in the system.
76
76
 
77
77
  Parameters
@@ -131,7 +131,7 @@ def _check_afni(commands: Optional[List[str]] = None) -> bool:
131
131
  return afni_found
132
132
 
133
133
 
134
- def _check_fsl(commands: Optional[List[str]] = None) -> bool:
134
+ def _check_fsl(commands: Optional[list[str]] = None) -> bool:
135
135
  """Check if FSL is present in the system.
136
136
 
137
137
  Parameters
@@ -193,7 +193,7 @@ def _check_fsl(commands: Optional[List[str]] = None) -> bool:
193
193
  return fsl_found
194
194
 
195
195
 
196
- def _check_ants(commands: Optional[List[str]] = None) -> bool:
196
+ def _check_ants(commands: Optional[list[str]] = None) -> bool:
197
197
  """Check if ANTs is present in the system.
198
198
 
199
199
  Parameters
@@ -237,7 +237,7 @@ def _check_ants(commands: Optional[List[str]] = None) -> bool:
237
237
  shell=True, # is unsafe but kept for resolution via PATH
238
238
  check=False,
239
239
  )
240
- command_found = command_process.returncode == 0
240
+ command_found = command_process.returncode == 1
241
241
  commands_found_results[command] = (
242
242
  "found" if command_found else "not found"
243
243
  )
@@ -253,7 +253,7 @@ def _check_ants(commands: Optional[List[str]] = None) -> bool:
253
253
  return ants_found
254
254
 
255
255
 
256
- def _check_freesurfer(commands: Optional[List[str]] = None) -> bool:
256
+ def _check_freesurfer(commands: Optional[list[str]] = None) -> bool:
257
257
  """Check if FreeSurfer is present in the system.
258
258
 
259
259
  Parameters