junifer 0.0.4.dev831__py3-none-any.whl → 0.0.5__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 (206) hide show
  1. junifer/__init__.py +17 -0
  2. junifer/_version.py +2 -2
  3. junifer/api/__init__.py +4 -1
  4. junifer/api/cli.py +91 -1
  5. junifer/api/decorators.py +9 -0
  6. junifer/api/functions.py +56 -10
  7. junifer/api/parser.py +3 -0
  8. junifer/api/queue_context/__init__.py +4 -1
  9. junifer/api/queue_context/gnu_parallel_local_adapter.py +16 -6
  10. junifer/api/queue_context/htcondor_adapter.py +16 -5
  11. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +41 -12
  12. junifer/api/queue_context/tests/test_htcondor_adapter.py +48 -15
  13. junifer/api/res/afni/run_afni_docker.sh +1 -1
  14. junifer/api/res/ants/run_ants_docker.sh +1 -1
  15. junifer/api/res/freesurfer/mri_binarize +3 -0
  16. junifer/api/res/freesurfer/mri_mc +3 -0
  17. junifer/api/res/freesurfer/mri_pretess +3 -0
  18. junifer/api/res/freesurfer/mris_convert +3 -0
  19. junifer/api/res/freesurfer/run_freesurfer_docker.sh +61 -0
  20. junifer/api/res/fsl/run_fsl_docker.sh +1 -1
  21. junifer/api/res/{run_conda.sh → run_conda.bash} +1 -1
  22. junifer/api/res/run_conda.zsh +23 -0
  23. junifer/api/res/run_venv.bash +22 -0
  24. junifer/api/res/{run_venv.sh → run_venv.zsh} +1 -1
  25. junifer/api/tests/test_api_utils.py +4 -2
  26. junifer/api/tests/test_cli.py +83 -0
  27. junifer/api/tests/test_functions.py +27 -2
  28. junifer/configs/__init__.py +1 -1
  29. junifer/configs/juseless/__init__.py +4 -1
  30. junifer/configs/juseless/datagrabbers/__init__.py +10 -1
  31. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +4 -3
  32. junifer/configs/juseless/datagrabbers/camcan_vbm.py +3 -0
  33. junifer/configs/juseless/datagrabbers/ixi_vbm.py +4 -3
  34. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +1 -3
  35. junifer/configs/juseless/datagrabbers/ucla.py +12 -9
  36. junifer/configs/juseless/datagrabbers/ukb_vbm.py +3 -0
  37. junifer/data/__init__.py +21 -1
  38. junifer/data/coordinates.py +10 -19
  39. junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
  40. junifer/data/masks.py +58 -87
  41. junifer/data/parcellations.py +14 -3
  42. junifer/data/template_spaces.py +4 -1
  43. junifer/data/tests/test_masks.py +26 -37
  44. junifer/data/utils.py +3 -0
  45. junifer/datagrabber/__init__.py +18 -1
  46. junifer/datagrabber/aomic/__init__.py +3 -0
  47. junifer/datagrabber/aomic/id1000.py +70 -37
  48. junifer/datagrabber/aomic/piop1.py +69 -36
  49. junifer/datagrabber/aomic/piop2.py +71 -38
  50. junifer/datagrabber/aomic/tests/test_id1000.py +44 -100
  51. junifer/datagrabber/aomic/tests/test_piop1.py +65 -108
  52. junifer/datagrabber/aomic/tests/test_piop2.py +45 -102
  53. junifer/datagrabber/base.py +13 -6
  54. junifer/datagrabber/datalad_base.py +13 -1
  55. junifer/datagrabber/dmcc13_benchmark.py +36 -53
  56. junifer/datagrabber/hcp1200/__init__.py +3 -0
  57. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -0
  58. junifer/datagrabber/hcp1200/hcp1200.py +4 -1
  59. junifer/datagrabber/multiple.py +45 -6
  60. junifer/datagrabber/pattern.py +170 -62
  61. junifer/datagrabber/pattern_datalad.py +25 -12
  62. junifer/datagrabber/pattern_validation_mixin.py +388 -0
  63. junifer/datagrabber/tests/test_datalad_base.py +4 -4
  64. junifer/datagrabber/tests/test_dmcc13_benchmark.py +46 -19
  65. junifer/datagrabber/tests/test_multiple.py +161 -84
  66. junifer/datagrabber/tests/test_pattern.py +45 -0
  67. junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
  68. junifer/datagrabber/tests/test_pattern_validation_mixin.py +249 -0
  69. junifer/datareader/__init__.py +4 -1
  70. junifer/datareader/default.py +95 -43
  71. junifer/external/BrainPrint/brainprint/__init__.py +4 -0
  72. junifer/external/BrainPrint/brainprint/_version.py +3 -0
  73. junifer/external/BrainPrint/brainprint/asymmetry.py +91 -0
  74. junifer/external/BrainPrint/brainprint/brainprint.py +441 -0
  75. junifer/external/BrainPrint/brainprint/surfaces.py +258 -0
  76. junifer/external/BrainPrint/brainprint/utils/__init__.py +1 -0
  77. junifer/external/BrainPrint/brainprint/utils/_config.py +112 -0
  78. junifer/external/BrainPrint/brainprint/utils/utils.py +188 -0
  79. junifer/external/__init__.py +1 -1
  80. junifer/external/nilearn/__init__.py +5 -1
  81. junifer/external/nilearn/junifer_connectivity_measure.py +483 -0
  82. junifer/external/nilearn/junifer_nifti_spheres_masker.py +23 -9
  83. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +1089 -0
  84. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +76 -1
  85. junifer/markers/__init__.py +23 -1
  86. junifer/markers/base.py +68 -28
  87. junifer/markers/brainprint.py +459 -0
  88. junifer/markers/collection.py +10 -2
  89. junifer/markers/complexity/__init__.py +10 -0
  90. junifer/markers/complexity/complexity_base.py +26 -43
  91. junifer/markers/complexity/hurst_exponent.py +3 -0
  92. junifer/markers/complexity/multiscale_entropy_auc.py +3 -0
  93. junifer/markers/complexity/perm_entropy.py +3 -0
  94. junifer/markers/complexity/range_entropy.py +3 -0
  95. junifer/markers/complexity/range_entropy_auc.py +3 -0
  96. junifer/markers/complexity/sample_entropy.py +3 -0
  97. junifer/markers/complexity/tests/test_hurst_exponent.py +11 -3
  98. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +11 -3
  99. junifer/markers/complexity/tests/test_perm_entropy.py +11 -3
  100. junifer/markers/complexity/tests/test_range_entropy.py +11 -3
  101. junifer/markers/complexity/tests/test_range_entropy_auc.py +11 -3
  102. junifer/markers/complexity/tests/test_sample_entropy.py +11 -3
  103. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +11 -3
  104. junifer/markers/complexity/weighted_perm_entropy.py +3 -0
  105. junifer/markers/ets_rss.py +27 -42
  106. junifer/markers/falff/__init__.py +3 -0
  107. junifer/markers/falff/_afni_falff.py +5 -2
  108. junifer/markers/falff/_junifer_falff.py +3 -0
  109. junifer/markers/falff/falff_base.py +20 -46
  110. junifer/markers/falff/falff_parcels.py +56 -27
  111. junifer/markers/falff/falff_spheres.py +60 -29
  112. junifer/markers/falff/tests/test_falff_parcels.py +39 -23
  113. junifer/markers/falff/tests/test_falff_spheres.py +39 -23
  114. junifer/markers/functional_connectivity/__init__.py +9 -0
  115. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +63 -60
  116. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +45 -32
  117. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +49 -36
  118. junifer/markers/functional_connectivity/functional_connectivity_base.py +71 -70
  119. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +34 -25
  120. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +40 -30
  121. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +11 -7
  122. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +27 -7
  123. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +28 -12
  124. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +35 -11
  125. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +36 -62
  126. junifer/markers/parcel_aggregation.py +47 -61
  127. junifer/markers/reho/__init__.py +3 -0
  128. junifer/markers/reho/_afni_reho.py +5 -2
  129. junifer/markers/reho/_junifer_reho.py +4 -1
  130. junifer/markers/reho/reho_base.py +8 -27
  131. junifer/markers/reho/reho_parcels.py +28 -17
  132. junifer/markers/reho/reho_spheres.py +27 -18
  133. junifer/markers/reho/tests/test_reho_parcels.py +8 -3
  134. junifer/markers/reho/tests/test_reho_spheres.py +8 -3
  135. junifer/markers/sphere_aggregation.py +43 -59
  136. junifer/markers/temporal_snr/__init__.py +3 -0
  137. junifer/markers/temporal_snr/temporal_snr_base.py +23 -32
  138. junifer/markers/temporal_snr/temporal_snr_parcels.py +9 -6
  139. junifer/markers/temporal_snr/temporal_snr_spheres.py +9 -6
  140. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +6 -3
  141. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +6 -3
  142. junifer/markers/tests/test_brainprint.py +58 -0
  143. junifer/markers/tests/test_collection.py +9 -8
  144. junifer/markers/tests/test_ets_rss.py +15 -9
  145. junifer/markers/tests/test_markers_base.py +17 -18
  146. junifer/markers/tests/test_parcel_aggregation.py +93 -32
  147. junifer/markers/tests/test_sphere_aggregation.py +72 -19
  148. junifer/onthefly/__init__.py +4 -1
  149. junifer/onthefly/read_transform.py +3 -0
  150. junifer/pipeline/__init__.py +9 -1
  151. junifer/pipeline/pipeline_step_mixin.py +21 -4
  152. junifer/pipeline/registry.py +3 -0
  153. junifer/pipeline/singleton.py +3 -0
  154. junifer/pipeline/tests/test_registry.py +1 -1
  155. junifer/pipeline/update_meta_mixin.py +3 -0
  156. junifer/pipeline/utils.py +67 -1
  157. junifer/pipeline/workdir_manager.py +3 -0
  158. junifer/preprocess/__init__.py +10 -2
  159. junifer/preprocess/base.py +6 -3
  160. junifer/preprocess/confounds/__init__.py +3 -0
  161. junifer/preprocess/confounds/fmriprep_confound_remover.py +47 -60
  162. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +72 -113
  163. junifer/preprocess/smoothing/__init__.py +9 -0
  164. junifer/preprocess/smoothing/_afni_smoothing.py +119 -0
  165. junifer/preprocess/smoothing/_fsl_smoothing.py +116 -0
  166. junifer/preprocess/smoothing/_nilearn_smoothing.py +69 -0
  167. junifer/preprocess/smoothing/smoothing.py +174 -0
  168. junifer/preprocess/smoothing/tests/test_smoothing.py +94 -0
  169. junifer/preprocess/warping/__init__.py +3 -0
  170. junifer/preprocess/warping/_ants_warper.py +3 -0
  171. junifer/preprocess/warping/_fsl_warper.py +3 -0
  172. junifer/stats.py +4 -1
  173. junifer/storage/__init__.py +9 -1
  174. junifer/storage/base.py +40 -1
  175. junifer/storage/hdf5.py +71 -9
  176. junifer/storage/pandas_base.py +3 -0
  177. junifer/storage/sqlite.py +3 -0
  178. junifer/storage/tests/test_hdf5.py +82 -10
  179. junifer/storage/utils.py +9 -0
  180. junifer/testing/__init__.py +4 -1
  181. junifer/testing/datagrabbers.py +13 -6
  182. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
  183. junifer/testing/utils.py +3 -0
  184. junifer/utils/__init__.py +13 -2
  185. junifer/utils/fs.py +3 -0
  186. junifer/utils/helpers.py +32 -1
  187. junifer/utils/logging.py +33 -4
  188. junifer/utils/tests/test_logging.py +8 -0
  189. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/METADATA +17 -16
  190. junifer-0.0.5.dist-info/RECORD +275 -0
  191. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/WHEEL +1 -1
  192. junifer/datagrabber/tests/test_datagrabber_utils.py +0 -218
  193. junifer/datagrabber/utils.py +0 -230
  194. junifer/preprocess/ants/__init__.py +0 -4
  195. junifer/preprocess/ants/ants_apply_transforms_warper.py +0 -185
  196. junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +0 -56
  197. junifer/preprocess/bold_warper.py +0 -265
  198. junifer/preprocess/fsl/__init__.py +0 -4
  199. junifer/preprocess/fsl/apply_warper.py +0 -179
  200. junifer/preprocess/fsl/tests/test_apply_warper.py +0 -45
  201. junifer/preprocess/tests/test_bold_warper.py +0 -159
  202. junifer-0.0.4.dev831.dist-info/RECORD +0 -257
  203. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/AUTHORS.rst +0 -0
  204. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/LICENSE.md +0 -0
  205. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/entry_points.txt +0 -0
  206. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,388 @@
