junifer 0.0.5.dev240__py3-none-any.whl → 0.0.6__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 +2 -31
- junifer/__init__.pyi +37 -0
- junifer/_version.py +9 -4
- junifer/api/__init__.py +3 -5
- junifer/api/__init__.pyi +4 -0
- junifer/api/decorators.py +14 -19
- junifer/api/functions.py +165 -109
- junifer/api/py.typed +0 -0
- junifer/api/queue_context/__init__.py +2 -4
- junifer/api/queue_context/__init__.pyi +5 -0
- junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
- junifer/api/queue_context/htcondor_adapter.py +23 -6
- junifer/api/queue_context/py.typed +0 -0
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
- junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
- junifer/api/tests/test_functions.py +168 -74
- junifer/cli/__init__.py +24 -0
- junifer/cli/__init__.pyi +3 -0
- junifer/{api → cli}/cli.py +141 -125
- junifer/cli/parser.py +235 -0
- junifer/cli/py.typed +0 -0
- junifer/{api → cli}/tests/test_cli.py +8 -8
- junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
- junifer/{api → cli}/tests/test_parser.py +2 -2
- junifer/{api → cli}/utils.py +6 -16
- junifer/configs/juseless/__init__.py +2 -2
- junifer/configs/juseless/__init__.pyi +3 -0
- junifer/configs/juseless/datagrabbers/__init__.py +2 -12
- junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
- junifer/configs/juseless/datagrabbers/py.typed +0 -0
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
- junifer/configs/juseless/datagrabbers/ucla.py +4 -4
- junifer/configs/juseless/py.typed +0 -0
- junifer/conftest.py +25 -0
- junifer/data/__init__.py +2 -42
- junifer/data/__init__.pyi +29 -0
- junifer/data/_dispatch.py +248 -0
- junifer/data/coordinates/__init__.py +9 -0
- junifer/data/coordinates/__init__.pyi +5 -0
- junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
- junifer/data/coordinates/_coordinates.py +385 -0
- junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
- junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
- junifer/data/masks/__init__.py +9 -0
- junifer/data/masks/__init__.pyi +6 -0
- junifer/data/masks/_ants_mask_warper.py +177 -0
- junifer/data/masks/_fsl_mask_warper.py +106 -0
- junifer/data/masks/_masks.py +802 -0
- junifer/data/{tests → masks/tests}/test_masks.py +67 -63
- junifer/data/parcellations/__init__.py +9 -0
- junifer/data/parcellations/__init__.pyi +6 -0
- junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
- junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
- junifer/data/parcellations/_parcellations.py +1388 -0
- junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
- junifer/data/pipeline_data_registry_base.py +76 -0
- junifer/data/py.typed +0 -0
- junifer/data/template_spaces.py +44 -79
- junifer/data/tests/test_data_utils.py +1 -2
- junifer/data/tests/test_template_spaces.py +8 -4
- junifer/data/utils.py +109 -4
- junifer/datagrabber/__init__.py +2 -26
- junifer/datagrabber/__init__.pyi +27 -0
- junifer/datagrabber/aomic/__init__.py +2 -4
- junifer/datagrabber/aomic/__init__.pyi +5 -0
- junifer/datagrabber/aomic/id1000.py +81 -52
- junifer/datagrabber/aomic/piop1.py +83 -55
- junifer/datagrabber/aomic/piop2.py +85 -56
- junifer/datagrabber/aomic/py.typed +0 -0
- junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
- junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
- junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
- junifer/datagrabber/base.py +22 -18
- junifer/datagrabber/datalad_base.py +71 -34
- junifer/datagrabber/dmcc13_benchmark.py +31 -18
- junifer/datagrabber/hcp1200/__init__.py +2 -3
- junifer/datagrabber/hcp1200/__init__.pyi +4 -0
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
- junifer/datagrabber/hcp1200/hcp1200.py +26 -15
- junifer/datagrabber/hcp1200/py.typed +0 -0
- junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
- junifer/datagrabber/multiple.py +14 -9
- junifer/datagrabber/pattern.py +132 -96
- junifer/datagrabber/pattern_validation_mixin.py +206 -94
- junifer/datagrabber/py.typed +0 -0
- junifer/datagrabber/tests/test_datalad_base.py +27 -12
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
- junifer/datagrabber/tests/test_multiple.py +48 -2
- junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
- junifer/datareader/__init__.py +2 -2
- junifer/datareader/__init__.pyi +3 -0
- junifer/datareader/default.py +6 -6
- junifer/datareader/py.typed +0 -0
- junifer/external/nilearn/__init__.py +2 -3
- junifer/external/nilearn/__init__.pyi +4 -0
- junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
- junifer/external/nilearn/py.typed +0 -0
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
- junifer/markers/__init__.py +2 -38
- junifer/markers/__init__.pyi +37 -0
- junifer/markers/base.py +11 -14
- junifer/markers/brainprint.py +12 -14
- junifer/markers/complexity/__init__.py +2 -18
- junifer/markers/complexity/__init__.pyi +17 -0
- junifer/markers/complexity/complexity_base.py +9 -11
- junifer/markers/complexity/hurst_exponent.py +7 -7
- junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
- junifer/markers/complexity/perm_entropy.py +7 -7
- junifer/markers/complexity/py.typed +0 -0
- junifer/markers/complexity/range_entropy.py +7 -7
- junifer/markers/complexity/range_entropy_auc.py +7 -7
- junifer/markers/complexity/sample_entropy.py +7 -7
- junifer/markers/complexity/tests/test_complexity_base.py +1 -1
- junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
- junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
- junifer/markers/complexity/tests/test_range_entropy.py +5 -5
- junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
- junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
- junifer/markers/complexity/weighted_perm_entropy.py +7 -7
- junifer/markers/ets_rss.py +12 -11
- junifer/markers/falff/__init__.py +2 -3
- junifer/markers/falff/__init__.pyi +4 -0
- junifer/markers/falff/_afni_falff.py +38 -45
- junifer/markers/falff/_junifer_falff.py +16 -19
- junifer/markers/falff/falff_base.py +7 -11
- junifer/markers/falff/falff_parcels.py +9 -9
- junifer/markers/falff/falff_spheres.py +8 -8
- junifer/markers/falff/py.typed +0 -0
- junifer/markers/falff/tests/test_falff_spheres.py +3 -1
- junifer/markers/functional_connectivity/__init__.py +2 -12
- junifer/markers/functional_connectivity/__init__.pyi +13 -0
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
- junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
- junifer/markers/functional_connectivity/py.typed +0 -0
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
- junifer/markers/parcel_aggregation.py +22 -17
- junifer/markers/py.typed +0 -0
- junifer/markers/reho/__init__.py +2 -3
- junifer/markers/reho/__init__.pyi +4 -0
- junifer/markers/reho/_afni_reho.py +29 -35
- junifer/markers/reho/_junifer_reho.py +13 -14
- junifer/markers/reho/py.typed +0 -0
- junifer/markers/reho/reho_base.py +7 -11
- junifer/markers/reho/reho_parcels.py +10 -10
- junifer/markers/reho/reho_spheres.py +9 -9
- junifer/markers/sphere_aggregation.py +22 -17
- junifer/markers/temporal_snr/__init__.py +2 -3
- junifer/markers/temporal_snr/__init__.pyi +4 -0
- junifer/markers/temporal_snr/py.typed +0 -0
- junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
- junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
- junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
- junifer/markers/tests/test_ets_rss.py +3 -3
- junifer/markers/tests/test_parcel_aggregation.py +24 -24
- junifer/markers/tests/test_sphere_aggregation.py +6 -6
- junifer/markers/utils.py +3 -3
- junifer/onthefly/__init__.py +2 -1
- junifer/onthefly/_brainprint.py +138 -0
- junifer/onthefly/read_transform.py +5 -8
- junifer/pipeline/__init__.py +2 -10
- junifer/pipeline/__init__.pyi +13 -0
- junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
- junifer/pipeline/pipeline_component_registry.py +294 -0
- junifer/pipeline/pipeline_step_mixin.py +15 -11
- junifer/pipeline/py.typed +0 -0
- junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
- junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
- junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
- junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
- junifer/pipeline/tests/test_workdir_manager.py +43 -0
- junifer/pipeline/update_meta_mixin.py +21 -17
- junifer/pipeline/utils.py +6 -6
- junifer/pipeline/workdir_manager.py +19 -5
- junifer/preprocess/__init__.py +2 -10
- junifer/preprocess/__init__.pyi +11 -0
- junifer/preprocess/base.py +10 -10
- junifer/preprocess/confounds/__init__.py +2 -2
- junifer/preprocess/confounds/__init__.pyi +3 -0
- junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
- junifer/preprocess/confounds/py.typed +0 -0
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
- junifer/preprocess/py.typed +0 -0
- junifer/preprocess/smoothing/__init__.py +2 -2
- junifer/preprocess/smoothing/__init__.pyi +3 -0
- junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
- junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
- junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
- junifer/preprocess/smoothing/py.typed +0 -0
- junifer/preprocess/smoothing/smoothing.py +11 -13
- junifer/preprocess/warping/__init__.py +2 -2
- junifer/preprocess/warping/__init__.pyi +3 -0
- junifer/preprocess/warping/_ants_warper.py +136 -32
- junifer/preprocess/warping/_fsl_warper.py +73 -22
- junifer/preprocess/warping/py.typed +0 -0
- junifer/preprocess/warping/space_warper.py +39 -11
- junifer/preprocess/warping/tests/test_space_warper.py +5 -9
- junifer/py.typed +0 -0
- junifer/stats.py +5 -5
- junifer/storage/__init__.py +2 -10
- junifer/storage/__init__.pyi +11 -0
- junifer/storage/base.py +47 -13
- junifer/storage/hdf5.py +95 -33
- junifer/storage/pandas_base.py +12 -11
- junifer/storage/py.typed +0 -0
- junifer/storage/sqlite.py +11 -11
- junifer/storage/tests/test_hdf5.py +86 -4
- junifer/storage/tests/test_sqlite.py +2 -2
- junifer/storage/tests/test_storage_base.py +5 -2
- junifer/storage/tests/test_utils.py +33 -7
- junifer/storage/utils.py +95 -9
- junifer/testing/__init__.py +2 -3
- junifer/testing/__init__.pyi +4 -0
- junifer/testing/datagrabbers.py +10 -11
- junifer/testing/py.typed +0 -0
- junifer/testing/registry.py +4 -7
- junifer/testing/tests/test_testing_registry.py +9 -17
- junifer/tests/test_stats.py +2 -2
- junifer/typing/__init__.py +9 -0
- junifer/typing/__init__.pyi +31 -0
- junifer/typing/_typing.py +68 -0
- junifer/utils/__init__.py +2 -12
- junifer/utils/__init__.pyi +18 -0
- junifer/utils/_config.py +110 -0
- junifer/utils/_yaml.py +16 -0
- junifer/utils/helpers.py +6 -6
- junifer/utils/logging.py +117 -8
- junifer/utils/py.typed +0 -0
- junifer/{pipeline → utils}/singleton.py +19 -14
- junifer/utils/tests/test_config.py +59 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
- junifer-0.0.6.dist-info/RECORD +350 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
- junifer-0.0.6.dist-info/entry_points.txt +2 -0
- junifer/api/parser.py +0 -118
- junifer/data/coordinates.py +0 -408
- junifer/data/masks.py +0 -670
- junifer/data/parcellations.py +0 -1828
- junifer/pipeline/registry.py +0 -177
- junifer/pipeline/tests/test_registry.py +0 -150
- junifer-0.0.5.dev240.dist-info/RECORD +0 -275
- junifer-0.0.5.dev240.dist-info/entry_points.txt +0 -2
- /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
- /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
- /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -3,8 +3,8 @@
|
|
3
3
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
4
|
# License: AGPL
|
5
5
|
|
6
|
-
from typing import Dict, List
|
7
6
|
|
7
|
+
from ..typing import DataGrabberPatterns
|
8
8
|
from ..utils import logger, raise_error, warn_with_log
|
9
9
|
|
10
10
|
|
@@ -33,10 +33,12 @@ PATTERNS_SCHEMA = {
|
|
33
33
|
"mandatory": ["pattern", "format"],
|
34
34
|
"optional": ["mappings"],
|
35
35
|
},
|
36
|
+
"reference": {"mandatory": ["pattern"], "optional": []},
|
37
|
+
"prewarp_space": {"mandatory": [], "optional": []},
|
36
38
|
},
|
37
39
|
},
|
38
40
|
"Warp": {
|
39
|
-
"mandatory": ["pattern", "src", "dst"],
|
41
|
+
"mandatory": ["pattern", "src", "dst", "warper"],
|
40
42
|
"optional": {},
|
41
43
|
},
|
42
44
|
"VBM_GM": {
|
@@ -72,7 +74,7 @@ PATTERNS_SCHEMA = {
|
|
72
74
|
class PatternValidationMixin:
|
73
75
|
"""Mixin class for pattern validation."""
|
74
76
|
|
75
|
-
def _validate_types(self, types:
|
77
|
+
def _validate_types(self, types: list[str]) -> None:
|
76
78
|
"""Validate the types.
|
77
79
|
|
78
80
|
Parameters
|
@@ -95,8 +97,8 @@ class PatternValidationMixin:
|
|
95
97
|
|
96
98
|
def _validate_replacements(
|
97
99
|
self,
|
98
|
-
replacements:
|
99
|
-
patterns:
|
100
|
+
replacements: list[str],
|
101
|
+
patterns: DataGrabberPatterns,
|
100
102
|
partial_pattern_ok: bool,
|
101
103
|
) -> None:
|
102
104
|
"""Validate the replacements.
|
@@ -132,45 +134,57 @@ class PatternValidationMixin:
|
|
132
134
|
|
133
135
|
if any(not isinstance(x, str) for x in replacements):
|
134
136
|
raise_error(
|
135
|
-
msg="`replacements` must be a list of strings
|
137
|
+
msg="`replacements` must be a list of strings",
|
136
138
|
klass=TypeError,
|
137
139
|
)
|
138
140
|
|
141
|
+
# Make a list of all patterns recursively
|
142
|
+
all_patterns = []
|
143
|
+
for dtype_val in patterns.values():
|
144
|
+
# Conditional for list dtype vals like Warp
|
145
|
+
if isinstance(dtype_val, list):
|
146
|
+
for entry in dtype_val:
|
147
|
+
all_patterns.append(entry.get("pattern", ""))
|
148
|
+
else:
|
149
|
+
all_patterns.append(dtype_val.get("pattern", ""))
|
150
|
+
# Check for stray replacements
|
139
151
|
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
|
-
):
|
152
|
+
if all(x not in y for y in all_patterns):
|
147
153
|
if partial_pattern_ok:
|
148
154
|
warn_with_log(
|
149
155
|
f"Replacement: `{x}` is not part of any pattern, "
|
150
156
|
"things might not work as expected if you are unsure "
|
151
|
-
"of what you are doing"
|
157
|
+
"of what you are doing."
|
152
158
|
)
|
153
159
|
else:
|
154
160
|
raise_error(
|
155
|
-
msg=f"Replacement: {x} is not part of any pattern
|
161
|
+
msg=f"Replacement: `{x}` is not part of any pattern"
|
156
162
|
)
|
157
163
|
|
158
164
|
# Check that at least one pattern has all the replacements
|
159
165
|
at_least_one = False
|
160
|
-
for
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
166
|
+
for dtype_val in patterns.values():
|
167
|
+
# Conditional for list dtype vals like Warp
|
168
|
+
if isinstance(dtype_val, list):
|
169
|
+
for entry in dtype_val:
|
170
|
+
if all(
|
171
|
+
x in entry.get("pattern", "") for x in replacements
|
172
|
+
):
|
173
|
+
at_least_one = True
|
174
|
+
else:
|
175
|
+
if all(
|
176
|
+
x in dtype_val.get("pattern", "") for x in replacements
|
177
|
+
):
|
178
|
+
at_least_one = True
|
165
179
|
if not at_least_one and not partial_pattern_ok:
|
166
180
|
raise_error(
|
167
|
-
msg="At least one pattern must contain all replacements
|
181
|
+
msg="At least one pattern must contain all replacements"
|
168
182
|
)
|
169
183
|
|
170
184
|
def _validate_mandatory_keys(
|
171
185
|
self,
|
172
|
-
keys:
|
173
|
-
schema:
|
186
|
+
keys: list[str],
|
187
|
+
schema: list[str],
|
174
188
|
data_type: str,
|
175
189
|
partial_pattern_ok: bool = False,
|
176
190
|
) -> None:
|
@@ -207,7 +221,7 @@ class PatternValidationMixin:
|
|
207
221
|
warn_with_log(
|
208
222
|
f"Mandatory key: `{key}` not found for {data_type}, "
|
209
223
|
"things might not work as expected if you are unsure "
|
210
|
-
"of what you are doing"
|
224
|
+
"of what you are doing."
|
211
225
|
)
|
212
226
|
else:
|
213
227
|
raise_error(
|
@@ -215,10 +229,10 @@ class PatternValidationMixin:
|
|
215
229
|
klass=KeyError,
|
216
230
|
)
|
217
231
|
else:
|
218
|
-
logger.debug(f"Mandatory key: `{key}` found for {data_type}")
|
232
|
+
logger.debug(f"Mandatory key: `{key}` found for {data_type}.")
|
219
233
|
|
220
234
|
def _identify_stray_keys(
|
221
|
-
self, keys:
|
235
|
+
self, keys: list[str], schema: list[str], data_type: str
|
222
236
|
) -> None:
|
223
237
|
"""Identify stray keys.
|
224
238
|
|
@@ -249,9 +263,9 @@ class PatternValidationMixin:
|
|
249
263
|
|
250
264
|
def validate_patterns(
|
251
265
|
self,
|
252
|
-
types:
|
253
|
-
replacements:
|
254
|
-
patterns:
|
266
|
+
types: list[str],
|
267
|
+
replacements: list[str],
|
268
|
+
patterns: DataGrabberPatterns,
|
255
269
|
partial_pattern_ok: bool = False,
|
256
270
|
) -> None:
|
257
271
|
"""Validate the patterns.
|
@@ -298,87 +312,185 @@ class PatternValidationMixin:
|
|
298
312
|
msg="`patterns` must contain all `types`", klass=ValueError
|
299
313
|
)
|
300
314
|
# Check against schema
|
301
|
-
for
|
315
|
+
for dtype_key, dtype_val in patterns.items():
|
302
316
|
# Check if valid data type is provided
|
303
|
-
if
|
317
|
+
if dtype_key not in PATTERNS_SCHEMA:
|
304
318
|
raise_error(
|
305
|
-
f"Unknown data type: {
|
319
|
+
f"Unknown data type: {dtype_key}, "
|
306
320
|
f"should be one of: {list(PATTERNS_SCHEMA.keys())}"
|
307
321
|
)
|
308
|
-
#
|
309
|
-
|
310
|
-
|
311
|
-
|
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
|
322
|
+
# Conditional for list dtype vals like Warp
|
323
|
+
if isinstance(dtype_val, list):
|
324
|
+
for idx, entry in enumerate(dtype_val):
|
325
|
+
# Check mandatory keys for data type
|
338
326
|
self._validate_mandatory_keys(
|
339
|
-
keys=list(
|
340
|
-
schema=
|
341
|
-
data_type=
|
327
|
+
keys=list(entry),
|
328
|
+
schema=PATTERNS_SCHEMA[dtype_key]["mandatory"],
|
329
|
+
data_type=f"{dtype_key}.{idx}",
|
342
330
|
partial_pattern_ok=partial_pattern_ok,
|
343
331
|
)
|
344
|
-
# Check optional keys for
|
345
|
-
for
|
346
|
-
|
332
|
+
# Check optional keys for data type
|
333
|
+
for optional_key, optional_val in PATTERNS_SCHEMA[
|
334
|
+
dtype_key
|
335
|
+
]["optional"].items():
|
336
|
+
if optional_key not in entry:
|
347
337
|
logger.debug(
|
348
|
-
f"Optional key: `{
|
349
|
-
f"
|
338
|
+
f"Optional key: `{optional_key}` missing for "
|
339
|
+
f"{dtype_key}.{idx}"
|
350
340
|
)
|
351
341
|
else:
|
352
342
|
logger.debug(
|
353
|
-
f"Optional key: `{
|
354
|
-
f"
|
343
|
+
f"Optional key: `{optional_key}` found for "
|
344
|
+
f"{dtype_key}.{idx}"
|
345
|
+
)
|
346
|
+
# Set nested type name for easier access
|
347
|
+
nested_dtype = f"{dtype_key}.{idx}.{optional_key}"
|
348
|
+
nested_mandatory_keys_schema = PATTERNS_SCHEMA[
|
349
|
+
dtype_key
|
350
|
+
]["optional"][optional_key]["mandatory"]
|
351
|
+
nested_optional_keys_schema = PATTERNS_SCHEMA[
|
352
|
+
dtype_key
|
353
|
+
]["optional"][optional_key]["optional"]
|
354
|
+
# Check mandatory keys for nested type
|
355
|
+
self._validate_mandatory_keys(
|
356
|
+
keys=list(optional_val["mandatory"]),
|
357
|
+
schema=nested_mandatory_keys_schema,
|
358
|
+
data_type=nested_dtype,
|
359
|
+
partial_pattern_ok=partial_pattern_ok,
|
360
|
+
)
|
361
|
+
# Check optional keys for nested type
|
362
|
+
for (
|
363
|
+
nested_optional_key
|
364
|
+
) in nested_optional_keys_schema:
|
365
|
+
if (
|
366
|
+
nested_optional_key
|
367
|
+
not in optional_val["optional"]
|
368
|
+
):
|
369
|
+
logger.debug(
|
370
|
+
f"Optional key: "
|
371
|
+
f"`{nested_optional_key}` missing for "
|
372
|
+
f"{nested_dtype}"
|
373
|
+
)
|
374
|
+
else:
|
375
|
+
logger.debug(
|
376
|
+
f"Optional key: "
|
377
|
+
f"`{nested_optional_key}` found for "
|
378
|
+
f"{nested_dtype}"
|
379
|
+
)
|
380
|
+
# Check stray key for nested data type
|
381
|
+
self._identify_stray_keys(
|
382
|
+
keys=(
|
383
|
+
optional_val["mandatory"]
|
384
|
+
+ optional_val["optional"]
|
385
|
+
),
|
386
|
+
schema=(
|
387
|
+
nested_mandatory_keys_schema
|
388
|
+
+ nested_optional_keys_schema
|
389
|
+
),
|
390
|
+
data_type=nested_dtype,
|
355
391
|
)
|
356
|
-
# Check stray key for
|
392
|
+
# Check stray key for data type
|
357
393
|
self._identify_stray_keys(
|
358
|
-
keys=
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
394
|
+
keys=list(entry.keys()),
|
395
|
+
schema=(
|
396
|
+
PATTERNS_SCHEMA[dtype_key]["mandatory"]
|
397
|
+
+ list(
|
398
|
+
PATTERNS_SCHEMA[dtype_key]["optional"].keys()
|
399
|
+
)
|
400
|
+
),
|
401
|
+
data_type=dtype_key,
|
363
402
|
)
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
403
|
+
# Wildcard check in patterns
|
404
|
+
if "}*" in entry.get("pattern", ""):
|
405
|
+
raise_error(
|
406
|
+
msg=(
|
407
|
+
f"`{dtype_key}.pattern` must not contain `*` "
|
408
|
+
"following a replacement"
|
409
|
+
),
|
410
|
+
klass=ValueError,
|
411
|
+
)
|
412
|
+
else:
|
413
|
+
# Check mandatory keys for data type
|
414
|
+
self._validate_mandatory_keys(
|
415
|
+
keys=list(dtype_val),
|
416
|
+
schema=PATTERNS_SCHEMA[dtype_key]["mandatory"],
|
417
|
+
data_type=dtype_key,
|
418
|
+
partial_pattern_ok=partial_pattern_ok,
|
419
|
+
)
|
420
|
+
# Check optional keys for data type
|
421
|
+
for optional_key, optional_val in PATTERNS_SCHEMA[dtype_key][
|
422
|
+
"optional"
|
423
|
+
].items():
|
424
|
+
if optional_key not in dtype_val:
|
425
|
+
logger.debug(
|
426
|
+
f"Optional key: `{optional_key}` missing for "
|
427
|
+
f"{dtype_key}."
|
428
|
+
)
|
429
|
+
else:
|
430
|
+
logger.debug(
|
431
|
+
f"Optional key: `{optional_key}` found for "
|
432
|
+
f"{dtype_key}."
|
433
|
+
)
|
434
|
+
# Set nested type name for easier access
|
435
|
+
nested_dtype = f"{dtype_key}.{optional_key}"
|
436
|
+
nested_mandatory_keys_schema = PATTERNS_SCHEMA[
|
437
|
+
dtype_key
|
438
|
+
]["optional"][optional_key]["mandatory"]
|
439
|
+
nested_optional_keys_schema = PATTERNS_SCHEMA[
|
440
|
+
dtype_key
|
441
|
+
]["optional"][optional_key]["optional"]
|
442
|
+
# Check mandatory keys for nested type
|
443
|
+
self._validate_mandatory_keys(
|
444
|
+
keys=list(optional_val["mandatory"]),
|
445
|
+
schema=nested_mandatory_keys_schema,
|
446
|
+
data_type=nested_dtype,
|
447
|
+
partial_pattern_ok=partial_pattern_ok,
|
448
|
+
)
|
449
|
+
# Check optional keys for nested type
|
450
|
+
for nested_optional_key in nested_optional_keys_schema:
|
451
|
+
if (
|
452
|
+
nested_optional_key
|
453
|
+
not in optional_val["optional"]
|
454
|
+
):
|
455
|
+
logger.debug(
|
456
|
+
f"Optional key: `{nested_optional_key}` "
|
457
|
+
f"missing for {nested_dtype}"
|
458
|
+
)
|
459
|
+
else:
|
460
|
+
logger.debug(
|
461
|
+
f"Optional key: `{nested_optional_key}` "
|
462
|
+
f"found for {nested_dtype}"
|
463
|
+
)
|
464
|
+
# Check stray key for nested data type
|
465
|
+
self._identify_stray_keys(
|
466
|
+
keys=(
|
467
|
+
optional_val["mandatory"]
|
468
|
+
+ optional_val["optional"]
|
469
|
+
),
|
470
|
+
schema=(
|
471
|
+
nested_mandatory_keys_schema
|
472
|
+
+ nested_optional_keys_schema
|
473
|
+
),
|
474
|
+
data_type=nested_dtype,
|
475
|
+
)
|
476
|
+
# Check stray key for data type
|
477
|
+
self._identify_stray_keys(
|
478
|
+
keys=list(dtype_val.keys()),
|
479
|
+
schema=(
|
480
|
+
PATTERNS_SCHEMA[dtype_key]["mandatory"]
|
481
|
+
+ list(PATTERNS_SCHEMA[dtype_key]["optional"].keys())
|
379
482
|
),
|
380
|
-
|
483
|
+
data_type=dtype_key,
|
381
484
|
)
|
485
|
+
# Wildcard check in patterns
|
486
|
+
if "}*" in dtype_val.get("pattern", ""):
|
487
|
+
raise_error(
|
488
|
+
msg=(
|
489
|
+
f"`{dtype_key}.pattern` must not contain `*` "
|
490
|
+
"following a replacement"
|
491
|
+
),
|
492
|
+
klass=ValueError,
|
493
|
+
)
|
382
494
|
|
383
495
|
# Validate replacements
|
384
496
|
self._validate_replacements(
|
File without changes
|
@@ -3,20 +3,21 @@
|
|
3
3
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
4
|
# License: AGPL
|
5
5
|
|
6
|
+
import warnings
|
6
7
|
from pathlib import Path
|
7
|
-
from typing import Type
|
8
8
|
|
9
9
|
import datalad.api as dl
|
10
10
|
import pytest
|
11
11
|
|
12
12
|
from junifer.datagrabber import DataladDataGrabber
|
13
|
+
from junifer.utils import config
|
13
14
|
|
14
15
|
|
15
16
|
_testing_dataset = {
|
16
17
|
"example_bids": {
|
17
18
|
"uri": "https://gin.g-node.org/juaml/datalad-example-bids",
|
18
|
-
"commit": "
|
19
|
-
"id": "
|
19
|
+
"commit": "3f288c8725207ae0c9b3616e093e78cda192b570",
|
20
|
+
"id": "582b9696-f13f-42e4-9587-b4e62aa2a8e7",
|
20
21
|
},
|
21
22
|
"example_bids_ses": {
|
22
23
|
"uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
|
@@ -27,7 +28,7 @@ _testing_dataset = {
|
|
27
28
|
|
28
29
|
|
29
30
|
@pytest.fixture
|
30
|
-
def concrete_datagrabber() ->
|
31
|
+
def concrete_datagrabber() -> type[DataladDataGrabber]:
|
31
32
|
"""Return a concrete datalad-based DataGrabber.
|
32
33
|
|
33
34
|
Returns
|
@@ -69,7 +70,7 @@ def concrete_datagrabber() -> Type[DataladDataGrabber]:
|
|
69
70
|
|
70
71
|
|
71
72
|
def test_DataladDataGrabber_install_errors(
|
72
|
-
tmp_path: Path, concrete_datagrabber:
|
73
|
+
tmp_path: Path, concrete_datagrabber: type
|
73
74
|
) -> None:
|
74
75
|
"""Test DataladDataGrabber install errors / warnings.
|
75
76
|
|
@@ -95,6 +96,12 @@ def test_DataladDataGrabber_install_errors(
|
|
95
96
|
with pytest.raises(ValueError, match=r"different ID"):
|
96
97
|
with dg:
|
97
98
|
pass
|
99
|
+
# Set config to skip id check and test
|
100
|
+
config.set(key="datagrabber.skipidcheck", val=True)
|
101
|
+
with dg:
|
102
|
+
pass
|
103
|
+
# Reset config
|
104
|
+
config.delete("datagrabber.skipidcheck")
|
98
105
|
|
99
106
|
elem1_t1w = datadir / "example_bids/sub-01/anat/sub-01_T1w.nii.gz"
|
100
107
|
elem1_t1w.unlink()
|
@@ -105,10 +112,18 @@ def test_DataladDataGrabber_install_errors(
|
|
105
112
|
with pytest.warns(RuntimeWarning, match=r"one file is not clean"):
|
106
113
|
with dg:
|
107
114
|
pass
|
115
|
+
# Set config to skip dirty check and test
|
116
|
+
with warnings.catch_warnings():
|
117
|
+
warnings.simplefilter("error")
|
118
|
+
config.set(key="datagrabber.skipdirtycheck", val=True)
|
119
|
+
with dg:
|
120
|
+
pass
|
121
|
+
# Reset config
|
122
|
+
config.delete("datagrabber.skipdirtycheck")
|
108
123
|
|
109
124
|
|
110
125
|
def test_DataladDataGrabber_clone_cleanup(
|
111
|
-
tmp_path: Path, concrete_datagrabber:
|
126
|
+
tmp_path: Path, concrete_datagrabber: type
|
112
127
|
) -> None:
|
113
128
|
"""Test DataladDataGrabber clone and remove.
|
114
129
|
|
@@ -157,7 +172,7 @@ def test_DataladDataGrabber_clone_cleanup(
|
|
157
172
|
|
158
173
|
|
159
174
|
def test_DataladDataGrabber_clone_create_cleanup(
|
160
|
-
concrete_datagrabber:
|
175
|
+
concrete_datagrabber: type,
|
161
176
|
) -> None:
|
162
177
|
"""Test DataladDataGrabber tempdir clone and remove.
|
163
178
|
|
@@ -203,7 +218,7 @@ def test_DataladDataGrabber_clone_create_cleanup(
|
|
203
218
|
|
204
219
|
|
205
220
|
def test_DataladDataGrabber_previously_cloned(
|
206
|
-
tmp_path: Path, concrete_datagrabber:
|
221
|
+
tmp_path: Path, concrete_datagrabber: type
|
207
222
|
) -> None:
|
208
223
|
"""Test DataladDataGrabber on cloned dataset.
|
209
224
|
|
@@ -247,7 +262,7 @@ def test_DataladDataGrabber_previously_cloned(
|
|
247
262
|
meta = elem1["BOLD"]["meta"]
|
248
263
|
assert "datagrabber" in meta
|
249
264
|
assert "datalad_dirty" in meta["datagrabber"]
|
250
|
-
assert meta["datagrabber"]["datalad_dirty"] is
|
265
|
+
assert meta["datagrabber"]["datalad_dirty"] is True
|
251
266
|
assert "datalad_commit_id" in meta["datagrabber"]
|
252
267
|
assert meta["datagrabber"]["datalad_commit_id"] == commit
|
253
268
|
assert "datalad_id" in meta["datagrabber"]
|
@@ -272,7 +287,7 @@ def test_DataladDataGrabber_previously_cloned(
|
|
272
287
|
|
273
288
|
|
274
289
|
def test_DataladDataGrabber_previously_cloned_and_get(
|
275
|
-
tmp_path: Path, concrete_datagrabber:
|
290
|
+
tmp_path: Path, concrete_datagrabber: type
|
276
291
|
) -> None:
|
277
292
|
"""Test DataladDataGrabber on cloned dataset with files present.
|
278
293
|
|
@@ -327,7 +342,7 @@ def test_DataladDataGrabber_previously_cloned_and_get(
|
|
327
342
|
meta = elem1["BOLD"]["meta"]
|
328
343
|
assert "datagrabber" in meta
|
329
344
|
assert "datalad_dirty" in meta["datagrabber"]
|
330
|
-
assert meta["datagrabber"]["datalad_dirty"] is
|
345
|
+
assert meta["datagrabber"]["datalad_dirty"] is True
|
331
346
|
assert "datalad_commit_id" in meta["datagrabber"]
|
332
347
|
assert meta["datagrabber"]["datalad_commit_id"] == commit
|
333
348
|
assert "datalad_id" in meta["datagrabber"]
|
@@ -355,7 +370,7 @@ def test_DataladDataGrabber_previously_cloned_and_get(
|
|
355
370
|
|
356
371
|
|
357
372
|
def test_DataladDataGrabber_previously_cloned_and_get_dirty(
|
358
|
-
tmp_path: Path, concrete_datagrabber:
|
373
|
+
tmp_path: Path, concrete_datagrabber: type
|
359
374
|
) -> None:
|
360
375
|
"""Test DataladDataGrabber on a dirty cloned dataset.
|
361
376
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
4
|
# License: AGPL
|
5
5
|
|
6
|
-
from typing import
|
6
|
+
from typing import Optional, Union
|
7
7
|
|
8
8
|
import pytest
|
9
9
|
|
@@ -116,7 +116,12 @@ def test_DMCC13Benchmark(
|
|
116
116
|
data_file_names.extend(
|
117
117
|
[
|
118
118
|
"sub-01_desc-preproc_T1w.nii.gz",
|
119
|
-
|
119
|
+
[
|
120
|
+
"sub-01_from-MNI152NLin2009cAsym_to-T1w"
|
121
|
+
"_mode-image_xfm.h5",
|
122
|
+
"sub-01_from-T1w_to-MNI152NLin2009cAsym"
|
123
|
+
"_mode-image_xfm.h5",
|
124
|
+
],
|
120
125
|
]
|
121
126
|
)
|
122
127
|
else:
|
@@ -127,14 +132,26 @@ def test_DMCC13Benchmark(
|
|
127
132
|
for data_type, data_file_name in zip(data_types, data_file_names):
|
128
133
|
# Assert data type
|
129
134
|
assert data_type in out
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
# Conditional for Warp
|
136
|
+
if data_type == "Warp":
|
137
|
+
for idx, fname in enumerate(data_file_name):
|
138
|
+
# Assert data file path exists
|
139
|
+
assert out[data_type][idx]["path"].exists()
|
140
|
+
# Assert data file path is a file
|
141
|
+
assert out[data_type][idx]["path"].is_file()
|
142
|
+
# Assert data file name
|
143
|
+
assert out[data_type][idx]["path"].name == fname
|
144
|
+
# Assert metadata
|
145
|
+
assert "meta" in out[data_type][idx]
|
146
|
+
else:
|
147
|
+
# Assert data file path exists
|
148
|
+
assert out[data_type]["path"].exists()
|
149
|
+
# Assert data file path is a file
|
150
|
+
assert out[data_type]["path"].is_file()
|
151
|
+
# Assert data file name
|
152
|
+
assert out[data_type]["path"].name == data_file_name
|
153
|
+
# Assert metadata
|
154
|
+
assert "meta" in out[data_type]
|
138
155
|
|
139
156
|
# Check BOLD nested data types
|
140
157
|
for type_, file_name in zip(
|
@@ -201,7 +218,7 @@ def test_DMCC13Benchmark(
|
|
201
218
|
],
|
202
219
|
)
|
203
220
|
def test_DMCC13Benchmark_partial_data_access(
|
204
|
-
types: Union[str,
|
221
|
+
types: Union[str, list[str]],
|
205
222
|
native_t1w: bool,
|
206
223
|
) -> None:
|
207
224
|
"""Test DMCC13Benchmark DataGrabber partial data access.
|