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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. junifer/_version.py +9 -19
  2. junifer/api/__init__.pyi +17 -2
  3. junifer/api/functions.py +222 -39
  4. junifer/api/queue_context/__init__.pyi +11 -3
  5. junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
  6. junifer/api/queue_context/htcondor_adapter.py +118 -181
  7. junifer/api/queue_context/queue_context_adapter.py +51 -7
  8. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
  9. junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
  10. junifer/api/res/fsl/run_fsl_docker.sh +4 -0
  11. junifer/api/tests/test_functions.py +863 -36
  12. junifer/cli/__init__.py +1 -7
  13. junifer/cli/cli.py +41 -22
  14. junifer/cli/parser.py +23 -147
  15. junifer/cli/tests/data/gmd_mean.yaml +6 -6
  16. junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
  17. junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
  18. junifer/cli/tests/test_cli.py +120 -4
  19. junifer/cli/tests/test_cli_utils.py +23 -6
  20. junifer/cli/utils.py +14 -9
  21. junifer/configs/__init__.py +5 -0
  22. junifer/configs/__init__.pyi +0 -0
  23. junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
  24. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
  25. junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
  26. junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
  27. junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
  28. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
  29. junifer/configs/juseless/datagrabbers/ucla.py +111 -104
  30. junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
  31. junifer/configs/py.typed +0 -0
  32. junifer/conftest.py +63 -24
  33. junifer/data/_dispatch.py +9 -19
  34. junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
  35. junifer/data/coordinates/_coordinates.py +51 -6
  36. junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
  37. junifer/data/coordinates/tests/test_coordinates.py +15 -3
  38. junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
  39. junifer/data/maps/_ants_maps_warper.py +18 -12
  40. junifer/data/maps/_fsl_maps_warper.py +7 -3
  41. junifer/data/maps/_maps.py +12 -8
  42. junifer/data/maps/tests/test_maps.py +13 -21
  43. junifer/data/masks/_ants_mask_warper.py +18 -12
  44. junifer/data/masks/_fsl_mask_warper.py +7 -3
  45. junifer/data/masks/_masks.py +15 -12
  46. junifer/data/masks/tests/test_masks.py +20 -17
  47. junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
  48. junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
  49. junifer/data/parcellations/_parcellations.py +125 -91
  50. junifer/data/parcellations/tests/test_parcellations.py +122 -1
  51. junifer/data/template_spaces.py +117 -50
  52. junifer/data/tests/test_template_spaces.py +10 -7
  53. junifer/data/utils.py +10 -8
  54. junifer/datagrabber/__init__.pyi +38 -5
  55. junifer/datagrabber/aomic/__init__.pyi +8 -1
  56. junifer/datagrabber/aomic/_types.py +22 -0
  57. junifer/datagrabber/aomic/id1000.py +193 -183
  58. junifer/datagrabber/aomic/piop1.py +215 -259
  59. junifer/datagrabber/aomic/piop2.py +211 -244
  60. junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
  61. junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
  62. junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
  63. junifer/datagrabber/base.py +253 -79
  64. junifer/datagrabber/datalad_base.py +127 -76
  65. junifer/datagrabber/dmcc13_benchmark.py +204 -289
  66. junifer/datagrabber/hcp1200/__init__.pyi +7 -2
  67. junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
  68. junifer/datagrabber/hcp1200/hcp1200.py +151 -164
  69. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
  70. junifer/datagrabber/multiple.py +183 -63
  71. junifer/datagrabber/pattern.py +358 -227
  72. junifer/datagrabber/pattern_datalad.py +84 -124
  73. junifer/datagrabber/pattern_validation_mixin.py +22 -50
  74. junifer/datagrabber/tests/test_base.py +17 -4
  75. junifer/datagrabber/tests/test_datalad_base.py +20 -27
  76. junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
  77. junifer/datagrabber/tests/test_multiple.py +293 -31
  78. junifer/datagrabber/tests/test_pattern.py +577 -18
  79. junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
  80. junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
  81. junifer/datareader/default.py +8 -5
  82. junifer/datareader/tests/test_default_reader.py +1 -1
  83. junifer/external/BrainPrint/.codecov.yml +2 -0
  84. junifer/external/BrainPrint/.codespellignore +2 -0
  85. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
  86. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
  87. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
  88. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
  89. junifer/external/BrainPrint/.github/dependabot.yml +6 -0
  90. junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
  91. junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
  92. junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
  93. junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
  94. junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
  95. junifer/external/BrainPrint/.gitignore +123 -0
  96. junifer/external/BrainPrint/LICENSE +21 -0
  97. junifer/external/BrainPrint/README.md +108 -0
  98. junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
  99. junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
  100. junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
  101. junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
  102. junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
  103. junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
  104. junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
  105. junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
  106. junifer/external/BrainPrint/doc/Makefile +20 -0
  107. junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
  108. junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
  109. junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
  110. junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
  111. junifer/external/BrainPrint/doc/api/index.rst +11 -0
  112. junifer/external/BrainPrint/doc/conf.py +272 -0
  113. junifer/external/BrainPrint/doc/index.rst +140 -0
  114. junifer/external/BrainPrint/doc/links.inc +36 -0
  115. junifer/external/BrainPrint/doc/make.bat +35 -0
  116. junifer/external/BrainPrint/doc/references.bib +23 -0
  117. junifer/external/BrainPrint/pyproject.toml +170 -0
  118. junifer/external/BrainPrint/setup.py +3 -0
  119. junifer/external/__init__.py +5 -0
  120. junifer/external/__init__.pyi +0 -0
  121. junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
  122. junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
  123. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
  124. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
  125. junifer/external/py.typed +0 -0
  126. junifer/markers/__init__.pyi +6 -3
  127. junifer/markers/base.py +126 -76
  128. junifer/markers/brainprint.py +30 -39
  129. junifer/markers/complexity/__init__.py +20 -3
  130. junifer/markers/complexity/complexity_base.py +40 -41
  131. junifer/markers/complexity/hurst_exponent.py +22 -35
  132. junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
  133. junifer/markers/complexity/perm_entropy.py +22 -36
  134. junifer/markers/complexity/range_entropy.py +22 -35
  135. junifer/markers/complexity/range_entropy_auc.py +27 -36
  136. junifer/markers/complexity/sample_entropy.py +22 -36
  137. junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
  138. junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
  139. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
  140. junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
  141. junifer/markers/complexity/tests/test_range_entropy.py +47 -36
  142. junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
  143. junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
  144. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
  145. junifer/markers/complexity/weighted_perm_entropy.py +22 -36
  146. junifer/markers/ets_rss.py +35 -31
  147. junifer/markers/falff/__init__.pyi +7 -1
  148. junifer/markers/falff/_afni_falff.py +22 -9
  149. junifer/markers/falff/_junifer_falff.py +47 -31
  150. junifer/markers/falff/falff_base.py +80 -60
  151. junifer/markers/falff/falff_maps.py +17 -41
  152. junifer/markers/falff/falff_parcels.py +26 -51
  153. junifer/markers/falff/falff_spheres.py +34 -61
  154. junifer/markers/falff/tests/test_falff_maps.py +20 -23
  155. junifer/markers/falff/tests/test_falff_mask.py +99 -0
  156. junifer/markers/falff/tests/test_falff_parcels.py +14 -10
  157. junifer/markers/falff/tests/test_falff_spheres.py +14 -10
  158. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
  159. junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
  160. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
  161. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
  162. junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
  163. junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
  164. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
  165. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
  166. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
  167. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
  168. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
  169. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
  170. junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
  171. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
  172. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
  173. junifer/markers/maps_aggregation.py +77 -55
  174. junifer/markers/parcel_aggregation.py +91 -70
  175. junifer/markers/reho/__init__.pyi +7 -1
  176. junifer/markers/reho/_afni_reho.py +23 -14
  177. junifer/markers/reho/_junifer_reho.py +112 -123
  178. junifer/markers/reho/reho_base.py +115 -37
  179. junifer/markers/reho/reho_maps.py +18 -32
  180. junifer/markers/reho/reho_parcels.py +26 -41
  181. junifer/markers/reho/reho_spheres.py +33 -54
  182. junifer/markers/reho/tests/test_junifer_reho.py +178 -0
  183. junifer/markers/reho/tests/test_reho_maps.py +27 -25
  184. junifer/markers/reho/tests/test_reho_parcels.py +28 -22
  185. junifer/markers/reho/tests/test_reho_spheres.py +16 -12
  186. junifer/markers/sphere_aggregation.py +88 -72
  187. junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
  188. junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
  189. junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
  190. junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
  191. junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
  192. junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
  193. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
  194. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
  195. junifer/markers/tests/test_brainprint.py +10 -9
  196. junifer/markers/tests/test_ets_rss.py +7 -3
  197. junifer/markers/tests/test_lost_regions.py +279 -0
  198. junifer/markers/tests/test_maps_aggregation.py +67 -114
  199. junifer/markers/tests/test_marker_utils.py +40 -1
  200. junifer/markers/tests/test_markers_base.py +275 -6
  201. junifer/markers/tests/test_parcel_aggregation.py +91 -53
  202. junifer/markers/tests/test_sphere_aggregation.py +53 -47
  203. junifer/markers/utils.py +267 -5
  204. junifer/onthefly/__init__.py +2 -3
  205. junifer/onthefly/__init__.pyi +4 -0
  206. junifer/onthefly/_brainprint.py +3 -5
  207. junifer/onthefly/py.typed +0 -0
  208. junifer/onthefly/read_transform.py +11 -9
  209. junifer/onthefly/tests/test_read_transform.py +7 -7
  210. junifer/pipeline/__init__.pyi +4 -2
  211. junifer/pipeline/marker_collection.py +14 -10
  212. junifer/pipeline/pipeline_component_registry.py +25 -6
  213. junifer/pipeline/pipeline_step_mixin.py +5 -12
  214. junifer/pipeline/tests/test_data_object_dumper.py +10 -10
  215. junifer/pipeline/tests/test_marker_collection.py +145 -168
  216. junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
  217. junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
  218. junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
  219. junifer/pipeline/tests/test_workdir_manager.py +39 -0
  220. junifer/pipeline/update_meta_mixin.py +13 -10
  221. junifer/pipeline/utils.py +22 -17
  222. junifer/pipeline/workdir_manager.py +41 -32
  223. junifer/preprocess/__init__.pyi +9 -4
  224. junifer/preprocess/_temporal_filter.py +34 -33
  225. junifer/preprocess/_temporal_slicer.py +17 -30
  226. junifer/preprocess/base.py +99 -55
  227. junifer/preprocess/confounds/__init__.pyi +10 -2
  228. junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
  229. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
  230. junifer/preprocess/smoothing/__init__.pyi +2 -2
  231. junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
  232. junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
  233. junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
  234. junifer/preprocess/smoothing/smoothing.py +59 -40
  235. junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
  236. junifer/preprocess/tests/test_preprocess_base.py +17 -9
  237. junifer/preprocess/tests/test_temporal_filter.py +37 -16
  238. junifer/preprocess/tests/test_temporal_slicer.py +12 -12
  239. junifer/preprocess/warping/__init__.pyi +2 -2
  240. junifer/preprocess/warping/_ants_warper.py +56 -40
  241. junifer/preprocess/warping/_fsl_warper.py +49 -36
  242. junifer/preprocess/warping/_utils.py +106 -0
  243. junifer/preprocess/warping/space_warper.py +99 -98
  244. junifer/preprocess/warping/tests/test_space_warper.py +46 -41
  245. junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
  246. junifer/stats.py +46 -34
  247. junifer/storage/__init__.pyi +8 -4
  248. junifer/storage/base.py +129 -64
  249. junifer/storage/hdf5.py +307 -199
  250. junifer/storage/pandas_base.py +46 -24
  251. junifer/storage/sqlite.py +99 -62
  252. junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
  253. junifer/storage/tests/test_hdf5.py +147 -12
  254. junifer/storage/tests/test_repeated_storage.py +293 -0
  255. junifer/storage/tests/test_sqlite.py +9 -20
  256. junifer/storage/tests/test_storage_base.py +18 -21
  257. junifer/storage/tests/test_utils.py +83 -22
  258. junifer/storage/utils.py +58 -38
  259. junifer/testing/__init__.pyi +2 -2
  260. junifer/testing/datagrabbers.py +480 -44
  261. junifer/testing/registry.py +12 -0
  262. junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
  263. junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
  264. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
  265. junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
  266. junifer/testing/tests/test_testing_registry.py +31 -0
  267. junifer/testing/tests/test_testing_utils.py +35 -0
  268. junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
  269. junifer/testing/utils.py +33 -1
  270. junifer/tests/test_stats.py +1 -5
  271. junifer/typing/__init__.pyi +2 -0
  272. junifer/typing/_typing.py +34 -19
  273. junifer/typing/py.typed +0 -0
  274. junifer/utils/__init__.pyi +10 -1
  275. junifer/utils/_config.py +1 -2
  276. junifer/utils/helpers.py +68 -2
  277. junifer/utils/logging.py +31 -7
  278. junifer/utils/tests/test_helpers.py +36 -1
  279. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
  280. junifer-0.0.8.dist-info/RECORD +402 -0
  281. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
  282. junifer/cli/tests/test_parser.py +0 -280
  283. junifer/external/h5io/h5io/__init__.py +0 -15
  284. junifer/external/h5io/h5io/_h5io.py +0 -767
  285. junifer/external/h5io/h5io/_version.py +0 -1
  286. junifer/external/h5io/h5io/chunked_array.py +0 -94
  287. junifer/external/h5io/h5io/chunked_list.py +0 -65
  288. junifer/markers/complexity/__init__.pyi +0 -17
  289. junifer-0.0.7.dev332.dist-info/RECORD +0 -353
  290. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
  291. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
  292. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
  293. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