1
+ """Provide mixin validation class for pattern-based DataGrabber."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import Dict, List
7
+
8
+ from ..utils import logger, raise_error, warn_with_log
9
+
10
+
11
+ __all__ = ["PatternValidationMixin"]
12
+
13
+
14
+ # Define schema for pattern-based datagrabber's patterns
15
+ PATTERNS_SCHEMA = {
16
+ "T1w": {
17
+ "mandatory": ["pattern", "space"],
18
+ "optional": {
19
+ "mask": {"mandatory": ["pattern", "space"], "optional": []},
20
+ },
21
+ },
22
+ "T2w": {
23
+ "mandatory": ["pattern", "space"],
24
+ "optional": {
25
+ "mask": {"mandatory": ["pattern", "space"], "optional": []},
26
+ },
27
+ },
28
+ "BOLD": {
29
+ "mandatory": ["pattern", "space"],
30
+ "optional": {
31
+ "mask": {"mandatory": ["pattern", "space"], "optional": []},
32
+ "confounds": {
33
+ "mandatory": ["pattern", "format"],
34
+ "optional": ["mappings"],
35
+ },
36
+ },
37
+ },
38
+ "Warp": {
39
+ "mandatory": ["pattern", "src", "dst"],
40
+ "optional": {},
41
+ },
42
+ "VBM_GM": {
43
+ "mandatory": ["pattern", "space"],
44
+ "optional": {},
45
+ },
46
+ "VBM_WM": {
47
+ "mandatory": ["pattern", "space"],
48
+ "optional": {},
49
+ },
50
+ "VBM_CSF": {
51
+ "mandatory": ["pattern", "space"],
52
+ "optional": {},
53
+ },
54
+ "DWI": {
55
+ "mandatory": ["pattern"],
56
+ "optional": {},
57
+ },
58
+ "FreeSurfer": {
59
+ "mandatory": ["pattern"],
60
+ "optional": {
61
+ "aseg": {"mandatory": ["pattern"], "optional": []},
62
+ "norm": {"mandatory": ["pattern"], "optional": []},
63
+ "lh_white": {"mandatory": ["pattern"], "optional": []},
64
+ "rh_white": {"mandatory": ["pattern"], "optional": []},
65
+ "lh_pial": {"mandatory": ["pattern"], "optional": []},
66
+ "rh_pial": {"mandatory": ["pattern"], "optional": []},
67
+ },
68
+ },
69
+ }
70
+
71
+
72
+ class PatternValidationMixin:
73
+ """Mixin class for pattern validation."""
74
+
75
+ def _validate_types(self, types: List[str]) -> None:
76
+ """Validate the types.
77
+
78
+ Parameters
79
+ ----------
80
+ types : list of str
81
+ The data types to validate.
82
+
83
+ Raises
84
+ ------
85
+ TypeError
86
+ If ``types`` is not a list or if the values are not string.
87
+
88
+ """
89
+ if not isinstance(types, list):
90
+ raise_error(msg="`types` must be a list", klass=TypeError)
91
+ if any(not isinstance(x, str) for x in types):
92
+ raise_error(
93
+ msg="`types` must be a list of strings", klass=TypeError
94
+ )
95
+
96
+ def _validate_replacements(
97
+ self,
98
+ replacements: List[str],
99
+ patterns: Dict[str, Dict[str, str]],
100
+ partial_pattern_ok: bool,
101
+ ) -> None:
102
+ """Validate the replacements.
103
+
104
+ Parameters
105
+ ----------
106
+ replacements : list of str
107
+ The replacements to validate.
108
+ patterns : dict
109
+ The patterns to validate replacements against.
110
+ partial_pattern_ok : bool
111
+ Whether to raise error if partial pattern for a data type is found.
112
+
113
+ Raises
114
+ ------
115
+ TypeError
116
+ If ``replacements`` is not a list or if the values are not string.
117
+ ValueError
118
+ If a value in ``replacements`` is not part of a data type pattern
119
+ and ``partial_pattern_ok=False`` or
120
+ if no data type patterns contain all values in ``replacements`` and
121
+ ``partial_pattern_ok=False``.
122
+
123
+ Warns
124
+ -----
125
+ RuntimeWarning
126
+ If a value in ``replacements`` is not part of the data type pattern
127
+ and ``partial_pattern_ok=True``.
128
+
129
+ """
130
+ if not isinstance(replacements, list):
131
+ raise_error(msg="`replacements` must be a list.", klass=TypeError)
132
+
133
+ if any(not isinstance(x, str) for x in replacements):
134
+ raise_error(
135
+ msg="`replacements` must be a list of strings.",
136
+ klass=TypeError,
137
+ )
138
+
139
+ 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
+ ):
147
+ if partial_pattern_ok:
148
+ warn_with_log(
149
+ f"Replacement: `{x}` is not part of any pattern, "
150
+ "things might not work as expected if you are unsure "
151
+ "of what you are doing"
152
+ )
153
+ else:
154
+ raise_error(
155
+ msg=f"Replacement: {x} is not part of any pattern."
156
+ )
157
+
158
+ # Check that at least one pattern has all the replacements
159
+ 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
165
+ if not at_least_one and not partial_pattern_ok:
166
+ raise_error(
167
+ msg="At least one pattern must contain all replacements."
168
+ )
169
+
170
+ def _validate_mandatory_keys(
171
+ self,
172
+ keys: List[str],
173
+ schema: List[str],
174
+ data_type: str,
175
+ partial_pattern_ok: bool = False,
176
+ ) -> None:
177
+ """Validate mandatory keys.
178
+
179
+ Parameters
180
+ ----------
181
+ keys : list of str
182
+ The keys to validate.
183
+ schema : list of str
184
+ The schema to validate against.
185
+ data_type : str
186
+ The data type being validated.
187
+ partial_pattern_ok : bool, optional
188
+ Whether to raise error if partial pattern for a data type is found
189
+ (default True).
190
+
191
+ Raises
192
+ ------
193
+ KeyError
194
+ If any mandatory key is missing for a data type and
195
+ ``partial_pattern_ok=False``.
196
+
197
+ Warns
198
+ -----
199
+ RuntimeWarning
200
+ If any mandatory key is missing for a data type and
201
+ ``partial_pattern_ok=True``.
202
+
203
+ """
204
+ for key in schema:
205
+ if key not in keys:
206
+ if partial_pattern_ok:
207
+ warn_with_log(
208
+ f"Mandatory key: `{key}` not found for {data_type}, "
209
+ "things might not work as expected if you are unsure "
210
+ "of what you are doing"
211
+ )
212
+ else:
213
+ raise_error(
214
+ msg=f"Mandatory key: `{key}` missing for {data_type}",
215
+ klass=KeyError,
216
+ )
217
+ else:
218
+ logger.debug(f"Mandatory key: `{key}` found for {data_type}")
219
+
220
+ def _identify_stray_keys(
221
+ self, keys: List[str], schema: List[str], data_type: str
222
+ ) -> None:
223
+ """Identify stray keys.
224
+
225
+ Parameters
226
+ ----------
227
+ keys : list of str
228
+ The keys to check.
229
+ schema : list of str
230
+ The schema to check against.
231
+ data_type : str
232
+ The data type being checked.
233
+
234
+ Raises
235
+ ------
236
+ RuntimeError
237
+ If an unknown key is found for a data type.
238
+
239
+ """
240
+ for key in keys:
241
+ if key not in schema:
242
+ raise_error(
243
+ msg=(
244
+ f"Key: {key} not accepted for {data_type} "
245
+ "pattern, remove it to proceed"
246
+ ),
247
+ klass=RuntimeError,
248
+ )
249
+
250
+ def validate_patterns(
251
+ self,
252
+ types: List[str],
253
+ replacements: List[str],
254
+ patterns: Dict[str, Dict[str, str]],
255
+ partial_pattern_ok: bool = False,
256
+ ) -> None:
257
+ """Validate the patterns.
258
+
259
+ Parameters
260
+ ----------
261
+ types : list of str
262
+ The data types to check patterns of.
263
+ replacements : list of str
264
+ The replacements to be replaced in the patterns.
265
+ patterns : dict
266
+ The patterns to validate.
267
+ partial_pattern_ok : bool, optional
268
+ Whether to raise error if partial pattern for a data type is found.
269
+ If False, a warning is issued instead of raising an error
270
+ (default False).
271
+
272
+ Raises
273
+ ------
274
+ TypeError
275
+ If ``patterns`` is not a dictionary.
276
+ ValueError
277
+ If length of ``types`` and ``patterns`` are different or
278
+ if ``patterns`` is missing entries from ``types`` or
279
+ if unknown data type is found in ``patterns`` or
280
+ if data type pattern key contains '*' as value.
281
+
282
+ """
283
+ # Validate types
284
+ self._validate_types(types=types)
285
+
286
+ # Validate patterns
287
+ if not isinstance(patterns, dict):
288
+ raise_error(msg="`patterns` must be a dict", klass=TypeError)
289
+ # Unequal length of objects
290
+ if len(types) > len(patterns):
291
+ raise_error(
292
+ msg="Length of `types` more than that of `patterns`",
293
+ klass=ValueError,
294
+ )
295
+ # Missing type in patterns
296
+ if any(x not in patterns for x in types):
297
+ raise_error(
298
+ msg="`patterns` must contain all `types`", klass=ValueError
299
+ )
300
+ # Check against schema
301
+ for data_type_key, data_type_val in patterns.items():
302
+ # Check if valid data type is provided
303
+ if data_type_key not in PATTERNS_SCHEMA:
304
+ raise_error(
305
+ f"Unknown data type: {data_type_key}, "
306
+ f"should be one of: {list(PATTERNS_SCHEMA.keys())}"
307
+ )
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
338
+ self._validate_mandatory_keys(
339
+ keys=list(optional_val["mandatory"]),
340
+ schema=nested_mandatory_keys_schema,
341
+ data_type=nested_data_type,
342
+ partial_pattern_ok=partial_pattern_ok,
343
+ )
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"]:
347
+ logger.debug(
348
+ f"Optional key: `{nested_optional_key}` "
349
+ f"missing for {nested_data_type}"
350
+ )
351
+ else:
352
+ logger.debug(
353
+ f"Optional key: `{nested_optional_key}` found "
354
+ f"for {nested_data_type}"
355
+ )
356
+ # Check stray key for nested data type
357
+ 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,
363
+ )
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"
379
+ ),
380
+ klass=ValueError,
381
+ )
382
+
383
+ # Validate replacements
384
+ self._validate_replacements(
385
+ replacements=replacements,
386
+ patterns=patterns,
387
+ partial_pattern_ok=partial_pattern_ok,
388
+ )
@@ -15,13 +15,13 @@ from junifer.datagrabber import DataladDataGrabber
15
15
  _testing_dataset = {
16
16
  "example_bids": {
17
17
  "uri": "https://gin.g-node.org/juaml/datalad-example-bids",
18
- "commit": "522dfb203afcd2cd55799bf347f9b211919a7338",
19
- "id": "fec92475-d9c0-4409-92ba-f041b6a12c40",
18
+ "commit": "b87897cbe51bf0ee5514becaa5c7dd76491db5ad",
19
+ "id": "8fddff30-6993-420a-9d1e-b5b028c59468",
20
20
  },
21
21
  "example_bids_ses": {
22
22
  "uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
23
- "commit": "3d08d55d1faad4f12ab64ac9497544a0d924d47a",
24
- "id": "c83500d0-532f-45be-baf1-0dab703bdc2a",
23
+ "commit": "6b163aa98af76a9eac0272273c27e14127850181",
24
+ "id": "715c17cf-a1b9-42d6-9af8-9f74c1a4a724",
25
25
  },
26
26
  }
27
27
 
@@ -93,13 +93,10 @@ def test_DMCC13Benchmark(
93
93
  # Available data types
94
94
  data_types = [
95
95
  "BOLD",
96
- "BOLD_confounds",
97
- "BOLD_mask",
98
96
  "VBM_CSF",
99
97
  "VBM_GM",
100
98
  "VBM_WM",
101
99
  "T1w",
102
- "T1w_mask",
103
100
  ]
104
101
  # Add Warp if native T1w is accessed
105
102
  if native_t1w:
@@ -111,14 +108,6 @@ def test_DMCC13Benchmark(
111
108
  f"sub-01_{ses}_task-{task}_acq-mb4{phase}_run-{run}_"
112
109
  "space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz"
113
110
  ),
114
- (
115
- f"sub-01_{ses}_task-{task}_acq-mb4{phase}_run-{run}_"
116
- "desc-confounds_regressors.tsv"
117
- ),
118
- (
119
- f"sub-01_{ses}_task-{task}_acq-mb4{phase}_run-{run}_"
120
- "space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz"
121
- ),
122
111
  "sub-01_space-MNI152NLin2009cAsym_label-CSF_probseg.nii.gz",
123
112
  "sub-01_space-MNI152NLin2009cAsym_label-GM_probseg.nii.gz",
124
113
  "sub-01_space-MNI152NLin2009cAsym_label-WM_probseg.nii.gz",
@@ -127,16 +116,12 @@ def test_DMCC13Benchmark(
127
116
  data_file_names.extend(
128
117
  [
129
118
  "sub-01_desc-preproc_T1w.nii.gz",
130
- "sub-01_desc-brain_mask.nii.gz",
131
119
  "sub-01_from-MNI152NLin2009cAsym_to-T1w_mode-image_xfm.h5",
132
120
  ]
133
121
  )
134
122
  else:
135
- data_file_names.extend(
136
- [
137
- "sub-01_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz",
138
- "sub-01_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz",
139
- ]
123
+ data_file_names.append(
124
+ "sub-01_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz"
140
125
  )
141
126
 
142
127
  for data_type, data_file_name in zip(data_types, data_file_names):
@@ -151,6 +136,48 @@ def test_DMCC13Benchmark(
151
136
  # Assert metadata
152
137
  assert "meta" in out[data_type]
153
138
 
139
+ # Check BOLD nested data types
140
+ for type_, file_name in zip(
141
+ ("mask", "confounds"),
142
+ (
143
+ (
144
+ f"sub-01_{ses}_task-{task}_acq-mb4{phase}_run-{run}_"
145
+ "space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz"
146
+ ),
147
+ (
148
+ f"sub-01_{ses}_task-{task}_acq-mb4{phase}_run-{run}_"
149
+ "desc-confounds_regressors.tsv"
150
+ ),
151
+ ),
152
+ ):
153
+ # Assert data type
154
+ assert type_ in out["BOLD"]
155
+ # Assert data file path exists
156
+ assert out["BOLD"][type_]["path"].exists()
157
+ # Assert data file path is a file
158
+ assert out["BOLD"][type_]["path"].is_file()
159
+ # Assert data file name
160
+ assert out["BOLD"][type_]["path"].name == file_name
161
+
162
+ # Check T1w nested data types
163
+ # Assert data type
164
+ assert "mask" in out["T1w"]
165
+ # Assert data file path exists
166
+ assert out["T1w"]["mask"]["path"].exists()
167
+ # Assert data file path is a file
168
+ assert out["T1w"]["mask"]["path"].is_file()
169
+ # Assert data file name
170
+ if native_t1w:
171
+ assert (
172
+ out["T1w"]["mask"]["path"].name
173
+ == "sub-01_desc-brain_mask.nii.gz"
174
+ )
175
+ else:
176
+ assert (
177
+ out["T1w"]["mask"]["path"].name
178
+ == "sub-01_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz"
179
+ )
180
+
154
181
 
155
182
  @pytest.mark.parametrize(
156
183
  "types, native_t1w",
@@ -165,8 +192,8 @@ def test_DMCC13Benchmark(
165
192
  ("VBM_GM", False),
166
193
  ("VBM_WM", True),
167
194
  ("VBM_WM", False),
168
- (["BOLD", "BOLD_confounds"], True),
169
- (["BOLD", "BOLD_confounds"], False),
195
+ (["BOLD", "VBM_CSF"], True),
196
+ (["BOLD", "VBM_CSF"], False),
170
197
  (["T1w", "VBM_CSF"], True),
171
198
  (["T1w", "VBM_CSF"], False),
172
199
  (["VBM_GM", "VBM_WM"], True),