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
@@ -7,6 +7,7 @@
7
7
 
8
8
  from pathlib import Path
9
9
 
10
+ import nibabel as nib
10
11
  import numpy as np
11
12
  import pytest
12
13
  from nilearn.image import new_img_like, resample_to_img
@@ -19,7 +20,10 @@ from junifer.data import (
19
20
  load_data,
20
21
  register_data,
21
22
  )
22
- from junifer.data.parcellations import merge_parcellations
23
+ from junifer.data.parcellations import (
24
+ ParcellationRegistry,
25
+ merge_parcellations,
26
+ )
23
27
  from junifer.data.parcellations._parcellations import (
24
28
  _retrieve_aicha,
25
29
  _retrieve_brainnetome,
@@ -38,6 +42,9 @@ from junifer.testing.datagrabbers import (
38
42
  )
39
43
 
40
44
 
45
+ pytestmark = pytest.mark.external
46
+
47
+
41
48
  def test_register_built_in_check() -> None:
42
49
  """Test parcellation registration check for built-in parcellations."""
43
50
  with pytest.raises(ValueError, match=r"built-in parcellation"):
@@ -1032,6 +1039,120 @@ def test_retrieve_julich_brain_incorrect_version() -> None:
1032
1039
  )
1033
1040
 
1034
1041
 
1042
+ # Parcellations whose labels are only numbers
1043
+ NUMBERED_PARCELLATIONS = ("Shen_2015_268", "Shen_2019_368")
1044
+
1045
+
1046
+ @pytest.mark.parametrize("name", ParcellationRegistry().list)
1047
+ def test_built_in_labels(name: str) -> None:
1048
+ """Test built-in parcellations have one unique label per region.
1049
+
1050
+ Parameters
1051
+ ----------
1052
+ name : str
1053
+ The parametrized parcellation name.
1054
+
1055
+ """
1056
+ space = ParcellationRegistry()._registry[name]["space"]
1057
+ img, labels, _, _ = load_data(
1058
+ kind="parcellation",
1059
+ name=name,
1060
+ target_space=space,
1061
+ # Lower resolution to make it faster; Shen 2019 is only in 1mm
1062
+ resolution=1 if name == "Shen_2019_368" else 2,
1063
+ )
1064
+ values = np.unique(np.asarray(img.dataobj))
1065
+ assert len(labels) == np.count_nonzero(values)
1066
+ assert len(set(labels)) == len(labels)
1067
+ if name not in NUMBERED_PARCELLATIONS:
1068
+ assert all(isinstance(x, str) and x for x in labels)
1069
+
1070
+
1071
+ @pytest.mark.parametrize(
1072
+ "name, value, label",
1073
+ [
1074
+ # Labels are read from look-up tables
1075
+ ("Yan100xYeo7", 1, "7networks_LH_Default_FPole"),
1076
+ ("Yan1000xKong17", 1000, "17networks_RH_VisualA_TempOcc_8"),
1077
+ # The label file is sorted by name, not by value
1078
+ ("Shen_2013_100", 1, "R.BA40.3"),
1079
+ ("Shen_2013_150", 1, "R.BA6.9"),
1080
+ # Repeated names are numbered
1081
+ ("Shen_2013_50", 1, "No_BA_region-1"),
1082
+ # Odd values are in the left hemisphere, even in the right one
1083
+ ("Brainnetome_thr0", 1, "SFG_L_7_1"),
1084
+ ("Brainnetome_thr0", 2, "SFG_R_7_1"),
1085
+ ("Brainnetome_thr0", 246, "Tha_R_8_8"),
1086
+ # Labels are the region names, ordered by value
1087
+ ("Julich-Brain_V1_18", 1, "Area 45 (IFG) right"),
1088
+ ("Julich-Brain_V3_0_3", 1, "Area 45 (IFG) left"),
1089
+ ],
1090
+ )
1091
+ def test_labels_by_value(name: str, value: int, label: str) -> None:
1092
+ """Test built-in parcellation labels match their values.
1093
+
1094
+ Parameters
1095
+ ----------
1096
+ name : str
1097
+ The parametrized parcellation name.
1098
+ value : int
1099
+ The parametrized value of the region in the parcellation.
1100
+ label : str
1101
+ The parametrized expected label of the region.
1102
+
1103
+ """
1104
+ space = ParcellationRegistry()._registry[name]["space"]
1105
+ img, labels, _, _ = load_data(
1106
+ kind="parcellation",
1107
+ name=name,
1108
+ target_space=space,
1109
+ )
1110
+ # Labels are in the order of the (sorted) values in the image
1111
+ values = np.unique(np.asarray(img.dataobj).astype(int))
1112
+ values = values[values != 0].tolist()
1113
+ assert labels[values.index(value)] == label
1114
+
1115
+
1116
+ def test_get_lost_regions(tmp_path: Path) -> None:
1117
+ """Test regions lost in resampling keep their labels.
1118
+
1119
+ Parameters
1120
+ ----------
1121
+ tmp_path : pathlib.Path
1122
+ The path to the test directory.
1123
+
1124
+ """
1125
+ data = np.zeros((12, 12, 12), dtype=np.int16)
1126
+ data[:6] = 1
1127
+ # A single voxel, not sampled by the 3mm grid (voxels 0, 3, 6 and 9)
1128
+ data[7, 7, 7] = 2
1129
+ data[8:] = 3
1130
+ parcellation_path = tmp_path / "lost.nii.gz"
1131
+ nib.save(nib.Nifti1Image(data, np.eye(4)), parcellation_path)
1132
+ register_data(
1133
+ kind="parcellation",
1134
+ name="LostRegion",
1135
+ parcellation_path=parcellation_path,
1136
+ parcels_labels=["a", "b", "c"],
1137
+ space="MNI152NLin6Asym",
1138
+ )
1139
+ target = {
1140
+ "data": nib.Nifti1Image(
1141
+ np.zeros((4, 4, 4)), np.diag([3.0, 3.0, 3.0, 1.0])
1142
+ ),
1143
+ "space": "MNI152NLin6Asym",
1144
+ }
1145
+ with pytest.warns(RuntimeWarning, match=r"1 region\(s\) .* \['b'\]"):
1146
+ img, labels = get_data(
1147
+ kind="parcellation", names="LostRegion", target_data=target
1148
+ )
1149
+ deregister_data(kind="parcellation", name="LostRegion")
1150
+ # The lost region is not in the image but keeps its label, and the
1151
+ # following region keeps its own label
1152
+ assert 2 not in np.unique(img.get_fdata())
1153
+ assert labels == {1: "a", 2: "b", 3: "c"}
1154
+
1155
+
1035
1156
  def test_merge_parcellations() -> None:
1036
1157
  """Test merging parcellations."""
1037
1158
  # load some parcellations for testing
@@ -3,29 +3,51 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ import re
7
+ from functools import cache
6
8
  from pathlib import Path
7
- from typing import Any, Optional, Union
9
+ from typing import Any, Union
8
10
 
9
11
  import nibabel as nib
10
12
  import numpy as np
13
+ import structlog
11
14
  from junifer_data import get
12
15
  from templateflow import api as tflow
13
16
 
14
- from ..utils import logger, raise_error
17
+ from ..typing import SpaceLike
18
+ from ..utils import raise_error
15
19
  from .utils import JUNIFER_DATA_PARAMS, closest_resolution, get_dataset_path
16
20
 
17
21
 
18
22
  __all__ = ["get_template", "get_xfm"]
19
23
 
24
+ _log = structlog.get_logger("junifer")
25
+ logger = _log.bind(pkg="data")
20
26
 
21
- def get_xfm(src: str, dst: str) -> Path: # pragma: no cover
27
+
28
+ @cache
29
+ def _get_templateflow_templates() -> frozenset[str]:
30
+ """Get the available templateflow template spaces.
31
+
32
+ Querying templateflow scans its whole layout, so the result is cached.
33
+
34
+ Returns
35
+ -------
36
+ frozenset of str
37
+ The available template spaces.
38
+
39
+ """
40
+ return frozenset(tflow.templates())
41
+
42
+
43
+ def get_xfm(src: SpaceLike, dst: SpaceLike) -> Path: # pragma: no cover
22
44
  """Fetch warp files to convert from ``src`` to ``dst``.
23
45
 
24
46
  Parameters
25
47
  ----------
26
- src : str
48
+ src : str or Enum
27
49
  The template space to transform from.
28
- dst : str
50
+ dst : str or Enum
29
51
  The template space to transform to.
30
52
 
31
53
  Returns
@@ -34,6 +56,9 @@ def get_xfm(src: str, dst: str) -> Path: # pragma: no cover
34
56
  The path to the transformation file.
35
57
 
36
58
  """
