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,12 +4,13 @@
4
4
  # License: AGPL
5
5
 
6
6
  import uuid
7
- from typing import TYPE_CHECKING, Any, Optional
7
+ from typing import TYPE_CHECKING, Any
8
8
 
9
9
  import nibabel as nib
10
10
 
11
11
  from ...pipeline import WorkDirManager
12
- from ...utils import logger, raise_error, run_ext_cmd
12
+ from ...typing import SpaceLike
13
+ from ...utils import raise_error, run_ext_cmd
13
14
  from ..template_spaces import get_template, get_xfm
14
15
 
15
16
 
@@ -31,10 +32,10 @@ class ANTsParcellationWarper:
31
32
  self,
32
33
  parcellation_name: str,
33
34
  parcellation_img: "Nifti1Image",
34
- src: str,
35
- dst: str,
35
+ src: SpaceLike,
36
+ dst: SpaceLike,
36
37
  target_data: dict[str, Any],
37
- warp_data: Optional[dict[str, Any]],
38
+ warp_data: dict[str, Any] | None,
38
39
  ) -> "Nifti1Image":
39
40
  """Warp ``parcellation_img`` to correct space.
40
41
 
@@ -44,10 +45,10 @@ class ANTsParcellationWarper:
44
45
  The name of the parcellation.
45
46
  parcellation_img : nibabel.nifti1.Nifti1Image
46
47
  The parcellation image to transform.
47
- src : str
48
+ src : str or Enum
48
49
  The data type or template space to warp from.
49
50
  It should be empty string if ``dst="T1w"``.
50
- dst : str
51
+ dst : str or Enum
51
52
  The data type or template space to warp to.
52
53
  `"T1w"` is the only allowed data type and it uses the resampled T1w
53
54
  found in ``target_data.reference``. The ``"reference"``
@@ -71,6 +72,9 @@ class ANTsParcellationWarper:
71
72
  If ``warp_data`` is None when ``dst="T1w"``.
72
73
 
73
74
  """
75
+ # Imported here to avoid circular import
76
+ from ._parcellations import logger
77
+
74
78
  # Create element-scoped tempdir so that warped parcellation is
75
79
  # available later as nibabel stores file path reference for
76
80
  # loading on computation
@@ -97,19 +101,20 @@ class ANTsParcellationWarper:
97
101
 
98
102
  # Save existing parcellation image to a tempfile
99
103
  prewarp_parcellation_path = (
100
- element_tempdir / "prewarp_parcellation.nii.gz"
104
+ element_tempdir / "prewarp_parcellation.nii"
101
105
  )
102
106
  nib.save(parcellation_img, prewarp_parcellation_path)
103
107
 
104
108
  # Create a tempfile for warped output
105
109
  warped_parcellation_path = (
106
- element_tempdir / "parcellation_warped.nii.gz"
110
+ element_tempdir / "parcellation_warped.nii"
107
111
  )
108
112
  # Set antsApplyTransforms command
109
113
  apply_transforms_cmd = [
110
114
  "antsApplyTransforms",
111
115
  "-d 3",
112
116
  "-e 3",
117
+ "--float 1",
113
118
  "-n 'GenericLabel[NearestNeighbor]'",
114
119
  f"-i {prewarp_parcellation_path.resolve()}",
115
120
  # use resampled reference
@@ -135,24 +140,25 @@ class ANTsParcellationWarper:
135
140
  extra_input=None,
136
141
  )
137
142
  # Save template to a tempfile
138
- template_space_img_path = element_tempdir / f"{dst}_T1w.nii.gz"
143
+ template_space_img_path = element_tempdir / f"{dst}_T1w.nii"
139
144
  nib.save(template_space_img, template_space_img_path)
140
145
 
141
146
  # Save existing parcellation image to a tempfile
142
147
  prewarp_parcellation_path = (
143
- element_tempdir / "prewarp_parcellation.nii.gz"
148
+ element_tempdir / "prewarp_parcellation.nii"
144
149
  )