@@ -4,19 +4,49 @@
4
4
  # License: AGPL
5
5
 
6
6
  from collections.abc import Sequence
7
- from typing import Any, ClassVar, Optional, Union
7
+ from enum import StrEnum
8
+ from typing import Annotated, Any, ClassVar, Literal
8
9
 
9
- from templateflow import api as tflow
10
+ from pydantic import BeforeValidator
10
11
 
11
12
  from ...api.decorators import register_preprocessor
13
+ from ...data.template_spaces import _get_templateflow_templates
14
+ from ...datagrabber import DataType
12
15
  from ...typing import ConditionalDependencies
13
- from ...utils import logger, raise_error
14
- from ..base import BasePreprocessor
16
+ from ...utils import ensure_list_or_none, raise_error
17
+ from ..base import BasePreprocessor, logger
15
18
  from ._ants_warper import ANTsWarper
16
19
  from ._fsl_warper import FSLWarper
17
20
 
18
21
 
19
- __all__ = ["SpaceWarper"]
22
+ __all__ = ["SpaceWarper", "SpaceWarpingImpl"]
23
+
24
+
25
+ class SpaceWarpingImpl(StrEnum):
26
+ """Accepted space warping implementations.
27
+
28
+ * ``fsl`` : FSL's ``applywarp``
29
+ * ``ants`` : ANTs' ``antsApplyTransforms``
30
+ * ``auto`` : Auto-select tool when ``reference="T1w"``
31
+
32
+ """
33
+
34
+ fsl = "fsl"
35
+ ants = "ants"
36
+ auto = "auto"
37
+
38
+
39
+ _on = Literal[
40
+ DataType.T1w,
41
+ DataType.T2w,
42
+ DataType.BOLD,
43
+ DataType.VBM_GM,
44
+ DataType.VBM_WM,
45
+ DataType.VBM_CSF,
46
+ DataType.FALFF,
47
+ DataType.GCOR,
48
+ DataType.LCOR,
49
+ ]
20
50
 