59
+ # Normalise enum-based spaces (e.g. `AOMICSpace`) to their values
60
+ src = getattr(src, "value", src)
61
+ dst = getattr(dst, "value", dst)
37
62
  # Set file path to retrieve
38
63
  xfm_file_path = Path(f"xfms/{src}_to_{dst}/{src}_to_{dst}_Composite.h5")
39
64
  # Retrieve file
@@ -44,18 +69,57 @@ def get_xfm(src: str, dst: str) -> Path: # pragma: no cover
44
69
  )
45
70
 
46
71
 
72
+ def _get_template_resolutions(
73
+ space: str, entities: dict[str, str | None]
74
+ ) -> dict[int, float]:
75
+ """Get the resolutions of a template that have the given files.
76
+
77
+ Not every resolution of a template has every file (e.g., in
78
+ templateflow 25, MNI152NLin2009cAsym lists resolutions 3 and 4, but has
79
+ no brain mask for them), and the resolution index is not the voxel size
80
+ (e.g., resolution 3 of MNI152NLin6Asym is 0.5mm).
81
+
82
+ Parameters
83
+ ----------
84
+ space : str
85
+ The name of the template space.
86
+ entities : dict
87
+ The templateflow entities of the files (e.g., ``suffix``).
88
+
89
+ Returns
90
+ -------
91
+ dict
92
+ The voxel size (in mm) of each resolution index with files.
93
+
94
+ """
95
+ # The voxel size of each resolution index. Indices are compared as
96
+ # integers, as file names can use "1" while metadata uses "01"
97
+ voxel_sizes = {
98
+ int(index): float(min(info["zooms"]))
99
+ for index, info in tflow.get_metadata(space)["res"].items()
100
+ }
101
+ # List the files without downloading them and keep their resolutions
102
+ resolutions = {}
103
+ for path in tflow.ls(space, extension="nii.gz", **entities):
104
+ match = re.search(r"_res-(\d+)_", Path(path).name)
105
+ if match is not None and int(match.group(1)) in voxel_sizes:
106
+ index = int(match.group(1))
107
+ resolutions[index] = voxel_sizes[index]
108
+ return resolutions
109
+
110
+
47
111
  def get_template(
48
- space: str,
112
+ space: SpaceLike,
49
113
  target_img: nib.Nifti1Image,
50
- extra_input: Optional[dict[str, Any]] = None,
114
+ extra_input: dict[str, Any] | None = None,
51
115
  template_type: str = "T1w",
52
- resolution: Optional[Union[int, "str"]] = None,
116
+ resolution: Union[int, "str"] | None = None,
53
117
  ) -> nib.Nifti1Image:
54
118
  """Get template for the space, tailored for the target image.
55
119
 
56
120
  Parameters
57
121
  ----------
58
- space : str
122
+ space : str or Enum
59
123
  The name of the template space.
60
124
  target_img : Nifti1Image
61
125
  The corresponding image for which the template space will be loaded.
@@ -66,9 +130,9 @@ def get_template(
66
130
  template_type : {"T1w", "brain", "gm", "wm", "csf"}, optional
67
131
  The template type to retrieve (default "T1w").
68
132
  resolution : int or "highest", optional
69
- The resolution of the template to fetch. If None, the closest
70
- resolution to the target image is used (default None). If "highest",
71
- the highest resolution is used.
133
+ The resolution (voxel size in mm) of the template to fetch. If None,
134
+ the closest resolution to the target image is used (default None).
135
+ If "highest", the highest resolution is used.
72
136
 
73
137
  Returns
74
138
  -------
@@ -84,8 +148,10 @@ def get_template(
84
148
  If required template is not found.
85
149
 
86
150
  """
151
+ # Normalise enum-based spaces (e.g. `AOMICSpace`) to their values
152
+ space = getattr(space, "value", space)
87
153
  # Check for invalid space; early check to raise proper error
