junifer 0.0.5.dev240__py3-none-any.whl → 0.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. junifer/__init__.py +2 -31
  2. junifer/__init__.pyi +37 -0
  3. junifer/_version.py +9 -4
  4. junifer/api/__init__.py +3 -5
  5. junifer/api/__init__.pyi +4 -0
  6. junifer/api/decorators.py +14 -19
  7. junifer/api/functions.py +165 -109
  8. junifer/api/py.typed +0 -0
  9. junifer/api/queue_context/__init__.py +2 -4
  10. junifer/api/queue_context/__init__.pyi +5 -0
  11. junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
  12. junifer/api/queue_context/htcondor_adapter.py +23 -6
  13. junifer/api/queue_context/py.typed +0 -0
  14. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
  15. junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
  16. junifer/api/tests/test_functions.py +168 -74
  17. junifer/cli/__init__.py +24 -0
  18. junifer/cli/__init__.pyi +3 -0
  19. junifer/{api → cli}/cli.py +141 -125
  20. junifer/cli/parser.py +235 -0
  21. junifer/cli/py.typed +0 -0
  22. junifer/{api → cli}/tests/test_cli.py +8 -8
  23. junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
  24. junifer/{api → cli}/tests/test_parser.py +2 -2
  25. junifer/{api → cli}/utils.py +6 -16
  26. junifer/configs/juseless/__init__.py +2 -2
  27. junifer/configs/juseless/__init__.pyi +3 -0
  28. junifer/configs/juseless/datagrabbers/__init__.py +2 -12
  29. junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
  30. junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
  31. junifer/configs/juseless/datagrabbers/py.typed +0 -0
  32. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
  33. junifer/configs/juseless/datagrabbers/ucla.py +4 -4
  34. junifer/configs/juseless/py.typed +0 -0
  35. junifer/conftest.py +25 -0
  36. junifer/data/__init__.py +2 -42
  37. junifer/data/__init__.pyi +29 -0
  38. junifer/data/_dispatch.py +248 -0
  39. junifer/data/coordinates/__init__.py +9 -0
  40. junifer/data/coordinates/__init__.pyi +5 -0
  41. junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
  42. junifer/data/coordinates/_coordinates.py +385 -0
  43. junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
  44. junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
  45. junifer/data/masks/__init__.py +9 -0
  46. junifer/data/masks/__init__.pyi +6 -0
  47. junifer/data/masks/_ants_mask_warper.py +177 -0
  48. junifer/data/masks/_fsl_mask_warper.py +106 -0
  49. junifer/data/masks/_masks.py +802 -0
  50. junifer/data/{tests → masks/tests}/test_masks.py +67 -63
  51. junifer/data/parcellations/__init__.py +9 -0
  52. junifer/data/parcellations/__init__.pyi +6 -0
  53. junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
  54. junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
  55. junifer/data/parcellations/_parcellations.py +1388 -0
  56. junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
  57. junifer/data/pipeline_data_registry_base.py +76 -0
  58. junifer/data/py.typed +0 -0
  59. junifer/data/template_spaces.py +44 -79
  60. junifer/data/tests/test_data_utils.py +1 -2
  61. junifer/data/tests/test_template_spaces.py +8 -4
  62. junifer/data/utils.py +109 -4
  63. junifer/datagrabber/__init__.py +2 -26
  64. junifer/datagrabber/__init__.pyi +27 -0
  65. junifer/datagrabber/aomic/__init__.py +2 -4
  66. junifer/datagrabber/aomic/__init__.pyi +5 -0
  67. junifer/datagrabber/aomic/id1000.py +81 -52
  68. junifer/datagrabber/aomic/piop1.py +83 -55
  69. junifer/datagrabber/aomic/piop2.py +85 -56
  70. junifer/datagrabber/aomic/py.typed +0 -0
  71. junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
  72. junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
  73. junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
  74. junifer/datagrabber/base.py +22 -18
  75. junifer/datagrabber/datalad_base.py +71 -34
  76. junifer/datagrabber/dmcc13_benchmark.py +31 -18
  77. junifer/datagrabber/hcp1200/__init__.py +2 -3
  78. junifer/datagrabber/hcp1200/__init__.pyi +4 -0
  79. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
  80. junifer/datagrabber/hcp1200/hcp1200.py +26 -15
  81. junifer/datagrabber/hcp1200/py.typed +0 -0
  82. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
  83. junifer/datagrabber/multiple.py +14 -9
  84. junifer/datagrabber/pattern.py +132 -96
  85. junifer/datagrabber/pattern_validation_mixin.py +206 -94
  86. junifer/datagrabber/py.typed +0 -0
  87. junifer/datagrabber/tests/test_datalad_base.py +27 -12
  88. junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
  89. junifer/datagrabber/tests/test_multiple.py +48 -2
  90. junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
  91. junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
  92. junifer/datareader/__init__.py +2 -2
  93. junifer/datareader/__init__.pyi +3 -0
  94. junifer/datareader/default.py +6 -6
  95. junifer/datareader/py.typed +0 -0
  96. junifer/external/nilearn/__init__.py +2 -3
  97. junifer/external/nilearn/__init__.pyi +4 -0
  98. junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
  99. junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
  100. junifer/external/nilearn/py.typed +0 -0
  101. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
  102. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
  103. junifer/markers/__init__.py +2 -38
  104. junifer/markers/__init__.pyi +37 -0
  105. junifer/markers/base.py +11 -14
  106. junifer/markers/brainprint.py +12 -14
  107. junifer/markers/complexity/__init__.py +2 -18
  108. junifer/markers/complexity/__init__.pyi +17 -0
  109. junifer/markers/complexity/complexity_base.py +9 -11
  110. junifer/markers/complexity/hurst_exponent.py +7 -7
  111. junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
  112. junifer/markers/complexity/perm_entropy.py +7 -7
  113. junifer/markers/complexity/py.typed +0 -0
  114. junifer/markers/complexity/range_entropy.py +7 -7
  115. junifer/markers/complexity/range_entropy_auc.py +7 -7
  116. junifer/markers/complexity/sample_entropy.py +7 -7
  117. junifer/markers/complexity/tests/test_complexity_base.py +1 -1
  118. junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
  119. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
  120. junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
  121. junifer/markers/complexity/tests/test_range_entropy.py +5 -5
  122. junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
  123. junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
  124. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
  125. junifer/markers/complexity/weighted_perm_entropy.py +7 -7
  126. junifer/markers/ets_rss.py +12 -11
  127. junifer/markers/falff/__init__.py +2 -3
  128. junifer/markers/falff/__init__.pyi +4 -0
  129. junifer/markers/falff/_afni_falff.py +38 -45
  130. junifer/markers/falff/_junifer_falff.py +16 -19
  131. junifer/markers/falff/falff_base.py +7 -11
  132. junifer/markers/falff/falff_parcels.py +9 -9
  133. junifer/markers/falff/falff_spheres.py +8 -8
  134. junifer/markers/falff/py.typed +0 -0
  135. junifer/markers/falff/tests/test_falff_spheres.py +3 -1
  136. junifer/markers/functional_connectivity/__init__.py +2 -12
  137. junifer/markers/functional_connectivity/__init__.pyi +13 -0
  138. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
  139. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
  140. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
  141. junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
  142. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
  143. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
  144. junifer/markers/functional_connectivity/py.typed +0 -0
  145. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
  146. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
  147. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
  148. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
  149. junifer/markers/parcel_aggregation.py +22 -17
  150. junifer/markers/py.typed +0 -0
  151. junifer/markers/reho/__init__.py +2 -3
  152. junifer/markers/reho/__init__.pyi +4 -0
  153. junifer/markers/reho/_afni_reho.py +29 -35
  154. junifer/markers/reho/_junifer_reho.py +13 -14
  155. junifer/markers/reho/py.typed +0 -0
  156. junifer/markers/reho/reho_base.py +7 -11
  157. junifer/markers/reho/reho_parcels.py +10 -10
  158. junifer/markers/reho/reho_spheres.py +9 -9
  159. junifer/markers/sphere_aggregation.py +22 -17
  160. junifer/markers/temporal_snr/__init__.py +2 -3
  161. junifer/markers/temporal_snr/__init__.pyi +4 -0
  162. junifer/markers/temporal_snr/py.typed +0 -0
  163. junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
  164. junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
  165. junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
  166. junifer/markers/tests/test_ets_rss.py +3 -3
  167. junifer/markers/tests/test_parcel_aggregation.py +24 -24
  168. junifer/markers/tests/test_sphere_aggregation.py +6 -6
  169. junifer/markers/utils.py +3 -3
  170. junifer/onthefly/__init__.py +2 -1
  171. junifer/onthefly/_brainprint.py +138 -0
  172. junifer/onthefly/read_transform.py +5 -8
  173. junifer/pipeline/__init__.py +2 -10
  174. junifer/pipeline/__init__.pyi +13 -0
  175. junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
  176. junifer/pipeline/pipeline_component_registry.py +294 -0
  177. junifer/pipeline/pipeline_step_mixin.py +15 -11
  178. junifer/pipeline/py.typed +0 -0
  179. junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
  180. junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
  181. junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
  182. junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
  183. junifer/pipeline/tests/test_workdir_manager.py +43 -0
  184. junifer/pipeline/update_meta_mixin.py +21 -17
  185. junifer/pipeline/utils.py +6 -6
  186. junifer/pipeline/workdir_manager.py +19 -5
  187. junifer/preprocess/__init__.py +2 -10
  188. junifer/preprocess/__init__.pyi +11 -0
  189. junifer/preprocess/base.py +10 -10
  190. junifer/preprocess/confounds/__init__.py +2 -2
  191. junifer/preprocess/confounds/__init__.pyi +3 -0
  192. junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
  193. junifer/preprocess/confounds/py.typed +0 -0
  194. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
  195. junifer/preprocess/py.typed +0 -0
  196. junifer/preprocess/smoothing/__init__.py +2 -2
  197. junifer/preprocess/smoothing/__init__.pyi +3 -0
  198. junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
  199. junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
  200. junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
  201. junifer/preprocess/smoothing/py.typed +0 -0
  202. junifer/preprocess/smoothing/smoothing.py +11 -13
  203. junifer/preprocess/warping/__init__.py +2 -2
  204. junifer/preprocess/warping/__init__.pyi +3 -0
  205. junifer/preprocess/warping/_ants_warper.py +136 -32
  206. junifer/preprocess/warping/_fsl_warper.py +73 -22
  207. junifer/preprocess/warping/py.typed +0 -0
  208. junifer/preprocess/warping/space_warper.py +39 -11
  209. junifer/preprocess/warping/tests/test_space_warper.py +5 -9
  210. junifer/py.typed +0 -0
  211. junifer/stats.py +5 -5
  212. junifer/storage/__init__.py +2 -10
  213. junifer/storage/__init__.pyi +11 -0
  214. junifer/storage/base.py +47 -13
  215. junifer/storage/hdf5.py +95 -33
  216. junifer/storage/pandas_base.py +12 -11
  217. junifer/storage/py.typed +0 -0
  218. junifer/storage/sqlite.py +11 -11
  219. junifer/storage/tests/test_hdf5.py +86 -4
  220. junifer/storage/tests/test_sqlite.py +2 -2
  221. junifer/storage/tests/test_storage_base.py +5 -2
  222. junifer/storage/tests/test_utils.py +33 -7
  223. junifer/storage/utils.py +95 -9
  224. junifer/testing/__init__.py +2 -3
  225. junifer/testing/__init__.pyi +4 -0
  226. junifer/testing/datagrabbers.py +10 -11
  227. junifer/testing/py.typed +0 -0
  228. junifer/testing/registry.py +4 -7
  229. junifer/testing/tests/test_testing_registry.py +9 -17
  230. junifer/tests/test_stats.py +2 -2
  231. junifer/typing/__init__.py +9 -0
  232. junifer/typing/__init__.pyi +31 -0
  233. junifer/typing/_typing.py +68 -0
  234. junifer/utils/__init__.py +2 -12
  235. junifer/utils/__init__.pyi +18 -0
  236. junifer/utils/_config.py +110 -0
  237. junifer/utils/_yaml.py +16 -0
  238. junifer/utils/helpers.py +6 -6
  239. junifer/utils/logging.py +117 -8
  240. junifer/utils/py.typed +0 -0
  241. junifer/{pipeline → utils}/singleton.py +19 -14
  242. junifer/utils/tests/test_config.py +59 -0
  243. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
  244. junifer-0.0.6.dist-info/RECORD +350 -0
  245. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
  246. junifer-0.0.6.dist-info/entry_points.txt +2 -0
  247. junifer/api/parser.py +0 -118
  248. junifer/data/coordinates.py +0 -408
  249. junifer/data/masks.py +0 -670
  250. junifer/data/parcellations.py +0 -1828
  251. junifer/pipeline/registry.py +0 -177
  252. junifer/pipeline/tests/test_registry.py +0 -150
  253. junifer-0.0.5.dev240.dist-info/RECORD +0 -275
  254. junifer-0.0.5.dev240.dist-info/entry_points.txt +0 -2
  255. /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
  256. /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
  257. /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
  258. /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
  259. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
  260. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
  261. /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
  262. /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
  263. /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
  264. /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
  265. /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
  266. /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
  267. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
  268. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
  269. /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
  270. /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
  271. /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
  272. /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
  273. /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
  274. /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
  275. /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
  276. /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
  277. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
  278. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
  279. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@