21
51
 
22
52
  @register_preprocessor
@@ -25,91 +55,67 @@ class SpaceWarper(BasePreprocessor):
25
55
 
26
56
  Parameters
27
57
  ----------
28
- using : {"fsl", "ants", "auto"}
29
- Implementation to use for warping:
30
-
31
- * "fsl" : Use FSL's ``applywarp``
32
- * "ants" : Use ANTs' ``antsApplyTransforms``
33
- * "auto" : Auto-select tool when ``reference="T1w"``
34
-
58
+ using : :enum:`.SpaceWarpingImpl`
35
59
  reference : str
36
60
  The data type to use as reference for warping, can be either a data
37
61
  type like ``"T1w"`` or a template space like ``"MNI152NLin2009cAsym"``.
38
62
  Use ``"T1w"`` for native space warping and named templates for
39
63
  template space warping.
40
- on : {"T1w", "T2w", "BOLD", "VBM_GM", "VBM_WM", "VBM_CSF", "fALFF", \
41
- "GCOR", "LCOR"} or list of the options
42
- The data type to warp.
43
-
44
- Raises
45
- ------
46
- ValueError
47
- If ``using`` is invalid or
48
- if ``reference`` is invalid.
64
+ on : {``DataType.T1w``, ``DataType.T2w``, ``DataType.BOLD``, \
65
+ ``DataType.VBM_GM``, ``DataType.VBM_WM``, ``DataType.VBM_CSF``, \
66
+ ``DataType.FALFF``, ``DataType.GCOR``, ``DataType.LCOR``} or \
67
+ list of them
68
+ The data type(s) to warp.
49
69
 