145
150
  nib.save(parcellation_img, prewarp_parcellation_path)
146
151
 
147
152
  # Create a tempfile for warped output
148
153
  warped_parcellation_path = (
149
- element_tempdir / "parcellation_warped.nii.gz"
154
+ element_tempdir / "parcellation_warped.nii"
150
155
  )
151
156
  # Set antsApplyTransforms command
152
157
  apply_transforms_cmd = [
153
158
  "antsApplyTransforms",
154
159
  "-d 3",
155
160
  "-e 3",
161
+ "--float 1",
156
162
  "-n 'GenericLabel[NearestNeighbor]'",
157
163
  f"-i {prewarp_parcellation_path.resolve()}",
158
164
  f"-r {template_space_img_path.resolve()}",
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any
9
9
  import nibabel as nib
10
10
 
11
11
  from ...pipeline import WorkDirManager
12
- from ...utils import logger, run_ext_cmd
12
+ from ...utils import run_ext_cmd
13
13
 
14
14
 
15
15
  if TYPE_CHECKING:
@@ -53,6 +53,9 @@ class FSLParcellationWarper:
53
53
  The transformed parcellation image.
54
54
 
55
55
  """
56
+ # Imported here to avoid circular import
57
+ from ._parcellations import logger
58
+
56
59
  logger.debug("Using FSL for parcellation transformation")
57
60
 
58
61
  # Create element-scoped tempdir so that warped parcellation is
@@ -64,16 +67,15 @@ class FSLParcellationWarper:
64
67
 
65
68
  # Save existing parcellation image to a tempfile
66
69
  prewarp_parcellation_path = (
67
- element_tempdir / "prewarp_parcellation.nii.gz"
70
+ element_tempdir / "prewarp_parcellation.nii"
68
71
  )
69
72
  nib.save(parcellation_img, prewarp_parcellation_path)
70
73
 
71
74
  # Create a tempfile for warped output
72
- warped_parcellation_path = (
73
- element_tempdir / "parcellation_warped.nii.gz"
74
- )
75
+ warped_parcellation_path = element_tempdir / "parcellation_warped.nii"
75
76
  # Set applywarp command
76
77
  applywarp_cmd = [
78
+ "FSLOUTPUTTYPE=NIFTI",
77
79
  "applywarp",
78
80
  "--interp=nn",
79
81
  f"-i {prewarp_parcellation_path.resolve()}",
@@ -5,17 +5,19 @@
5
5
  # Synchon Mandal <s.mandal@fz-juelich.de>
6
6
  # License: AGPL
7
7
 
8
+ from collections import Counter
8
9
  from itertools import product
9
10
  from pathlib import Path
10
- from typing import TYPE_CHECKING, Any, Optional, Union
11
+ from typing import TYPE_CHECKING, Any, Optional
11
12
 
12
13
  import nibabel as nib
13
14
  import nilearn.image as nimg
14
15
  import numpy as np
15
16
  import pandas as pd
17
+ import structlog
16
18
  from junifer_data import get
17
19
 
18
- from ...utils import logger, raise_error, warn_with_log
20
+ from ...utils import raise_error, warn_with_log
19
21
  from ..pipeline_data_registry_base import BasePipelineDataRegistry
20
22
  from ..utils import (
21
23
  JUNIFER_DATA_PARAMS,
@@ -36,6 +38,9 @@ __all__ = [
36
38
  "merge_parcellations",
37
39
  ]
38
40
 
41
+ _log = structlog.get_logger("junifer")
42
+ logger = _log.bind(pkg="data")
43
+
39
44
 
40
45
  class ParcellationRegistry(BasePipelineDataRegistry):
41
46
  """Class for parcellation data registry.
@@ -58,8 +63,12 @@ class ParcellationRegistry(BasePipelineDataRegistry):
58
63
  # The built-in parcellations are files that are shipped with the
59
64
  # junifer-data dataset.
60
65
  # Make built-in and external dictionaries for validation later
61
- self._builtin = {}
62
- self._external = {}
66
+ self._builtin: dict[
67
+ str, dict[str, str | int | Path | list[str] | list[int]]
68
+ ] = {}
69
+ self._external: dict[
70
+ str, dict[str, str | int | Path | list[str] | list[int]]
71
+ ] = {}
63
72
 
64
73
  # Add SUIT
65
74
  self._builtin.update(
@@ -226,7 +235,7 @@ class ParcellationRegistry(BasePipelineDataRegistry):
226
235
  def register(
227
236
  self,
228
237
  name: str,
229
- parcellation_path: Union[str, Path],
238
+ parcellation_path: str | Path,
230
239
  parcels_labels: list[str],
231
240
  space: str,
232
241
  overwrite: bool = False,
@@ -309,7 +318,7 @@ class ParcellationRegistry(BasePipelineDataRegistry):
309
318
  self,
310
319
  name: str,
311
320
  target_space: str,
312
- resolution: Optional[float] = None,
321
+ resolution: float | None = None,
313
322
  path_only: bool = False,
314
323
  ) -> tuple[Optional["Nifti1Image"], list[str], Path, str]:
315
324
  """Load parcellation and labels.
@@ -463,9 +472,9 @@ class ParcellationRegistry(BasePipelineDataRegistry):
463
472
 
464
473
  def get(
465
474
  self,
466
- parcellations: Union[str, list[str]],
475
+ parcellations: str | list[str],
467
476
  target_data: dict[str, Any],
468
- extra_input: Optional[dict[str, Any]] = None,
477
+ extra_input: dict[str, Any] | None = None,
469
478
  ) -> tuple["Nifti1Image", dict[int, str]]:
470
479
  """Get parcellation, tailored for the target image.
471
480
 
@@ -539,6 +548,17 @@ class ParcellationRegistry(BasePipelineDataRegistry):
539
548
  resolution=resolution,
540
549
  target_space=target_space,
541
550
  )
551
+ # Map the labels to the values before resampling or warping,
552
+ # as small regions can be lost in the process
553
+ value_labels = dict(
554
+ zip(
555
+ np.trim_zeros(
556
+ np.unique(np.asarray(img.dataobj).astype(int))
557
+ ).tolist(),
558
+ labels,
559
+ strict=True,
560
+ )
561
+ )
542
562
 
543
563
  # Convert parcellation spaces if required;
544
564
  # cannot be "native" due to earlier check
@@ -594,22 +614,27 @@ class ParcellationRegistry(BasePipelineDataRegistry):
594
614
  warp_data=warper_spec,
595
615
  )
