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.
- junifer/__init__.py +17 -0
- junifer/_version.py +2 -2
- junifer/api/__init__.py +4 -1
- junifer/api/cli.py +91 -1
- junifer/api/decorators.py +9 -0
- junifer/api/functions.py +56 -10
- junifer/api/parser.py +3 -0
- junifer/api/queue_context/__init__.py +4 -1
- junifer/api/queue_context/gnu_parallel_local_adapter.py +16 -6
- junifer/api/queue_context/htcondor_adapter.py +16 -5
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +41 -12
- junifer/api/queue_context/tests/test_htcondor_adapter.py +48 -15
- junifer/api/res/afni/run_afni_docker.sh +1 -1
- junifer/api/res/ants/run_ants_docker.sh +1 -1
- junifer/api/res/freesurfer/mri_binarize +3 -0
- junifer/api/res/freesurfer/mri_mc +3 -0
- junifer/api/res/freesurfer/mri_pretess +3 -0
- junifer/api/res/freesurfer/mris_convert +3 -0
- junifer/api/res/freesurfer/run_freesurfer_docker.sh +61 -0
- junifer/api/res/fsl/run_fsl_docker.sh +1 -1
- junifer/api/res/{run_conda.sh → run_conda.bash} +1 -1
- junifer/api/res/run_conda.zsh +23 -0
- junifer/api/res/run_venv.bash +22 -0
- junifer/api/res/{run_venv.sh → run_venv.zsh} +1 -1
- junifer/api/tests/test_api_utils.py +4 -2
- junifer/api/tests/test_cli.py +83 -0
- junifer/api/tests/test_functions.py +27 -2
- junifer/configs/__init__.py +1 -1
- junifer/configs/juseless/__init__.py +4 -1
- junifer/configs/juseless/datagrabbers/__init__.py +10 -1
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +4 -3
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +3 -0
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +4 -3
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +1 -3
- junifer/configs/juseless/datagrabbers/ucla.py +12 -9
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +3 -0
- junifer/data/__init__.py +21 -1
- junifer/data/coordinates.py +10 -19
- junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
- junifer/data/masks.py +58 -87
- junifer/data/parcellations.py +14 -3
- junifer/data/template_spaces.py +4 -1
- junifer/data/tests/test_masks.py +26 -37
- junifer/data/utils.py +3 -0
- junifer/datagrabber/__init__.py +18 -1
- junifer/datagrabber/aomic/__init__.py +3 -0
- junifer/datagrabber/aomic/id1000.py +70 -37
- junifer/datagrabber/aomic/piop1.py +69 -36
- junifer/datagrabber/aomic/piop2.py +71 -38
- junifer/datagrabber/aomic/tests/test_id1000.py +44 -100
- junifer/datagrabber/aomic/tests/test_piop1.py +65 -108
- junifer/datagrabber/aomic/tests/test_piop2.py +45 -102
- junifer/datagrabber/base.py +13 -6
- junifer/datagrabber/datalad_base.py +13 -1
- junifer/datagrabber/dmcc13_benchmark.py +36 -53
- junifer/datagrabber/hcp1200/__init__.py +3 -0
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -0
- junifer/datagrabber/hcp1200/hcp1200.py +4 -1
- junifer/datagrabber/multiple.py +45 -6
- junifer/datagrabber/pattern.py +170 -62
- junifer/datagrabber/pattern_datalad.py +25 -12
- junifer/datagrabber/pattern_validation_mixin.py +388 -0
- junifer/datagrabber/tests/test_datalad_base.py +4 -4
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +46 -19
- junifer/datagrabber/tests/test_multiple.py +161 -84
- junifer/datagrabber/tests/test_pattern.py +45 -0
- junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +249 -0
- junifer/datareader/__init__.py +4 -1
- junifer/datareader/default.py +95 -43
- junifer/external/BrainPrint/brainprint/__init__.py +4 -0
- junifer/external/BrainPrint/brainprint/_version.py +3 -0
- junifer/external/BrainPrint/brainprint/asymmetry.py +91 -0
- junifer/external/BrainPrint/brainprint/brainprint.py +441 -0
- junifer/external/BrainPrint/brainprint/surfaces.py +258 -0
- junifer/external/BrainPrint/brainprint/utils/__init__.py +1 -0
- junifer/external/BrainPrint/brainprint/utils/_config.py +112 -0
- junifer/external/BrainPrint/brainprint/utils/utils.py +188 -0
- junifer/external/__init__.py +1 -1
- junifer/external/nilearn/__init__.py +5 -1
- junifer/external/nilearn/junifer_connectivity_measure.py +483 -0
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +23 -9
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +1089 -0
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +76 -1
- junifer/markers/__init__.py +23 -1
- junifer/markers/base.py +68 -28
- junifer/markers/brainprint.py +459 -0
- junifer/markers/collection.py +10 -2
- junifer/markers/complexity/__init__.py +10 -0
- junifer/markers/complexity/complexity_base.py +26 -43
- junifer/markers/complexity/hurst_exponent.py +3 -0
- junifer/markers/complexity/multiscale_entropy_auc.py +3 -0
- junifer/markers/complexity/perm_entropy.py +3 -0
- junifer/markers/complexity/range_entropy.py +3 -0
- junifer/markers/complexity/range_entropy_auc.py +3 -0
- junifer/markers/complexity/sample_entropy.py +3 -0
- junifer/markers/complexity/tests/test_hurst_exponent.py +11 -3
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +11 -3
- junifer/markers/complexity/tests/test_perm_entropy.py +11 -3
- junifer/markers/complexity/tests/test_range_entropy.py +11 -3
- junifer/markers/complexity/tests/test_range_entropy_auc.py +11 -3
- junifer/markers/complexity/tests/test_sample_entropy.py +11 -3
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +11 -3
- junifer/markers/complexity/weighted_perm_entropy.py +3 -0
- junifer/markers/ets_rss.py +27 -42
- junifer/markers/falff/__init__.py +3 -0
- junifer/markers/falff/_afni_falff.py +5 -2
- junifer/markers/falff/_junifer_falff.py +3 -0
- junifer/markers/falff/falff_base.py +20 -46
- junifer/markers/falff/falff_parcels.py +56 -27
- junifer/markers/falff/falff_spheres.py +60 -29
- junifer/markers/falff/tests/test_falff_parcels.py +39 -23
- junifer/markers/falff/tests/test_falff_spheres.py +39 -23
- junifer/markers/functional_connectivity/__init__.py +9 -0
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +63 -60
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +45 -32
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +49 -36
- junifer/markers/functional_connectivity/functional_connectivity_base.py +71 -70
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +34 -25
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +40 -30
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +11 -7
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +27 -7
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +28 -12
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +35 -11
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +36 -62
- junifer/markers/parcel_aggregation.py +47 -61
- junifer/markers/reho/__init__.py +3 -0
- junifer/markers/reho/_afni_reho.py +5 -2
- junifer/markers/reho/_junifer_reho.py +4 -1
- junifer/markers/reho/reho_base.py +8 -27
- junifer/markers/reho/reho_parcels.py +28 -17
- junifer/markers/reho/reho_spheres.py +27 -18
- junifer/markers/reho/tests/test_reho_parcels.py +8 -3
- junifer/markers/reho/tests/test_reho_spheres.py +8 -3
- junifer/markers/sphere_aggregation.py +43 -59
- junifer/markers/temporal_snr/__init__.py +3 -0
- junifer/markers/temporal_snr/temporal_snr_base.py +23 -32
- junifer/markers/temporal_snr/temporal_snr_parcels.py +9 -6
- junifer/markers/temporal_snr/temporal_snr_spheres.py +9 -6
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +6 -3
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +6 -3
- junifer/markers/tests/test_brainprint.py +58 -0
- junifer/markers/tests/test_collection.py +9 -8
- junifer/markers/tests/test_ets_rss.py +15 -9
- junifer/markers/tests/test_markers_base.py +17 -18
- junifer/markers/tests/test_parcel_aggregation.py +93 -32
- junifer/markers/tests/test_sphere_aggregation.py +72 -19
- junifer/onthefly/__init__.py +4 -1
- junifer/onthefly/read_transform.py +3 -0
- junifer/pipeline/__init__.py +9 -1
- junifer/pipeline/pipeline_step_mixin.py +21 -4
- junifer/pipeline/registry.py +3 -0
- junifer/pipeline/singleton.py +3 -0
- junifer/pipeline/tests/test_registry.py +1 -1
- junifer/pipeline/update_meta_mixin.py +3 -0
- junifer/pipeline/utils.py +67 -1
- junifer/pipeline/workdir_manager.py +3 -0
- junifer/preprocess/__init__.py +10 -2
- junifer/preprocess/base.py +6 -3
- junifer/preprocess/confounds/__init__.py +3 -0
- junifer/preprocess/confounds/fmriprep_confound_remover.py +47 -60
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +72 -113
- junifer/preprocess/smoothing/__init__.py +9 -0
- junifer/preprocess/smoothing/_afni_smoothing.py +119 -0
- junifer/preprocess/smoothing/_fsl_smoothing.py +116 -0
- junifer/preprocess/smoothing/_nilearn_smoothing.py +69 -0
- junifer/preprocess/smoothing/smoothing.py +174 -0
- junifer/preprocess/smoothing/tests/test_smoothing.py +94 -0
- junifer/preprocess/warping/__init__.py +3 -0
- junifer/preprocess/warping/_ants_warper.py +3 -0
- junifer/preprocess/warping/_fsl_warper.py +3 -0
- junifer/stats.py +4 -1
- junifer/storage/__init__.py +9 -1
- junifer/storage/base.py +40 -1
- junifer/storage/hdf5.py +71 -9
- junifer/storage/pandas_base.py +3 -0
- junifer/storage/sqlite.py +3 -0
- junifer/storage/tests/test_hdf5.py +82 -10
- junifer/storage/utils.py +9 -0
- junifer/testing/__init__.py +4 -1
- junifer/testing/datagrabbers.py +13 -6
- junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
- junifer/testing/utils.py +3 -0
- junifer/utils/__init__.py +13 -2
- junifer/utils/fs.py +3 -0
- junifer/utils/helpers.py +32 -1
- junifer/utils/logging.py +33 -4
- junifer/utils/tests/test_logging.py +8 -0
- {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/METADATA +17 -16
- junifer-0.0.5.dist-info/RECORD +275 -0
- {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/WHEEL +1 -1
- junifer/datagrabber/tests/test_datagrabber_utils.py +0 -218
- junifer/datagrabber/utils.py +0 -230
- junifer/preprocess/ants/__init__.py +0 -4
- junifer/preprocess/ants/ants_apply_transforms_warper.py +0 -185
- junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +0 -56
- junifer/preprocess/bold_warper.py +0 -265
- junifer/preprocess/fsl/__init__.py +0 -4
- junifer/preprocess/fsl/apply_warper.py +0 -179
- junifer/preprocess/fsl/tests/test_apply_warper.py +0 -45
- junifer/preprocess/tests/test_bold_warper.py +0 -159
- junifer-0.0.4.dev831.dist-info/RECORD +0 -257
- {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/AUTHORS.rst +0 -0
- {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/LICENSE.md +0 -0
- {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/top_level.txt +0 -0
junifer/datagrabber/multiple.py
CHANGED
@@ -7,10 +7,15 @@
|
|
7
7
|
|
8
8
|
from typing import Dict, List, Tuple, Union
|
9
9
|
|
10
|
-
from ..
|
10
|
+
from ..api.decorators import register_datagrabber
|
11
|
+
from ..utils import deep_update, raise_error
|
11
12
|
from .base import BaseDataGrabber
|
12
13
|
|
13
14
|
|
15
|
+
__all__ = ["MultipleDataGrabber"]
|
16
|
+
|
17
|
+
|
18
|
+
@register_datagrabber
|
14
19
|
class MultipleDataGrabber(BaseDataGrabber):
|
15
20
|
"""Concrete implementation for multi sourced data fetching.
|
16
21
|
|
@@ -24,19 +29,53 @@ class MultipleDataGrabber(BaseDataGrabber):
|
|
24
29
|
**kwargs
|
25
30
|
Keyword arguments passed to superclass.
|
26
31
|
|
32
|
+
Raises
|
33
|
+
------
|
34
|
+
RuntimeError
|
35
|
+
If ``datagrabbers`` have different element keys or
|
36
|
+
overlapping data types or nested data types.
|
37
|
+
|
27
38
|
"""
|
28
39
|
|
29
40
|
def __init__(self, datagrabbers: List[BaseDataGrabber], **kwargs) -> None:
|
30
41
|
# Check datagrabbers consistency
|
31
|
-
#
|
42
|
+
# Check for same element keys
|
32
43
|
first_keys = datagrabbers[0].get_element_keys()
|
33
44
|
for dg in datagrabbers[1:]:
|
34
45
|
if dg.get_element_keys() != first_keys:
|
35
|
-
raise_error(
|
36
|
-
|
46
|
+
raise_error(
|
47
|
+
msg="DataGrabbers have different element keys",
|
48
|
+
klass=RuntimeError,
|
49
|
+
)
|
50
|
+
# Check for no overlapping types (and nested data types)
|
37
51
|
types = [x for dg in datagrabbers for x in dg.get_types()]
|
38
52
|
if len(types) != len(set(types)):
|
39
|
-
|
53
|
+
if all(hasattr(dg, "patterns") for dg in datagrabbers):
|
54
|
+
first_patterns = datagrabbers[0].patterns
|
55
|
+
for dg in datagrabbers[1:]:
|
56
|
+
for data_type in set(types):
|
57
|
+
dtype_pattern = dg.patterns.get(data_type)
|
58
|
+
if dtype_pattern is None:
|
59
|
+
continue
|
60
|
+
# Check if first-level keys of data type are same
|
61
|
+
if (
|
62
|
+
dtype_pattern.keys()
|
63
|
+
== first_patterns[data_type].keys()
|
64
|
+
):
|
65
|
+
raise_error(
|
66
|
+
msg=(
|
67
|
+
"DataGrabbers have overlapping mandatory "
|
68
|
+
"and / or optional key(s) for data type: "
|
69
|
+
f"`{data_type}`"
|
70
|
+
),
|
71
|
+
klass=RuntimeError,
|
72
|
+
)
|
73
|
+
else:
|
74
|
+
# Can't check further
|
75
|
+
raise_error(
|
76
|
+
msg="DataGrabbers have overlapping types",
|
77
|
+
klass=RuntimeError,
|
78
|
+
)
|
40
79
|
self._datagrabbers = datagrabbers
|
41
80
|
|
42
81
|
def __getitem__(self, element: Union[str, Tuple]) -> Dict:
|
@@ -62,7 +101,7 @@ class MultipleDataGrabber(BaseDataGrabber):
|
|
62
101
|
metas = []
|
63
102
|
for dg in self._datagrabbers:
|
64
103
|
t_out = dg[element]
|
65
|
-
out
|
104
|
+
deep_update(out, t_out)
|
66
105
|
# Now get the meta for this datagrabber
|
67
106
|
t_meta = {}
|
68
107
|
dg.update_meta(t_meta, "datagrabber")
|
junifer/datagrabber/pattern.py
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
# License: AGPL
|
7
7
|
|
8
8
|
import re
|
9
|
+
from copy import deepcopy
|
9
10
|
from pathlib import Path
|
10
11
|
from typing import Dict, List, Optional, Tuple, Union
|
11
12
|
|
@@ -14,7 +15,10 @@ import numpy as np
|
|
14
15
|
from ..api.decorators import register_datagrabber
|
15
16
|
from ..utils import logger, raise_error
|
16
17
|
from .base import BaseDataGrabber
|
17
|
-
from .
|
18
|
+
from .pattern_validation_mixin import PatternValidationMixin
|
19
|
+
|
20
|
+
|
21
|
+
__all__ = ["PatternDataGrabber"]
|
18
22
|
|
19
23
|
|
20
24
|
# Accepted formats for confounds specification
|
@@ -22,7 +26,7 @@ _CONFOUNDS_FORMATS = ("fmriprep", "adhoc")
|
|
22
26
|
|
23
27
|
|
24
28
|
@register_datagrabber
|
25
|
-
class PatternDataGrabber(BaseDataGrabber):
|
29
|
+
class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
26
30
|
"""Concrete implementation for pattern-based data fetching.
|
27
31
|
|
28
32
|
Implements a DataGrabber that understands patterns to grab data.
|
@@ -40,7 +44,12 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
40
44
|
|
41
45
|
{
|
42
46
|
"mandatory": ["pattern", "space"],
|
43
|
-
"optional":
|
47
|
+
"optional": {
|
48
|
+
"mask": {
|
49
|
+
"mandatory": ["pattern", "space"],
|
50
|
+
"optional": []
|
51
|
+
}
|
52
|
+
}
|
44
53
|
}
|
45
54
|
|
46
55
|
* ``"T2w"`` :
|
@@ -49,7 +58,12 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
49
58
|
|
50
59
|
{
|
51
60
|
"mandatory": ["pattern", "space"],
|
52
|
-
"optional":
|
61
|
+
"optional": {
|
62
|
+
"mask": {
|
63
|
+
"mandatory": ["pattern", "space"],
|
64
|
+
"optional": []
|
65
|
+
}
|
66
|
+
}
|
53
67
|
}
|
54
68
|
|
55
69
|
* ``"BOLD"`` :
|
@@ -58,7 +72,16 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
58
72
|
|
59
73
|
{
|
60
74
|
"mandatory": ["pattern", "space"],
|
61
|
-
"optional":
|
75
|
+
"optional": {
|
76
|
+
"mask": {
|
77
|
+
"mandatory": ["pattern", "space"],
|
78
|
+
"optional": []
|
79
|
+
}
|
80
|
+
"confounds": {
|
81
|
+
"mandatory": ["pattern", "format"],
|
82
|
+
"optional": []
|
83
|
+
}
|
84
|
+
}
|
62
85
|
}
|
63
86
|
|
64
87
|
* ``"Warp"`` :
|
@@ -70,15 +93,6 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
70
93
|
"optional": []
|
71
94
|
}
|
72
95
|
|
73
|
-
* ``"BOLD_confounds"`` :
|
74
|
-
|
75
|
-
.. code-block:: none
|
76
|
-
|
77
|
-
{
|
78
|
-
"mandatory": ["pattern", "format"],
|
79
|
-
"optional": []
|
80
|
-
}
|
81
|
-
|
82
96
|
* ``"VBM_GM"`` :
|
83
97
|
|
84
98
|
.. code-block:: none
|
@@ -128,6 +142,13 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
128
142
|
The directory where the data is / will be stored.
|
129
143
|
confounds_format : {"fmriprep", "adhoc"} or None, optional
|
130
144
|
The format of the confounds for the dataset (default None).
|
145
|
+
partial_pattern_ok : bool, optional
|
146
|
+
Whether to raise error if partial pattern for a data type is found.
|
147
|
+
This allows to bypass mandatory key check and issue a warning
|
148
|
+
instead of raising error. This allows one to have a DataGrabber
|
149
|
+
with data types without the corresponding mandatory keys and is
|
150
|
+
powerful when used with :class:`.MultipleDataGrabber`
|
151
|
+
(default True).
|
131
152
|
|
132
153
|
Raises
|
133
154
|
------
|
@@ -143,17 +164,21 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
143
164
|
replacements: Union[List[str], str],
|
144
165
|
datadir: Union[str, Path],
|
145
166
|
confounds_format: Optional[str] = None,
|
167
|
+
partial_pattern_ok: bool = False,
|
146
168
|
) -> None:
|
147
|
-
# Validate patterns
|
148
|
-
validate_patterns(types=types, patterns=patterns)
|
149
|
-
self.patterns = patterns
|
150
|
-
|
151
169
|
# Convert replacements to list if not already
|
152
170
|
if not isinstance(replacements, list):
|
153
171
|
replacements = [replacements]
|
154
|
-
# Validate
|
155
|
-
|
172
|
+
# Validate patterns
|
173
|
+
self.validate_patterns(
|
174
|
+
types=types,
|
175
|
+
replacements=replacements,
|
176
|
+
patterns=patterns,
|
177
|
+
partial_pattern_ok=partial_pattern_ok,
|
178
|
+
)
|
156
179
|
self.replacements = replacements
|
180
|
+
self.patterns = patterns
|
181
|
+
self.partial_pattern_ok = partial_pattern_ok
|
157
182
|
|
158
183
|
# Validate confounds format
|
159
184
|
if (
|
@@ -204,18 +229,25 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
204
229
|
t_replacements = [
|
205
230
|
x for x in self.replacements if f"{{{x}}}" in pattern
|
206
231
|
]
|
207
|
-
|
232
|
+
# Ops on re_pattern
|
233
|
+
# Remove negated unix glob pattern i.e., [!...] for re_pattern
|
234
|
+
re_pattern = re.sub(r"\[!.?\]", "", re_pattern)
|
235
|
+
# Remove enclosing square brackets from unix glob pattern i.e., [...]
|
236
|
+
# for re_pattern
|
237
|
+
re_pattern = re.sub(r"\[|\]", "", re_pattern)
|
238
|
+
# Iteratively replace the first of each with a named group definition
|
208
239
|
for t_r in t_replacements:
|
209
|
-
# Replace the first of each with a named group definition
|
210
240
|
re_pattern = re_pattern.replace(f"{{{t_r}}}", f"(?P<{t_r}>.*)", 1)
|
211
|
-
|
241
|
+
# Iteratively replace the second appearance of each with the named
|
242
|
+
# group back reference
|
212
243
|
for t_r in t_replacements:
|
213
|
-
# Replace the second appearance of each with the named group
|
214
|
-
# back reference
|
215
244
|
re_pattern = re_pattern.replace(f"{{{t_r}}}", f"(?P={t_r})")
|
216
|
-
|
245
|
+
# Ops on glob_pattern
|
246
|
+
# Iteratively replace replacements with wildcard i.e., *
|
247
|
+
# for glob_pattern
|
217
248
|
for t_r in t_replacements:
|
218
249
|
glob_pattern = glob_pattern.replace(f"{{{t_r}}}", "*")
|
250
|
+
|
219
251
|
return re_pattern, glob_pattern, t_replacements
|
220
252
|
|
221
253
|
def _replace_patterns_glob(self, element: Dict, pattern: str) -> str:
|
@@ -244,8 +276,70 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
244
276
|
f"The element keys must be {self.replacements}, "
|
245
277
|
f"element has {list(element.keys())}."
|
246
278
|
)
|
279
|
+
# Remove negated unix glob pattern i.e., [!...]
|
280
|
+
pattern = re.sub(r"\[!.?\]", "", pattern)
|
281
|
+
# Remove enclosing square brackets from unix glob pattern i.e., [...]
|
282
|
+
pattern = re.sub(r"\[|\]", "", pattern)
|
247
283
|
return pattern.format(**element)
|
248
284
|
|
285
|
+
def _get_path_from_patterns(
|
286
|
+
self, element: Dict, pattern: str, data_type: str
|
287
|
+
) -> Path:
|
288
|
+
"""Get path from resolved patterns.
|
289
|
+
|
290
|
+
Parameters
|
291
|
+
----------
|
292
|
+
element : dict
|
293
|
+
The element to be used in the replacement.
|
294
|
+
pattern : str
|
295
|
+
The pattern to be replaced.
|
296
|
+
data_type : str
|
297
|
+
The data type of the pattern.
|
298
|
+
|
299
|
+
Returns
|
300
|
+
-------
|
301
|
+
pathlib.Path
|
302
|
+
The path for the resolved pattern.
|
303
|
+
|
304
|
+
Raises
|
305
|
+
------
|
306
|
+
RuntimeError
|
307
|
+
If more than one file matches for a data type's pattern or
|
308
|
+
if no file matches for a data type's pattern or
|
309
|
+
if file cannot be accessed for an element.
|
310
|
+
|
311
|
+
"""
|
312
|
+
# Replace element in the pattern for globbing
|
313
|
+
resolved_pattern = self._replace_patterns_glob(element, pattern)
|
314
|
+
# Resolve path for wildcard
|
315
|
+
if "*" in resolved_pattern:
|
316
|
+
t_matches = list(self.datadir.absolute().glob(resolved_pattern))
|
317
|
+
# Multiple matches
|
318
|
+
if len(t_matches) > 1:
|
319
|
+
raise_error(
|
320
|
+
f"More than one file matches for {element} / {data_type}:"
|
321
|
+
f" {t_matches}",
|
322
|
+
klass=RuntimeError,
|
323
|
+
)
|
324
|
+
# No matches
|
325
|
+
elif len(t_matches) == 0:
|
326
|
+
raise_error(
|
327
|
+
f"No file matches for {element} / {data_type}",
|
328
|
+
klass=RuntimeError,
|
329
|
+
)
|
330
|
+
path = t_matches[0]
|
331
|
+
else:
|
332
|
+
path = self.datadir / resolved_pattern
|
333
|
+
if not self.skip_file_check:
|
334
|
+
if not path.exists() and not path.is_symlink():
|
335
|
+
raise_error(
|
336
|
+
f"Cannot access {data_type} for {element}: "
|
337
|
+
f"File {path} does not exist",
|
338
|
+
klass=RuntimeError,
|
339
|
+
)
|
340
|
+
|
341
|
+
return path
|
342
|
+
|
249
343
|
def get_element_keys(self) -> List[str]:
|
250
344
|
"""Get element keys.
|
251
345
|
|
@@ -279,47 +373,49 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
279
373
|
Dictionary of dictionaries for each type of data required for the
|
280
374
|
specified element.
|
281
375
|
|
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
|
-
|
289
376
|
"""
|
290
377
|
out = {}
|
291
378
|
for t_type in self.types:
|
379
|
+
# Data type dictionary
|
292
380
|
t_pattern = self.patterns[t_type]
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
if
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
381
|
+
# Copy data type dictionary in output
|
382
|
+
out[t_type] = deepcopy(t_pattern)
|
383
|
+
# Iterate to check for nested "types" like mask
|
384
|
+
for k, v in t_pattern.items():
|
385
|
+
# Resolve pattern for base data type
|
386
|
+
if k == "pattern":
|
387
|
+
logger.info(f"Resolving path from pattern for {t_type}")
|
388
|
+
# Resolve pattern
|
389
|
+
base_data_type_pattern_path = self._get_path_from_patterns(
|
390
|
+
element=element,
|
391
|
+
pattern=v,
|
392
|
+
data_type=t_type,
|
303
393
|
)
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
394
|
+
# Remove pattern key
|
395
|
+
out[t_type].pop("pattern")
|
396
|
+
# Add path key
|
397
|
+
out[t_type].update({"path": base_data_type_pattern_path})
|
398
|
+
# Resolve pattern for nested data type
|
399
|
+
if isinstance(v, dict) and "pattern" in v:
|
400
|
+
# Set nested type key for easier access
|
401
|
+
t_nested_type = f"{t_type}.{k}"
|
402
|
+
logger.info(
|
403
|
+
f"Resolving path from pattern for {t_nested_type}"
|
308
404
|
)
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
f"Cannot access {t_type} for {element}: "
|
316
|
-
f"File {t_out} does not exist",
|
317
|
-
klass=RuntimeError,
|
405
|
+
# Resolve pattern
|
406
|
+
nested_data_type_pattern_path = (
|
407
|
+
self._get_path_from_patterns(
|
408
|
+
element=element,
|
409
|
+
pattern=v["pattern"],
|
410
|
+
data_type=t_nested_type,
|
318
411
|
)
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
412
|
+
)
|
413
|
+
# Remove pattern key
|
414
|
+
out[t_type][k].pop("pattern")
|
415
|
+
# Add path key
|
416
|
+
out[t_type][k].update(
|
417
|
+
{"path": nested_data_type_pattern_path}
|
418
|
+
)
|
323
419
|
|
324
420
|
return out
|
325
421
|
|
@@ -351,14 +447,26 @@ class PatternDataGrabber(BaseDataGrabber):
|
|
351
447
|
for t_idx in reversed(order):
|
352
448
|
t_type = self.types[t_idx]
|
353
449
|
types_element = set()
|
354
|
-
|
450
|
+
|
451
|
+
# Get the pattern dict
|
355
452
|
t_pattern = self.patterns[t_type]
|
453
|
+
# Conditional fetch of base pattern for getting elements
|
454
|
+
pattern = None
|
455
|
+
# Try for data type pattern
|
456
|
+
pattern = t_pattern.get("pattern")
|
457
|
+
# Try for nested data type pattern
|
458
|
+
if pattern is None and self.partial_pattern_ok:
|
459
|
+
for v in t_pattern.values():
|
460
|
+
if isinstance(v, dict) and "pattern" in v:
|
461
|
+
pattern = v["pattern"]
|
462
|
+
break
|
463
|
+
|
356
464
|
# Replace the pattern
|
357
465
|
(
|
358
466
|
re_pattern,
|
359
467
|
glob_pattern,
|
360
468
|
t_replacements,
|
361
|
-
) = self._replace_patterns_regex(
|
469
|
+
) = self._replace_patterns_regex(pattern)
|
362
470
|
for fname in self.datadir.glob(glob_pattern):
|
363
471
|
suffix = fname.relative_to(self.datadir).as_posix()
|
364
472
|
m = re.match(re_pattern, suffix)
|
@@ -12,6 +12,9 @@ from .datalad_base import DataladDataGrabber
|
|
12
12
|
from .pattern import PatternDataGrabber
|
13
13
|
|
14
14
|
|
15
|
+
__all__ = ["PatternDataladDataGrabber"]
|
16
|
+
|
17
|
+
|
15
18
|
@register_datagrabber
|
16
19
|
class PatternDataladDataGrabber(DataladDataGrabber, PatternDataGrabber):
|
17
20
|
"""Concrete implementation for pattern and datalad based data fetching.
|
@@ -32,7 +35,12 @@ class PatternDataladDataGrabber(DataladDataGrabber, PatternDataGrabber):
|
|
32
35
|
|
33
36
|
{
|
34
37
|
"mandatory": ["pattern", "space"],
|
35
|
-
"optional":
|
38
|
+
"optional": {
|
39
|
+
"mask": {
|
40
|
+
"mandatory": ["pattern", "space"],
|
41
|
+
"optional": []
|
42
|
+
}
|
43
|
+
}
|
36
44
|
}
|
37
45
|
|
38
46
|
* ``"T2w"`` :
|
@@ -41,7 +49,12 @@ class PatternDataladDataGrabber(DataladDataGrabber, PatternDataGrabber):
|
|
41
49
|
|
42
50
|
{
|
43
51
|
"mandatory": ["pattern", "space"],
|
44
|
-
"optional":
|
52
|
+
"optional": {
|
53
|
+
"mask": {
|
54
|
+
"mandatory": ["pattern", "space"],
|
55
|
+
"optional": []
|
56
|
+
}
|
57
|
+
}
|
45
58
|
}
|
46
59
|
|
47
60
|
* ``"BOLD"`` :
|
@@ -50,7 +63,16 @@ class PatternDataladDataGrabber(DataladDataGrabber, PatternDataGrabber):
|
|
50
63
|
|
51
64
|
{
|
52
65
|
"mandatory": ["pattern", "space"],
|
53
|
-
"optional":
|
66
|
+
"optional": {
|
67
|
+
"mask": {
|
68
|
+
"mandatory": ["pattern", "space"],
|
69
|
+
"optional": []
|
70
|
+
}
|
71
|
+
"confounds": {
|
72
|
+
"mandatory": ["pattern", "format"],
|
73
|
+
"optional": []
|
74
|
+
}
|
75
|
+
}
|
54
76
|
}
|
55
77
|
|
56
78
|
* ``"Warp"`` :
|
@@ -62,15 +84,6 @@ class PatternDataladDataGrabber(DataladDataGrabber, PatternDataGrabber):
|
|
62
84
|
"optional": []
|
63
85
|
}
|
64
86
|
|
65
|
-
* ``"BOLD_confounds"`` :
|
66
|
-
|
67
|
-
.. code-block:: none
|
68
|
-
|
69
|
-
{
|
70
|
-
"mandatory": ["pattern", "format"],
|
71
|
-
"optional": []
|
72
|
-
}
|
73
|
-
|
74
87
|
* ``"VBM_GM"`` :
|
75
88
|
|
76
89
|
.. code-block:: none
|