junifer 0.0.4.dev782__py3-none-any.whl → 0.0.4.dev810__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/configs/juseless/datagrabbers/aomic_id1000_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +6 -1
- junifer/configs/juseless/datagrabbers/ucla.py +42 -24
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +6 -1
- junifer/datagrabber/aomic/id1000.py +98 -91
- junifer/datagrabber/aomic/piop1.py +97 -73
- junifer/datagrabber/aomic/piop2.py +97 -73
- junifer/datagrabber/base.py +6 -4
- junifer/datagrabber/datalad_base.py +0 -2
- junifer/datagrabber/dmcc13_benchmark.py +87 -50
- junifer/datagrabber/hcp1200/hcp1200.py +21 -19
- junifer/datagrabber/pattern.py +124 -25
- junifer/datagrabber/pattern_datalad.py +111 -13
- junifer/datagrabber/tests/test_base.py +0 -6
- junifer/datagrabber/tests/test_datagrabber_utils.py +204 -76
- junifer/datagrabber/tests/test_datalad_base.py +0 -6
- junifer/datagrabber/tests/test_multiple.py +43 -10
- junifer/datagrabber/tests/test_pattern.py +125 -178
- junifer/datagrabber/tests/test_pattern_datalad.py +44 -25
- junifer/datagrabber/utils.py +141 -21
- junifer/datareader/default.py +6 -7
- {junifer-0.0.4.dev782.dist-info → junifer-0.0.4.dev810.dist-info}/METADATA +1 -1
- {junifer-0.0.4.dev782.dist-info → junifer-0.0.4.dev810.dist-info}/RECORD +30 -30
- {junifer-0.0.4.dev782.dist-info → junifer-0.0.4.dev810.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.4.dev782.dist-info → junifer-0.0.4.dev810.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.4.dev782.dist-info → junifer-0.0.4.dev810.dist-info}/WHEEL +0 -0
- {junifer-0.0.4.dev782.dist-info → junifer-0.0.4.dev810.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.4.dev782.dist-info → junifer-0.0.4.dev810.dist-info}/top_level.txt +0 -0
junifer/datagrabber/pattern.py
CHANGED
@@ -32,11 +32,98 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
32
32
|
types : list of str
|
33
33
|
The types of data to be grabbed.
|
34
34
|
patterns : dict
|
35
|
-
|
36
|
-
|
37
|
-
``
|
35
|
+
Data type patterns as a dictionary. It has the following schema:
|
36
|
+
|
37
|
+
* ``"T1w"`` :
|
38
|
+
|
39
|
+
.. code-block:: none
|
40
|
+
|
41
|
+
{
|
42
|
+
"mandatory": ["pattern", "space"],
|
43
|
+
"optional": []
|
44
|
+
}
|
45
|
+
|
46
|
+
* ``"T2w"`` :
|
47
|
+
|
48
|
+
.. code-block:: none
|
49
|
+
|
50
|
+
{
|
51
|
+
"mandatory": ["pattern", "space"],
|
52
|
+
"optional": []
|
53
|
+
}
|
54
|
+
|
55
|
+
* ``"BOLD"`` :
|
56
|
+
|
57
|
+
.. code-block:: none
|
58
|
+
|
59
|
+
{
|
60
|
+
"mandatory": ["pattern", "space"],
|
61
|
+
"optional": ["mask_item"]
|
62
|
+
}
|
63
|
+
|
64
|
+
* ``"Warp"`` :
|
65
|
+
|
66
|
+
.. code-block:: none
|
67
|
+
|
68
|
+
{
|
69
|
+
"mandatory": ["pattern", "src", "dst"],
|
70
|
+
"optional": []
|
71
|
+
}
|
72
|
+
|
73
|
+
* ``"BOLD_confounds"`` :
|
74
|
+
|
75
|
+
.. code-block:: none
|
76
|
+
|
77
|
+
{
|
78
|
+
"mandatory": ["pattern", "format"],
|
79
|
+
"optional": []
|
80
|
+
}
|
81
|
+
|
82
|
+
* ``"VBM_GM"`` :
|
83
|
+
|
84
|
+
.. code-block:: none
|
85
|
+
|
86
|
+
{
|
87
|
+
"mandatory": ["pattern", "space"],
|
88
|
+
"optional": []
|
89
|
+
}
|
90
|
+
|
91
|
+
* ``"VBM_WM"`` :
|
92
|
+
|
93
|
+
.. code-block:: none
|
94
|
+
|
95
|
+
{
|
96
|
+
"mandatory": ["pattern", "space"],
|
97
|
+
"optional": []
|
98
|
+
}
|
99
|
+
|
100
|
+
Basically, for each data type, one needs to provide ``mandatory`` keys
|
101
|
+
and can choose to also provide ``optional`` keys. The value for each
|
102
|
+
key is a string. So, one needs to provide necessary data types as a
|
103
|
+
dictionary, for example:
|
104
|
+
|
105
|
+
.. code-block:: none
|
106
|
+
|
107
|
+
{
|
108
|
+
"BOLD": {
|
109
|
+
"pattern": "...",
|
110
|
+
"space": "...",
|
111
|
+
},
|
112
|
+
"T1w": {
|
113
|
+
"pattern": "...",
|
114
|
+
"space": "...",
|
115
|
+
},
|
116
|
+
"Warp": {
|
117
|
+
"pattern": "...",
|
118
|
+
"src": "...",
|
119
|
+
"dst": "...",
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
taken from :class:`.HCP1200`.
|
38
124
|
replacements : str or list of str
|
39
|
-
Replacements in the
|
125
|
+
Replacements in the ``pattern`` key of each data type. The value needs
|
126
|
+
to be a list of all possible replacements.
|
40
127
|
datadir : str or pathlib.Path
|
41
128
|
The directory where the data is / will be stored.
|
42
129
|
confounds_format : {"fmriprep", "adhoc"} or None, optional
|
@@ -52,7 +139,7 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
52
139
|
def __init__(
|
53
140
|
self,
|
54
141
|
types: List[str],
|
55
|
-
patterns: Dict[str, str],
|
142
|
+
patterns: Dict[str, Dict[str, str]],
|
56
143
|
replacements: Union[List[str], str],
|
57
144
|
datadir: Union[str, Path],
|
58
145
|
confounds_format: Optional[str] = None,
|
@@ -69,7 +156,10 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
69
156
|
self.replacements = replacements
|
70
157
|
|
71
158
|
# Validate confounds format
|
72
|
-
if
|
159
|
+
if (
|
160
|
+
confounds_format is not None
|
161
|
+
and confounds_format not in _CONFOUNDS_FORMATS
|
162
|
+
):
|
73
163
|
raise_error(
|
74
164
|
"Invalid value for `confounds_format`, should be one of "
|
75
165
|
f"{_CONFOUNDS_FORMATS}."
|
@@ -143,6 +233,11 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
143
233
|
str
|
144
234
|
The pattern with the element replaced.
|
145
235
|
|
236
|
+
Raises
|
237
|
+
------
|
238
|
+
ValueError
|
239
|
+
If element keys do not match with replacements.
|
240
|
+
|
146
241
|
"""
|
147
242
|
if list(element.keys()) != self.replacements:
|
148
243
|
raise_error(
|
@@ -167,7 +262,7 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
167
262
|
return self.replacements
|
168
263
|
|
169
264
|
def get_item(self, **element: str) -> Dict[str, Dict]:
|
170
|
-
"""Implement single element indexing
|
265
|
+
"""Implement single element indexing for the datagrabber.
|
171
266
|
|
172
267
|
This method constructs a real path to the requested item's data, by
|
173
268
|
replacing the ``patterns`` with actual values passed via ``**element``.
|
@@ -184,20 +279,33 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
184
279
|
Dictionary of dictionaries for each type of data required for the
|
185
280
|
specified element.
|
186
281
|
|
282
|
+
Raises
|
283
|
+
------
|
284
|
+
RuntimeError
|
285
|
+
If more than one file matches for a data type's pattern or
|
286
|
+
if no file matches for a data type's pattern or
|
287
|
+
if file cannot be accessed for an element.
|
288
|
+
|
187
289
|
"""
|
188
290
|
out = {}
|
189
291
|
for t_type in self.types:
|
190
292
|
t_pattern = self.patterns[t_type]
|
191
|
-
t_replace = self._replace_patterns_glob(
|
293
|
+
t_replace = self._replace_patterns_glob(
|
294
|
+
element, t_pattern["pattern"]
|
295
|
+
)
|
192
296
|
if "*" in t_replace:
|
193
297
|
t_matches = list(self.datadir.absolute().glob(t_replace))
|
194
298
|
if len(t_matches) > 1:
|
195
299
|
raise_error(
|
196
300
|
f"More than one file matches for {element} / {t_type}:"
|
197
|
-
f" {t_matches}"
|
301
|
+
f" {t_matches}",
|
302
|
+
klass=RuntimeError,
|
198
303
|
)
|
199
304
|
elif len(t_matches) == 0:
|
200
|
-
raise_error(
|
305
|
+
raise_error(
|
306
|
+
f"No file matches for {element} / {t_type}",
|
307
|
+
klass=RuntimeError,
|
308
|
+
)
|
201
309
|
t_out = t_matches[0]
|
202
310
|
else:
|
203
311
|
t_out = self.datadir / t_replace
|
@@ -205,22 +313,13 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
205
313
|
if not t_out.exists() and not t_out.is_symlink():
|
206
314
|
raise_error(
|
207
315
|
f"Cannot access {t_type} for {element}: "
|
208
|
-
f"File {t_out} does not exist"
|
316
|
+
f"File {t_out} does not exist",
|
317
|
+
klass=RuntimeError,
|
209
318
|
)
|
210
319
|
# Update path for the element
|
211
|
-
out[t_type] =
|
212
|
-
#
|
213
|
-
|
214
|
-
if t_type == "BOLD_confounds":
|
215
|
-
if not self.confounds_format:
|
216
|
-
raise_error(
|
217
|
-
"`confounds_format` needs to be one of "
|
218
|
-
f"{_CONFOUNDS_FORMATS}, None provided. "
|
219
|
-
"As the DataGrabber used specifies "
|
220
|
-
"'BOLD_confounds', None is invalid."
|
221
|
-
)
|
222
|
-
# Set the format
|
223
|
-
out[t_type].update({"format": self.confounds_format})
|
320
|
+
out[t_type] = t_pattern.copy() # copy data type dictionary
|
321
|
+
out[t_type].pop("pattern") # remove pattern key
|
322
|
+
out[t_type].update({"path": t_out}) # add path key
|
224
323
|
|
225
324
|
return out
|
226
325
|
|
@@ -259,7 +358,7 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
259
358
|
re_pattern,
|
260
359
|
glob_pattern,
|
261
360
|
t_replacements,
|
262
|
-
) = self._replace_patterns_regex(t_pattern)
|
361
|
+
) = self._replace_patterns_regex(t_pattern["pattern"])
|
263
362
|
for fname in self.datadir.glob(glob_pattern):
|
264
363
|
suffix = fname.relative_to(self.datadir).as_posix()
|
265
364
|
m = re.match(re_pattern, suffix)
|
@@ -5,12 +5,11 @@
|
|
5
5
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
6
6
|
# License: AGPL
|
7
7
|
|
8
|
-
from typing import Dict, List
|
9
8
|
|
10
9
|
from ..api.decorators import register_datagrabber
|
10
|
+
from ..utils import logger
|
11
11
|
from .datalad_base import DataladDataGrabber
|
12
12
|
from .pattern import PatternDataGrabber
|
13
|
-
from .utils import validate_patterns
|
14
13
|
|
15
14
|
|
16
15
|
@register_datagrabber
|
@@ -25,11 +24,109 @@ class PatternDataladDataGrabber(DataladDataGrabber, PatternDataGrabber):
|
|
25
24
|
types : list of str
|
26
25
|
The types of data to be grabbed.
|
27
26
|
patterns : dict
|
28
|
-
|
29
|
-
|
30
|
-
``
|
31
|
-
|
32
|
-
|
27
|
+
Data type patterns as a dictionary. It has the following schema:
|
28
|
+
|
29
|
+
* ``"T1w"`` :
|
30
|
+
|
31
|
+
.. code-block:: none
|
32
|
+
|
33
|
+
{
|
34
|
+
"mandatory": ["pattern", "space"],
|
35
|
+
"optional": []
|
36
|
+
}
|
37
|
+
|
38
|
+
* ``"T2w"`` :
|
39
|
+
|
40
|
+
.. code-block:: none
|
41
|
+
|
42
|
+
{
|
43
|
+
"mandatory": ["pattern", "space"],
|
44
|
+
"optional": []
|
45
|
+
}
|
46
|
+
|
47
|
+
* ``"BOLD"`` :
|
48
|
+
|
49
|
+
.. code-block:: none
|
50
|
+
|
51
|
+
{
|
52
|
+
"mandatory": ["pattern", "space"],
|
53
|
+
"optional": ["mask_item"]
|
54
|
+
}
|
55
|
+
|
56
|
+
* ``"Warp"`` :
|
57
|
+
|
58
|
+
.. code-block:: none
|
59
|
+
|
60
|
+
{
|
61
|
+
"mandatory": ["pattern", "src", "dst"],
|
62
|
+
"optional": []
|
63
|
+
}
|
64
|
+
|
65
|
+
* ``"BOLD_confounds"`` :
|
66
|
+
|
67
|
+
.. code-block:: none
|
68
|
+
|
69
|
+
{
|
70
|
+
"mandatory": ["pattern", "format"],
|
71
|
+
"optional": []
|
72
|
+
}
|
73
|
+
|
74
|
+
* ``"VBM_GM"`` :
|
75
|
+
|
76
|
+
.. code-block:: none
|
77
|
+
|
78
|
+
{
|
79
|
+
"mandatory": ["pattern", "space"],
|
80
|
+
"optional": []
|
81
|
+
}
|
82
|
+
|
83
|
+
* ``"VBM_WM"`` :
|
84
|
+
|
85
|
+
.. code-block:: none
|
86
|
+
|
87
|
+
{
|
88
|
+
"mandatory": ["pattern", "space"],
|
89
|
+
"optional": []
|
90
|
+
}
|
91
|
+
|
92
|
+
Basically, for each data type, one needs to provide ``mandatory`` keys
|
93
|
+
and can choose to also provide ``optional`` keys. The value for each
|
94
|
+
key is a string. So, one needs to provide necessary data types as a
|
95
|
+
dictionary, for example:
|
96
|
+
|
97
|
+
.. code-block:: none
|
98
|
+
|
99
|
+
{
|
100
|
+
"BOLD": {
|
101
|
+
"pattern": "...",
|
102
|
+
"space": "...",
|
103
|
+
},
|
104
|
+
"T1w": {
|
105
|
+
"pattern": "...",
|
106
|
+
"space": "...",
|
107
|
+
},
|
108
|
+
"Warp": {
|
109
|
+
"pattern": "...",
|
110
|
+
"src": "...",
|
111
|
+
"dst": "...",
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
taken from :class:`.HCP1200`.
|
116
|
+
replacements : str or list of str
|
117
|
+
Replacements in the ``pattern`` key of each data type. The value needs
|
118
|
+
to be a list of all possible replacements.
|
119
|
+
confounds_format : {"fmriprep", "adhoc"} or None, optional
|
120
|
+
The format of the confounds for the dataset (default None).
|
121
|
+
datadir : str or pathlib.Path or None, optional
|
122
|
+
That directory where the datalad dataset will be cloned. If None,
|
123
|
+
the datalad dataset will be cloned into a temporary directory
|
124
|
+
(default None).
|
125
|
+
rootdir : str or pathlib.Path, optional
|
126
|
+
The path within the datalad dataset to the root directory
|
127
|
+
(default ".").
|
128
|
+
uri : str or None, optional
|
129
|
+
URI of the datalad sibling (default None).
|
33
130
|
|
34
131
|
See Also
|
35
132
|
--------
|
@@ -42,12 +139,13 @@ class PatternDataladDataGrabber(DataladDataGrabber, PatternDataGrabber):
|
|
42
139
|
|
43
140
|
def __init__(
|
44
141
|
self,
|
45
|
-
types: List[str],
|
46
|
-
patterns: Dict[str, str],
|
47
142
|
**kwargs,
|
48
143
|
) -> None:
|
49
|
-
#
|
50
|
-
|
144
|
+
# TODO(synchon): needs to be reworked, DataladDataGrabber needs to be
|
145
|
+
# a mixin to avoid multiple inheritance wherever possible.
|
146
|
+
|
147
|
+
logger.debug("Initializing PatternDataladDataGrabber")
|
148
|
+
for key, val in kwargs.items():
|
149
|
+
logger.debug(f"\t{key} = {val}")
|
51
150
|
|
52
|
-
super().__init__(
|
53
|
-
self.patterns = patterns
|
151
|
+
super().__init__(**kwargs)
|
@@ -12,12 +12,6 @@ import pytest
|
|
12
12
|
from junifer.datagrabber import BaseDataGrabber
|
13
13
|
|
14
14
|
|
15
|
-
def test_BaseDataGrabber_abstractness() -> None:
|
16
|
-
"""Test BaseDataGrabber is abstract base class."""
|
17
|
-
with pytest.raises(TypeError, match=r"abstract"):
|
18
|
-
BaseDataGrabber(datadir="/tmp", types=["func"]) # type: ignore
|
19
|
-
|
20
|
-
|
21
15
|
def test_BaseDataGrabber() -> None:
|
22
16
|
"""Test BaseDataGrabber."""
|
23
17
|
|
@@ -3,6 +3,9 @@
|
|
3
3
|
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
|
4
4
|
# License: AGPL
|
5
5
|
|
6
|
+
from contextlib import nullcontext
|
7
|
+
from typing import ContextManager, Dict, List, Union
|
8
|
+
|
6
9
|
import pytest
|
7
10
|
|
8
11
|
from junifer.datagrabber.utils import (
|
@@ -12,79 +15,204 @@ from junifer.datagrabber.utils import (
|
|
12
15
|
)
|
13
16
|
|
14
17
|
|
15
|
-
|
16
|
-
"
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
with
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
18
|
+
@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",
|
46
|
+
[
|
47
|
+
(
|
48
|
+
"wrong",
|
49
|
+
"also wrong",
|
50
|
+
pytest.raises(TypeError, match="must be a list"),
|
51
|
+
),
|
52
|
+
(
|
53
|
+
[1],
|
54
|
+
{
|
55
|
+
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
56
|
+
"BOLD": {
|
57
|
+
"pattern": "{subject}/func/{subject}_task-rest_bold.nii.gz"
|
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"),
|
79
|
+
),
|
80
|
+
(
|
81
|
+
["subject"],
|
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"],
|
118
|
+
"wrong",
|
119
|
+
pytest.raises(TypeError, match="must be a dict"),
|
120
|
+
),
|
121
|
+
(
|
122
|
+
["T1w", "BOLD"],
|
123
|
+
{
|
124
|
+
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
125
|
+
},
|
126
|
+
pytest.raises(
|
127
|
+
ValueError,
|
128
|
+
match="Length of `types` more than that of `patterns`.",
|
129
|
+
),
|
130
|
+
),
|
131
|
+
(
|
132
|
+
["T1w", "BOLD"],
|
133
|
+
{
|
134
|
+
"T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
|
135
|
+
"T2w": {"pattern": "{subject}/anat/{subject}_T2w.nii.gz"},
|
136
|
+
},
|
137
|
+
pytest.raises(ValueError, match="contain all"),
|
138
|
+
),
|
139
|
+
(
|
140
|
+
["T3w"],
|
141
|
+
{
|
142
|
+
"T3w": {"pattern": "{subject}/anat/{subject}_T3w.nii.gz"},
|
143
|
+
},
|
144
|
+
pytest.raises(ValueError, match="Unknown data type"),
|
145
|
+
),
|
146
|
+
(
|
147
|
+
["BOLD"],
|
148
|
+
{
|
149
|
+
"BOLD": {"patterns": "{subject}/func/{subject}_BOLD.nii.gz"},
|
150
|
+
},
|
151
|
+
pytest.raises(KeyError, match="Mandatory key"),
|
152
|
+
),
|
153
|
+
(
|
154
|
+
["BOLD_confounds"],
|
155
|
+
{
|
156
|
+
"BOLD_confounds": {
|
157
|
+
"pattern": "{subject}/func/{subject}_confounds.tsv",
|
158
|
+
"format": "fmriprep",
|
159
|
+
"space": "MNINLin6Asym",
|
160
|
+
},
|
161
|
+
},
|
162
|
+
pytest.raises(RuntimeError, match="not accepted"),
|
163
|
+
),
|
164
|
+
(
|
165
|
+
["T1w"],
|
166
|
+
{
|
167
|
+
"T1w": {
|
168
|
+
"pattern": "{subject}/anat/{subject}*.nii",
|
169
|
+
"space": "native",
|
170
|
+
},
|
171
|
+
},
|
172
|
+
pytest.raises(ValueError, match="following a replacement"),
|
173
|
+
),
|
174
|
+
(
|
175
|
+
["T1w", "T2w", "BOLD", "BOLD_confounds"],
|
176
|
+
{
|
177
|
+
"T1w": {
|
178
|
+
"pattern": "{subject}/anat/{subject}_T1w.nii.gz",
|
179
|
+
"space": "native",
|
180
|
+
},
|
181
|
+
"T2w": {
|
182
|
+
"pattern": "{subject}/anat/{subject}_T2w.nii.gz",
|
183
|
+
"space": "native",
|
184
|
+
},
|
185
|
+
"BOLD": {
|
186
|
+
"pattern": (
|
187
|
+
"{subject}/func/{subject}_task-rest_bold.nii.gz"
|
188
|
+
),
|
189
|
+
"space": "MNI152NLin6Asym",
|
190
|
+
},
|
191
|
+
"BOLD_confounds": {
|
192
|
+
"pattern": "{subject}/func/{subject}_confounds.tsv",
|
193
|
+
"format": "fmriprep",
|
194
|
+
},
|
195
|
+
},
|
196
|
+
nullcontext(),
|
197
|
+
),
|
198
|
+
],
|
199
|
+
)
|
200
|
+
def test_validate_patterns(
|
201
|
+
types: List[str],
|
202
|
+
patterns: Union[str, Dict[str, Dict[str, str]]],
|
203
|
+
expect: ContextManager,
|
204
|
+
) -> None:
|
205
|
+
"""Test validation of patterns.
|
206
|
+
|
207
|
+
Parameters
|
208
|
+
----------
|
209
|
+
types : list of str
|
210
|
+
The parametrized data types.
|
211
|
+
patterns : str, dict
|
212
|
+
The patterns to validate.
|
213
|
+
expect : typing.ContextManager
|
214
|
+
The parametrized ContextManager object.
|
215
|
+
|
216
|
+
"""
|
217
|
+
with expect:
|
218
|
+
validate_patterns(types=types, patterns=patterns) # type: ignore
|
@@ -26,12 +26,6 @@ _testing_dataset = {
|
|
26
26
|
}
|
27
27
|
|
28
28
|
|
29
|
-
def test_DataladDataGrabber_abstractness() -> None:
|
30
|
-
"""Test DataladDataGrabber is abstract base class."""
|
31
|
-
with pytest.raises(TypeError, match=r"abstract"):
|
32
|
-
DataladDataGrabber() # type: ignore
|
33
|
-
|
34
|
-
|
35
29
|
@pytest.fixture
|
36
30
|
def concrete_datagrabber() -> Type[DataladDataGrabber]:
|
37
31
|
"""Return a concrete datalad-based DataGrabber.
|