596
616
 
617
+ # Keep the lost regions, so all elements have the same regions
618
+ present = set(
619
+ np.unique(np.asarray(img.dataobj).astype(int)).tolist()
620
+ )
621
+ lost = [
622
+ label
623
+ for value, label in value_labels.items()
624
+ if value not in present
625
+ ]
626
+ if lost:
627
+ warn_with_log(
628
+ f"{len(lost)} region(s) of {name} have no voxels in the "
629
+ f"target image, so they will have no data: {lost}"
630
+ )
597
631
  all_parcellations.append(img)
598
- all_labels.append(labels)
632
+ all_labels.append(value_labels)
599
633
 
600
634
  # Avoid merging if there is only one parcellation
601
635
  if len(all_parcellations) == 1:
602
636
  resampled_parcellation_img = all_parcellations[0]
603
- labels = dict(
604
- zip(
605
- np.trim_zeros(
606
- np.unique(
607
- resampled_parcellation_img.get_fdata().astype(int)
608
- )
609
- ),
610
- all_labels[0],
611
- )
612
- )
637
+ labels = all_labels[0]
613
638
  # Parcellations are already transformed to target standard space
614
639
  else:
615
640
  logger.debug("Merging parcellations.")
@@ -623,8 +648,8 @@ class ParcellationRegistry(BasePipelineDataRegistry):
623
648
 