50
70
  """
51
71
 
52
72
  _CONDITIONAL_DEPENDENCIES: ClassVar[ConditionalDependencies] = [
53
73
  {
54
- "using": "fsl",
55
- "depends_on": FSLWarper,
74
+ "using": SpaceWarpingImpl.fsl,
75
+ "depends_on": [FSLWarper],
56
76
  },
57
77
  {
58
- "using": "ants",
59
- "depends_on": ANTsWarper,
78
+ "using": SpaceWarpingImpl.ants,
79
+ "depends_on": [ANTsWarper],
60
80
  },
61
81
  {
62
- "using": "auto",
82
+ "using": SpaceWarpingImpl.auto,
63
83
  "depends_on": [FSLWarper, ANTsWarper],
64
84
  },
65
85
  ]
66
- _VALID_DATA_TYPES: ClassVar[Sequence[str]] = [
67
- "T1w",
68
- "T2w",
69
- "BOLD",
70
- "VBM_GM",
71
- "VBM_WM",
72
- "VBM_CSF",
73
- "fALFF",
74
- "GCOR",
75
- "LCOR",
86
+ _VALID_DATA_TYPES: ClassVar[Sequence[DataType]] = [
87
+ DataType.T1w,
88
+ DataType.T2w,
89
+ DataType.BOLD,
90
+ DataType.VBM_GM,
91
+ DataType.VBM_WM,
92
+ DataType.VBM_CSF,
93
+ DataType.FALFF,
94
+ DataType.GCOR,
95
+ DataType.LCOR,
76
96
  ]
77
97
 
78
- def __init__(
79
- self, using: str, reference: str, on: Union[list[str], str]
80
- ) -> None:
81
- """Initialize the class."""
82
- # Validate `using` parameter
83
- valid_using = [dep["using"] for dep in self._CONDITIONAL_DEPENDENCIES]
84
- if using not in valid_using:
85
- raise_error(
86
- f"Invalid value for `using`, should be one of: {valid_using}"
87
- )
88
- self.using = using
89
- self.reference = reference
90
- # Set required data types based on reference and
91
- # initialize superclass
92
- if self.reference == "T1w": # pragma: no cover
93
- required_data_types = [self.reference, "Warp"]
94
- # Listify on
95
- if not isinstance(on, list):
96
- on = [on]
97
- # Extend required data types
98
- required_data_types.extend(on)
98
+ using: SpaceWarpingImpl
99
+ reference: str
100
+ on: Annotated[
101
+ _on | list[_on],
102
+ BeforeValidator(ensure_list_or_none),
103
+ ]
99
104
 
100
- super().__init__(
101
- on=on,
102
- required_data_types=required_data_types,
103
- )
104
- elif self.reference in tflow.templates():
105
- super().__init__(on=on)
106
- else:
105
+ def validate_preprocessor_params(self) -> None:
106
+ """Run extra logical validation for preprocessor."""
107
+ # Set required data types based on reference
108
+ if self.reference == "T1w": # pragma: no cover
109
+ # Update required data types
110
+ self.required_data_types = [DataType.T1w, DataType.Warp]
111
+ self.required_data_types.extend(self.on)
112
+ elif self.reference not in _get_templateflow_templates():
107
113
  raise_error(f"Unknown reference: {self.reference}")
108
114
 
109
115
  def preprocess( # noqa: C901
110
116
  self,
111
117
  input: dict[str, Any],
112
- extra_input: Optional[dict[str, Any]] = None,
118
+ extra_input: dict[str, Any] | None = None,
113
119
  ) -> dict[str, Any]:
114
120
  """Preprocess.
