junifer 0.0.5__py3-none-any.whl → 0.0.5.dev24__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.
- junifer/__init__.py +0 -17
- junifer/_version.py +2 -2
- junifer/api/__init__.py +1 -4
- junifer/api/cli.py +1 -91
- junifer/api/decorators.py +0 -9
- junifer/api/functions.py +10 -56
- junifer/api/parser.py +0 -3
- junifer/api/queue_context/__init__.py +1 -4
- junifer/api/res/afni/run_afni_docker.sh +1 -1
- junifer/api/res/ants/run_ants_docker.sh +1 -1
- junifer/api/res/fsl/run_fsl_docker.sh +1 -1
- junifer/api/tests/test_api_utils.py +2 -4
- junifer/api/tests/test_cli.py +0 -83
- junifer/api/tests/test_functions.py +2 -27
- junifer/configs/__init__.py +1 -1
- junifer/configs/juseless/__init__.py +1 -4
- junifer/configs/juseless/datagrabbers/__init__.py +1 -10
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +0 -3
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +0 -3
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +0 -3
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +3 -1
- junifer/configs/juseless/datagrabbers/ucla.py +9 -12
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +0 -3
- junifer/data/__init__.py +1 -21
- junifer/data/coordinates.py +19 -10
- junifer/data/masks.py +87 -58
- junifer/data/parcellations.py +3 -14
- junifer/data/template_spaces.py +1 -4
- junifer/data/tests/test_masks.py +37 -26
- junifer/data/utils.py +0 -3
- junifer/datagrabber/__init__.py +1 -18
- junifer/datagrabber/aomic/__init__.py +0 -3
- junifer/datagrabber/aomic/id1000.py +37 -70
- junifer/datagrabber/aomic/piop1.py +36 -69
- junifer/datagrabber/aomic/piop2.py +38 -71
- junifer/datagrabber/aomic/tests/test_id1000.py +99 -44
- junifer/datagrabber/aomic/tests/test_piop1.py +108 -65
- junifer/datagrabber/aomic/tests/test_piop2.py +102 -45
- junifer/datagrabber/base.py +6 -13
- junifer/datagrabber/datalad_base.py +1 -13
- junifer/datagrabber/dmcc13_benchmark.py +53 -36
- junifer/datagrabber/hcp1200/__init__.py +0 -3
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +0 -3
- junifer/datagrabber/hcp1200/hcp1200.py +1 -4
- junifer/datagrabber/multiple.py +6 -45
- junifer/datagrabber/pattern.py +62 -170
- junifer/datagrabber/pattern_datalad.py +12 -25
- junifer/datagrabber/tests/test_datagrabber_utils.py +218 -0
- junifer/datagrabber/tests/test_datalad_base.py +4 -4
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +19 -46
- junifer/datagrabber/tests/test_multiple.py +84 -161
- junifer/datagrabber/tests/test_pattern.py +0 -45
- junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
- junifer/datagrabber/utils.py +230 -0
- junifer/datareader/__init__.py +1 -4
- junifer/datareader/default.py +43 -95
- junifer/external/__init__.py +1 -1
- junifer/external/nilearn/__init__.py +1 -5
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +9 -23
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +1 -76
- junifer/markers/__init__.py +1 -23
- junifer/markers/base.py +28 -68
- junifer/markers/collection.py +2 -10
- junifer/markers/complexity/__init__.py +0 -10
- junifer/markers/complexity/complexity_base.py +43 -26
- junifer/markers/complexity/hurst_exponent.py +0 -3
- junifer/markers/complexity/multiscale_entropy_auc.py +0 -3
- junifer/markers/complexity/perm_entropy.py +0 -3
- junifer/markers/complexity/range_entropy.py +0 -3
- junifer/markers/complexity/range_entropy_auc.py +0 -3
- junifer/markers/complexity/sample_entropy.py +0 -3
- junifer/markers/complexity/tests/test_hurst_exponent.py +3 -11
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +3 -11
- junifer/markers/complexity/tests/test_perm_entropy.py +3 -11
- junifer/markers/complexity/tests/test_range_entropy.py +3 -11
- junifer/markers/complexity/tests/test_range_entropy_auc.py +3 -11
- junifer/markers/complexity/tests/test_sample_entropy.py +3 -11
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +3 -11
- junifer/markers/complexity/weighted_perm_entropy.py +0 -3
- junifer/markers/ets_rss.py +42 -27
- junifer/markers/falff/__init__.py +0 -3
- junifer/markers/falff/_afni_falff.py +2 -5
- junifer/markers/falff/_junifer_falff.py +0 -3
- junifer/markers/falff/falff_base.py +46 -20
- junifer/markers/falff/falff_parcels.py +27 -56
- junifer/markers/falff/falff_spheres.py +29 -60
- junifer/markers/falff/tests/test_falff_parcels.py +23 -39
- junifer/markers/falff/tests/test_falff_spheres.py +23 -39
- junifer/markers/functional_connectivity/__init__.py +0 -9
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +60 -63
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +32 -45
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +36 -49
- junifer/markers/functional_connectivity/functional_connectivity_base.py +70 -71
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +25 -34
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +30 -40
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +7 -11
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +7 -27
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +12 -28
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +11 -35
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +62 -36
- junifer/markers/parcel_aggregation.py +61 -47
- junifer/markers/reho/__init__.py +0 -3
- junifer/markers/reho/_afni_reho.py +2 -5
- junifer/markers/reho/_junifer_reho.py +1 -4
- junifer/markers/reho/reho_base.py +27 -8
- junifer/markers/reho/reho_parcels.py +17 -28
- junifer/markers/reho/reho_spheres.py +18 -27
- junifer/markers/reho/tests/test_reho_parcels.py +3 -8
- junifer/markers/reho/tests/test_reho_spheres.py +3 -8
- junifer/markers/sphere_aggregation.py +59 -43
- junifer/markers/temporal_snr/__init__.py +0 -3
- junifer/markers/temporal_snr/temporal_snr_base.py +32 -23
- junifer/markers/temporal_snr/temporal_snr_parcels.py +6 -9
- junifer/markers/temporal_snr/temporal_snr_spheres.py +6 -9
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +3 -6
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +3 -6
- junifer/markers/tests/test_collection.py +8 -9
- junifer/markers/tests/test_ets_rss.py +9 -15
- junifer/markers/tests/test_markers_base.py +18 -17
- junifer/markers/tests/test_parcel_aggregation.py +32 -93
- junifer/markers/tests/test_sphere_aggregation.py +19 -72
- junifer/onthefly/__init__.py +1 -4
- junifer/onthefly/read_transform.py +0 -3
- junifer/pipeline/__init__.py +1 -9
- junifer/pipeline/pipeline_step_mixin.py +4 -21
- junifer/pipeline/registry.py +0 -3
- junifer/pipeline/singleton.py +0 -3
- junifer/pipeline/tests/test_registry.py +1 -1
- junifer/pipeline/update_meta_mixin.py +0 -3
- junifer/pipeline/utils.py +1 -67
- junifer/pipeline/workdir_manager.py +0 -3
- junifer/preprocess/__init__.py +2 -9
- junifer/preprocess/ants/__init__.py +4 -0
- junifer/preprocess/ants/ants_apply_transforms_warper.py +185 -0
- junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +56 -0
- junifer/preprocess/base.py +3 -6
- junifer/preprocess/bold_warper.py +265 -0
- junifer/preprocess/confounds/__init__.py +0 -3
- junifer/preprocess/confounds/fmriprep_confound_remover.py +60 -47
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +113 -72
- junifer/preprocess/fsl/__init__.py +4 -0
- junifer/preprocess/fsl/apply_warper.py +179 -0
- junifer/preprocess/fsl/tests/test_apply_warper.py +45 -0
- junifer/preprocess/smoothing/__init__.py +0 -3
- junifer/preprocess/smoothing/_afni_smoothing.py +1 -1
- junifer/preprocess/tests/test_bold_warper.py +159 -0
- junifer/preprocess/warping/__init__.py +0 -3
- junifer/preprocess/warping/_ants_warper.py +0 -3
- junifer/preprocess/warping/_fsl_warper.py +0 -3
- junifer/stats.py +1 -4
- junifer/storage/__init__.py +1 -9
- junifer/storage/base.py +1 -40
- junifer/storage/hdf5.py +9 -71
- junifer/storage/pandas_base.py +0 -3
- junifer/storage/sqlite.py +0 -3
- junifer/storage/tests/test_hdf5.py +10 -82
- junifer/storage/utils.py +0 -9
- junifer/testing/__init__.py +1 -4
- junifer/testing/datagrabbers.py +6 -13
- junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
- junifer/testing/utils.py +0 -3
- junifer/utils/__init__.py +2 -13
- junifer/utils/fs.py +0 -3
- junifer/utils/helpers.py +1 -32
- junifer/utils/logging.py +4 -33
- junifer/utils/tests/test_logging.py +0 -8
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/METADATA +16 -17
- junifer-0.0.5.dev24.dist-info/RECORD +265 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/WHEEL +1 -1
- junifer/api/res/freesurfer/mri_binarize +0 -3
- junifer/api/res/freesurfer/mri_mc +0 -3
- junifer/api/res/freesurfer/mri_pretess +0 -3
- junifer/api/res/freesurfer/mris_convert +0 -3
- junifer/api/res/freesurfer/run_freesurfer_docker.sh +0 -61
- junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
- junifer/datagrabber/pattern_validation_mixin.py +0 -388
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +0 -249
- junifer/external/BrainPrint/brainprint/__init__.py +0 -4
- junifer/external/BrainPrint/brainprint/_version.py +0 -3
- junifer/external/BrainPrint/brainprint/asymmetry.py +0 -91
- junifer/external/BrainPrint/brainprint/brainprint.py +0 -441
- junifer/external/BrainPrint/brainprint/surfaces.py +0 -258
- junifer/external/BrainPrint/brainprint/utils/__init__.py +0 -1
- junifer/external/BrainPrint/brainprint/utils/_config.py +0 -112
- junifer/external/BrainPrint/brainprint/utils/utils.py +0 -188
- junifer/external/nilearn/junifer_connectivity_measure.py +0 -483
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +0 -1089
- junifer/markers/brainprint.py +0 -459
- junifer/markers/tests/test_brainprint.py +0 -58
- junifer-0.0.5.dist-info/RECORD +0 -275
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/top_level.txt +0 -0
@@ -1,388 +0,0 @@
|
|
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
|
-
)
|
@@ -1,249 +0,0 @@
|
|
1
|
-
"""Provide tests for PatternValidationMixin."""
|
2
|
-
|
3
|
-
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
|
4
|
-
# Synchon Mandal <s.mandal@fz-juelich.de>
|
5
|
-
# License: AGPL
|
6
|
-
|
7
|
-
from contextlib import nullcontext
|
8
|
-
from typing import ContextManager, Dict, List, Union
|
9
|
-
|
10
|
-
import pytest
|
11
|
-
|
12
|
-
from junifer.datagrabber.pattern_validation_mixin import PatternValidationMixin
|
13
|
-
|
14
|
-
|
15
|
-
@pytest.mark.parametrize(
|
16
|
-
"types, replacements, patterns, expect",
|
17
|
-
[
|
18
|
-
(
|
19
|
-
"wrong",
|
20
|
-
[],
|
21
|
-
{},
|
22
|
-
pytest.raises(TypeError, match="`types` must be a list"),
|
23
|
-
),
|
24
|
-
(
|
25
|
-
[1],
|
26
|
-
[],
|
27
|
-
{},
|
28
|
-
pytest.raises(
|
29
|
-
TypeError, match="`types` must be a list of strings"
|
30
|
-
),
|
31
|
-
),
|
32
|
-
(
|
33
|
-
["BOLD"],
|
34
|
-
[],
|
35
|
-
"wrong",
|
36
|
-
pytest.raises(TypeError, match="`patterns` must be a dict"),
|
37
|
-
),
|
38
|
-
(
|
39
|
-
["T1w", "BOLD"],
|
40
|
-
"",
|
41
|
-
{
|
42
|
-
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
43
|
-
},
|
44
|
-
pytest.raises(
|
45
|
-
ValueError,
|
46
|
-
match="Length of `types` more than that of `patterns`",
|
47
|
-
),
|
48
|
-
),
|
49
|
-
(
|
50
|
-
["T1w", "BOLD"],
|
51
|
-
"",
|
52
|
-
{
|
53
|
-
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
54
|
-
"T2w": {"pattern": "{subject}/anat/{subject}_T2w.nii.gz"},
|
55
|
-
},
|
56
|
-
pytest.raises(
|
57
|
-
ValueError, match="`patterns` must contain all `types`"
|
58
|
-
),
|
59
|
-
),
|
60
|
-
(
|
61
|
-
["T3w"],
|
62
|
-
"",
|
63
|
-
{
|
64
|
-
"T3w": {"pattern": "{subject}/anat/{subject}_T3w.nii.gz"},
|
65
|
-
},
|
66
|
-
pytest.raises(ValueError, match="Unknown data type"),
|
67
|
-
),
|
68
|
-
(
|
69
|
-
["BOLD"],
|
70
|
-
"",
|
71
|
-
{
|
72
|
-
"BOLD": {"patterns": "{subject}/func/{subject}_BOLD.nii.gz"},
|
73
|
-
},
|
74
|
-
pytest.raises(KeyError, match="Mandatory key"),
|
75
|
-
),
|
76
|
-
(
|
77
|
-
["BOLD"],
|
78
|
-
"",
|
79
|
-
{
|
80
|
-
"BOLD": {
|
81
|
-
"pattern": (
|
82
|
-
"{subject}/func/{subject}_task-rest_bold.nii.gz"
|
83
|
-
),
|
84
|
-
"space": "MNINLin6Asym",
|
85
|
-
"confounds": {
|
86
|
-
"pattern": "{subject}/func/{subject}_confounds.tsv",
|
87
|
-
"format": "fmriprep",
|
88
|
-
},
|
89
|
-
"zip": "zap",
|
90
|
-
},
|
91
|
-
},
|
92
|
-
pytest.raises(RuntimeError, match="not accepted"),
|
93
|
-
),
|
94
|
-
(
|
95
|
-
["T1w"],
|
96
|
-
"",
|
97
|
-
{
|
98
|
-
"T1w": {
|
99
|
-
"pattern": "{subject}/anat/{subject}*.nii",
|
100
|
-
"space": "native",
|
101
|
-
},
|
102
|
-
},
|
103
|
-
pytest.raises(ValueError, match="following a replacement"),
|
104
|
-
),
|
105
|
-
(
|
106
|
-
["T1w"],
|
107
|
-
"wrong",
|
108
|
-
{
|
109
|
-
"T1w": {
|
110
|
-
"pattern": "{subject}/anat/{subject}_T1w.nii",
|
111
|
-
"space": "native",
|
112
|
-
},
|
113
|
-
},
|
114
|
-
pytest.raises(TypeError, match="`replacements` must be a list"),
|
115
|
-
),
|
116
|
-
(
|
117
|
-
["T1w"],
|
118
|
-
[1],
|
119
|
-
{
|
120
|
-
"T1w": {
|
121
|
-
"pattern": "{subject}/anat/{subject}_T1w.nii",
|
122
|
-
"space": "native",
|
123
|
-
},
|
124
|
-
},
|
125
|
-
pytest.raises(
|
126
|
-
TypeError, match="`replacements` must be a list of strings"
|
127
|
-
),
|
128
|
-
),
|
129
|
-
(
|
130
|
-
["T1w", "BOLD"],
|
131
|
-
["subject", "session"],
|
132
|
-
{
|
133
|
-
"T1w": {
|
134
|
-
"pattern": "{subject}/anat/{subject}_T1w.nii.gz",
|
135
|
-
"space": "native",
|
136
|
-
},
|
137
|
-
"BOLD": {
|
138
|
-
"pattern": (
|
139
|
-
"{subject}/func/{subject}_task-rest_bold.nii.gz"
|
140
|
-
),
|
141
|
-
"space": "MNI152NLin6Asym",
|
142
|
-
},
|
143
|
-
},
|
144
|
-
pytest.raises(ValueError, match="is not part of any pattern"),
|
145
|
-
),
|
146
|
-
(
|
147
|
-
["BOLD"],
|
148
|
-
["subject", "session"],
|
149
|
-
{
|
150
|
-
"T1w": {
|
151
|
-
"pattern": "{subject}/anat/_T1w.nii.gz",
|
152
|
-
"space": "native",
|
153
|
-
},
|
154
|
-
"BOLD": {
|
155
|
-
"pattern": "{session}/func/_task-rest_bold.nii.gz",
|
156
|
-
"space": "MNI152NLin6Asym",
|
157
|
-
},
|
158
|
-
},
|
159
|
-
pytest.raises(ValueError, match="At least one pattern"),
|
160
|
-
),
|
161
|
-
(
|
162
|
-
["T1w", "T2w", "BOLD"],
|
163
|
-
["subject"],
|
164
|
-
{
|
165
|
-
"T1w": {
|
166
|
-
"pattern": "{subject}/anat/{subject}_T1w.nii.gz",
|
167
|
-
"space": "native",
|
168
|
-
},
|
169
|
-
"T2w": {
|
170
|
-
"pattern": "{subject}/anat/{subject}_T2w.nii.gz",
|
171
|
-
"space": "native",
|
172
|
-
},
|
173
|
-
"BOLD": {
|
174
|
-
"pattern": (
|
175
|
-
"{subject}/func/{session}/{subject}_task-rest_bold.nii.gz"
|
176
|
-
),
|
177
|
-
"space": "MNI152NLin6Asym",
|
178
|
-
"confounds": {
|
179
|
-
"pattern": "{subject}/func/{subject}_confounds.tsv",
|
180
|
-
"format": "fmriprep",
|
181
|
-
},
|
182
|
-
},
|
183
|
-
},
|
184
|
-
nullcontext(),
|
185
|
-
),
|
186
|
-
],
|
187
|
-
)
|
188
|
-
def test_PatternValidationMixin(
|
189
|
-
types: Union[str, List[str], List[int]],
|
190
|
-
replacements: Union[str, List[str], List[int]],
|
191
|
-
patterns: Union[str, Dict[str, Dict[str, str]]],
|
192
|
-
expect: ContextManager,
|
193
|
-
) -> None:
|
194
|
-
"""Test validation.
|
195
|
-
|
196
|
-
Parameters
|
197
|
-
----------
|
198
|
-
types : str, list of int or str
|
199
|
-
The parametrized data types to validate.
|
200
|
-
replacements : str, list of str or int
|
201
|
-
The parametrized pattern replacements to validate.
|
202
|
-
patterns : str, dict
|
203
|
-
The parametrized patterns to validate against.
|
204
|
-
expect : typing.ContextManager
|
205
|
-
The parametrized ContextManager object.
|
206
|
-
|
207
|
-
"""
|
208
|
-
|
209
|
-
class MockDataGrabber(PatternValidationMixin):
|
210
|
-
def __init__(
|
211
|
-
self,
|
212
|
-
types,
|
213
|
-
replacements,
|
214
|
-
patterns,
|
215
|
-
) -> None:
|
216
|
-
self.types = types
|
217
|
-
self.replacements = replacements
|
218
|
-
self.patterns = patterns
|
219
|
-
|
220
|
-
def validate(self) -> None:
|
221
|
-
self.validate_patterns(
|
222
|
-
types=self.types,
|
223
|
-
replacements=self.replacements,
|
224
|
-
patterns=self.patterns,
|
225
|
-
)
|
226
|
-
|
227
|
-
dg = MockDataGrabber(types, replacements, patterns)
|
228
|
-
with expect:
|
229
|
-
dg.validate()
|
230
|
-
|
231
|
-
|
232
|
-
# This test is kept separate as bool doesn't support context manager protocol,
|
233
|
-
# used in the earlier test
|
234
|
-
def test_PatternValidationMixin_partial_pattern_check() -> None:
|
235
|
-
"""Test validation for partial patterns."""
|
236
|
-
with pytest.warns(RuntimeWarning, match="might not work as expected"):
|
237
|
-
PatternValidationMixin().validate_patterns(
|
238
|
-
types=["BOLD"],
|
239
|
-
replacements=["subject"],
|
240
|
-
patterns={
|
241
|
-
"BOLD": {
|
242
|
-
"mask": {
|
243
|
-
"pattern": "{subject}/func/{subject}_BOLD.nii.gz",
|
244
|
-
"space": "MNI152NLin6Asym",
|
245
|
-
},
|
246
|
-
},
|
247
|
-
}, # type: ignore
|
248
|
-
partial_pattern_ok=True,
|
249
|
-
)
|