88
- if space not in tflow.templates():
154
+ if space not in _get_templateflow_templates():
89
155
  raise_error(f"Unknown template space: {space}")
90
156
 
91
157
  # Check for template type
@@ -97,60 +163,61 @@ def get_template(
97
163
  "Invalid resolution value. Must be an integer or 'highest'"
98
164
  )
99
165
 
100
- # Fetch available resolutions for the template
101
- available_resolutions = [
102
- int(min(val["zooms"]))
103
- for val in tflow.get_metadata(space)["res"].values()
104
- ]
166
+ # Get the templateflow entities of the template type
167
+ if template_type == "T1w":
168
+ entities = {"suffix": "T1w", "desc": None, "label": None}
169
+ elif template_type == "brain":
170
+ entities = {"suffix": "mask", "desc": "brain", "label": None}
171
+ else:
172
+ entities = {
173
+ "suffix": "probseg",
174
+ "desc": None,
175
+ "label": template_type.upper(),
176
+ }
105
177
 
106
- # Get the min of the voxels sizes and use it as the resolution
107
- if resolution is None:
108
- resolution = np.min(target_img.header.get_zooms()[:3]).astype(int)
109
- elif resolution == "highest":
110
- resolution = 0
178
+ # Get the resolutions that have the template type
179
+ resolutions = _get_template_resolutions(space, entities)
180
+ if not resolutions:
181
+ raise_error(
182
+ msg=f"Template {space} ({template_type}) not found",
183
+ klass=RuntimeError,
184
+ )
185
+
186
+ # Get the desired voxel size; None means the highest resolution
187
+ if resolution == "highest":
188
+ desired_voxel_size = None
189
+ elif resolution is None:
190
+ desired_voxel_size = float(np.min(target_img.header.get_zooms()[:3]))
191
+ else:
192
+ desired_voxel_size = resolution
111
193
 
112
- # Use the closest resolution if desired resolution is not found
113
- resolution = closest_resolution(resolution, available_resolutions)
194
+ # Use the closest voxel size if the desired one is not available
195
+ voxel_size = closest_resolution(
196
+ desired_voxel_size, list(resolutions.values())
197
+ )
198
+ # Get the resolution index of that voxel size
199
+ res_index = {size: index for index, size in resolutions.items()}[
200
+ voxel_size
201
+ ]
114
202
 
115
203
  logger.info(
116
204
  f"Downloading template {space} ({template_type} in "
117
- f"resolution {resolution})"
205
+ f"resolution {voxel_size}mm)"
118
206
  )
119
207
  # Retrieve template
120
208
  try:
121
- suffix = None
122
- desc = None
123
- label = None
124
- if template_type == "T1w":
125
- suffix = template_type
126
- desc = None
127
- label = None
128
- elif template_type == "brain":
129
- suffix = "mask"
130
- desc = "brain"
131
- label = None
132
- elif template_type in ["gm", "wm", "csf"]:
133
- suffix = "probseg"
134
- desc = None
135
- label = template_type.upper()
136
- # Set kwargs for fetching
137
- kwargs = {
138
- "suffix": suffix,
139
- "desc": desc,
140
- "label": label,
141
- }
142
209
  template_path = tflow.get(
143
210
  space,
144
211
  raise_empty=True,
145
- resolution=resolution,
212
+ resolution=res_index,
146
213
  extension="nii.gz",
147
- **kwargs,
214
+ **entities,
148
215
  )
149
216
  except Exception: # noqa: BLE001
150
217
  raise_error(
151
218
  msg=(
152
219
  f"Template {space} ({template_type}) with resolution "
153
- f"{resolution}) not found"
220
+ f"{voxel_size}mm not found"
154
221
  ),
155
222
  klass=RuntimeError,
156
223
  )
@@ -12,11 +12,14 @@ import pytest
12
12
  from junifer.data import get_template, get_xfm
13
13
  from junifer.datareader import DefaultDataReader
14
14
  from junifer.testing.datagrabbers import (
15
- OasisVBMTestingDataGrabber,
16
15
  PartlyCloudyTestingDataGrabber,
16
+ UCLACNPVBMTestingDataGrabber,
17
17
  )