624
649
 
625
650
  def _retrieve_schaefer(
626
- resolution: Optional[float] = None,
627
- n_rois: Optional[int] = None,
651
+ resolution: float | None = None,
652
+ n_rois: int | None = None,
628
653
  yeo_networks: int = 7,
629
654
  ) -> tuple[Path, list[str]]:
630
655
  """Retrieve Schaefer parcellation.
@@ -707,8 +732,8 @@ def _retrieve_schaefer(
707
732
 
708
733
 
709
734
  def _retrieve_tian(
710
- resolution: Optional[float] = None,
711
- scale: Optional[int] = None,
735
+ resolution: float | None = None,
736
+ scale: int | None = None,
712
737
  space: str = "MNI152NLin6Asym",
713
738
  magneticfield: str = "3T",
714
739
  ) -> tuple[Path, list[str]]:
@@ -847,7 +872,7 @@ def _retrieve_tian(
847
872
 
848
873
 
849
874
  def _retrieve_suit(
850
- resolution: Optional[float],
875
+ resolution: float | None,
851
876
  space: str = "MNI152NLin6Asym",
852
877
  ) -> tuple[Path, list[str]]:
853
878
  """Retrieve SUIT parcellation.
@@ -919,7 +944,7 @@ def _retrieve_suit(
919
944
 
920
945
 
921
946
  def _retrieve_aicha(
922
- resolution: Optional[float] = None,
947
+ resolution: float | None = None,
923
948
  version: int = 2,
924
949
  ) -> tuple[Path, list[str]]:
925
950
  """Retrieve AICHA parcellation.
@@ -1013,7 +1038,7 @@ def _retrieve_aicha(
1013
1038
 
1014
1039
 
1015
1040
  def _retrieve_shen(
1016
- resolution: Optional[float] = None,
1041
+ resolution: float | None = None,
1017
1042
  year: int = 2015,
1018
1043
  n_rois: int = 268,
1019
1044
  ) -> tuple[Path, list[str]]:
@@ -1109,16 +1134,29 @@ def _retrieve_shen(
1109
1134
  dataset_path=get_dataset_path(),
1110
1135
  **JUNIFER_DATA_PARAMS,
1111
1136
  )
1112
- labels = (
1137
+ # The rows are sorted by name, so order them by the parcel value
1138
+ # (first column) to match the parcellation image
1139
+ names = (
1113
1140
  pd.read_csv(
1114
1141
  parcellation_label_path,
1115
1142
  sep=",",
1116
1143
  header=None,
1117
1144
  skiprows=[0],
1118
- )[1]
1145
+ )
1146
+ .sort_values(0)[1]
1119
1147
  .map(lambda x: x.strip()) # fix formatting
1120
1148
  .to_list()
1121
1149
  )
1150
+ # Regions outside Brodmann areas share a name ("No_BA_region"), so
1151
+ # number every occurrence of repeated names
1152
+ counts = Counter(names)
1153
+ seen: Counter = Counter()
1154
+ labels = []
1155
+ for name in names:
1156
+ if counts[name] > 1:
1157
+ seen[name] += 1
1158
+ name = f"{name}-{seen[name]}"
1159
+ labels.append(name)
1122
1160
  elif year == 2015:
1123
1161
  parcellation_img_path = get(
1124
1162
  file_path=path_prefix
@@ -1139,10 +1177,10 @@ def _retrieve_shen(
1139
1177
 
1140
1178
 
1141
1179
  def _retrieve_yan(
1142
- resolution: Optional[float] = None,
1143
- n_rois: Optional[int] = None,
1144
- yeo_networks: Optional[int] = None,
1145
- kong_networks: Optional[int] = None,
1180
+ resolution: float | None = None,
1181
+ n_rois: int | None = None,
1182
+ yeo_networks: int | None = None,
1183
+ kong_networks: int | None = None,
1146
1184
  ) -> tuple[Path, list[str]]:
1147
1185
  """Retrieve Yan parcellation.
1148
1186
 
@@ -1255,8 +1293,9 @@ def _retrieve_yan(
1255
1293
  **JUNIFER_DATA_PARAMS,
1256
1294
  )
1257
1295
 
1258
- # Load label file
1259
- labels = pd.read_csv(parcellation_label_path, sep=" ", header=None)[
1296
+ # Load label file; the index is right-aligned with spaces, so split on
1297
+ # any whitespace
1298
+ labels = pd.read_csv(parcellation_label_path, sep=r"\s+", header=None)[
1260
1299
  1
1261
1300
  ].to_list()
1262
1301
 
@@ -1264,8 +1303,8 @@ def _retrieve_yan(
1264
1303
 
1265
1304
 
1266
1305
  def _retrieve_brainnetome(
1267
- resolution: Optional[float] = None,
1268
- threshold: Optional[int] = None,
1306
+ resolution: float | None = None,
1307
+ threshold: int | None = None,
1269
1308
  ) -> tuple[Path, list[str]]:
1270
1309
  """Retrieve Brainnetome parcellation.
1271
1310
 
@@ -1322,40 +1361,23 @@ def _retrieve_brainnetome(
1322
1361
  **JUNIFER_DATA_PARAMS,
1323
1362
  )
1324
1363
 
1325
- # Load labels
1364
+ # Load labels (one row per label, ordered by label value)
1365
+ parcellation_label_path = get(
1366
+ file_path=Path("parcellations/Brainnetome/labels.csv"),
1367
+ dataset_path=get_dataset_path(),
1368
+ **JUNIFER_DATA_PARAMS,
1369
+ )
1326
1370
  labels = (
1327
- sorted([f"SFG_L(R)_7_{i}" for i in range(1, 8)] * 2)
1328
- + sorted([f"MFG_L(R)_7_{i}" for i in range(1, 8)] * 2)
1329
- + sorted([f"IFG_L(R)_6_{i}" for i in range(1, 7)] * 2)
1330
- + sorted([f"OrG_L(R)_6_{i}" for i in range(1, 7)] * 2)
1331
- + sorted([f"PrG_L(R)_6_{i}" for i in range(1, 7)] * 2)
1332
- + sorted([f"PCL_L(R)_2_{i}" for i in range(1, 3)] * 2)
1333
- + sorted([f"STG_L(R)_6_{i}" for i in range(1, 7)] * 2)
1334
- + sorted([f"MTG_L(R)_4_{i}" for i in range(1, 5)] * 2)
1335
- + sorted([f"ITG_L(R)_7_{i}" for i in range(1, 8)] * 2)
1336
- + sorted([f"FuG_L(R)_3_{i}" for i in range(1, 4)] * 2)
1337
- + sorted([f"PhG_L(R)_6_{i}" for i in range(1, 7)] * 2)
1338
- + sorted([f"pSTS_L(R)_2_{i}" for i in range(1, 3)] * 2)
1339
- + sorted([f"SPL_L(R)_5_{i}" for i in range(1, 6)] * 2)
1340
- + sorted([f"IPL_L(R)_6_{i}" for i in range(1, 7)] * 2)
1341
- + sorted([f"PCun_L(R)_4_{i}" for i in range(1, 5)] * 2)
1342
- + sorted([f"PoG_L(R)_4_{i}" for i in range(1, 5)] * 2)
1343
- + sorted([f"INS_L(R)_6_{i}" for i in range(1, 7)] * 2)
1344
- + sorted([f"CG_L(R)_7_{i}" for i in range(1, 8)] * 2)
1345
- + sorted([f"MVOcC _L(R)_5_{i}" for i in range(1, 6)] * 2)
1346
- + sorted([f"LOcC_L(R)_4_{i}" for i in range(1, 5)] * 2)
1347
- + sorted([f"LOcC_L(R)_2_{i}" for i in range(1, 3)] * 2)
1348
- + sorted([f"Amyg_L(R)_2_{i}" for i in range(1, 3)] * 2)
1349
- + sorted([f"Hipp_L(R)_2_{i}" for i in range(1, 3)] * 2)
1350
- + sorted([f"BG_L(R)_6_{i}" for i in range(1, 7)] * 2)
1351
- + sorted([f"Tha_L(R)_8_{i}" for i in range(1, 9)] * 2)
1371
+ pd.read_csv(parcellation_label_path, sep=",")
1372
+ .sort_values("idx")["label"]
1373
+ .to_list()
1352
1374
  )
1353
1375
 
1354
1376
  return parcellation_img_path, labels
1355
1377
 
1356
1378
 
1357
1379
  def _retrieve_aseg(
1358
- resolution: Optional[float] = None,
1380
+ resolution: float | None = None,
1359
1381
  ) -> tuple[Path, list[str]]:
1360
1382
  """Retrieve aseg generated from FreeSurfer 7.4.1 .
1361
1383
 
@@ -1455,7 +1477,7 @@ def _retrieve_aseg(
1455
1477
 
1456
1478
 
1457
1479
  def _retrieve_glasser(
1458
- resolution: Optional[float] = None,
1480
+ resolution: float | None = None,
1459
1481
  ) -> tuple[Path, list[str]]:
1460
1482
  """Retrieve Glasser v1.0 .
1461
1483
 
@@ -1499,7 +1521,7 @@ def _retrieve_glasser(
1499
1521
 
1500
1522
 
1501
1523
  def _retrieve_julich_brain(
1502
- resolution: Optional[float] = None,
1524
+ resolution: float | None = None,
1503
1525
  version: str = "v3_1",
1504
1526
  ) -> tuple[Path, list[str]]:
1505
1527
  """Retrieve Julich-Brain labelled parcellations.
@@ -1554,7 +1576,12 @@ def _retrieve_julich_brain(
1554
1576
  dataset_path=get_dataset_path(),
1555
1577
  **JUNIFER_DATA_PARAMS,
1556
1578
  )
1557
- labels = pd.read_csv(parcellation_label_path, sep=",")["label"].to_list()
1579
+ # The "label" column is the value in the image and "region" the name
1580
+ labels = (
1581
+ pd.read_csv(parcellation_label_path, sep=",")
1582
+ .sort_values("label")["region"]
1583
+ .to_list()
1584
+ )
1558
1585
 
1559
1586
  return parcellation_img_path, labels
1560
1587
 
@@ -1562,7 +1589,7 @@ def _retrieve_julich_brain(
1562
1589
  def merge_parcellations(
1563
1590
  parcellations_list: list["Nifti1Image"],
1564
1591
  parcellations_names: list[str],
1565
- labels_lists: list[list[str]],
1592
+ labels_lists: list[list[str] | dict[int, str]],
1566
1593
  ) -> tuple["Nifti1Image", dict[int, str]]:
1567
1594
  """Merge multiple parcellations.
1568
1595
 
@@ -1572,9 +1599,12 @@ def merge_parcellations(
1572
1599
  List of parcellations to merge.
1573
1600
  parcellations_names: list of str
1574
1601
  List of names for parcellations at the corresponding indices.
1575
- labels_lists : list of list of str
1576
- A list of lists. Each list in the list contains the labels for the
1577
- parcellation at the corresponding index.
1602
+ labels_lists : list of list of str or dict
1603
+ The labels for the parcellation at the corresponding index, either
1604
+ as a mapping of value to label or as a list of labels for the
1605
+ (sorted) values in the parcellation. Use the mapping if regions
1606
+ might be missing from the parcellation (e.g., after resampling), so
1607
+ they keep their label.
1578
1608
 
1579
1609
  Returns
1580
1610
  -------
@@ -1585,39 +1615,42 @@ def merge_parcellations(
1585
1615
  Merged parcellation value to label mappings.
1586
1616
 
1587
1617
  """
1618
+ # Get the value to label mapping of each parcellation; lists are
1619
+ # matched to the values in the parcellation
1620
+ label_maps = []
1621
+ for parc, labels in zip(parcellations_list, labels_lists, strict=True):
1622
+ if isinstance(labels, dict):
1623
+ label_maps.append(dict(labels))
1624
+ else:
1625
+ values = np.trim_zeros(np.unique(parc.get_fdata().astype(int)))
1626
+ label_maps.append(dict(zip(values.tolist(), labels, strict=True)))
1627
+
1588
1628
  # Check for duplicated labels
1589
- labels_lists_flat = [item for sublist in labels_lists for item in sublist]
1590
- if len(labels_lists_flat) != len(set(labels_lists_flat)):
1629
+ labels_flat = [label for m in label_maps for label in m.values()]
1630
+ if len(labels_flat) != len(set(labels_flat)):
1591
1631
  warn_with_log(
1592
1632
  "The parcellations have duplicated labels. "
1593
1633
  "Each label will be prefixed with the parcellation name."
1594
1634
  )
1595
- for i_parcellation, t_labels in enumerate(labels_lists):
1596
- labels_lists[i_parcellation] = [
1597
- f"{parcellations_names[i_parcellation]}_{t_label}"
1598
- for t_label in t_labels
1599
- ]
1635
+ label_maps = [
1636
+ {value: f"{name}_{label}" for value, label in m.items()}
1637
+ for name, m in zip(parcellations_names, label_maps, strict=True)
1638
+ ]
1600
1639
  overlapping_voxels = False
1601
1640
  ref_parc = parcellations_list[0]
1602
1641
  ref_parc_data = ref_parc.get_fdata()
1603
1642
 
1604
- # Get max value for the parcellation ROIs to get a reference for ROI value
1605
- # increment
1606
- max_val = np.max(
1607
- np.concatenate(
1608
- [np.unique(p.get_fdata().astype(int)) for p in parcellations_list]
1609
- )
1643
+ # Get max value for the parcellation ROIs (including the ones that might
1644
+ # not be in the images) to get a reference for ROI value increment
1645
+ max_val = max(
1646
+ max(np.max(p.get_fdata().astype(int)) for p in parcellations_list),
1647
+ max(max(m, default=0) for m in label_maps),
1610
1648
  )
1611
1649
  # Setup parcellation value to label mapping
1612
- val_label_map = dict(
1613
- zip(
1614
- np.trim_zeros(np.unique(ref_parc_data.astype(int))),
1615
- labels_lists[0],
1616
- ),
1617
- )
1650
+ val_label_map = dict(label_maps[0])
1618
1651
 
1619
1652
  for idx, (parc, labs) in enumerate(
1620
- zip(parcellations_list[1:], labels_lists[1:])
1653
+ zip(parcellations_list[1:], label_maps[1:], strict=True)
1621
1654
  ):
1622
1655
  # Resample to reference (1st in the list) parcellation
1623
1656
  if parc.shape != ref_parc.shape:
@@ -1636,8 +1669,9 @@ def merge_parcellations(
1636
1669
  # Increase the values of each ROI to match the labels
1637
1670
  # and update label mapping
1638
1671
  parc_data[parc_data != 0] += (idx + 1) * max_val
1672
+ offset = (idx + 1) * max_val
1639
1673
  val_label_map.update(
1640
- dict(zip(np.trim_zeros(np.unique(parc_data.astype(int))), labs))
1674
+ {value + offset: label for value, label in labs.items()}
1641
1675
  )
1642
1676
  # Only set new values for the voxels that are 0
1643
1677
  # This makes sure that the voxels that are in multiple