115
121
 
@@ -129,20 +135,21 @@ class SpaceWarper(BasePreprocessor):
129
135
  ------
130
136
  ValueError
131
137
  If ``extra_input`` is None when transforming to native space
132
- i.e., using ``"T1w"`` as reference.
138
+ i.e., using ``"T1w"`` as reference or converting from native to
139
+ template space or
140
+ if the ``reference`` key is missing from ``input`` when converting
141
+ from native to template space.
133
142
  RuntimeError
134
143
  If warper could not be found in ``extra_input`` when
135
144
  ``using="auto"`` or converting from native space or
136
145
  if the data is in the correct space and does not require
137
146
  warping or
138
- if FSL is used when ``reference="T1w"``.
147
+ if FSL or "auto" is used when ``reference!="T1w"``.
139
148
 
140
149
  """
141
150
  logger.info(f"Warping to {self.reference} space using SpaceWarper")
142
151
  # Transform to native space
143
- if (
144
- self.using in ["fsl", "ants", "auto"] and self.reference == "T1w"
145
- ): # pragma: no cover
152
+ if self.reference == "T1w": # pragma: no cover
146
153
  # Check for extra inputs
147
154
  if extra_input is None:
148
155
  raise_error(
@@ -150,41 +157,30 @@ class SpaceWarper(BasePreprocessor):
150
157
  f"`{self.reference}` data types in particular."
151
158
  )
152
159
  # Conditional preprocessor
153
- if self.using == "fsl":
160
+ warper = None
161
+ if self.using == "auto":
162
+ for entry in extra_input["Warp"]:
163
+ if entry["dst"] == "native":
164
+ warper = entry["warper"]
165
+ if warper is None:
166
+ raise_error(
167
+ klass=RuntimeError, msg="Could not find correct warper"
168
+ )
169
+ else:
170
+ warper = self.using
171
+ if warper == "fsl":
154
172
  input = FSLWarper().preprocess(
155
173
  input=input,
156
174
  extra_input=extra_input,
157
175
  reference=self.reference,
158
176
  )
159
- elif self.using == "ants":
177
+ elif warper == "ants":
160
178
  input = ANTsWarper().preprocess(
161
179
  input=input,
162
180
  extra_input=extra_input,
163
181
  reference=self.reference,
164
182
  )
165
- elif self.using == "auto":
166
- warper = None
167
- for entry in extra_input["Warp"]:
168
- if entry["dst"] == "native":
169
- warper = entry["warper"]
170
- if warper is None:
171
- raise_error(
172
- klass=RuntimeError, msg="Could not find correct warper"
173
- )
174
- if warper == "fsl":
175
- input = FSLWarper().preprocess(
176
- input=input,
177
- extra_input=extra_input,
178
- reference=self.reference,
179
- )
180
- elif warper == "ants":
181
- input = ANTsWarper().preprocess(
182
- input=input,
183
- extra_input=extra_input,
184
- reference=self.reference,
185
- )
186
- # Transform to template space
187
- if self.using in ["fsl", "ants"] and self.reference != "T1w":
183
+ else:
188
184
  input_space = input["space"]
189
185
  # Check pre-requirements for space manipulation
190
186
  if self.using == "ants" and self.reference == input_space:
@@ -241,12 +237,12 @@ class SpaceWarper(BasePreprocessor):
241
237
  )
242
238
  else:
243
239
  # Transform from MNI to MNI template space not possible
244
- if self.using == "fsl":
240
+ if self.using in ["fsl", "auto"]:
245
241
  raise_error(
246
242
  (
247
243
  f"Warping from {input_space} space to "
248
244
  f"{self.reference} space not possible with "
249
- "FSL, use ANTs instead."
245
+ f"{self.using}, use ANTs instead."
250
246
  ),
251
247
  klass=RuntimeError,
252
248
  )
@@ -257,5 +253,10 @@ class SpaceWarper(BasePreprocessor):
257
253
  extra_input={},
258
254
  reference=self.reference,
259
255
  )
260
-
256
+ logger.debug("Completed warping step")
257
+ logger.debug("Warped data types: ")
258
+ for k, v in input.items():
259
+ if k in ["data", "meta"]:
260
+ continue
261
+ logger.debug(f"\t{k}: {v}")
261
262
  return input
@@ -8,25 +8,36 @@ import socket
8
8
  import pytest
9
9
  from numpy.testing import assert_array_equal, assert_raises
10
10
 
11
- from junifer.datagrabber import DataladHCP1200, DMCC13Benchmark
11
+ from junifer.datagrabber import (
12
+ DataladHCP1200,
13
+ DataType,
14
+ DMCC13Benchmark,
15
+ DMCCPhaseEncoding,
16
+ DMCCRun,
17
+ DMCCSession,
18
+ DMCCTask,
19
+ HCP1200PhaseEncoding,
20
+ HCP1200Task,
21
+ )
12
22
  from junifer.datareader import DefaultDataReader
13
23
  from junifer.pipeline.utils import _check_ants, _check_fsl
14
- from junifer.preprocess import SpaceWarper
24
+ from junifer.preprocess import SpaceWarper, SpaceWarpingImpl
15
25
  from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
16
26
  from junifer.typing import DataGrabberLike
17
27
 
18
28
 
29
+ pytestmark = pytest.mark.external
30
+
31
+
19
32
  @pytest.mark.parametrize(
20
33
  "using, reference, error_type, error_msg",
21
34
  [
22
- ("jam", "T1w", ValueError, "`using`"),
23
- ("ants", "juice", ValueError, "reference"),
24
- ("ants", "MNI152NLin2009cAsym", RuntimeError, "remove"),
25
- ("fsl", "MNI152NLin2009cAsym", RuntimeError, "ANTs"),
35
+ (SpaceWarpingImpl.ants, "MNI152NLin2009cAsym", RuntimeError, "remove"),
36
+ (SpaceWarpingImpl.fsl, "MNI152NLin2009cAsym", RuntimeError, "ANTs"),
26
37
  ],
27
38
  )
28
39
  def test_SpaceWarper_errors(
29
- using: str,
40
+ using: SpaceWarpingImpl,
30
41
  reference: str,
31
42
  error_type: type[Exception],
32
43
  error_msg: str,
@@ -35,7 +46,7 @@ def test_SpaceWarper_errors(
35
46
 
36
47
  Parameters
37
48
  ----------
38
- using : str
49
+ using : SpaceWarpingImpl
39
50
  The parametrized implementation method.
40
51
  reference : str
41
52
  The parametrized reference to use.
@@ -46,14 +57,12 @@ def test_SpaceWarper_errors(
46
57
 
47
58
  """