7
7
 
8
8
  import socket
9
9
  from pathlib import Path
10
- from typing import Callable, Dict, List, Optional, Union
10
+ from typing import Callable, Optional, Union
11
11
 
12
12
  import nibabel as nib
13
13
  import numpy as np
@@ -20,15 +20,11 @@ from nilearn.masking import (
20
20
  )
21
21
  from numpy.testing import assert_array_almost_equal, assert_array_equal
22
22
 
23
- from junifer.data.masks import (
24
- _available_masks,
23
+ from junifer.data import MaskRegistry
24
+ from junifer.data.masks import compute_brain_mask
25
+ from junifer.data.masks._masks import (
25
26
  _load_ukb_mask,
26
27
  _load_vickery_patil_mask,
27
- compute_brain_mask,
28
- get_mask,
29
- list_masks,
30
- load_mask,
31
- register_mask,
32
28
  )
33
29
  from junifer.datagrabber import DMCC13Benchmark
34
30
  from junifer.datareader import DefaultDataReader
@@ -68,10 +64,9 @@ def test_compute_brain_mask(mask_type: str, threshold: float) -> None:
68
64
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
69
65
  mask = compute_brain_mask(
70
66
  target_data=element_data["BOLD"],
71
- extra_input=None,
72
67
  mask_type=mask_type,
73
68
  )
74
- assert isinstance(mask, nib.Nifti1Image)
69
+ assert isinstance(mask, nib.nifti1.Nifti1Image)
75
70
 
76
71
 
77
72
  @pytest.mark.skipif(
@@ -111,13 +106,13 @@ def test_compute_brain_mask_for_native(mask_type: str) -> None:
111
106
  extra_input=None,
112
107
  mask_type=mask_type,
113
108
  )
114
- assert isinstance(mask, nib.Nifti1Image)
109
+ assert isinstance(mask, nib.nifti1.Nifti1Image)
115
110
 
116
111
 
117
- def test_register_mask_built_in_check() -> None:
112
+ def test_register_built_in_check() -> None:
118
113
  """Test mask registration check for built-in masks."""
119
114
  with pytest.raises(ValueError, match=r"built-in mask"):
120
- register_mask(
115
+ MaskRegistry().register(
121
116
  name="GM_prob0.2",
122
117
  mask_path="testmask.nii.gz",
123
118
  space="MNI",
@@ -125,39 +120,38 @@ def test_register_mask_built_in_check() -> None:
125
120
  )
126
121
 
127
122
 
128
- def test_list_masks_incorrect() -> None:
123
+ def test_list_incorrect() -> None:
129
124
  """Test incorrect information check for list masks."""
130
- masks = list_masks()
131
- assert "testmask" not in masks
125
+ assert "testmask" not in MaskRegistry().list
132
126
 
133
127
 
134
- def test_register_mask_already_registered() -> None:
128
+ def test_register_already_registered() -> None:
135
129
  """Test mask registration check for already registered."""
136
130
  # Register custom mask
137
- register_mask(
131
+ MaskRegistry().register(
138
132
  name="testmask",
139
133
  mask_path="testmask.nii.gz",
140
134
  space="MNI",
141
135
  )
142
- out = load_mask("testmask", path_only=True)
136
+ out = MaskRegistry().load("testmask", path_only=True)
143
137
  assert out[1] is not None
144
138
  assert out[1].name == "testmask.nii.gz"
145
139
 
146
140
  # Try registering again
147
141
  with pytest.raises(ValueError, match=r"already registered."):
148
- register_mask(
142
+ MaskRegistry().register(
149
143
  name="testmask",
150
144
  mask_path="testmask.nii.gz",
151
145
  space="MNI",
152
146
  )
153
- register_mask(
147
+ MaskRegistry().register(
154
148
  name="testmask",
155
149
  mask_path="testmask2.nii.gz",
156
150
  space="MNI",
157
151
  overwrite=True,
158
152
  )
159
153
 
160
- out = load_mask("testmask", path_only=True)
154
+ out = MaskRegistry().load("testmask", path_only=True)
161
155
  assert out[1] is not None
162
156
  assert out[1].name == "testmask2.nii.gz"
163
157
 
@@ -170,7 +164,7 @@ def test_register_mask_already_registered() -> None:
170
164
  ("testmask_3", Path("testmask_3.nii.gz"), "MNI", True),
171
165
  ],
172
166
  )
173
- def test_register_mask(
167
+ def test_register(
174
168
  name: str,
175
169
  mask_path: str,
176
170
  space: str,
@@ -191,17 +185,16 @@ def test_register_mask(
191
185
 
192
186
  """
193
187
  # Register custom mask
194
- register_mask(
188
+ MaskRegistry().register(
195
189
  name=name,
196
190
  mask_path=mask_path,
197
191
  space=space,
198
192
  overwrite=overwrite,
199
193
  )
200
194
  # List available mask and check registration
201
- masks = list_masks()
202
- assert name in masks
195
+ assert name in MaskRegistry().list
203
196
  # Load registered mask
204
- _, fname, mask_space = load_mask(name=name, path_only=True)
197
+ _, fname, mask_space = MaskRegistry().load(name=name, path_only=True)
205
198
  # Check values for registered mask
206
199
  assert fname is not None
207
200
  assert fname.name == f"{name}.nii.gz"
@@ -216,7 +209,7 @@ def test_register_mask(
216
209
  "UKB_15K_GM",
217
210
  ],
218
211
  )
219
- def test_list_masks_correct(mask_name: str) -> None:
212
+ def test_list_correct(mask_name: str) -> None:
220
213
  """Test correct information check for list masks.
221
214
 
222
215
  Parameters
@@ -225,14 +218,13 @@ def test_list_masks_correct(mask_name: str) -> None:
225
218
  The parametrized mask name.
226
219
 
227
220
  """
228
- masks = list_masks()
229
- assert mask_name in masks
221
+ assert mask_name in MaskRegistry().list
230
222
 
231
223
 
232
- def test_load_mask_incorrect() -> None:
224
+ def test_load_incorrect() -> None:
233
225
  """Test loading of invalid masks."""
234
226
  with pytest.raises(ValueError, match=r"not found"):
235
- load_mask("wrongmask")
227
+ MaskRegistry().load("wrongmask")
236
228
 
237
229
 
238
230
  @pytest.mark.parametrize(
@@ -261,7 +253,7 @@ def test_load_mask_incorrect() -> None:
261
253
  def test_vickery_patil(
262
254
  name: str,
263
255
  resolution: Optional[float],
264
- pixdim: List[float],
256
+ pixdim: list[float],
265
257
  fname: str,
266
258
  ) -> None:
267
259
  """Test Vickery-Patil mask.
@@ -278,7 +270,7 @@ def test_vickery_patil(
278
270
  The parametrized name of the mask file.
279
271
 
280
272
  """
281
- mask, mask_fname, space = load_mask(name, resolution=resolution)
273
+ mask, mask_fname, space = MaskRegistry().load(name, resolution=resolution)
282
274
  assert_array_almost_equal(
283
275
  mask.header["pixdim"][1:4], pixdim # type: ignore
284
276
  )
@@ -295,7 +287,7 @@ def test_vickery_patil_error() -> None:
295
287
 
296
288
  def test_ukb() -> None:
297
289
  """Test UKB mask."""
298
- mask, mask_fname, space = load_mask("UKB_15K_GM", resolution=2.0)
290
+ mask, mask_fname, space = MaskRegistry().load("UKB_15K_GM", resolution=2.0)
299
291
  assert_array_almost_equal(mask.header["pixdim"][1:4], 2.0) # type: ignore
300
292
  assert space == "MNI152NLin6Asym"
301
293
  assert mask_fname is not None
@@ -308,18 +300,20 @@ def test_ukb_error() -> None:
308
300
  _load_ukb_mask(name="wrong")
309
301
 
310
302
 
311
- def test_get_mask() -> None:
312
- """Test the get_mask function."""
303
+ def test_get() -> None:
304
+ """Test tailored mask fetch."""
313
305
  with OasisVBMTestingDataGrabber() as dg:
314
306
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
315
307
  vbm_gm = element_data["VBM_GM"]
316
308
  vbm_gm_img = vbm_gm["data"]
317
- mask = get_mask(masks="compute_brain_mask", target_data=vbm_gm)
309
+ mask = MaskRegistry().get(
310
+ masks="compute_brain_mask", target_data=vbm_gm
311
+ )
318
312
 
319
313
  assert mask.shape == vbm_gm_img.shape
320
314
  assert_array_equal(mask.affine, vbm_gm_img.affine)
321
315
 
322
- raw_mask_callable, _, _ = load_mask(
316
+ raw_mask_callable, _, _ = MaskRegistry().load(
323
317
  "compute_brain_mask", resolution=1.5
324
318
  )
325
319
  raw_mask_img = raw_mask_callable(vbm_gm) # type: ignore
@@ -338,7 +332,7 @@ def test_mask_callable() -> None:
338
332
  def ident(x):
339
333
  return x
340
334
 
341
- _available_masks["identity"] = {
335
+ MaskRegistry()._registry["identity"] = {
342
336
  "family": "Callable",
343
337
  "func": ident,
344
338
  "space": "MNI152Lin",
@@ -347,44 +341,48 @@ def test_mask_callable() -> None:
347
341
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
348
342
  vbm_gm = element_data["VBM_GM"]
349
343
  vbm_gm_img = vbm_gm["data"]
350
- mask = get_mask(masks="identity", target_data=vbm_gm)
344
+ mask = MaskRegistry().get(masks="identity", target_data=vbm_gm)
351
345
 
352
346
  assert_array_equal(mask.get_fdata(), vbm_gm_img.get_fdata())
353
347
 
354
- del _available_masks["identity"]
348
+ del MaskRegistry()._registry["identity"]
355
349
 
356
350
 
357
- def test_get_mask_errors() -> None:
358
- """Test passing wrong parameters to get_mask."""
351
+ def test_get_errors() -> None:
352
+ """Test passing wrong parameters to fetch mask."""
359
353
  with OasisVBMTestingDataGrabber() as dg:
360
354
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
361
355
  vbm_gm = element_data["VBM_GM"]
362
356
  # Test wrong masks definitions (more than one key per dict)
363
357
  with pytest.raises(ValueError, match=r"only one key"):
364
- get_mask(masks={"GM_prob0.2": {}, "Other": {}}, target_data=vbm_gm)
358
+ MaskRegistry().get(
359
+ masks={"GM_prob0.2": {}, "Other": {}}, target_data=vbm_gm
360
+ )
365
361
 
366
362
  # Test wrong masks definitions (pass paramaeters to non-callable mask)
367
363
  with pytest.raises(ValueError, match=r"callable params"):
368
- get_mask(masks={"GM_prob0.2": {"param": 1}}, target_data=vbm_gm)
364
+ MaskRegistry().get(
365
+ masks={"GM_prob0.2": {"param": 1}}, target_data=vbm_gm
366
+ )
369
367
 
370
368
  # Pass only parameters to the intersection function
371
369
  with pytest.raises(
372
370
  ValueError, match=r" At least one mask is required."
373
371
  ):
374
- get_mask(masks={"threshold": 1}, target_data=vbm_gm)
372
+ MaskRegistry().get(masks={"threshold": 1}, target_data=vbm_gm)
375
373
 
376
374
  # Pass parameters to the intersection function when only one mask
377
375
  with pytest.raises(
378
376
  ValueError, match=r"parameters to the intersection"
379
377
  ):
380
- get_mask(
378
+ MaskRegistry().get(
381
379
  masks=["compute_brain_mask", {"threshold": 1}],
382
380
  target_data=vbm_gm,
383
381
  )
384
382
 
385
383
  # Test "inherited" masks error
386
384
  with pytest.raises(ValueError, match=r"provide `mask`"):
387
- get_mask(masks="inherit", target_data=vbm_gm)
385
+ MaskRegistry().get(masks="inherit", target_data=vbm_gm)
388
386
 
389
387
 
390
388
  @pytest.mark.parametrize(
@@ -397,7 +395,7 @@ def test_get_mask_errors() -> None:
397
395
  def test_nilearn_compute_masks(
398
396
  mask_name: str,
399
397
  function: Callable,
400
- params: Union[Dict, None],
398
+ params: Union[dict, None],
401
399
  resample: bool,
402
400
  ) -> None:
403
401
  """Test using nilearn compute mask functions.
@@ -425,7 +423,7 @@ def test_nilearn_compute_masks(
425
423
  else:
426
424
  mask_spec = {mask_name: params}
427
425
 
428
- mask = get_mask(masks=mask_spec, target_data=bold)
426
+ mask = MaskRegistry().get(masks=mask_spec, target_data=bold)
429
427
 
430
428
  assert_array_equal(mask.affine, bold_img.affine)
431
429
 
@@ -443,15 +441,15 @@ def test_nilearn_compute_masks(
443
441
  assert_array_equal(mask.get_fdata(), ni_mask.get_fdata())
444
442
 
445
443
 
446
- def test_get_mask_inherit() -> None:
447
- """Test using the inherit mask functionality."""
444
+ def test_get_inherit() -> None:
445
+ """Test mask fetch using the inherit mask functionality."""
448
446
  with SPMAuditoryTestingDataGrabber() as dg:
449
447
  element_data = DefaultDataReader().fit_transform(dg["sub001"])
450
448
  # Compute brain mask using nilearn
451
449
  gm_mask = compute_brain_mask(element_data["BOLD"], threshold=0.2)
452
450
 
453
451
  # Get mask using the compute_brain_mask function
454
- mask1 = get_mask(
452
+ mask1 = MaskRegistry().get(
455
453
  masks={"compute_brain_mask": {"threshold": 0.2}},
456
454
  target_data=element_data["BOLD"],
457
455
  )
@@ -463,7 +461,7 @@ def test_get_mask_inherit() -> None:
463
461
  "data": gm_mask,
464
462
  "space": element_data["BOLD"]["space"],
465
463
  }
466
- mask2 = get_mask(
464
+ mask2 = MaskRegistry().get(
467
465
  masks="inherit",
468
466
  target_data=bold_dict,
469
467
  )
@@ -479,8 +477,8 @@ def test_get_mask_inherit() -> None:
479
477
  (["compute_brain_mask", "compute_epi_mask"], {}),
480
478
  ],
481
479
  )
482
- def test_get_mask_multiple(
483
- masks: Union[str, Dict, List[Union[Dict, str]]], params: Dict
480
+ def test_get_multiple(
481
+ masks: Union[str, dict, list[Union[dict, str]]], params: dict
484
482
  ) -> None:
485
483
  """Test getting multiple masks.
486
484
 
@@ -505,7 +503,7 @@ def test_get_mask_multiple(
505
503
  target_img = element_data["BOLD"]["data"]
506
504
  resolution = np.min(target_img.header.get_zooms()[:3])
507
505
 
508
- computed = get_mask(
506
+ computed = MaskRegistry().get(
509
507
  masks=junifer_masks, target_data=element_data["BOLD"]
510
508
  )
511
509
 
@@ -516,16 +514,18 @@ def test_get_mask_multiple(
516
514
  mask_funcs = [
517
515
  x
518
516
  for x in masks_names
519
- if _available_masks[x]["family"] == "Callable"
517
+ if MaskRegistry()._registry[x]["family"] == "Callable"
520
518
  ]
521
519
  mask_files = [
522
520
  x
523
521
  for x in masks_names
524
- if _available_masks[x]["family"] != "Callable"
522
+ if MaskRegistry()._registry[x]["family"] != "Callable"
525
523
  ]
526
524
 
527
525
  mask_imgs = [
528
- load_mask(t_mask, path_only=False, resolution=resolution)[0]
526
+ MaskRegistry().load(
527
+ t_mask, path_only=False, resolution=resolution
528
+ )[0]
529
529
  for t_mask in mask_files
530
530
  ]
531
531
 
@@ -533,10 +533,14 @@ def test_get_mask_multiple(
533
533
  # Bypass for custom mask
534
534
  if t_func == "compute_brain_mask":
535
535
  mask_imgs.append(
536
- _available_masks[t_func]["func"](element_data["BOLD"])
536
+ MaskRegistry()._registry[t_func]["func"](
537
+ element_data["BOLD"]
538
+ )
537
539
  )
538
540
  else:
539
- mask_imgs.append(_available_masks[t_func]["func"](target_img))
541
+ mask_imgs.append(
542
+ MaskRegistry()._registry[t_func]["func"](target_img)
543
+ )
540
544
 
541
545
  mask_imgs = [
542
546
  resample_to_img(
@@ -0,0 +1,9 @@
1
+ """Parcellations."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import lazy_loader as lazy
7
+
8
+
9
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,6 @@
1
+ __all__ = [
2
+ "ParcellationRegistry",
3
+ "merge_parcellations",
4
+ ]
5
+
6
+ from ._parcellations import ParcellationRegistry, merge_parcellations
@@ -0,0 +1,166 @@
1
+ """Provide class for parcellation space warping via ANTs."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import uuid
7
+ from typing import TYPE_CHECKING, Any, Optional
8
+
9
+ import nibabel as nib
10
+
11
+ from ...pipeline import WorkDirManager
12
+ from ...utils import logger, raise_error, run_ext_cmd
13
+ from ..template_spaces import get_template, get_xfm
14
+
15
+
16
+ if TYPE_CHECKING:
17
+ from nibabel.nifti1 import Nifti1Image
18
+
19
+
20
+ __all__ = ["ANTsParcellationWarper"]
21
+
22
+
23
+ class ANTsParcellationWarper:
24
+ """Class for parcellation space warping via ANTs.
25
+
26
+ This class uses ANTs ``antsApplyTransforms`` for transformation.
27
+
28
+ """
29
+
30
+ def warp(
31
+ self,
32
+ parcellation_name: str,
33
+ parcellation_img: "Nifti1Image",
34
+ src: str,
35
+ dst: str,
36
+ target_data: dict[str, Any],
37
+ warp_data: Optional[dict[str, Any]],
38
+ ) -> "Nifti1Image":
39
+ """Warp ``parcellation_img`` to correct space.
40
+
41
+ Parameters
42
+ ----------
43
+ parcellation_name : str
44
+ The name of the parcellation.
45
+ parcellation_img : nibabel.nifti1.Nifti1Image
46
+ The parcellation image to transform.
47
+ src : str
48
+ The data type or template space to warp from.
49
+ It should be empty string if ``dst="T1w"``.
50
+ dst : str
51
+ The data type or template space to warp to.
52
+ `"T1w"` is the only allowed data type and it uses the resampled T1w
53
+ found in ``target_data.reference``. The ``"reference"``
54
+ key is added if the :class:`.SpaceWarper` is used or if the
55
+ data is provided in native space.
56
+ target_data : dict
57
+ The corresponding item of the data object to which the parcellation
58
+ will be applied.
59
+ warp_data : dict or None
60
+ The warp data item of the data object. The value is unused if
61
+ ``dst!="T1w"``.
62
+
63
+ Returns
64
+ -------
65
+ nibabel.nifti1.Nifti1Image
66
+ The transformed parcellation image.
67
+
68
+ Raises
69
+ ------
70
+ ValueError
71
+ If ``warp_data`` is None when ``dst="T1w"``.
72
+
73
+ """
74
+ # Create element-scoped tempdir so that warped parcellation is
75
+ # available later as nibabel stores file path reference for
76
+ # loading on computation
77
+ prefix = (
78
+ f"ants_parcellation_warper_{parcellation_name}"
79
+ f"{'' if not src else f'_from_{src}'}_to_{dst}_"
80
+ f"{uuid.uuid1()}"
81
+ )
82
+ element_tempdir = WorkDirManager().get_element_tempdir(
83
+ prefix=prefix,
84
+ )
85
+
86
+ # Native space warping
87
+ if dst == "native":
88
+ # Warp data check
89
+ if warp_data is None:
90
+ raise_error("No `warp_data` provided")
91
+ if "reference" not in target_data:
92
+ raise_error("No `reference` provided")
93
+ if "path" not in target_data["reference"]:
94
+ raise_error("No `path` provided in `reference`")
95
+
96
+ logger.debug("Using ANTs for parcellation transformation")
97
+
98
+ # Save existing parcellation image to a tempfile
99
+ prewarp_parcellation_path = (
100
+ element_tempdir / "prewarp_parcellation.nii.gz"
101
+ )
102
+ nib.save(parcellation_img, prewarp_parcellation_path)
103
+
104
+ # Create a tempfile for warped output
105
+ warped_parcellation_path = (
106
+ element_tempdir / "parcellation_warped.nii.gz"
107
+ )
108
+ # Set antsApplyTransforms command
109
+ apply_transforms_cmd = [
110
+ "antsApplyTransforms",
111
+ "-d 3",
112
+ "-e 3",
113
+ "-n 'GenericLabel[NearestNeighbor]'",
114
+ f"-i {prewarp_parcellation_path.resolve()}",
115
+ # use resampled reference
116
+ f"-r {target_data['reference']['path'].resolve()}",
117
+ f"-t {warp_data['path'].resolve()}",
118
+ f"-o {warped_parcellation_path.resolve()}",
119
+ ]
120
+ # Call antsApplyTransforms
121
+ run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
122
+
123
+ # Template space warping
124
+ else:
125
+ logger.debug(
126
+ f"Using ANTs to warp parcellation from {src} to {dst}"
127
+ )
128
+
129
+ # Get xfm file
130
+ xfm_file_path = get_xfm(src=src, dst=dst)
131
+ # Get template space image
132
+ template_space_img = get_template(
133
+ space=dst,
134
+ target_img=parcellation_img,
135
+ extra_input=None,
136
+ )
137
+ # Save template to a tempfile
138
+ template_space_img_path = element_tempdir / f"{dst}_T1w.nii.gz"
139
+ nib.save(template_space_img, template_space_img_path)
140
+
141
+ # Save existing parcellation image to a tempfile
142
+ prewarp_parcellation_path = (
143
+ element_tempdir / "prewarp_parcellation.nii.gz"
144
+ )
145
+ nib.save(parcellation_img, prewarp_parcellation_path)
146
+
147
+ # Create a tempfile for warped output
148
+ warped_parcellation_path = (
149
+ element_tempdir / "parcellation_warped.nii.gz"
150
+ )
151
+ # Set antsApplyTransforms command
152
+ apply_transforms_cmd = [
153
+ "antsApplyTransforms",
154
+ "-d 3",
155
+ "-e 3",
156
+ "-n 'GenericLabel[NearestNeighbor]'",
157
+ f"-i {prewarp_parcellation_path.resolve()}",
158
+ f"-r {template_space_img_path.resolve()}",
159
+ f"-t {xfm_file_path.resolve()}",
160
+ f"-o {warped_parcellation_path.resolve()}",
161
+ ]
162
+ # Call antsApplyTransforms
163
+ run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
164
+
165
+ # Load nifti
166
+ return nib.load(warped_parcellation_path)
@@ -0,0 +1,89 @@
1
+ """Provide class for parcellation space warping via FSL FLIRT."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import uuid
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ import nibabel as nib
10
+
11
+ from ...pipeline import WorkDirManager
12
+ from ...utils import logger, run_ext_cmd
13
+
14
+
15
+ if TYPE_CHECKING:
16
+ from nibabel.nifti1 import Nifti1Image
17
+
18
+
19
+ __all__ = ["FSLParcellationWarper"]
20
+
21
+
22
+ class FSLParcellationWarper:
23
+ """Class for parcellation space warping via FSL FLIRT.
24
+
25
+ This class uses FSL FLIRT's ``applywarp`` for transformation.
26
+
27
+ """
28
+
29
+ def warp(
30
+ self,
31
+ parcellation_name: str,
32
+ parcellation_img: "Nifti1Image",
33
+ target_data: dict[str, Any],
34
+ warp_data: dict[str, Any],
35
+ ) -> "Nifti1Image":
36
+ """Warp ``parcellation_img`` to correct space.
37
+
38
+ Parameters
39
+ ----------
40
+ parcellation_name : str
41
+ The name of the parcellation.
42
+ parcellation_img : nibabel.nifti1.Nifti1Image
43
+ The parcellation image to transform.
44
+ target_data : dict
45
+ The corresponding item of the data object to which the parcellation
46
+ will be applied.
47
+ warp_data : dict
48
+ The warp data item of the data object.
49
+
50
+ Returns
51
+ -------
52
+ nibabel.nifti1.Nifti1Image
53
+ The transformed parcellation image.
54
+
55
+ """
56
+ logger.debug("Using FSL for parcellation transformation")
57
+
58
+ # Create element-scoped tempdir so that warped parcellation is
59
+ # available later as nibabel stores file path reference for
60
+ # loading on computation
61
+ element_tempdir = WorkDirManager().get_element_tempdir(
62
+ prefix=f"fsl_parcellation_warper_{parcellation_name}_{uuid.uuid1()}"
63
+ )
64
+
65
+ # Save existing parcellation image to a tempfile
66
+ prewarp_parcellation_path = (
67
+ element_tempdir / "prewarp_parcellation.nii.gz"
68
+ )
69
+ nib.save(parcellation_img, prewarp_parcellation_path)
70
+
71
+ # Create a tempfile for warped output
72
+ warped_parcellation_path = (
73
+ element_tempdir / "parcellation_warped.nii.gz"
74
+ )
75
+ # Set applywarp command
76
+ applywarp_cmd = [
77
+ "applywarp",
78
+ "--interp=nn",
79
+ f"-i {prewarp_parcellation_path.resolve()}",
80
+ # use resampled reference
81
+ f"-r {target_data['reference']['path'].resolve()}",
82
+ f"-w {warp_data['path'].resolve()}",
83
+ f"-o {warped_parcellation_path.resolve()}",
84
+ ]
85
+ # Call applywarp
86
+ run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
87
+
88
+ # Load nifti
89
+ return nib.load(warped_parcellation_path)