junifer 0.0.5.dev219__py3-none-any.whl → 0.0.5.dev242__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/_version.py +2 -2
- junifer/datagrabber/__init__.py +2 -0
- junifer/datagrabber/base.py +10 -6
- junifer/datagrabber/hcp1200/hcp1200.py +1 -1
- junifer/datagrabber/multiple.py +42 -6
- junifer/datagrabber/pattern.py +33 -10
- junifer/datagrabber/pattern_validation_mixin.py +388 -0
- junifer/datagrabber/tests/test_multiple.py +161 -84
- junifer/datagrabber/tests/{test_datagrabber_utils.py → test_pattern_validation_mixin.py} +133 -108
- junifer/utils/__init__.py +2 -1
- junifer/utils/helpers.py +30 -2
- junifer/utils/logging.py +18 -1
- junifer/utils/tests/test_logging.py +8 -0
- {junifer-0.0.5.dev219.dist-info → junifer-0.0.5.dev242.dist-info}/METADATA +1 -1
- {junifer-0.0.5.dev219.dist-info → junifer-0.0.5.dev242.dist-info}/RECORD +20 -20
- {junifer-0.0.5.dev219.dist-info → junifer-0.0.5.dev242.dist-info}/WHEEL +1 -1
- junifer/datagrabber/utils.py +0 -317
- {junifer-0.0.5.dev219.dist-info → junifer-0.0.5.dev242.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.5.dev219.dist-info → junifer-0.0.5.dev242.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.5.dev219.dist-info → junifer-0.0.5.dev242.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.5.dev219.dist-info → junifer-0.0.5.dev242.dist-info}/top_level.txt +0 -0
@@ -25,28 +25,26 @@ def test_MultipleDataGrabber() -> None:
|
|
25
25
|
repo_uri = _testing_dataset["example_bids_ses"]["uri"]
|
26
26
|
rootdir = "example_bids_ses"
|
27
27
|
replacements = ["subject", "session"]
|
28
|
-
|
29
|
-
"T1w": {
|
30
|
-
"pattern": (
|
31
|
-
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
32
|
-
),
|
33
|
-
"space": "native",
|
34
|
-
},
|
35
|
-
}
|
36
|
-
pattern2 = {
|
37
|
-
"BOLD": {
|
38
|
-
"pattern": (
|
39
|
-
"{subject}/{session}/func/"
|
40
|
-
"{subject}_{session}_task-rest_bold.nii.gz"
|
41
|
-
),
|
42
|
-
"space": "MNI152NLin6Asym",
|
43
|
-
},
|
44
|
-
}
|
28
|
+
|
45
29
|
dg1 = PatternDataladDataGrabber(
|
46
30
|
rootdir=rootdir,
|
47
31
|
uri=repo_uri,
|
48
32
|
types=["T1w"],
|
49
|
-
patterns=
|
33
|
+
patterns={
|
34
|
+
"T1w": {
|
35
|
+
"pattern": (
|
36
|
+
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
37
|
+
),
|
38
|
+
"space": "native",
|
39
|
+
"mask": {
|
40
|
+
"pattern": (
|
41
|
+
"{subject}/{session}/anat/{subject}_{session}_"
|
42
|
+
"brain_mask.nii.gz"
|
43
|
+
),
|
44
|
+
"space": "native",
|
45
|
+
},
|
46
|
+
},
|
47
|
+
},
|
50
48
|
replacements=replacements,
|
51
49
|
)
|
52
50
|
|
@@ -54,7 +52,22 @@ def test_MultipleDataGrabber() -> None:
|
|
54
52
|
rootdir=rootdir,
|
55
53
|
uri=repo_uri,
|
56
54
|
types=["BOLD"],
|
57
|
-
patterns=
|
55
|
+
patterns={
|
56
|
+
"BOLD": {
|
57
|
+
"pattern": (
|
58
|
+
"{subject}/{session}/func/"
|
59
|
+
"{subject}_{session}_task-rest_bold.nii.gz"
|
60
|
+
),
|
61
|
+
"space": "MNI152NLin6Asym",
|
62
|
+
"mask": {
|
63
|
+
"pattern": (
|
64
|
+
"{subject}/{session}/func/"
|
65
|
+
"{subject}_{session}_task-rest_brain_mask.nii.gz"
|
66
|
+
),
|
67
|
+
"space": "MNI152NLin6Asym",
|
68
|
+
},
|
69
|
+
},
|
70
|
+
},
|
58
71
|
replacements=replacements,
|
59
72
|
)
|
60
73
|
|
@@ -73,14 +86,17 @@ def test_MultipleDataGrabber() -> None:
|
|
73
86
|
with dg:
|
74
87
|
subs = list(dg)
|
75
88
|
assert set(subs) == set(expected_subs)
|
76
|
-
|
89
|
+
# Check data type
|
77
90
|
elem = dg[("sub-01", "ses-01")]
|
91
|
+
# Check data types
|
78
92
|
assert "T1w" in elem
|
79
93
|
assert "BOLD" in elem
|
94
|
+
# Check meta
|
80
95
|
assert "meta" in elem["BOLD"]
|
81
96
|
meta = elem["BOLD"]["meta"]["datagrabber"]
|
82
97
|
assert "class" in meta
|
83
98
|
assert meta["class"] == "MultipleDataGrabber"
|
99
|
+
# Check datagrabbers
|
84
100
|
assert "datagrabbers" in meta
|
85
101
|
assert len(meta["datagrabbers"]) == 2
|
86
102
|
assert meta["datagrabbers"][0]["class"] == "PatternDataladDataGrabber"
|
@@ -89,40 +105,37 @@ def test_MultipleDataGrabber() -> None:
|
|
89
105
|
|
90
106
|
def test_MultipleDataGrabber_no_intersection() -> None:
|
91
107
|
"""Test MultipleDataGrabber without intersection (0 elements)."""
|
92
|
-
repo_uri1 = _testing_dataset["example_bids"]["uri"]
|
93
|
-
repo_uri2 = _testing_dataset["example_bids_ses"]["uri"]
|
94
108
|
rootdir = "example_bids_ses"
|
95
109
|
replacements = ["subject", "session"]
|
96
|
-
|
97
|
-
"T1w": {
|
98
|
-
"pattern": (
|
99
|
-
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
100
|
-
),
|
101
|
-
"space": "native",
|
102
|
-
},
|
103
|
-
}
|
104
|
-
pattern2 = {
|
105
|
-
"BOLD": {
|
106
|
-
"pattern": (
|
107
|
-
"{subject}/{session}/func/"
|
108
|
-
"{subject}_{session}_task-rest_bold.nii.gz"
|
109
|
-
),
|
110
|
-
"space": "MNI152NLin6Asym",
|
111
|
-
},
|
112
|
-
}
|
110
|
+
|
113
111
|
dg1 = PatternDataladDataGrabber(
|
114
112
|
rootdir=rootdir,
|
115
|
-
uri=
|
113
|
+
uri=_testing_dataset["example_bids"]["uri"],
|
116
114
|
types=["T1w"],
|
117
|
-
patterns=
|
115
|
+
patterns={
|
116
|
+
"T1w": {
|
117
|
+
"pattern": (
|
118
|
+
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
119
|
+
),
|
120
|
+
"space": "native",
|
121
|
+
},
|
122
|
+
},
|
118
123
|
replacements=replacements,
|
119
124
|
)
|
120
125
|
|
121
126
|
dg2 = PatternDataladDataGrabber(
|
122
127
|
rootdir=rootdir,
|
123
|
-
uri=
|
128
|
+
uri=_testing_dataset["example_bids_ses"]["uri"],
|
124
129
|
types=["BOLD"],
|
125
|
-
patterns=
|
130
|
+
patterns={
|
131
|
+
"BOLD": {
|
132
|
+
"pattern": (
|
133
|
+
"{subject}/{session}/func/"
|
134
|
+
"{subject}_{session}_task-rest_bold.nii.gz"
|
135
|
+
),
|
136
|
+
"space": "MNI152NLin6Asym",
|
137
|
+
},
|
138
|
+
},
|
126
139
|
replacements=replacements,
|
127
140
|
)
|
128
141
|
|
@@ -135,23 +148,19 @@ def test_MultipleDataGrabber_no_intersection() -> None:
|
|
135
148
|
|
136
149
|
def test_MultipleDataGrabber_get_item() -> None:
|
137
150
|
"""Test MultipleDataGrabber get_item() error."""
|
138
|
-
repo_uri1 = _testing_dataset["example_bids"]["uri"]
|
139
|
-
rootdir = "example_bids_ses"
|
140
|
-
replacements = ["subject", "session"]
|
141
|
-
pattern1 = {
|
142
|
-
"T1w": {
|
143
|
-
"pattern": (
|
144
|
-
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
145
|
-
),
|
146
|
-
"space": "native",
|
147
|
-
},
|
148
|
-
}
|
149
151
|
dg1 = PatternDataladDataGrabber(
|
150
|
-
rootdir=
|
151
|
-
uri=
|
152
|
+
rootdir="example_bids_ses",
|
153
|
+
uri=_testing_dataset["example_bids"]["uri"],
|
152
154
|
types=["T1w"],
|
153
|
-
patterns=
|
154
|
-
|
155
|
+
patterns={
|
156
|
+
"T1w": {
|
157
|
+
"pattern": (
|
158
|
+
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
159
|
+
),
|
160
|
+
"space": "native",
|
161
|
+
},
|
162
|
+
},
|
163
|
+
replacements=["subject", "session"],
|
155
164
|
)
|
156
165
|
|
157
166
|
dg = MultipleDataGrabber([dg1])
|
@@ -161,43 +170,111 @@ def test_MultipleDataGrabber_get_item() -> None:
|
|
161
170
|
|
162
171
|
def test_MultipleDataGrabber_validation() -> None:
|
163
172
|
"""Test MultipleDataGrabber init validation."""
|
164
|
-
repo_uri1 = _testing_dataset["example_bids"]["uri"]
|
165
|
-
repo_uri2 = _testing_dataset["example_bids_ses"]["uri"]
|
166
173
|
rootdir = "example_bids_ses"
|
167
|
-
|
168
|
-
replacement2 = ["subject"]
|
169
|
-
pattern1 = {
|
170
|
-
"T1w": {
|
171
|
-
"pattern": (
|
172
|
-
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
173
|
-
),
|
174
|
-
"space": "native",
|
175
|
-
},
|
176
|
-
}
|
177
|
-
pattern2 = {
|
178
|
-
"BOLD": {
|
179
|
-
"pattern": "{subject}/func/{subject}_task-rest_bold.nii.gz",
|
180
|
-
"space": "MNI152NLin6Asym",
|
181
|
-
},
|
182
|
-
}
|
174
|
+
|
183
175
|
dg1 = PatternDataladDataGrabber(
|
184
176
|
rootdir=rootdir,
|
185
|
-
uri=
|
177
|
+
uri=_testing_dataset["example_bids"]["uri"],
|
186
178
|
types=["T1w"],
|
187
|
-
patterns=
|
188
|
-
|
179
|
+
patterns={
|
180
|
+
"T1w": {
|
181
|
+
"pattern": (
|
182
|
+
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
183
|
+
),
|
184
|
+
"space": "native",
|
185
|
+
},
|
186
|
+
},
|
187
|
+
replacements=["subject", "session"],
|
189
188
|
)
|
190
189
|
|
191
190
|
dg2 = PatternDataladDataGrabber(
|
192
191
|
rootdir=rootdir,
|
193
|
-
uri=
|
192
|
+
uri=_testing_dataset["example_bids_ses"]["uri"],
|
194
193
|
types=["BOLD"],
|
195
|
-
patterns=
|
196
|
-
|
194
|
+
patterns={
|
195
|
+
"BOLD": {
|
196
|
+
"pattern": "{subject}/func/{subject}_task-rest_bold.nii.gz",
|
197
|
+
"space": "MNI152NLin6Asym",
|
198
|
+
},
|
199
|
+
},
|
200
|
+
replacements=["subject"],
|
197
201
|
)
|
198
202
|
|
199
|
-
with pytest.raises(
|
203
|
+
with pytest.raises(RuntimeError, match="have different element keys"):
|
200
204
|
MultipleDataGrabber([dg1, dg2])
|
201
205
|
|
202
|
-
with pytest.raises(
|
206
|
+
with pytest.raises(RuntimeError, match="have overlapping mandatory"):
|
203
207
|
MultipleDataGrabber([dg1, dg1])
|
208
|
+
|
209
|
+
|
210
|
+
def test_MultipleDataGrabber_partial_pattern() -> None:
|
211
|
+
"""Test MultipleDataGrabber partial pattern."""
|
212
|
+
repo_uri = _testing_dataset["example_bids_ses"]["uri"]
|
213
|
+
rootdir = "example_bids_ses"
|
214
|
+
replacements = ["subject", "session"]
|
215
|
+
|
216
|
+
dg1 = PatternDataladDataGrabber(
|
217
|
+
rootdir=rootdir,
|
218
|
+
uri=repo_uri,
|
219
|
+
types=["BOLD"],
|
220
|
+
patterns={
|
221
|
+
"BOLD": {
|
222
|
+
"pattern": (
|
223
|
+
"{subject}/{session}/func/"
|
224
|
+
"{subject}_{session}_task-rest_bold.nii.gz"
|
225
|
+
),
|
226
|
+
"space": "MNI152NLin6Asym",
|
227
|
+
},
|
228
|
+
},
|
229
|
+
replacements=replacements,
|
230
|
+
)
|
231
|
+
|
232
|
+
dg2 = PatternDataladDataGrabber(
|
233
|
+
rootdir=rootdir,
|
234
|
+
uri=repo_uri,
|
235
|
+
types=["BOLD"],
|
236
|
+
patterns={
|
237
|
+
"BOLD": {
|
238
|
+
"confounds": {
|
239
|
+
"pattern": (
|
240
|
+
"{subject}/{session}/func/"
|
241
|
+
"{subject}_{session}_task-rest_"
|
242
|
+
"confounds_regressors.tsv"
|
243
|
+
),
|
244
|
+
"format": "fmriprep",
|
245
|
+
},
|
246
|
+
},
|
247
|
+
},
|
248
|
+
replacements=["subject", "session"],
|
249
|
+
partial_pattern_ok=True,
|
250
|
+
)
|
251
|
+
|
252
|
+
dg = MultipleDataGrabber([dg1, dg2])
|
253
|
+
|
254
|
+
types = dg.get_types()
|
255
|
+
assert "BOLD" in types
|
256
|
+
|
257
|
+
expected_subs = [
|
258
|
+
(f"sub-{i:02d}", f"ses-{j:02d}")
|
259
|
+
for j in range(1, 3)
|
260
|
+
for i in range(1, 10)
|
261
|
+
]
|
262
|
+
|
263
|
+
with dg:
|
264
|
+
subs = list(dg)
|
265
|
+
assert set(subs) == set(expected_subs)
|
266
|
+
# Fetch element
|
267
|
+
elem = dg[("sub-01", "ses-01")]
|
268
|
+
# Check data type and nested data type
|
269
|
+
assert "BOLD" in elem
|
270
|
+
assert "confounds" in elem["BOLD"]
|
271
|
+
# Check meta
|
272
|
+
assert "meta" in elem["BOLD"]
|
273
|
+
meta = elem["BOLD"]["meta"]["datagrabber"]
|
274
|
+
assert "class" in meta
|
275
|
+
assert meta["class"] == "MultipleDataGrabber"
|
276
|
+
# Check datagrabbers
|
277
|
+
assert "datagrabbers" in meta
|
278
|
+
assert len(meta["datagrabbers"]) == 2
|
279
|
+
assert meta["datagrabbers"][0]["class"] == "PatternDataladDataGrabber"
|
280
|
+
assert meta["datagrabbers"][1]["class"] == "PatternDataladDataGrabber"
|
@@ -1,6 +1,7 @@
|
|
1
|
-
"""Provide tests for
|
1
|
+
"""Provide tests for PatternValidationMixin."""
|
2
2
|
|
3
3
|
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
|
4
|
+
# Synchon Mandal <s.mandal@fz-juelich.de>
|
4
5
|
# License: AGPL
|
5
6
|
|
6
7
|
from contextlib import nullcontext
|
@@ -8,136 +9,57 @@ from typing import ContextManager, Dict, List, Union
|
|
8
9
|
|
9
10
|
import pytest
|
10
11
|
|
11
|
-
from junifer.datagrabber.
|
12
|
-
validate_patterns,
|
13
|
-
validate_replacements,
|
14
|
-
validate_types,
|
15
|
-
)
|
12
|
+
from junifer.datagrabber.pattern_validation_mixin import PatternValidationMixin
|
16
13
|
|
17
14
|
|
18
15
|
@pytest.mark.parametrize(
|
19
|
-
"types, expect",
|
20
|
-
[
|
21
|
-
("wrong", pytest.raises(TypeError, match="must be a list")),
|
22
|
-
([1], pytest.raises(TypeError, match="must be a list of strings")),
|
23
|
-
(["T1w", "BOLD"], nullcontext()),
|
24
|
-
],
|
25
|
-
)
|
26
|
-
def test_validate_types(
|
27
|
-
types: Union[str, List[str], List[int]],
|
28
|
-
expect: ContextManager,
|
29
|
-
) -> None:
|
30
|
-
"""Test validation of types.
|
31
|
-
|
32
|
-
Parameters
|
33
|
-
----------
|
34
|
-
types : str, list of int or str
|
35
|
-
The parametrized data types to validate.
|
36
|
-
expect : typing.ContextManager
|
37
|
-
The parametrized ContextManager object.
|
38
|
-
|
39
|
-
"""
|
40
|
-
with expect:
|
41
|
-
validate_types(types) # type: ignore
|
42
|
-
|
43
|
-
|
44
|
-
@pytest.mark.parametrize(
|
45
|
-
"replacements, patterns, expect",
|
16
|
+
"types, replacements, patterns, expect",
|
46
17
|
[
|
47
18
|
(
|
48
19
|
"wrong",
|
49
|
-
|
50
|
-
|
20
|
+
[],
|
21
|
+
{},
|
22
|
+
pytest.raises(TypeError, match="`types` must be a list"),
|
51
23
|
),
|
52
24
|
(
|
53
25
|
[1],
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
},
|
60
|
-
pytest.raises(TypeError, match="must be a list of strings"),
|
61
|
-
),
|
62
|
-
(
|
63
|
-
["session"],
|
64
|
-
{
|
65
|
-
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
66
|
-
"BOLD": {
|
67
|
-
"pattern": "{subject}/func/{subject}_task-rest_bold.nii.gz"
|
68
|
-
},
|
69
|
-
},
|
70
|
-
pytest.raises(ValueError, match="is not part of"),
|
71
|
-
),
|
72
|
-
(
|
73
|
-
["subject", "session"],
|
74
|
-
{
|
75
|
-
"T1w": {"pattern": "{subject}/anat/_T1w.nii.gz"},
|
76
|
-
"BOLD": {"pattern": "{session}/func/_task-rest_bold.nii.gz"},
|
77
|
-
},
|
78
|
-
pytest.raises(ValueError, match="At least one pattern"),
|
26
|
+
[],
|
27
|
+
{},
|
28
|
+
pytest.raises(
|
29
|
+
TypeError, match="`types` must be a list of strings"
|
30
|
+
),
|
79
31
|
),
|
80
32
|
(
|
81
|
-
["
|
82
|
-
|
83
|
-
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
84
|
-
"BOLD": {
|
85
|
-
"pattern": "{subject}/func/{subject}_task-rest_bold.nii.gz"
|
86
|
-
},
|
87
|
-
},
|
88
|
-
nullcontext(),
|
89
|
-
),
|
90
|
-
],
|
91
|
-
)
|
92
|
-
def test_validate_replacements(
|
93
|
-
replacements: Union[str, List[str], List[int]],
|
94
|
-
patterns: Union[str, Dict[str, Dict[str, str]]],
|
95
|
-
expect: ContextManager,
|
96
|
-
) -> None:
|
97
|
-
"""Test validation of replacements.
|
98
|
-
|
99
|
-
Parameters
|
100
|
-
----------
|
101
|
-
replacements : str, list of str or int
|
102
|
-
The parametrized pattern replacements to validate.
|
103
|
-
patterns : str, dict
|
104
|
-
The parametrized patterns to validate against.
|
105
|
-
expect : typing.ContextManager
|
106
|
-
The parametrized ContextManager object.
|
107
|
-
|
108
|
-
"""
|
109
|
-
with expect:
|
110
|
-
validate_replacements(replacements=replacements, patterns=patterns) # type: ignore
|
111
|
-
|
112
|
-
|
113
|
-
@pytest.mark.parametrize(
|
114
|
-
"types, patterns, expect",
|
115
|
-
[
|
116
|
-
(
|
117
|
-
["T1w", "BOLD"],
|
33
|
+
["BOLD"],
|
34
|
+
[],
|
118
35
|
"wrong",
|
119
|
-
pytest.raises(TypeError, match="must be a dict"),
|
36
|
+
pytest.raises(TypeError, match="`patterns` must be a dict"),
|
120
37
|
),
|
121
38
|
(
|
122
39
|
["T1w", "BOLD"],
|
40
|
+
"",
|
123
41
|
{
|
124
42
|
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
125
43
|
},
|
126
44
|
pytest.raises(
|
127
45
|
ValueError,
|
128
|
-
match="Length of `types` more than that of `patterns
|
46
|
+
match="Length of `types` more than that of `patterns`",
|
129
47
|
),
|
130
48
|
),
|
131
49
|
(
|
132
50
|
["T1w", "BOLD"],
|
51
|
+
"",
|
133
52
|
{
|
134
53
|
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
135
54
|
"T2w": {"pattern": "{subject}/anat/{subject}_T2w.nii.gz"},
|
136
55
|
},
|
137
|
-
pytest.raises(
|
56
|
+
pytest.raises(
|
57
|
+
ValueError, match="`patterns` must contain all `types`"
|
58
|
+
),
|
138
59
|
),
|
139
60
|
(
|
140
61
|
["T3w"],
|
62
|
+
"",
|
141
63
|
{
|
142
64
|
"T3w": {"pattern": "{subject}/anat/{subject}_T3w.nii.gz"},
|
143
65
|
},
|
@@ -145,6 +67,7 @@ def test_validate_replacements(
|
|
145
67
|
),
|
146
68
|
(
|
147
69
|
["BOLD"],
|
70
|
+
"",
|
148
71
|
{
|
149
72
|
"BOLD": {"patterns": "{subject}/func/{subject}_BOLD.nii.gz"},
|
150
73
|
},
|
@@ -152,6 +75,7 @@ def test_validate_replacements(
|
|
152
75
|
),
|
153
76
|
(
|
154
77
|
["BOLD"],
|
78
|
+
"",
|
155
79
|
{
|
156
80
|
"BOLD": {
|
157
81
|
"pattern": (
|
@@ -169,6 +93,7 @@ def test_validate_replacements(
|
|
169
93
|
),
|
170
94
|
(
|
171
95
|
["T1w"],
|
96
|
+
"",
|
172
97
|
{
|
173
98
|
"T1w": {
|
174
99
|
"pattern": "{subject}/anat/{subject}*.nii",
|
@@ -177,8 +102,65 @@ def test_validate_replacements(
|
|
177
102
|
},
|
178
103
|
pytest.raises(ValueError, match="following a replacement"),
|
179
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
|
+
),
|
180
161
|
(
|
181
162
|
["T1w", "T2w", "BOLD"],
|
163
|
+
["subject"],
|
182
164
|
{
|
183
165
|
"T1w": {
|
184
166
|
"pattern": "{subject}/anat/{subject}_T1w.nii.gz",
|
@@ -190,7 +172,7 @@ def test_validate_replacements(
|
|
190
172
|
},
|
191
173
|
"BOLD": {
|
192
174
|
"pattern": (
|
193
|
-
"{subject}/func/{subject}_task-rest_bold.nii.gz"
|
175
|
+
"{subject}/func/{session}/{subject}_task-rest_bold.nii.gz"
|
194
176
|
),
|
195
177
|
"space": "MNI152NLin6Asym",
|
196
178
|
"confounds": {
|
@@ -203,22 +185,65 @@ def test_validate_replacements(
|
|
203
185
|
),
|
204
186
|
],
|
205
187
|
)
|
206
|
-
def
|
207
|
-
types: List[str],
|
188
|
+
def test_PatternValidationMixin(
|
189
|
+
types: Union[str, List[str], List[int]],
|
190
|
+
replacements: Union[str, List[str], List[int]],
|
208
191
|
patterns: Union[str, Dict[str, Dict[str, str]]],
|
209
192
|
expect: ContextManager,
|
210
193
|
) -> None:
|
211
|
-
"""Test validation
|
194
|
+
"""Test validation.
|
212
195
|
|
213
196
|
Parameters
|
214
197
|
----------
|
215
|
-
types : list of str
|
216
|
-
The parametrized data types.
|
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.
|
217
202
|
patterns : str, dict
|
218
|
-
The patterns to validate.
|
203
|
+
The parametrized patterns to validate against.
|
219
204
|
expect : typing.ContextManager
|
220
205
|
The parametrized ContextManager object.
|
221
206
|
|
222
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)
|
223
228
|
with expect:
|
224
|
-
|
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
|
+
)
|
junifer/utils/__init__.py
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
from .fs import make_executable
|
8
8
|
from .logging import configure_logging, logger, raise_error, warn_with_log
|
9
|
-
from .helpers import run_ext_cmd
|
9
|
+
from .helpers import run_ext_cmd, deep_update
|
10
10
|
|
11
11
|
|
12
12
|
__all__ = [
|
@@ -16,4 +16,5 @@ __all__ = [
|
|
16
16
|
"raise_error",
|
17
17
|
"warn_with_log",
|
18
18
|
"run_ext_cmd",
|
19
|
+
"deep_update",
|
19
20
|
]
|