48
59
  with PartlyCloudyTestingDataGrabber() as dg:
49
- # Read data
50
60
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
51
- # Preprocess data
52
61
  with pytest.raises(error_type, match=error_msg):
53
62
  SpaceWarper(
54
63
  using=using,
55
64
  reference=reference,
56
- on="BOLD",
65
+ on=DataType.BOLD,
57
66
  ).preprocess(
58
67
  input=element_data["BOLD"],
59
68
  extra_input=element_data,
@@ -65,24 +74,24 @@ def test_SpaceWarper_errors(
65
74
  [
66
75
  [
67
76
  DMCC13Benchmark(
68
- types=["BOLD", "T1w", "Warp"],
69
- sessions=["ses-wave1bas"],
70
- tasks=["Rest"],
71
- phase_encodings=["AP"],
72
- runs=["1"],
77
+ types=[DataType.BOLD, DataType.T1w, DataType.Warp],
78
+ sessions=DMCCSession.Wave1Bas,
79
+ tasks=DMCCTask.Rest,
80
+ phase_encodings=DMCCPhaseEncoding.AP,
81
+ runs=DMCCRun.One,
73
82
  native_t1w=True,
74
83
  ),
75
84
  ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
76
- "ants",
85
+ SpaceWarpingImpl.ants,
77
86
  ],
78
87
  [
79
88
  DataladHCP1200(
80
- tasks=["REST1"],
81
- phase_encodings=["LR"],
89
+ tasks=HCP1200Task.REST1,
90
+ phase_encodings=HCP1200PhaseEncoding.LR,
82
91
  ica_fix=True,
83
92
  ),
84
93
  ("100206", "REST1", "LR"),
85
- "fsl",
94
+ SpaceWarpingImpl.fsl,
86
95
  ],
87
96
  ],
88
97
  )
@@ -95,7 +104,9 @@ def test_SpaceWarper_errors(
95
104
  reason="only for juseless",
96
105
  )
97
106
  def test_SpaceWarper_native(
98
- datagrabber: DataGrabberLike, element: tuple[str, ...], using: str
107
+ datagrabber: DataGrabberLike,
108
+ element: tuple[str, ...],
109
+ using: SpaceWarpingImpl,
99
110
  ) -> None:
100
111
  """Test SpaceWarper for native space warping.
101
112
 
@@ -105,23 +116,20 @@ def test_SpaceWarper_native(
105
116
  The parametrized DataGrabber objects.
106
117
  element : tuple of str
107
118
  The parametrized elements.
108
- using : str
119
+ using : SpaceWarpingImpl
109
120
  The parametrized implementation method.
110
121
 
111
122
  """
112
123
  with datagrabber as dg:
113
- # Read data
114
124
  element_data = DefaultDataReader().fit_transform(dg[element])
115
- # Preprocess data
116
125
  output = SpaceWarper(
117
126
  using=using,
118
127
  reference="T1w",
119
- on="BOLD",
128
+ on=DataType.BOLD,
120
129
  ).preprocess(
121
130
  input=element_data["BOLD"],
122
131
  extra_input=element_data,
123
132
  )
124
- # Check
125
133
  assert isinstance(output, dict)
126
134
 
127
135
 
@@ -130,11 +138,11 @@ def test_SpaceWarper_native(
130
138
  [
131
139
  [
132
140
  DMCC13Benchmark(
133
- types=["T1w"],
134
- sessions=["ses-wave1bas"],
135
- tasks=["Rest"],
136
- phase_encodings=["AP"],
137
- runs=["1"],
141
+ types=DataType.T1w,
142
+ sessions=DMCCSession.Wave1Bas,
143
+ tasks=DMCCTask.Rest,
144
+ phase_encodings=DMCCPhaseEncoding.AP,
145
+ runs=DMCCRun.One,
138
146
  native_t1w=False,
139
147
  ),
140
148
  ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
@@ -142,11 +150,11 @@ def test_SpaceWarper_native(
142
150
  ],
143
151
  [
144
152
  DMCC13Benchmark(
145
- types=["T1w"],
146
- sessions=["ses-wave1bas"],
147
- tasks=["Rest"],
148
- phase_encodings=["AP"],
149
- runs=["1"],
153
+ types=DataType.T1w,
154
+ sessions=DMCCSession.Wave1Bas,
155
+ tasks=DMCCTask.Rest,
156
+ phase_encodings=DMCCPhaseEncoding.AP,
157
+ runs=DMCCRun.One,
150
158
  native_t1w=False,
151
159
  ),
152
160
  ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
@@ -175,19 +183,16 @@ def test_SpaceWarper_multi_mni(
175
183
 
176
184
  """
177
185
  with datagrabber as dg:
178
- # Read data
179
186
  element_data = DefaultDataReader().fit_transform(dg[element])
180
187
  pre_xfm_data = element_data["T1w"]["data"].get_fdata().copy()
181
- # Preprocess data
182
188
  output = SpaceWarper(
183
- using="ants",
189
+ using=SpaceWarpingImpl.ants,
184
190
  reference=space,
185
- on=["T1w"],
191
+ on=DataType.T1w,
186
192
  ).preprocess(
187
193
  input=element_data["T1w"],
188
194
  extra_input=element_data,
189
195
  )
190
- # Checks
191
196
  assert isinstance(output, dict)
192
197
  assert output["space"] == space
193
198
  with assert_raises(AssertionError):
@@ -0,0 +1,163 @@
1
+ """Provide tests for the warping utilities."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from pathlib import Path
7
+
8
+ import nibabel as nib
9
+ import numpy as np
10
+ import pytest
11
+ from numpy.testing import assert_array_equal
12
+
13
+ from junifer.datagrabber import DataType
14
+ from junifer.datareader import DefaultDataReader
15
+ from junifer.pipeline.utils import _check_ants
16
+ from junifer.preprocess import SpaceWarper, SpaceWarpingImpl
17
+ from junifer.preprocess.warping._utils import warp_in_chunks
18
+ from junifer.testing import config_override
19
+ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
20
+
21
+
22
+ def _copy(in_path: Path, out_path: Path) -> list[str]:
23
+ """Return a command copying the image, to test the chunks."""
24
+ return ["cp", str(in_path), str(out_path)]
25
+
26
+
27
+ @pytest.mark.parametrize("suffix", [".nii", ".nii.gz"])
28
+ @pytest.mark.parametrize(
29
+ "n_volumes, chunk_size", [(10, 3), (10, 5), (10, 10), (10, 100), (1, 1)]
30
+ )
31
+ @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.int16])
32
+ def test_warp_in_chunks(
33
+ tmp_path: Path,
34
+ suffix: str,
35
+ n_volumes: int,
36
+ chunk_size: int,
37
+ dtype: type,
38
+ ) -> None:
39
+ """Test warping in chunks gives the same image.
40
+
41
+ Parameters
42
+ ----------
43
+ tmp_path : pathlib.Path
44
+ The path to the test directory.
45
+ suffix : str
46
+ The parametrized file extension.
47
+ n_volumes : int
48
+ The parametrized number of volumes.
49
+ chunk_size : int
50
+ The parametrized number of volumes of the chunks.
51
+ dtype : type
52
+ The parametrized data type of the image.
53
+
54
+ """
55
+ rng = np.random.default_rng(0)
56
+ data = rng.normal(size=(4, 5, 6, n_volumes)) * 100
57
+ img = nib.Nifti1Image(data.astype(dtype), np.diag([2, 2, 2, 1]))
58
+ if dtype is np.int16:
59
+ # Scaled data
60
+ img.header.set_slope_inter(0.5, 10)
61
+ in_path = tmp_path / f"input{suffix}"
62
+ out_path = tmp_path / f"output{suffix}"
63
+ nib.save(img, in_path)
64
+ with config_override("preprocessing.warping.chunksize", chunk_size):
65
+ warp_in_chunks(
66
+ name="cp",
67
+ input_path=in_path,
68
+ output_path=out_path,
69
+ cmd=_copy,
70
+ tempdir=tmp_path,
71
+ )
72
+ out = nib.load(out_path)
73
+ expected = nib.load(in_path)
74
+ assert out.shape == expected.shape
75
+ if dtype is not np.int16:
76
+ assert out.get_data_dtype() == expected.get_data_dtype()
77
+ assert_array_equal(out.affine, expected.affine)
78
+ assert_array_equal(out.get_fdata(), expected.get_fdata())
79
+ # Only the input and the output are left
80
+ assert sorted(p.name for p in tmp_path.iterdir()) == sorted(
81
+ [in_path.name, out_path.name]
82
+ )
83
+
84
+
85
+ def test_warp_in_chunks_3d(tmp_path: Path) -> None:
86
+ """Test warping a 3D image is not done in chunks.
87
+
88
+ Parameters
89
+ ----------
90
+ tmp_path : pathlib.Path
91
+ The path to the test directory.
92
+
93
+ """
94
+ img = nib.Nifti1Image(np.ones((4, 5, 6), dtype=np.float32), np.eye(4))
95
+ in_path = tmp_path / "input.nii.gz"
96
+ out_path = tmp_path / "output.nii.gz"
97
+ nib.save(img, in_path)
98
+ calls = []
99
+
100
+ def cmd(in_path: Path, out_path: Path) -> list[str]:
101
+ calls.append(in_path)
102
+ return _copy(in_path, out_path)
103
+
104
+ with config_override("preprocessing.warping.chunksize", 1):
105
+ warp_in_chunks(
106
+ name="cp",
107
+ input_path=in_path,
108
+ output_path=out_path,
109
+ cmd=cmd,
110
+ tempdir=tmp_path,
111
+ )
112
+ assert calls == [in_path]
113
+ assert_array_equal(nib.load(out_path).get_fdata(), img.get_fdata())
114
+
115
+
116
+ @pytest.mark.parametrize("chunk_size", [0, -1, 2.5, "10"])
117
+ def test_warp_in_chunks_error(tmp_path: Path, chunk_size: object) -> None:
118
+ """Test an invalid chunk size.
119
+
120
+ Parameters
121
+ ----------
122
+ tmp_path : pathlib.Path
123
+ The path to the test directory.
124
+ chunk_size : object
125
+ The parametrized invalid chunk size.
126
+
127
+ """
128
+ with config_override("preprocessing.warping.chunksize", chunk_size):
129
+ with pytest.raises(ValueError, match="must be a positive integer"):
130
+ warp_in_chunks(
131
+ name="cp",
132
+ input_path=tmp_path / "input.nii.gz",
133
+ output_path=tmp_path / "output.nii.gz",
134
+ cmd=_copy,
135
+ tempdir=tmp_path,
136
+ )
137
+
138
+
139
+ @pytest.mark.external
140
+ @pytest.mark.skipif(
141
+ _check_ants() is False, reason="requires ANTs to be in PATH"
142
+ )
143
+ def test_SpaceWarper_ants_chunks() -> None:
144
+ """Test warping with ANTs in chunks gives the same data."""
145
+ outputs = []
146
+ for chunk_size in (100, 3):
147
+ with PartlyCloudyTestingDataGrabber(n_timepoints=8) as dg:
148
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
149
+ with config_override(
150
+ "preprocessing.warping.chunksize", chunk_size
151
+ ):
152
+ output = SpaceWarper(
153
+ using=SpaceWarpingImpl.ants,
154
+ reference="MNI152NLin6Asym",
155
+ on=DataType.BOLD,
156
+ ).preprocess(
157
+ input=element_data["BOLD"],
158
+ extra_input=element_data,
159
+ )
160
+ assert output["data"].get_data_dtype() == np.float32
161
+ outputs.append(np.asarray(output["data"].dataobj))
162
+ assert outputs[0].shape[-1] == 8
163
+ assert_array_equal(outputs[0], outputs[1])