junifer 0.0.5.dev242__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.dev242.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.dev242.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.dev242.dist-info/RECORD +0 -275
  254. junifer-0.0.5.dev242.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.dev242.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
  278. {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
  279. {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,385 @@
1
+ """Provide a class for centralized coordinates data registry."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # Synchon Mandal <s.mandal@fz-juelich.de>
5
+ # License: AGPL
6
+
7
+ from pathlib import Path
8
+ from typing import Any, Optional
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+ from junifer_data import get
13
+ from numpy.typing import ArrayLike
14
+
15
+ from ...utils import logger, raise_error
16
+ from ...utils.singleton import Singleton
17
+ from ..pipeline_data_registry_base import BasePipelineDataRegistry
18
+ from ..utils import JUNIFER_DATA_PARAMS, get_dataset_path, get_native_warper
19
+ from ._ants_coordinates_warper import ANTsCoordinatesWarper
20
+ from ._fsl_coordinates_warper import FSLCoordinatesWarper
21
+
22
+
23
+ __all__ = ["CoordinatesRegistry"]
24
+
25
+
26
+ class CoordinatesRegistry(BasePipelineDataRegistry, metaclass=Singleton):
27
+ """Class for coordinates data registry.
28
+
29
+ This class is a singleton and is used for managing available coordinates
30
+ data in a centralized manner.
31
+
32
+ """
33
+
34
+ def __init__(self) -> None:
35
+ """Initialize the class."""
36
+ super().__init__()
37
+ # Each entry in registry is a dictionary that must contain at least
38
+ # the following keys:
39
+ # * 'space': the coordinates' space (e.g., 'MNI')
40
+ # The built-in coordinates are files that are shipped with the
41
+ # junifer-data dataset. The user can also register their own
42
+ # coordinates, which will be stored as numpy arrays in the dictionary.
43
+ # Make built-in and external dictionaries for validation later
44
+ self._builtin = {}
45
+ self._external = {}
46
+
47
+ self._builtin.update(
48
+ {
49
+ "CogAC": {
50
+ "file_path_suffix": "CogAC/CogAC_VOIs.txt",
51
+ "space": "MNI",
52
+ },
53
+ "CogAR": {
54
+ "file_path_suffix": "CogAR/CogAR_VOIs.txt",
55
+ "space": "MNI",
56
+ },
57
+ "DMNBuckner": {
58
+ "file_path_suffix": "DMNBuckner/DMNBuckner_VOIs.txt",
59
+ "space": "MNI",
60
+ },
61
+ "eMDN": {
62
+ "file_path_suffix": "eMDN/eMDN_VOIs.txt",
63
+ "space": "MNI",
64
+ },
65
+ "Empathy": {
66
+ "file_path_suffix": "Empathy/Empathy_VOIs.txt",
67
+ "space": "MNI",
68
+ },
69
+ "eSAD": {
70
+ "file_path_suffix": "eSAD/eSAD_VOIs.txt",
71
+ "space": "MNI",
72
+ },
73
+ "extDMN": {
74
+ "file_path_suffix": "extDMN/extDMN_VOIs.txt",
75
+ "space": "MNI",
76
+ },
77
+ "Motor": {
78
+ "file_path_suffix": "Motor/Motor_VOIs.txt",
79
+ "space": "MNI",
80
+ },
81
+ "MultiTask": {
82
+ "file_path_suffix": "MultiTask/MultiTask_VOIs.txt",
83
+ "space": "MNI",
84
+ },
85
+ "PhysioStress": {
86
+ "file_path_suffix": "PhysioStress/PhysioStress_VOIs.txt",
87
+ "space": "MNI",
88
+ },
89
+ "Rew": {
90
+ "file_path_suffix": "Rew/Rew_VOIs.txt",
91
+ "space": "MNI",
92
+ },
93
+ "Somatosensory": {
94
+ "file_path_suffix": "Somatosensory/Somatosensory_VOIs.txt",
95
+ "space": "MNI",
96
+ },
97
+ "ToM": {
98
+ "file_path_suffix": "ToM/ToM_VOIs.txt",
99
+ "space": "MNI",
100
+ },
101
+ "VigAtt": {
102
+ "file_path_suffix": "VigAtt/VigAtt_VOIs.txt",
103
+ "space": "MNI",
104
+ },
105
+ "WM": {
106
+ "file_path_suffix": "WM/WM_VOIs.txt",
107
+ "space": "MNI",
108
+ },
109
+ "Power2011": {
110
+ "file_path_suffix": "Power/Power2011_MNI_VOIs.txt",
111
+ "space": "MNI",
112
+ },
113
+ "Dosenbach": {
114
+ "file_path_suffix": "Dosenbach/Dosenbach2010_MNI_VOIs.txt",
115
+ "space": "MNI",
116
+ },
117
+ "AutobiographicalMemory": {
118
+ "file_path_suffix": (
119
+ "AutobiographicalMemory/AutobiographicalMemory_VOIs.txt"
120
+ ),
121
+ "space": "MNI",
122
+ },
123
+ "Seitzman2018": {
124
+ "file_path_suffix": "Seitzman/Seitzman2018_MNI_VOIs.txt",
125
+ "space": "MNI",
126
+ },
127
+ }
128
+ )
129
+
130
+ # Update registry with built-in ones
131
+ self._registry.update(self._builtin)
132
+
133
+ def register(
134
+ self,
135
+ name: str,
136
+ coordinates: ArrayLike,
137
+ voi_names: list[str],
138
+ space: str,
139
+ overwrite: Optional[bool] = False,
140
+ ) -> None:
141
+ """Register a custom user coordinates.
142
+
143
+ Parameters
144
+ ----------
145
+ name : str
146
+ The name of the coordinates.
147
+ coordinates : numpy.ndarray
148
+ The coordinates. This should be a 2-dimensional array with three
149
+ columns. Each row corresponds to a volume-of-interest (VOI) and
150
+ each column corresponds to a spatial dimension (i.e. x, y, and
151
+ z-coordinates).
152
+ voi_names : list of str
153
+ The names of the VOIs.
154
+ space : str
155
+ The space of the coordinates, e.g., "MNI".
156
+ overwrite : bool, optional
157
+ If True, overwrite an existing list of coordinates with the same
158
+ name. Does not apply to built-in coordinates (default False).
159
+
160
+ Raises
161
+ ------
162
+ ValueError
163
+ If the coordinates ``name`` is a built-in coordinates or
164
+ if the coordinates ``name`` is already registered and
165
+ ``overwrite=False`` or
166
+ if the ``coordinates`` is not a 2D array or
167
+ if coordinate value does not have 3 components or
168
+ if the ``voi_names`` shape does not match the
169
+ ``coordinates`` shape.
170
+ TypeError
171
+ If ``coordinates`` is not a ``numpy.ndarray``.
172
+
173
+ """
174
+ # Check for attempt of overwriting built-in coordinates
175
+ if name in self._builtin:
176
+ raise_error(
177
+ f"Coordinates: {name} already registered as built-in "
178
+ "coordinates."
179
+ )
180
+ # Check for attempt of overwriting external coordinates
181
+ if name in self._external:
182
+ if overwrite:
183
+ logger.info(f"Overwriting coordinates: {name}")
184
+ else:
185
+ raise_error(
186
+ f"Coordinates: {name} already registered. "
187
+ "Set `overwrite=True` to update its value."
188
+ )
189
+ # Further checks
190
+ if not isinstance(coordinates, np.ndarray):
191
+ raise_error(
192
+ "Coordinates must be a `numpy.ndarray`, "
193
+ f"not {type(coordinates)}.",
194
+ klass=TypeError,
195
+ )
196
+ if coordinates.ndim != 2:
197
+ raise_error(
198
+ f"Coordinates must be a 2D array, not {coordinates.ndim}D."
199
+ )
200
+ if coordinates.shape[1] != 3:
201
+ raise_error(
202
+ "Each coordinate must have 3 values, "
203
+ f"not {coordinates.shape[1]}"
204
+ )
205
+ if len(voi_names) != coordinates.shape[0]:
206
+ raise_error(
207
+ f"Length of `voi_names` ({len(voi_names)}) does not match the "
208
+ f"number of `coordinates` ({coordinates.shape[0]})."
209
+ )
210
+ # Registration
211
+ logger.info(f"Registering coordinates: {name}")
212
+ # Add coordinates info
213
+ self._external[name] = {
214
+ "coords": coordinates,
215
+ "voi_names": voi_names,
216
+ "space": space,
217
+ }
218
+ # Update registry
219
+ self._registry[name] = {
220
+ "coords": coordinates,
221
+ "voi_names": voi_names,
222
+ "space": space,
223
+ }
224
+
225
+ def deregister(self, name: str) -> None:
226
+ """De-register a custom user coordinates.
227
+
228
+ Parameters
229
+ ----------
230
+ name : str
231
+ The name of the coordinates.
232
+
233
+ """
234
+ logger.info(f"De-registering coordinates: {name}")
235
+ # Remove coordinates info
236
+ _ = self._external.pop(name)
237
+ # Update registry
238
+ _ = self._registry.pop(name)
239
+
240
+ def load(self, name: str) -> tuple[ArrayLike, list[str], str]:
241
+ """Load coordinates.
242
+
243
+ Parameters
244
+ ----------
245
+ name : str
246
+ The name of the coordinates.
247
+
248
+ Returns
249
+ -------
250
+ numpy.ndarray
251
+ The coordinates.
252
+ list of str
253
+ The names of the VOIs.
254
+ str
255
+ The space of the coordinates.
256
+
257
+ Raises
258
+ ------
259
+ ValueError
260
+ If ``name`` is invalid.
261
+ RuntimeError
262
+ If there is a problem fetching the coordinates file.
263
+
264
+ """
265
+ # Check for valid coordinates name
266
+ if name not in self._registry:
267
+ raise_error(
268
+ f"Coordinates: {name} not found. "
269
+ f"Valid options are: {self.list}"
270
+ )
271
+ # Load coordinates info
272
+ t_coord = self._registry[name]
273
+
274
+ # Load data for in-built ones
275
+ if t_coord.get("file_path_suffix") is not None:
276
+ # Set file path to retrieve
277
+ coords_file_path = Path(
278
+ f"coordinates/{t_coord['file_path_suffix']}"
279
+ )
280
+ logger.debug(f"Loading coordinates: `{name}`")
281
+ # Load via pandas
282
+ df_coords = pd.read_csv(
283
+ get(
284
+ file_path=coords_file_path,
285
+ dataset_path=get_dataset_path(),
286
+ **JUNIFER_DATA_PARAMS,
287
+ ),
288
+ sep="\t",
289
+ header=None,
290
+ )
291
+ # Convert dataframe to numpy ndarray
292
+ coords = df_coords.iloc[:, [0, 1, 2]].to_numpy()
293
+ # Get label names
294
+ names = list(df_coords.iloc[:, [3]].values[:, 0])
295
+ # Load data for external ones
296
+ else:
297
+ coords = t_coord["coords"]
298
+ names = t_coord["voi_names"]
299
+
300
+ return coords, names, t_coord["space"]
301
+
302
+ def get(
303
+ self,
304
+ coords: str,
305
+ target_data: dict[str, Any],
306
+ extra_input: Optional[dict[str, Any]] = None,
307
+ ) -> tuple[ArrayLike, list[str]]:
308
+ """Get coordinates, tailored for the target data.
309
+
310
+ Parameters
311
+ ----------
312
+ coords : str
313
+ The name of the coordinates.
314
+ target_data : dict
315
+ The corresponding item of the data object to which the coordinates
316
+ will be applied.
317
+ extra_input : dict, optional
318
+ The other fields in the data object. Useful for accessing other
319
+ data kinds that needs to be used in the computation of coordinates
320
+ (default None).
321
+
322
+ Returns
323
+ -------
324
+ numpy.ndarray
325
+ The coordinates.
326
+ list of str
327
+ The names of the VOIs.
328
+
329
+ Raises
330
+ ------
331
+ RuntimeError
332
+ If warping specification required for warping using ANTs, is not
333
+ found.
334
+ ValueError
335
+ If ``extra_input`` is None when ``target_data``'s space is native.
336
+
337
+ """
338
+ # Load the coordinates
339
+ seeds, labels, _ = self.load(name=coords)
340
+
341
+ # Transform coordinate if target data is native
342
+ if target_data["space"] == "native":
343
+ # Check for extra inputs
344
+ if extra_input is None:
345
+ raise_error(
346
+ "No extra input provided, requires `Warp` and `T1w` "
347
+ "data types in particular for transformation to "
348
+ f"{target_data['space']} space for further computation."
349
+ )
350
+
351
+ # Get native space warper spec
352
+ warper_spec = get_native_warper(
353
+ target_data=target_data,
354
+ other_data=extra_input,
355
+ )
356
+ # Conditional for warping tool implementation
357
+ if warper_spec["warper"] == "fsl":
358
+ seeds = FSLCoordinatesWarper().warp(
359
+ seeds=seeds,
360
+ target_data=target_data,
361
+ warp_data=warper_spec,
362
+ )
363
+ elif warper_spec["warper"] == "ants":
364
+ # Requires the inverse warp
365
+ inverse_warper_spec = get_native_warper(
366
+ target_data=target_data,
367
+ other_data=extra_input,
368
+ inverse=True,
369
+ )
370
+ # Check warper
371
+ if inverse_warper_spec["warper"] != "ants":
372
+ raise_error(
373
+ klass=RuntimeError,
374
+ msg=(
375
+ "Warping specification mismatch for native space "
376
+ "warping of coordinates using ANTs."
377
+ ),
378
+ )
379
+ seeds = ANTsCoordinatesWarper().warp(
380
+ seeds=seeds,
381
+ target_data=target_data,
382
+ warp_data=inverse_warper_spec,
383
+ )
384
+
385
+ return seeds, labels
@@ -0,0 +1,81 @@
1
+ """Provide class for coordinates space warping via FSL FLIRT."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+ from numpy.typing import ArrayLike
10
+
11
+ from ...pipeline import WorkDirManager
12
+ from ...utils import logger, run_ext_cmd
13
+
14
+
15
+ __all__ = ["FSLCoordinatesWarper"]
16
+
17
+
18
+ class FSLCoordinatesWarper:
19
+ """Class for coordinates space warping via FSL FLIRT.
20
+
21
+ This class uses FSL FLIRT's ``img2imgcoord`` for transformation.
22
+
23
+ """
24
+
25
+ def warp(
26
+ self,
27
+ seeds: ArrayLike,
28
+ target_data: dict[str, Any],
29
+ warp_data: dict[str, Any],
30
+ ) -> ArrayLike:
31
+ """Warp ``seeds`` to correct space.
32
+
33
+ Parameters
34
+ ----------
35
+ seeds : array-like
36
+ The coordinates to transform.
37
+ target_data : dict
38
+ The corresponding item of the data object to which the coordinates
39
+ will be applied.
40
+ warp_data : dict
41
+ The warp data item of the data object.
42
+
43
+ Returns
44
+ -------
45
+ numpy.ndarray
46
+ The transformed coordinates.
47
+
48
+ """
49
+ logger.debug("Using FSL for coordinates transformation")
50
+
51
+ # Create element-specific tempdir for storing post-warping assets
52
+ element_tempdir = WorkDirManager().get_element_tempdir(
53
+ prefix="fsl_coordinates_warper"
54
+ )
55
+
56
+ # Save existing coordinates to a tempfile
57
+ pretransform_coordinates_path = (
58
+ element_tempdir / "pretransform_coordinates.txt"
59
+ )
60
+ np.savetxt(pretransform_coordinates_path, seeds)
61
+
62
+ # Create a tempfile for transformed coordinates output
63
+ transformed_coords_path = (
64
+ element_tempdir / "coordinates_transformed.txt"
65
+ )
66
+ # Set img2imgcoord command
67
+ img2imgcoord_cmd = [
68
+ "cat",
69
+ f"{pretransform_coordinates_path.resolve()}",
70
+ "| img2imgcoord -mm",
71
+ f"-src {target_data['path'].resolve()}",
72
+ f"-dest {target_data['reference']['path'].resolve()}",
73
+ f"-warp {warp_data['path'].resolve()}",
74
+ f"> {transformed_coords_path.resolve()};",
75
+ f"sed -i 1d {transformed_coords_path.resolve()}",
76
+ ]
77
+ # Call img2imgcoord
78
+ run_ext_cmd(name="img2imgcoord", cmd=img2imgcoord_cmd)
79
+
80
+ # Load coordinates
81
+ return np.loadtxt(transformed_coords_path)
@@ -1,52 +1,47 @@
1
1
  """Provide tests for coordinates."""
2
2
 
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # Synchon Mandal <s.mandal@fz-juelich.de>
4
5
  # License: AGPL
5
6
 
6
7
  import numpy as np
7
8
  import pytest
8
9
  from numpy.testing import assert_array_equal
9
10
 
10
- from junifer.data.coordinates import (
11
- get_coordinates,
12
- list_coordinates,
13
- load_coordinates,
14
- register_coordinates,
15
- )
11
+ from junifer.data import CoordinatesRegistry
16
12
  from junifer.datareader import DefaultDataReader
17
13
  from junifer.testing.datagrabbers import OasisVBMTestingDataGrabber
18
14
 
19
15
 
20
- def test_register_coordinates_built_in_check() -> None:
16
+ def test_register_built_in_check() -> None:
21
17
  """Test coordinates registration check for built-in coordinates."""
22
18
  with pytest.raises(ValueError, match=r"built-in"):
23
- register_coordinates(
19
+ CoordinatesRegistry().register(
24
20
  name="DMNBuckner",
25
21
  coordinates=np.zeros(2),
26
22
  voi_names=["1", "2"],
27
23
  space="MNI",
28
- overwrite=True,
29
24
  )
30
25
 
31
26
 
32
- def test_register_coordinates_overwrite() -> None:
27
+ def test_register_overwrite() -> None:
33
28
  """Test coordinates registration check for overwriting."""
34
- register_coordinates(
29
+ CoordinatesRegistry().register(
35
30
  name="MyList",
36
31
  coordinates=np.zeros((2, 3)),
37
32
  voi_names=["roi1", "roi2"],
38
33
  space="MNI",
39
- overwrite=True,
40
34
  )
41
35
  with pytest.raises(ValueError, match=r"already registered"):
42
- register_coordinates(
36
+ CoordinatesRegistry().register(
43
37
  name="MyList",
44
38
  coordinates=np.ones((2, 3)),
45
39
  voi_names=["roi2", "roi3"],
46
40
  space="MNI",
41
+ overwrite=False,
47
42
  )
48
43
 
49
- register_coordinates(
44
+ CoordinatesRegistry().register(
50
45
  name="MyList",
51
46
  coordinates=np.ones((2, 3)),
52
47
  voi_names=["roi2", "roi3"],
@@ -54,16 +49,16 @@ def test_register_coordinates_overwrite() -> None:
54
49
  overwrite=True,
55
50
  )
56
51
 
57
- coord, names, space = load_coordinates("MyList")
52
+ coord, names, space = CoordinatesRegistry().load("MyList")
58
53
  assert_array_equal(coord, np.ones((2, 3)))
59
54
  assert names == ["roi2", "roi3"]
60
55
  assert space == "MNI"
61
56
 
62
57
 
63
- def test_register_coordinates_valid_input() -> None:
58
+ def test_register_valid_input() -> None:
64
59
  """Test coordinates registration check for valid input."""
65
60
  with pytest.raises(TypeError, match=r"numpy.ndarray"):
66
- register_coordinates(
61
+ CoordinatesRegistry().register(
67
62
  name="MyList",
68
63
  coordinates=[1, 2],
69
64
  voi_names=["roi1", "roi2"],
@@ -71,7 +66,7 @@ def test_register_coordinates_valid_input() -> None:
71
66
  overwrite=True,
72
67
  )
73
68
  with pytest.raises(ValueError, match=r"2D array"):
74
- register_coordinates(
69
+ CoordinatesRegistry().register(
75
70
  name="MyList",
76
71
  coordinates=np.zeros((2, 3, 4)),
77
72
  voi_names=["roi1", "roi2"],
@@ -80,7 +75,7 @@ def test_register_coordinates_valid_input() -> None:
80
75
  )
81
76
 
82
77
  with pytest.raises(ValueError, match=r"3 values"):
83
- register_coordinates(
78
+ CoordinatesRegistry().register(
84
79
  name="MyList",
85
80
  coordinates=np.zeros((2, 4)),
86
81
  voi_names=["roi1", "roi2"],
@@ -88,7 +83,7 @@ def test_register_coordinates_valid_input() -> None:
88
83
  overwrite=True,
89
84
  )
90
85
  with pytest.raises(ValueError, match=r"voi_names"):
91
- register_coordinates(
86
+ CoordinatesRegistry().register(
92
87
  name="MyList",
93
88
  coordinates=np.zeros((2, 3)),
94
89
  voi_names=["roi1", "roi2", "roi3"],
@@ -97,30 +92,28 @@ def test_register_coordinates_valid_input() -> None:
97
92
  )
98
93
 
99
94
 
100
- def test_list_coordinates() -> None:
95
+ def test_list() -> None:
101
96
  """Test listing of available coordinates."""
102
- available_coordinates = list_coordinates()
103
- assert "DMNBuckner" in available_coordinates
104
- assert "MultiTask" in available_coordinates
105
- assert "VigAtt" in available_coordinates
106
- assert "WM" in available_coordinates
97
+ assert {"DMNBuckner", "MultiTask", "VigAtt", "WM"}.issubset(
98
+ set(CoordinatesRegistry().list)
99
+ )
107
100
 
108
101
 
109
- def test_load_coordinates() -> None:
102
+ def test_load() -> None:
110
103
  """Test loading coordinates from file."""
111
- coord, names, space = load_coordinates("DMNBuckner")
104
+ coord, names, space = CoordinatesRegistry().load("DMNBuckner")
112
105
  assert coord.shape == (6, 3) # type: ignore
113
106
  assert names == ["PCC", "MPFC", "lAG", "rAG", "lHF", "rHF"]
114
107
  assert space == "MNI"
115
108
 
116
109
 
117
- def test_load_coordinates_nonexisting() -> None:
110
+ def test_load_nonexisting() -> None:
118
111
  """Test loading coordinates that not exist."""
119
112
  with pytest.raises(ValueError, match=r"not found"):
120
- load_coordinates("NonExisting")
113
+ CoordinatesRegistry().load("NonExisting")
121
114
 
122
115
 
123
- def test_get_coordinates() -> None:
116
+ def test_get() -> None:
124
117
  """Test tailored coordinates fetch."""
125
118
  reader = DefaultDataReader()
126
119
  with OasisVBMTestingDataGrabber() as dg:
@@ -128,11 +121,11 @@ def test_get_coordinates() -> None:
128
121
  element_data = reader.fit_transform(element)
129
122
  vbm_gm = element_data["VBM_GM"]
130
123
  # Get tailored coordinates
131
- tailored_coords, tailored_labels = get_coordinates(
124
+ tailored_coords, tailored_labels = CoordinatesRegistry().get(
132
125
  coords="DMNBuckner", target_data=vbm_gm
133
126
  )
134
127
  # Get raw coordinates
135
- raw_coords, raw_labels, _ = load_coordinates("DMNBuckner")
128
+ raw_coords, raw_labels, _ = CoordinatesRegistry().load("DMNBuckner")
136
129
  # Both tailored and raw should be same for now
137
130
  assert_array_equal(tailored_coords, raw_coords)
138
131
  assert tailored_labels == raw_labels
@@ -0,0 +1,9 @@
1
+ """Masks."""
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
+ "MaskRegistry",
3
+ "compute_brain_mask",
4
+ ]
5
+
6
+ from ._masks import MaskRegistry, compute_brain_mask