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
@@ -3,8 +3,8 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import Dict, List
7
6
 
7
+ from ..typing import DataGrabberPatterns
8
8
  from ..utils import logger, raise_error, warn_with_log
9
9
 
10
10
 
@@ -33,10 +33,12 @@ PATTERNS_SCHEMA = {
33
33
  "mandatory": ["pattern", "format"],
34
34
  "optional": ["mappings"],
35
35
  },
36
+ "reference": {"mandatory": ["pattern"], "optional": []},
37
+ "prewarp_space": {"mandatory": [], "optional": []},
36
38
  },
37
39
  },
38
40
  "Warp": {
39
- "mandatory": ["pattern", "src", "dst"],
41
+ "mandatory": ["pattern", "src", "dst", "warper"],
40
42
  "optional": {},
41
43
  },
42
44
  "VBM_GM": {
@@ -72,7 +74,7 @@ PATTERNS_SCHEMA = {
72
74
  class PatternValidationMixin:
73
75
  """Mixin class for pattern validation."""
74
76
 
75
- def _validate_types(self, types: List[str]) -> None:
77
+ def _validate_types(self, types: list[str]) -> None:
76
78
  """Validate the types.
77
79
 
78
80
  Parameters
@@ -95,8 +97,8 @@ class PatternValidationMixin:
95
97
 
96
98
  def _validate_replacements(
97
99
  self,
98
- replacements: List[str],
99
- patterns: Dict[str, Dict[str, str]],
100
+ replacements: list[str],
101
+ patterns: DataGrabberPatterns,
100
102
  partial_pattern_ok: bool,
101
103
  ) -> None:
102
104
  """Validate the replacements.
@@ -132,45 +134,57 @@ class PatternValidationMixin:
132
134
 
133
135
  if any(not isinstance(x, str) for x in replacements):
134
136
  raise_error(
135
- msg="`replacements` must be a list of strings.",
137
+ msg="`replacements` must be a list of strings",
136
138
  klass=TypeError,
137
139
  )
138
140
 
141
+ # Make a list of all patterns recursively
142
+ all_patterns = []
143
+ for dtype_val in patterns.values():
144
+ # Conditional for list dtype vals like Warp
145
+ if isinstance(dtype_val, list):
146
+ for entry in dtype_val:
147
+ all_patterns.append(entry.get("pattern", ""))
148
+ else:
149
+ all_patterns.append(dtype_val.get("pattern", ""))
150
+ # Check for stray replacements
139
151
  for x in replacements:
140
- if all(
141
- x not in y
142
- for y in [
143
- data_type_val.get("pattern", "")
144
- for data_type_val in patterns.values()
145
- ]
146
- ):
152
+ if all(x not in y for y in all_patterns):
147
153
  if partial_pattern_ok:
148
154
  warn_with_log(
149
155
  f"Replacement: `{x}` is not part of any pattern, "
150
156
  "things might not work as expected if you are unsure "
151
- "of what you are doing"
157
+ "of what you are doing."
152
158
  )
153
159
  else:
154
160
  raise_error(
155
- msg=f"Replacement: {x} is not part of any pattern."
161
+ msg=f"Replacement: `{x}` is not part of any pattern"
156
162
  )
157
163
 
158
164
  # Check that at least one pattern has all the replacements
159
165
  at_least_one = False
160
- for data_type_val in patterns.values():
161
- if all(
162
- x in data_type_val.get("pattern", "") for x in replacements
163
- ):
164
- at_least_one = True
166
+ for dtype_val in patterns.values():
167
+ # Conditional for list dtype vals like Warp
168
+ if isinstance(dtype_val, list):
169
+ for entry in dtype_val:
170
+ if all(
171
+ x in entry.get("pattern", "") for x in replacements
172
+ ):
173
+ at_least_one = True
174
+ else:
175
+ if all(
176
+ x in dtype_val.get("pattern", "") for x in replacements
177
+ ):
178
+ at_least_one = True
165
179
  if not at_least_one and not partial_pattern_ok:
166
180
  raise_error(
167
- msg="At least one pattern must contain all replacements."
181
+ msg="At least one pattern must contain all replacements"
168
182
  )
169
183
 
170
184
  def _validate_mandatory_keys(
171
185
  self,
172
- keys: List[str],
173
- schema: List[str],
186
+ keys: list[str],
187
+ schema: list[str],
174
188
  data_type: str,
175
189
  partial_pattern_ok: bool = False,
176
190
  ) -> None:
@@ -207,7 +221,7 @@ class PatternValidationMixin:
207
221
  warn_with_log(
208
222
  f"Mandatory key: `{key}` not found for {data_type}, "
209
223
  "things might not work as expected if you are unsure "
210
- "of what you are doing"
224
+ "of what you are doing."
211
225
  )
212
226
  else:
213
227
  raise_error(
@@ -215,10 +229,10 @@ class PatternValidationMixin:
215
229
  klass=KeyError,
216
230
  )
217
231
  else:
218
- logger.debug(f"Mandatory key: `{key}` found for {data_type}")
232
+ logger.debug(f"Mandatory key: `{key}` found for {data_type}.")
219
233
 
220
234
  def _identify_stray_keys(
221
- self, keys: List[str], schema: List[str], data_type: str
235
+ self, keys: list[str], schema: list[str], data_type: str
222
236
  ) -> None:
223
237
  """Identify stray keys.
224
238
 
@@ -249,9 +263,9 @@ class PatternValidationMixin:
249
263
 
250
264
  def validate_patterns(
251
265
  self,
252
- types: List[str],
253
- replacements: List[str],
254
- patterns: Dict[str, Dict[str, str]],
266
+ types: list[str],
267
+ replacements: list[str],
268
+ patterns: DataGrabberPatterns,
255
269
  partial_pattern_ok: bool = False,
256
270
  ) -> None:
257
271
  """Validate the patterns.
@@ -298,87 +312,185 @@ class PatternValidationMixin:
298
312
  msg="`patterns` must contain all `types`", klass=ValueError
299
313
  )
300
314
  # Check against schema
301
- for data_type_key, data_type_val in patterns.items():
315
+ for dtype_key, dtype_val in patterns.items():
302
316
  # Check if valid data type is provided
303
- if data_type_key not in PATTERNS_SCHEMA:
317
+ if dtype_key not in PATTERNS_SCHEMA:
304
318
  raise_error(
305
- f"Unknown data type: {data_type_key}, "
319
+ f"Unknown data type: {dtype_key}, "
306
320
  f"should be one of: {list(PATTERNS_SCHEMA.keys())}"
307
321
  )
308
- # Check mandatory keys for data type
309
- self._validate_mandatory_keys(
310
- keys=list(data_type_val),
311
- schema=PATTERNS_SCHEMA[data_type_key]["mandatory"],
312
- data_type=data_type_key,
313
- partial_pattern_ok=partial_pattern_ok,
314
- )
315
- # Check optional keys for data type
316
- for optional_key, optional_val in PATTERNS_SCHEMA[data_type_key][
317
- "optional"
318
- ].items():
319
- if optional_key not in data_type_val:
320
- logger.debug(
321
- f"Optional key: `{optional_key}` missing for "
322
- f"{data_type_key}"
323
- )
324
- else:
325
- logger.debug(
326
- f"Optional key: `{optional_key}` found for "
327
- f"{data_type_key}"
328
- )
329
- # Set nested type name for easier access
330
- nested_data_type = f"{data_type_key}.{optional_key}"
331
- nested_mandatory_keys_schema = PATTERNS_SCHEMA[
332
- data_type_key
333
- ]["optional"][optional_key]["mandatory"]
334
- nested_optional_keys_schema = PATTERNS_SCHEMA[
335
- data_type_key
336
- ]["optional"][optional_key]["optional"]
337
- # Check mandatory keys for nested type
322
+ # Conditional for list dtype vals like Warp
323
+ if isinstance(dtype_val, list):
324
+ for idx, entry in enumerate(dtype_val):
325
+ # Check mandatory keys for data type
338
326
  self._validate_mandatory_keys(
339
- keys=list(optional_val["mandatory"]),
340
- schema=nested_mandatory_keys_schema,
341
- data_type=nested_data_type,
327
+ keys=list(entry),
328
+ schema=PATTERNS_SCHEMA[dtype_key]["mandatory"],
329
+ data_type=f"{dtype_key}.{idx}",
342
330
  partial_pattern_ok=partial_pattern_ok,
343
331
  )
344
- # Check optional keys for nested type
345
- for nested_optional_key in nested_optional_keys_schema:
346
- if nested_optional_key not in optional_val["optional"]:
332
+ # Check optional keys for data type
333
+ for optional_key, optional_val in PATTERNS_SCHEMA[
334
+ dtype_key
335
+ ]["optional"].items():
336
+ if optional_key not in entry:
347
337
  logger.debug(
348
- f"Optional key: `{nested_optional_key}` "
349
- f"missing for {nested_data_type}"
338
+ f"Optional key: `{optional_key}` missing for "
339
+ f"{dtype_key}.{idx}"
350
340
  )
351
341
  else:
352
342
  logger.debug(
353
- f"Optional key: `{nested_optional_key}` found "
354
- f"for {nested_data_type}"
343
+ f"Optional key: `{optional_key}` found for "
344
+ f"{dtype_key}.{idx}"
345
+ )
346
+ # Set nested type name for easier access
347
+ nested_dtype = f"{dtype_key}.{idx}.{optional_key}"
348
+ nested_mandatory_keys_schema = PATTERNS_SCHEMA[
349
+ dtype_key
350
+ ]["optional"][optional_key]["mandatory"]
351
+ nested_optional_keys_schema = PATTERNS_SCHEMA[
352
+ dtype_key
353
+ ]["optional"][optional_key]["optional"]
354
+ # Check mandatory keys for nested type
355
+ self._validate_mandatory_keys(
356
+ keys=list(optional_val["mandatory"]),
357
+ schema=nested_mandatory_keys_schema,
358
+ data_type=nested_dtype,
359
+ partial_pattern_ok=partial_pattern_ok,
360
+ )
361
+ # Check optional keys for nested type
362
+ for (
363
+ nested_optional_key
364
+ ) in nested_optional_keys_schema:
365
+ if (
366
+ nested_optional_key
367
+ not in optional_val["optional"]
368
+ ):
369
+ logger.debug(
370
+ f"Optional key: "
371
+ f"`{nested_optional_key}` missing for "
372
+ f"{nested_dtype}"
373
+ )
374
+ else:
375
+ logger.debug(
376
+ f"Optional key: "
377
+ f"`{nested_optional_key}` found for "
378
+ f"{nested_dtype}"
379
+ )
380
+ # Check stray key for nested data type
381
+ self._identify_stray_keys(
382
+ keys=(
383
+ optional_val["mandatory"]
384
+ + optional_val["optional"]
385
+ ),
386
+ schema=(
387
+ nested_mandatory_keys_schema
388
+ + nested_optional_keys_schema
389
+ ),
390
+ data_type=nested_dtype,
355
391
  )
356
- # Check stray key for nested data type
392
+ # Check stray key for data type
357
393
  self._identify_stray_keys(
358
- keys=optional_val["mandatory"]
359
- + optional_val["optional"],
360
- schema=nested_mandatory_keys_schema
361
- + nested_optional_keys_schema,
362
- data_type=nested_data_type,
394
+ keys=list(entry.keys()),
395
+ schema=(
396
+ PATTERNS_SCHEMA[dtype_key]["mandatory"]
397
+ + list(
398
+ PATTERNS_SCHEMA[dtype_key]["optional"].keys()
399
+ )
400
+ ),
401
+ data_type=dtype_key,
363
402
  )
364
- # Check stray key for data type
365
- self._identify_stray_keys(
366
- keys=list(data_type_val.keys()),
367
- schema=(
368
- PATTERNS_SCHEMA[data_type_key]["mandatory"]
369
- + list(PATTERNS_SCHEMA[data_type_key]["optional"].keys())
370
- ),
371
- data_type=data_type_key,
372
- )
373
- # Wildcard check in patterns
374
- if "}*" in data_type_val.get("pattern", ""):
375
- raise_error(
376
- msg=(
377
- f"`{data_type_key}.pattern` must not contain `*` "
378
- "following a replacement"
403
+ # Wildcard check in patterns
404
+ if "}*" in entry.get("pattern", ""):
405
+ raise_error(
406
+ msg=(
407
+ f"`{dtype_key}.pattern` must not contain `*` "
408
+ "following a replacement"
409
+ ),
410
+ klass=ValueError,
411
+ )
412
+ else:
413
+ # Check mandatory keys for data type
414
+ self._validate_mandatory_keys(
415
+ keys=list(dtype_val),
416
+ schema=PATTERNS_SCHEMA[dtype_key]["mandatory"],
417
+ data_type=dtype_key,
418
+ partial_pattern_ok=partial_pattern_ok,
419
+ )
420
+ # Check optional keys for data type
421
+ for optional_key, optional_val in PATTERNS_SCHEMA[dtype_key][
422
+ "optional"
423
+ ].items():
424
+ if optional_key not in dtype_val:
425
+ logger.debug(
426
+ f"Optional key: `{optional_key}` missing for "
427
+ f"{dtype_key}."
428
+ )
429
+ else:
430
+ logger.debug(
431
+ f"Optional key: `{optional_key}` found for "
432
+ f"{dtype_key}."
433
+ )
434
+ # Set nested type name for easier access
435
+ nested_dtype = f"{dtype_key}.{optional_key}"
436
+ nested_mandatory_keys_schema = PATTERNS_SCHEMA[
437
+ dtype_key
438
+ ]["optional"][optional_key]["mandatory"]
439
+ nested_optional_keys_schema = PATTERNS_SCHEMA[
440
+ dtype_key
441
+ ]["optional"][optional_key]["optional"]
442
+ # Check mandatory keys for nested type
443
+ self._validate_mandatory_keys(
444
+ keys=list(optional_val["mandatory"]),
445
+ schema=nested_mandatory_keys_schema,
446
+ data_type=nested_dtype,
447
+ partial_pattern_ok=partial_pattern_ok,
448
+ )
449
+ # Check optional keys for nested type
450
+ for nested_optional_key in nested_optional_keys_schema:
451
+ if (
452
+ nested_optional_key
453
+ not in optional_val["optional"]
454
+ ):
455
+ logger.debug(
456
+ f"Optional key: `{nested_optional_key}` "
457
+ f"missing for {nested_dtype}"
458
+ )
459
+ else:
460
+ logger.debug(
461
+ f"Optional key: `{nested_optional_key}` "
462
+ f"found for {nested_dtype}"
463
+ )
464
+ # Check stray key for nested data type
465
+ self._identify_stray_keys(
466
+ keys=(
467
+ optional_val["mandatory"]
468
+ + optional_val["optional"]
469
+ ),
470
+ schema=(
471
+ nested_mandatory_keys_schema
472
+ + nested_optional_keys_schema
473
+ ),
474
+ data_type=nested_dtype,
475
+ )
476
+ # Check stray key for data type
477
+ self._identify_stray_keys(
478
+ keys=list(dtype_val.keys()),
479
+ schema=(
480
+ PATTERNS_SCHEMA[dtype_key]["mandatory"]
481
+ + list(PATTERNS_SCHEMA[dtype_key]["optional"].keys())
379
482
  ),
380
- klass=ValueError,
483
+ data_type=dtype_key,
381
484
  )
485
+ # Wildcard check in patterns
486
+ if "}*" in dtype_val.get("pattern", ""):
487
+ raise_error(
488
+ msg=(
489
+ f"`{dtype_key}.pattern` must not contain `*` "
490
+ "following a replacement"
491
+ ),
492
+ klass=ValueError,
493
+ )
382
494
 
383
495
  # Validate replacements
384
496
  self._validate_replacements(
File without changes
@@ -3,20 +3,21 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ import warnings
6
7
  from pathlib import Path
7
- from typing import Type
8
8
 
9
9
  import datalad.api as dl
10
10
  import pytest
11
11
 
12
12
  from junifer.datagrabber import DataladDataGrabber
13
+ from junifer.utils import config
13
14
 
14
15
 
15
16
  _testing_dataset = {
16
17
  "example_bids": {
17
18
  "uri": "https://gin.g-node.org/juaml/datalad-example-bids",
18
- "commit": "b87897cbe51bf0ee5514becaa5c7dd76491db5ad",
19
- "id": "8fddff30-6993-420a-9d1e-b5b028c59468",
19
+ "commit": "3f288c8725207ae0c9b3616e093e78cda192b570",
20
+ "id": "582b9696-f13f-42e4-9587-b4e62aa2a8e7",
20
21
  },
21
22
  "example_bids_ses": {
22
23
  "uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
@@ -27,7 +28,7 @@ _testing_dataset = {
27
28
 
28
29
 
29
30
  @pytest.fixture
30
- def concrete_datagrabber() -> Type[DataladDataGrabber]:
31
+ def concrete_datagrabber() -> type[DataladDataGrabber]:
31
32
  """Return a concrete datalad-based DataGrabber.
32
33
 
33
34
  Returns
@@ -69,7 +70,7 @@ def concrete_datagrabber() -> Type[DataladDataGrabber]:
69
70
 
70
71
 
71
72
  def test_DataladDataGrabber_install_errors(
72
- tmp_path: Path, concrete_datagrabber: Type
73
+ tmp_path: Path, concrete_datagrabber: type
73
74
  ) -> None:
74
75
  """Test DataladDataGrabber install errors / warnings.
75
76
 
@@ -95,6 +96,12 @@ def test_DataladDataGrabber_install_errors(
95
96
  with pytest.raises(ValueError, match=r"different ID"):
96
97
  with dg:
97
98
  pass
99
+ # Set config to skip id check and test
100
+ config.set(key="datagrabber.skipidcheck", val=True)
101
+ with dg:
102
+ pass
103
+ # Reset config
104
+ config.delete("datagrabber.skipidcheck")
98
105
 
99
106
  elem1_t1w = datadir / "example_bids/sub-01/anat/sub-01_T1w.nii.gz"
100
107
  elem1_t1w.unlink()
@@ -105,10 +112,18 @@ def test_DataladDataGrabber_install_errors(
105
112
  with pytest.warns(RuntimeWarning, match=r"one file is not clean"):
106
113
  with dg:
107
114
  pass
115
+ # Set config to skip dirty check and test
116
+ with warnings.catch_warnings():
117
+ warnings.simplefilter("error")
118
+ config.set(key="datagrabber.skipdirtycheck", val=True)
119
+ with dg:
120
+ pass
121
+ # Reset config
122
+ config.delete("datagrabber.skipdirtycheck")
108
123
 
109
124
 
110
125
  def test_DataladDataGrabber_clone_cleanup(
111
- tmp_path: Path, concrete_datagrabber: Type
126
+ tmp_path: Path, concrete_datagrabber: type
112
127
  ) -> None:
113
128
  """Test DataladDataGrabber clone and remove.
114
129
 
@@ -157,7 +172,7 @@ def test_DataladDataGrabber_clone_cleanup(
157
172
 
158
173
 
159
174
  def test_DataladDataGrabber_clone_create_cleanup(
160
- concrete_datagrabber: Type,
175
+ concrete_datagrabber: type,
161
176
  ) -> None:
162
177
  """Test DataladDataGrabber tempdir clone and remove.
163
178
 
@@ -203,7 +218,7 @@ def test_DataladDataGrabber_clone_create_cleanup(
203
218
 
204
219
 
205
220
  def test_DataladDataGrabber_previously_cloned(
206
- tmp_path: Path, concrete_datagrabber: Type
221
+ tmp_path: Path, concrete_datagrabber: type
207
222
  ) -> None:
208
223
  """Test DataladDataGrabber on cloned dataset.
209
224
 
@@ -247,7 +262,7 @@ def test_DataladDataGrabber_previously_cloned(
247
262
  meta = elem1["BOLD"]["meta"]
248
263
  assert "datagrabber" in meta
249
264
  assert "datalad_dirty" in meta["datagrabber"]
250
- assert meta["datagrabber"]["datalad_dirty"] is False
265
+ assert meta["datagrabber"]["datalad_dirty"] is True
251
266
  assert "datalad_commit_id" in meta["datagrabber"]
252
267
  assert meta["datagrabber"]["datalad_commit_id"] == commit
253
268
  assert "datalad_id" in meta["datagrabber"]
@@ -272,7 +287,7 @@ def test_DataladDataGrabber_previously_cloned(
272
287
 
273
288
 
274
289
  def test_DataladDataGrabber_previously_cloned_and_get(
275
- tmp_path: Path, concrete_datagrabber: Type
290
+ tmp_path: Path, concrete_datagrabber: type
276
291
  ) -> None:
277
292
  """Test DataladDataGrabber on cloned dataset with files present.
278
293
 
@@ -327,7 +342,7 @@ def test_DataladDataGrabber_previously_cloned_and_get(
327
342
  meta = elem1["BOLD"]["meta"]
328
343
  assert "datagrabber" in meta
329
344
  assert "datalad_dirty" in meta["datagrabber"]
330
- assert meta["datagrabber"]["datalad_dirty"] is False
345
+ assert meta["datagrabber"]["datalad_dirty"] is True
331
346
  assert "datalad_commit_id" in meta["datagrabber"]
332
347
  assert meta["datagrabber"]["datalad_commit_id"] == commit
333
348
  assert "datalad_id" in meta["datagrabber"]
@@ -355,7 +370,7 @@ def test_DataladDataGrabber_previously_cloned_and_get(
355
370
 
356
371
 
357
372
  def test_DataladDataGrabber_previously_cloned_and_get_dirty(
358
- tmp_path: Path, concrete_datagrabber: Type
373
+ tmp_path: Path, concrete_datagrabber: type
359
374
  ) -> None:
360
375
  """Test DataladDataGrabber on a dirty cloned dataset.
361
376
 
@@ -3,7 +3,7 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import List, Optional, Union
6
+ from typing import Optional, Union
7
7
 
8
8
  import pytest
9
9
 
@@ -116,7 +116,12 @@ def test_DMCC13Benchmark(
116
116
  data_file_names.extend(
117
117
  [
118
118
  "sub-01_desc-preproc_T1w.nii.gz",
119
- "sub-01_from-MNI152NLin2009cAsym_to-T1w_mode-image_xfm.h5",
119
+ [
120
+ "sub-01_from-MNI152NLin2009cAsym_to-T1w"
121
+ "_mode-image_xfm.h5",
122
+ "sub-01_from-T1w_to-MNI152NLin2009cAsym"
123
+ "_mode-image_xfm.h5",
124
+ ],
120
125
  ]
121
126
  )
122
127
  else:
@@ -127,14 +132,26 @@ def test_DMCC13Benchmark(
127
132
  for data_type, data_file_name in zip(data_types, data_file_names):
128
133
  # Assert data type
129
134
  assert data_type in out
130
- # Assert data file path exists
131
- assert out[data_type]["path"].exists()
132
- # Assert data file path is a file
133
- assert out[data_type]["path"].is_file()
134
- # Assert data file name
135
- assert out[data_type]["path"].name == data_file_name
136
- # Assert metadata
137
- assert "meta" in out[data_type]
135
+ # Conditional for Warp
136
+ if data_type == "Warp":
137
+ for idx, fname in enumerate(data_file_name):
138
+ # Assert data file path exists
139
+ assert out[data_type][idx]["path"].exists()
140
+ # Assert data file path is a file
141
+ assert out[data_type][idx]["path"].is_file()
142
+ # Assert data file name
143
+ assert out[data_type][idx]["path"].name == fname
144
+ # Assert metadata
145
+ assert "meta" in out[data_type][idx]
146
+ else:
147
+ # Assert data file path exists
148
+ assert out[data_type]["path"].exists()
149
+ # Assert data file path is a file
150
+ assert out[data_type]["path"].is_file()
151
+ # Assert data file name
152
+ assert out[data_type]["path"].name == data_file_name
153
+ # Assert metadata
154
+ assert "meta" in out[data_type]
138
155
 
139
156
  # Check BOLD nested data types
140
157
  for type_, file_name in zip(
@@ -201,7 +218,7 @@ def test_DMCC13Benchmark(
201
218
  ],
202
219
  )
203
220
  def test_DMCC13Benchmark_partial_data_access(
204
- types: Union[str, List[str]],
221
+ types: Union[str, list[str]],
205
222
  native_t1w: bool,
206
223
  ) -> None:
207
224
  """Test DMCC13Benchmark DataGrabber partial data access.