18
18
 
19
19
 
20
+ pytestmark = pytest.mark.external
21
+
22
+
20
23
  @pytest.mark.skipif(
21
24
  socket.gethostname() != "juseless",
22
25
  reason="only for juseless",
@@ -70,8 +73,8 @@ def test_get_template(template_type: str) -> None:
70
73
 
71
74
  def test_get_template_invalid_space() -> None:
72
75
  """Test invalid space check for template fetch."""
73
- with OasisVBMTestingDataGrabber() as dg:
74
- element = dg["sub-01"]
76
+ with UCLACNPVBMTestingDataGrabber() as dg:
77
+ element = dg["sub-10206"]
75
78
  element_data = DefaultDataReader().fit_transform(element)
76
79
  vbm_gm = element_data["VBM_GM"]
77
80
  # Get tailored parcellation
@@ -81,8 +84,8 @@ def test_get_template_invalid_space() -> None:
81
84
 
82
85
  def test_get_template_invalid_template_type() -> None:
83
86
  """Test invalid template type check for template fetch."""
84
- with OasisVBMTestingDataGrabber() as dg:
85
- element = dg["sub-01"]
87
+ with UCLACNPVBMTestingDataGrabber() as dg:
88
+ element = dg["sub-10206"]
86
89
  element_data = DefaultDataReader().fit_transform(element)
87
90
  vbm_gm = element_data["VBM_GM"]
88
91
  # Get tailored parcellation
@@ -96,8 +99,8 @@ def test_get_template_invalid_template_type() -> None:
96
99
 
97
100
  def test_get_template_closest_resolution() -> None:
98
101
  """Test closest resolution check for template fetch."""
99
- with OasisVBMTestingDataGrabber() as dg:
100
- element = dg["sub-01"]
102
+ with UCLACNPVBMTestingDataGrabber() as dg:
103
+ element = dg["sub-10206"]
101
104
  element_data = DefaultDataReader().fit_transform(element)
102
105
  vbm_gm = element_data["VBM_GM"]
103
106
  # Change header resolution to fetch closest resolution
junifer/data/utils.py CHANGED
@@ -6,11 +6,11 @@
6
6
 
7
7
  from collections.abc import MutableMapping
8
8
  from pathlib import Path
9
- from typing import Optional, Union
10
9
 
11
10
  import numpy as np
11
+ import structlog
12
12
 
13
- from ..utils import config, logger, raise_error
13
+ from ..utils import config, raise_error
14
14
 
15
15
 
16
16
  __all__ = [
@@ -22,12 +22,14 @@ __all__ = [
22
22
  "get_native_warper",
23
23
  ]
24
24
 
25
+ _log = structlog.get_logger("junifer")
26
+ logger = _log.bind(pkg="data")
25
27
 
26
28
  # junifer-data version constant
27
- JUNIFER_DATA_VERSION = "7"
29
+ JUNIFER_DATA_VERSION = "8"
28
30
 
29
31
  # junifer-data hexsha constant
30
- JUNIFER_DATA_HEXSHA = "f5144e6fef7d6f4f26508c3653d94a622f10207c"
32
+ JUNIFER_DATA_HEXSHA = "c03636438bb1345299c7b79bd6428e25035102c5"
31
33
 
32
34
  JUNIFER_DATA_PARAMS = {
33
35
  "tag": JUNIFER_DATA_VERSION,
@@ -36,9 +38,9 @@ JUNIFER_DATA_PARAMS = {
36
38
 
37
39
 
38
40
  def closest_resolution(
39
- resolution: Optional[Union[float, int]],
40
- valid_resolution: Union[list[float], list[int], np.ndarray],
41
- ) -> Union[float, int]:
41
+ resolution: float | int | None,
42
+ valid_resolution: list[float] | list[int] | np.ndarray,
43
+ ) -> float | int:
42
44
  """Find the closest resolution.
43
45
 
44
46
  Parameters
@@ -136,7 +138,7 @@ def get_native_warper(
136
138
  return possible_warpers[0]
137
139
 
138
140
 
139
- def get_dataset_path() -> Optional[Path]:
141
+ def get_dataset_path() -> Path | None:
140
142
  """Get junifer-data dataset path.
141
143
 
142
144
  Returns
@@ -3,30 +3,63 @@ __all__ = [
3
3
  "DataladDataGrabber",
4
4
  "PatternDataGrabber",
5
5
  "PatternDataladDataGrabber",
6
+ "AOMICSpace",
7
+ "AOMICTask",
6
8
  "DataladAOMICID1000",
7
9
  "DataladAOMICPIOP1",
8
10
  "DataladAOMICPIOP2",
9
11
  "HCP1200",
12
+ "HCP1200Task",
13
+ "HCP1200PhaseEncoding",
10
14
  "DataladHCP1200",
11
15
  "MultipleDataGrabber",
12
16
  "DMCC13Benchmark",
17
+ "DMCCSession",
18
+ "DMCCTask",
19
+ "DMCCPhaseEncoding",
20
+ "DMCCRun",
13
21
  "DataTypeManager",
14
22
  "DataTypeSchema",
15
23
  "OptionalTypeSchema",
16
24
  "PatternValidationMixin",
17
25
  "register_data_type",
26
+ "DataType",
27
+ "ConfoundsFormat",
28
+ "register_confounds_format",
29
+ "logger",
18
30
  ]
19
31
 
20
32
  # These 4 need to be in this order, otherwise it is a circular import
21
- from .base import BaseDataGrabber
33
+ from .base import BaseDataGrabber, DataType, logger
22
34
  from .datalad_base import DataladDataGrabber
23
- from .pattern import PatternDataGrabber
35
+ from .pattern import (
36
+ PatternDataGrabber,
37
+ ConfoundsFormat,
38
+ register_confounds_format,
39
+ )
24
40
  from .pattern_datalad import PatternDataladDataGrabber
25
41
 
26
- from .aomic import DataladAOMICID1000, DataladAOMICPIOP1, DataladAOMICPIOP2
27
- from .hcp1200 import HCP1200, DataladHCP1200
42
+ from .aomic import (
43
+ AOMICSpace,
44
+ AOMICTask,
45
+ DataladAOMICID1000,
46
+ DataladAOMICPIOP1,
47
+ DataladAOMICPIOP2,
48
+ )
49
+ from .hcp1200 import (
50
+ HCP1200,
51
+ HCP1200Task,
52
+ HCP1200PhaseEncoding,
53
+ DataladHCP1200,
54
+ )
28
55
  from .multiple import MultipleDataGrabber
29
- from .dmcc13_benchmark import DMCC13Benchmark
56
+ from .dmcc13_benchmark import (
57
+ DMCC13Benchmark,
58
+ DMCCSession,
59
+ DMCCTask,
60
+ DMCCPhaseEncoding,
61
+ DMCCRun,
62
+ )
30
63
 
31
64
  from .pattern_validation_mixin import (
32
65
  DataTypeManager,
@@ -1,5 +1,12 @@
1
- __all__ = ["DataladAOMICID1000", "DataladAOMICPIOP1", "DataladAOMICPIOP2"]
1
+ __all__ = [
2
+ "AOMICSpace",
3
+ "AOMICTask",
4
+ "DataladAOMICID1000",
5
+ "DataladAOMICPIOP1",
6
+ "DataladAOMICPIOP2",
7
+ ]
2
8
 
9
+ from ._types import AOMICSpace, AOMICTask
3
10
  from .id1000 import DataladAOMICID1000
4
11
  from .piop1 import DataladAOMICPIOP1
5
12
  from .piop2 import DataladAOMICPIOP2
@@ -0,0 +1,22 @@
1
+ """Provide common types for AOMIC DataGrabbers."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class AOMICSpace(StrEnum):
7
+ """Accepted spaces for AOMIC."""
8
+
9
+ Native = "native"
10
+ MNI152NLin2009cAsym = "MNI152NLin2009cAsym"
11
+
12
+
13
+ class AOMICTask(StrEnum):
14
+ """Accepted tasks for AOMIC."""
15
+
16
+ RestingState = "restingstate"
17
+ Anticipation = "anticipation"
18
+ EmoMatching = "emomatching"
19
+ Faces = "faces"
20
+ Gstroop = "gstroop"
21
+ WorkingMemory = "workingmemory"
22
+ StopSignal = "stopsignal"