junifer 0.0.7.dev332__py3-none-any.whl → 0.0.8__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 +9 -19
- junifer/api/__init__.pyi +17 -2
- junifer/api/functions.py +222 -39
- junifer/api/queue_context/__init__.pyi +11 -3
- junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
- junifer/api/queue_context/htcondor_adapter.py +118 -181
- junifer/api/queue_context/queue_context_adapter.py +51 -7
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
- junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
- junifer/api/res/fsl/run_fsl_docker.sh +4 -0
- junifer/api/tests/test_functions.py +863 -36
- junifer/cli/__init__.py +1 -7
- junifer/cli/cli.py +41 -22
- junifer/cli/parser.py +23 -147
- junifer/cli/tests/data/gmd_mean.yaml +6 -6
- junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
- junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
- junifer/cli/tests/test_cli.py +120 -4
- junifer/cli/tests/test_cli_utils.py +23 -6
- junifer/cli/utils.py +14 -9
- junifer/configs/__init__.py +5 -0
- junifer/configs/__init__.pyi +0 -0
- junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
- junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
- junifer/configs/juseless/datagrabbers/ucla.py +111 -104
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
- junifer/configs/py.typed +0 -0
- junifer/conftest.py +63 -24
- junifer/data/_dispatch.py +9 -19
- junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
- junifer/data/coordinates/_coordinates.py +51 -6
- junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
- junifer/data/coordinates/tests/test_coordinates.py +15 -3
- junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
- junifer/data/maps/_ants_maps_warper.py +18 -12
- junifer/data/maps/_fsl_maps_warper.py +7 -3
- junifer/data/maps/_maps.py +12 -8
- junifer/data/maps/tests/test_maps.py +13 -21
- junifer/data/masks/_ants_mask_warper.py +18 -12
- junifer/data/masks/_fsl_mask_warper.py +7 -3
- junifer/data/masks/_masks.py +15 -12
- junifer/data/masks/tests/test_masks.py +20 -17
- junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
- junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
- junifer/data/parcellations/_parcellations.py +125 -91
- junifer/data/parcellations/tests/test_parcellations.py +122 -1
- junifer/data/template_spaces.py +117 -50
- junifer/data/tests/test_template_spaces.py +10 -7
- junifer/data/utils.py +10 -8
- junifer/datagrabber/__init__.pyi +38 -5
- junifer/datagrabber/aomic/__init__.pyi +8 -1
- junifer/datagrabber/aomic/_types.py +22 -0
- junifer/datagrabber/aomic/id1000.py +193 -183
- junifer/datagrabber/aomic/piop1.py +215 -259
- junifer/datagrabber/aomic/piop2.py +211 -244
- junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
- junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
- junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
- junifer/datagrabber/base.py +253 -79
- junifer/datagrabber/datalad_base.py +127 -76
- junifer/datagrabber/dmcc13_benchmark.py +204 -289
- junifer/datagrabber/hcp1200/__init__.pyi +7 -2
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
- junifer/datagrabber/hcp1200/hcp1200.py +151 -164
- junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
- junifer/datagrabber/multiple.py +183 -63
- junifer/datagrabber/pattern.py +358 -227
- junifer/datagrabber/pattern_datalad.py +84 -124
- junifer/datagrabber/pattern_validation_mixin.py +22 -50
- junifer/datagrabber/tests/test_base.py +17 -4
- junifer/datagrabber/tests/test_datalad_base.py +20 -27
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
- junifer/datagrabber/tests/test_multiple.py +293 -31
- junifer/datagrabber/tests/test_pattern.py +577 -18
- junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
- junifer/datareader/default.py +8 -5
- junifer/datareader/tests/test_default_reader.py +1 -1
- junifer/external/BrainPrint/.codecov.yml +2 -0
- junifer/external/BrainPrint/.codespellignore +2 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
- junifer/external/BrainPrint/.github/dependabot.yml +6 -0
- junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
- junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
- junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
- junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
- junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
- junifer/external/BrainPrint/.gitignore +123 -0
- junifer/external/BrainPrint/LICENSE +21 -0
- junifer/external/BrainPrint/README.md +108 -0
- junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
- junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
- junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
- junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
- junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
- junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
- junifer/external/BrainPrint/doc/Makefile +20 -0
- junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
- junifer/external/BrainPrint/doc/api/index.rst +11 -0
- junifer/external/BrainPrint/doc/conf.py +272 -0
- junifer/external/BrainPrint/doc/index.rst +140 -0
- junifer/external/BrainPrint/doc/links.inc +36 -0
- junifer/external/BrainPrint/doc/make.bat +35 -0
- junifer/external/BrainPrint/doc/references.bib +23 -0
- junifer/external/BrainPrint/pyproject.toml +170 -0
- junifer/external/BrainPrint/setup.py +3 -0
- junifer/external/__init__.py +5 -0
- junifer/external/__init__.pyi +0 -0
- junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
- junifer/external/py.typed +0 -0
- junifer/markers/__init__.pyi +6 -3
- junifer/markers/base.py +126 -76
- junifer/markers/brainprint.py +30 -39
- junifer/markers/complexity/__init__.py +20 -3
- junifer/markers/complexity/complexity_base.py +40 -41
- junifer/markers/complexity/hurst_exponent.py +22 -35
- junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
- junifer/markers/complexity/perm_entropy.py +22 -36
- junifer/markers/complexity/range_entropy.py +22 -35
- junifer/markers/complexity/range_entropy_auc.py +27 -36
- junifer/markers/complexity/sample_entropy.py +22 -36
- junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
- junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
- junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
- junifer/markers/complexity/tests/test_range_entropy.py +47 -36
- junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
- junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
- junifer/markers/complexity/weighted_perm_entropy.py +22 -36
- junifer/markers/ets_rss.py +35 -31
- junifer/markers/falff/__init__.pyi +7 -1
- junifer/markers/falff/_afni_falff.py +22 -9
- junifer/markers/falff/_junifer_falff.py +47 -31
- junifer/markers/falff/falff_base.py +80 -60
- junifer/markers/falff/falff_maps.py +17 -41
- junifer/markers/falff/falff_parcels.py +26 -51
- junifer/markers/falff/falff_spheres.py +34 -61
- junifer/markers/falff/tests/test_falff_maps.py +20 -23
- junifer/markers/falff/tests/test_falff_mask.py +99 -0
- junifer/markers/falff/tests/test_falff_parcels.py +14 -10
- junifer/markers/falff/tests/test_falff_spheres.py +14 -10
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
- junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
- junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
- junifer/markers/maps_aggregation.py +77 -55
- junifer/markers/parcel_aggregation.py +91 -70
- junifer/markers/reho/__init__.pyi +7 -1
- junifer/markers/reho/_afni_reho.py +23 -14
- junifer/markers/reho/_junifer_reho.py +112 -123
- junifer/markers/reho/reho_base.py +115 -37
- junifer/markers/reho/reho_maps.py +18 -32
- junifer/markers/reho/reho_parcels.py +26 -41
- junifer/markers/reho/reho_spheres.py +33 -54
- junifer/markers/reho/tests/test_junifer_reho.py +178 -0
- junifer/markers/reho/tests/test_reho_maps.py +27 -25
- junifer/markers/reho/tests/test_reho_parcels.py +28 -22
- junifer/markers/reho/tests/test_reho_spheres.py +16 -12
- junifer/markers/sphere_aggregation.py +88 -72
- junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
- junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
- junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
- junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
- junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
- junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
- junifer/markers/tests/test_brainprint.py +10 -9
- junifer/markers/tests/test_ets_rss.py +7 -3
- junifer/markers/tests/test_lost_regions.py +279 -0
- junifer/markers/tests/test_maps_aggregation.py +67 -114
- junifer/markers/tests/test_marker_utils.py +40 -1
- junifer/markers/tests/test_markers_base.py +275 -6
- junifer/markers/tests/test_parcel_aggregation.py +91 -53
- junifer/markers/tests/test_sphere_aggregation.py +53 -47
- junifer/markers/utils.py +267 -5
- junifer/onthefly/__init__.py +2 -3
- junifer/onthefly/__init__.pyi +4 -0
- junifer/onthefly/_brainprint.py +3 -5
- junifer/onthefly/py.typed +0 -0
- junifer/onthefly/read_transform.py +11 -9
- junifer/onthefly/tests/test_read_transform.py +7 -7
- junifer/pipeline/__init__.pyi +4 -2
- junifer/pipeline/marker_collection.py +14 -10
- junifer/pipeline/pipeline_component_registry.py +25 -6
- junifer/pipeline/pipeline_step_mixin.py +5 -12
- junifer/pipeline/tests/test_data_object_dumper.py +10 -10
- junifer/pipeline/tests/test_marker_collection.py +145 -168
- junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
- junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
- junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
- junifer/pipeline/tests/test_workdir_manager.py +39 -0
- junifer/pipeline/update_meta_mixin.py +13 -10
- junifer/pipeline/utils.py +22 -17
- junifer/pipeline/workdir_manager.py +41 -32
- junifer/preprocess/__init__.pyi +9 -4
- junifer/preprocess/_temporal_filter.py +34 -33
- junifer/preprocess/_temporal_slicer.py +17 -30
- junifer/preprocess/base.py +99 -55
- junifer/preprocess/confounds/__init__.pyi +10 -2
- junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
- junifer/preprocess/smoothing/__init__.pyi +2 -2
- junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
- junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
- junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
- junifer/preprocess/smoothing/smoothing.py +59 -40
- junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
- junifer/preprocess/tests/test_preprocess_base.py +17 -9
- junifer/preprocess/tests/test_temporal_filter.py +37 -16
- junifer/preprocess/tests/test_temporal_slicer.py +12 -12
- junifer/preprocess/warping/__init__.pyi +2 -2
- junifer/preprocess/warping/_ants_warper.py +56 -40
- junifer/preprocess/warping/_fsl_warper.py +49 -36
- junifer/preprocess/warping/_utils.py +106 -0
- junifer/preprocess/warping/space_warper.py +99 -98
- junifer/preprocess/warping/tests/test_space_warper.py +46 -41
- junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
- junifer/stats.py +46 -34
- junifer/storage/__init__.pyi +8 -4
- junifer/storage/base.py +129 -64
- junifer/storage/hdf5.py +307 -199
- junifer/storage/pandas_base.py +46 -24
- junifer/storage/sqlite.py +99 -62
- junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
- junifer/storage/tests/test_hdf5.py +147 -12
- junifer/storage/tests/test_repeated_storage.py +293 -0
- junifer/storage/tests/test_sqlite.py +9 -20
- junifer/storage/tests/test_storage_base.py +18 -21
- junifer/storage/tests/test_utils.py +83 -22
- junifer/storage/utils.py +58 -38
- junifer/testing/__init__.pyi +2 -2
- junifer/testing/datagrabbers.py +480 -44
- junifer/testing/registry.py +12 -0
- junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
- junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
- junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
- junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
- junifer/testing/tests/test_testing_registry.py +31 -0
- junifer/testing/tests/test_testing_utils.py +35 -0
- junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
- junifer/testing/utils.py +33 -1
- junifer/tests/test_stats.py +1 -5
- junifer/typing/__init__.pyi +2 -0
- junifer/typing/_typing.py +34 -19
- junifer/typing/py.typed +0 -0
- junifer/utils/__init__.pyi +10 -1
- junifer/utils/_config.py +1 -2
- junifer/utils/helpers.py +68 -2
- junifer/utils/logging.py +31 -7
- junifer/utils/tests/test_helpers.py +36 -1
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
- junifer-0.0.8.dist-info/RECORD +402 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
- junifer/cli/tests/test_parser.py +0 -280
- junifer/external/h5io/h5io/__init__.py +0 -15
- junifer/external/h5io/h5io/_h5io.py +0 -767
- junifer/external/h5io/h5io/_version.py +0 -1
- junifer/external/h5io/h5io/chunked_array.py +0 -94
- junifer/external/h5io/h5io/chunked_list.py +0 -65
- junifer/markers/complexity/__init__.pyi +0 -17
- junifer-0.0.7.dev332.dist-info/RECORD +0 -353
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
junifer/datagrabber/pattern.py
CHANGED
|
@@ -5,154 +5,158 @@
|
|
|
5
5
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
|
6
6
|
# License: AGPL
|
|
7
7
|
|
|
8
|
+
import glob
|
|
8
9
|
import re
|
|
10
|
+
from collections import defaultdict
|
|
9
11
|
from copy import deepcopy
|
|
12
|
+
from enum import Enum
|
|
10
13
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
14
|
+
from typing import ClassVar
|
|
12
15
|
|
|
13
|
-
import
|
|
16
|
+
from aenum import Enum as AEnum
|
|
17
|
+
from aenum import extend_enum
|
|
18
|
+
from pydantic import Field
|
|
14
19
|
|
|
15
20
|
from ..api.decorators import register_datagrabber
|
|
16
21
|
from ..typing import DataGrabberPatterns, Elements
|
|
17
|
-
from ..utils import
|
|
18
|
-
from .base import BaseDataGrabber
|
|
22
|
+
from ..utils import ensure_list, raise_error
|
|
23
|
+
from .base import BaseDataGrabber, logger
|
|
19
24
|
from .pattern_validation_mixin import PatternValidationMixin
|
|
20
25
|
|
|
21
26
|
|
|
22
|
-
__all__ = [
|
|
27
|
+
__all__ = [
|
|
28
|
+
"ConfoundsFormat",
|
|
29
|
+
"PatternDataGrabber",
|
|
30
|
+
"register_confounds_format",
|
|
31
|
+
]
|
|
23
32
|
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
class ConfoundsFormat(str, AEnum):
|
|
35
|
+
"""Accepted confounds format."""
|
|
27
36
|
|
|
37
|
+
FMRIPrep = "fmriprep"
|
|
38
|
+
AdHoc = "adhoc"
|
|
28
39
|
|
|
29
|
-
@register_datagrabber
|
|
30
|
-
class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
31
|
-
"""Concrete implementation for pattern-based data fetching.
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
def register_confounds_format(name: str, alias: str) -> None:
|
|
42
|
+
"""Register custom confounds format.
|
|
34
43
|
|
|
35
44
|
Parameters
|
|
36
45
|
----------
|
|
37
|
-
|
|
38
|
-
The
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* ``"T1w"`` :
|
|
43
|
-
|
|
44
|
-
.. code-block:: none
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
"mandatory": ["pattern", "space"],
|
|
48
|
-
"optional": {
|
|
49
|
-
"mask": {
|
|
50
|
-
"mandatory": ["pattern", "space"],
|
|
51
|
-
"optional": []
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
* ``"T2w"`` :
|
|
46
|
+
name : str
|
|
47
|
+
The confounds format name to be referred.
|
|
48
|
+
alias : str
|
|
49
|
+
The confounds format alias for string representation.
|
|
57
50
|
|
|
58
|
-
|
|
51
|
+
"""
|
|
52
|
+
extend_enum(ConfoundsFormat, name, alias)
|
|
59
53
|
|
|
60
|
-
{
|
|
61
|
-
"mandatory": ["pattern", "space"],
|
|
62
|
-
"optional": {
|
|
63
|
-
"mask": {
|
|
64
|
-
"mandatory": ["pattern", "space"],
|
|
65
|
-
"optional": []
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
54
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.. code-block:: none
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
"mandatory": ["pattern", "space"],
|
|
76
|
-
"optional": {
|
|
77
|
-
"mask": {
|
|
78
|
-
"mandatory": ["pattern", "space"],
|
|
79
|
-
"optional": []
|
|
80
|
-
}
|
|
81
|
-
"confounds": {
|
|
82
|
-
"mandatory": ["pattern", "format"],
|
|
83
|
-
"optional": []
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
55
|
+
def _value(value: object) -> object:
|
|
56
|
+
"""Get the value of an enum member, or the value itself.
|
|
87
57
|
|
|
88
|
-
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
value : object
|
|
61
|
+
The value.
|
|
89
62
|
|
|
90
|
-
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
object
|
|
66
|
+
The value.
|
|
91
67
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
68
|
+
"""
|
|
69
|
+
return value.value if isinstance(value, Enum) else value
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _join_elements(
|
|
73
|
+
left: set[tuple[str, ...]],
|
|
74
|
+
left_keys: list[str],
|
|
75
|
+
right: set[tuple[str, ...]],
|
|
76
|
+
right_keys: list[str],
|
|
77
|
+
) -> tuple[set[tuple[str, ...]], list[str]]:
|
|
78
|
+
"""Join two sets of elements on their common keys.
|
|
79
|
+
|
|
80
|
+
Each data type (or pattern) has its own elements, with the values of the
|
|
81
|
+
replacements in its pattern. The elements of the datagrabber are the ones
|
|
82
|
+
available for all the data types, so the elements of two data types are
|
|
83
|
+
joined like database tables: an element of the first set is combined with
|
|
84
|
+
each element of the second set that has the same values for the common
|
|
85
|
+
keys, adding the values of the keys that only the second set has. An
|
|
86
|
+
element without a match in the other set is dropped.
|
|
87
|
+
|
|
88
|
+
For example, with BOLD elements ``(subject, session, task)``:
|
|
89
|
+
``(sub-01, ses-1, rest)``, ``(sub-01, ses-1, movie)`` and
|
|
90
|
+
``(sub-02, ses-1, rest)``:
|
|
91
|
+
|
|
92
|
+
* VBM elements ``(subject)``: ``(sub-01)`` and ``(sub-03)`` keep the BOLD
|
|
93
|
+
elements of ``sub-01``, as the VBM data only depends on the subject.
|
|
94
|
+
* DWI elements ``(subject, run)``: ``(sub-01, 1)`` and ``(sub-01, 2)``
|
|
95
|
+
give the elements ``(subject, session, task, run)`` for each BOLD
|
|
96
|
+
element of ``sub-01`` with each run, i.e., all the combinations.
|
|
97
|
+
|
|
98
|
+
The result is the same regardless of which set is first (except for the
|
|
99
|
+
order of the keys), so the order of the data types does not matter.
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
left : set of tuple of str
|
|
104
|
+
The first elements, as tuples of the values of ``left_keys``.
|
|
105
|
+
left_keys : list of str
|
|
106
|
+
The keys of the first elements.
|
|
107
|
+
right : set of tuple of str
|
|
108
|
+
The second elements, as tuples of the values of ``right_keys``.
|
|
109
|
+
right_keys : list of str
|
|
110
|
+
The keys of the second elements.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
set of tuple of str
|
|
115
|
+
The elements with the values of the first and the second elements that
|
|
116
|
+
have the same values for the common keys.
|
|
117
|
+
list of str
|
|
118
|
+
The keys of the elements: ``left_keys`` and the other ``right_keys``.
|
|
100
119
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
"""
|
|
121
|
+
common = [k for k in right_keys if k in left_keys]
|
|
122
|
+
other = [i for i, k in enumerate(right_keys) if k not in left_keys]
|
|
123
|
+
# Values of the other keys of the second elements, by the common values
|
|
124
|
+
right_by_common = defaultdict(list)
|
|
125
|
+
for element in right:
|
|
126
|
+
common_values = tuple(element[right_keys.index(k)] for k in common)
|
|
127
|
+
right_by_common[common_values].append(tuple(element[i] for i in other))
|
|
128
|
+
joined = {
|
|
129
|
+
element + other_values
|
|
130
|
+
for element in left
|
|
131
|
+
for other_values in right_by_common[
|
|
132
|
+
tuple(element[left_keys.index(k)] for k in common)
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
return joined, left_keys + [right_keys[i] for i in other]
|
|
105
136
|
|
|
106
|
-
* ``"VBM_WM"`` :
|
|
107
137
|
|
|
108
|
-
|
|
138
|
+
@register_datagrabber
|
|
139
|
+
class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
140
|
+
"""Concrete implementation for pattern-based data fetching.
|
|
109
141
|
|
|
110
|
-
|
|
111
|
-
"mandatory": ["pattern", "space"],
|
|
112
|
-
"optional": []
|
|
113
|
-
}
|
|
142
|
+
Implements a DataGrabber that understands patterns to grab data.
|
|
114
143
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
except ``Warp``, which needs to be a list of dictionaries as there can
|
|
134
|
-
be multiple spaces to warp (for example, with fMRIPrep):
|
|
135
|
-
|
|
136
|
-
.. code-block:: none
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
"Warp": [
|
|
140
|
-
{
|
|
141
|
-
"pattern": "...",
|
|
142
|
-
"src": "...",
|
|
143
|
-
"dst": "...",
|
|
144
|
-
"warper": "...",
|
|
145
|
-
},
|
|
146
|
-
],
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
taken from :class:`.HCP1200`.
|
|
150
|
-
replacements : str or list of str
|
|
151
|
-
Replacements in the ``pattern`` key of each data type. The value needs
|
|
152
|
-
to be a list of all possible replacements.
|
|
153
|
-
datadir : str or pathlib.Path
|
|
154
|
-
The directory where the data is / will be stored.
|
|
155
|
-
confounds_format : {"fmriprep", "adhoc"} or None, optional
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
types : :enum:`.DataType` or list of variants
|
|
147
|
+
The data type(s) to grab.
|
|
148
|
+
datadir : pathlib.Path
|
|
149
|
+
The path where the data is stored.
|
|
150
|
+
patterns : ``DataGrabberPatterns``
|
|
151
|
+
The datagrabber patterns. Check :class:`.DataTypeSchema` for the \
|
|
152
|
+
schema.
|
|
153
|
+
replacements : list of str or dict
|
|
154
|
+
All possible replacements in ``patterns.<data_type>.pattern``. As a
|
|
155
|
+
dictionary, the keys are the replacements and the values are the lists
|
|
156
|
+
of values to grab (or None to grab all of them), e.g.,
|
|
157
|
+
``{"subject": None, "task": ["rest", "movie"]}`` to grab all the
|
|
158
|
+
subjects and only these tasks.
|
|
159
|
+
confounds_format : :enum:`.ConfoundsFormat` or None, optional
|
|
156
160
|
The format of the confounds for the dataset (default None).
|
|
157
161
|
partial_pattern_ok : bool, optional
|
|
158
162
|
Whether to raise error if partial pattern for a data type is found.
|
|
@@ -162,52 +166,57 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
162
166
|
powerful when used with :class:`.MultipleDataGrabber`
|
|
163
167
|
(default True).
|
|
164
168
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
If ``confounds_format`` is invalid.
|
|
169
|
+
Attributes
|
|
170
|
+
----------
|
|
171
|
+
skip_file_check
|
|
169
172
|
|
|
170
173
|
"""
|
|
171
174
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
replacements = [replacements]
|
|
175
|
+
patterns: DataGrabberPatterns = Field(frozen=True)
|
|
176
|
+
replacements: list[str] | dict[str, list[str] | None] = Field(frozen=True)
|
|
177
|
+
|
|
178
|
+
# Fields with the values of the replacements to grab, by replacement, e.g.,
|
|
179
|
+
# ``{"task": "tasks"}``
|
|
180
|
+
_REPLACEMENT_FIELDS: ClassVar[dict[str, str]] = {}
|
|
181
|
+
confounds_format: ConfoundsFormat | None = Field(None, frozen=True)
|
|
182
|
+
partial_pattern_ok: bool = Field(False, frozen=True)
|
|
183
|
+
|
|
184
|
+
def validate_datagrabber_params(self) -> None:
|
|
185
|
+
"""Run extra logical validation for datagrabber."""
|
|
184
186
|
# Validate patterns
|
|
185
187
|
self.validate_patterns(
|
|
186
|
-
types=types,
|
|
187
|
-
replacements=replacements,
|
|
188
|
-
patterns=patterns,
|
|
189
|
-
partial_pattern_ok=partial_pattern_ok,
|
|
188
|
+
types=self.types,
|
|
189
|
+
replacements=list(self.replacements),
|
|
190
|
+
patterns=self.patterns,
|
|
191
|
+
partial_pattern_ok=self.partial_pattern_ok,
|
|
190
192
|
)
|
|
191
|
-
|
|
192
|
-
self.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
193
|
+
# Validate the fields with the values of the replacements
|
|
194
|
+
for replacement, field in self._REPLACEMENT_FIELDS.items():
|
|
195
|
+
if replacement not in self.replacements:
|
|
196
|
+
raise_error(
|
|
197
|
+
msg=(
|
|
198
|
+
f"Replacement: `{replacement}` of the field `{field}` "
|
|
199
|
+
f"is not one of the replacements: "
|
|
200
|
+
f"{list(self.replacements)}"
|
|
201
|
+
),
|
|
202
|
+
klass=ValueError,
|
|
203
|
+
)
|
|
204
|
+
if (
|
|
205
|
+
isinstance(self.replacements, dict)
|
|
206
|
+
and self.replacements[replacement] is not None
|
|
207
|
+
):
|
|
208
|
+
raise_error(
|
|
209
|
+
msg=(
|
|
210
|
+
f"The values of the replacement `{replacement}` are "
|
|
211
|
+
f"given by the field `{field}`, they cannot be given "
|
|
212
|
+
"in `replacements`"
|
|
213
|
+
),
|
|
214
|
+
klass=ValueError,
|
|
215
|
+
)
|
|
207
216
|
logger.debug("Initializing PatternDataGrabber")
|
|
208
|
-
logger.debug(f"\tpatterns = {patterns}")
|
|
209
|
-
logger.debug(f"\treplacements = {replacements}")
|
|
210
|
-
logger.debug(f"\tconfounds_format = {confounds_format}")
|
|
217
|
+
logger.debug(f"\tpatterns = {self.patterns}")
|
|
218
|
+
logger.debug(f"\treplacements = {self.replacements}")
|
|
219
|
+
logger.debug(f"\tconfounds_format = {self.confounds_format}")
|
|
211
220
|
|
|
212
221
|
@property
|
|
213
222
|
def skip_file_check(self) -> bool:
|
|
@@ -242,18 +251,32 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
242
251
|
x for x in self.replacements if f"{{{x}}}" in pattern
|
|
243
252
|
]
|
|
244
253
|
# Ops on re_pattern
|
|
245
|
-
# Remove negated unix glob pattern i.e., [!...] for re_pattern
|
|
254
|
+
# Remove negated unix glob pattern i.e., [!...] for re_pattern, as the
|
|
255
|
+
# character is part of the following replacement (e.g., [!f]{subject})
|
|
246
256
|
re_pattern = re.sub(r"\[!.?\]", "", re_pattern)
|
|
247
257
|
# Remove enclosing square brackets from unix glob pattern i.e., [...]
|
|
248
258
|
# for re_pattern
|
|
249
259
|
re_pattern = re.sub(r"\[|\]", "", re_pattern)
|
|
250
|
-
#
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
for
|
|
256
|
-
|
|
260
|
+
# Translate the replacements to named groups (the first appearance of
|
|
261
|
+
# each) or back references (the others), the unix glob wildcards to
|
|
262
|
+
# their regular expressions and escape the rest
|
|
263
|
+
parts = []
|
|
264
|
+
seen = set()
|
|
265
|
+
for token in re.split(r"(\{[^{}]*\}|\*|\?)", re_pattern):
|
|
266
|
+
name = token[1:-1]
|
|
267
|
+
if token.startswith("{") and name in t_replacements:
|
|
268
|
+
if name in seen:
|
|
269
|
+
parts.append(f"(?P={name})")
|
|
270
|
+
else:
|
|
271
|
+
parts.append(f"(?P<{name}>.*)")
|
|
272
|
+
seen.add(name)
|
|
273
|
+
elif token == "*":
|
|
274
|
+
parts.append("[^/]*")
|
|
275
|
+
elif token == "?":
|
|
276
|
+
parts.append("[^/]")
|
|
277
|
+
else:
|
|
278
|
+
parts.append(re.escape(token))
|
|
279
|
+
re_pattern = "".join(parts)
|
|
257
280
|
# Ops on glob_pattern
|
|
258
281
|
# Iteratively replace replacements with wildcard i.e., *
|
|
259
282
|
# for glob_pattern
|
|
@@ -283,9 +306,9 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
283
306
|
If element keys do not match with replacements.
|
|
284
307
|
|
|
285
308
|
"""
|
|
286
|
-
if list(element.keys()) != self.
|
|
309
|
+
if list(element.keys()) != self.get_element_keys():
|
|
287
310
|
raise_error(
|
|
288
|
-
f"The element keys must be {self.
|
|
311
|
+
f"The element keys must be {self.get_element_keys()}, "
|
|
289
312
|
f"element has {list(element.keys())}."
|
|
290
313
|
)
|
|
291
314
|
# Remove negated unix glob pattern i.e., [!...]
|
|
@@ -323,9 +346,18 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
323
346
|
"""
|
|
324
347
|
# Replace element in the pattern for globbing
|
|
325
348
|
resolved_pattern = self._replace_patterns_glob(element, pattern)
|
|
326
|
-
# Resolve path for
|
|
327
|
-
if "*" in resolved_pattern:
|
|
328
|
-
|
|
349
|
+
# Resolve path for wildcards
|
|
350
|
+
if "*" in resolved_pattern or "?" in resolved_pattern:
|
|
351
|
+
# glob.glob (unlike pathlib before Python 3.12) also matches the
|
|
352
|
+
# broken symbolic links, e.g., the files of a DataLad dataset that
|
|
353
|
+
# are not downloaded yet.
|
|
354
|
+
# TODO: go back to Path.glob once Python 3.11 is dropped (end of
|
|
355
|
+
# life in October 2027)
|
|
356
|
+
fulldir = self.fulldir.absolute()
|
|
357
|
+
t_matches = [
|
|
358
|
+
fulldir / x
|
|
359
|
+
for x in glob.glob(resolved_pattern, root_dir=fulldir)
|
|
360
|
+
]
|
|
329
361
|
# Multiple matches
|
|
330
362
|
if len(t_matches) > 1:
|
|
331
363
|
raise_error(
|
|
@@ -341,7 +373,8 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
341
373
|
)
|
|
342
374
|
path = t_matches[0]
|
|
343
375
|
else:
|
|
344
|
-
path
|
|
376
|
+
# Absolute path, as for the patterns with wildcards
|
|
377
|
+
path = self.fulldir.absolute() / resolved_pattern
|
|
345
378
|
if not self.skip_file_check:
|
|
346
379
|
if not path.exists() and not path.is_symlink():
|
|
347
380
|
raise_error(
|
|
@@ -352,23 +385,131 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
352
385
|
|
|
353
386
|
return path
|
|
354
387
|
|
|
388
|
+
def _get_type_patterns(self, data_type: str) -> list[str]:
|
|
389
|
+
"""Get the patterns of a data type, including the nested ones.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
data_type : str
|
|
394
|
+
The data type.
|
|
395
|
+
|
|
396
|
+
Returns
|
|
397
|
+
-------
|
|
398
|
+
list of str
|
|
399
|
+
The patterns.
|
|
400
|
+
|
|
401
|
+
"""
|
|
402
|
+
dtype_val = self.patterns[data_type]
|
|
403
|
+
# Conditional for list dtype vals like Warp
|
|
404
|
+
if not isinstance(dtype_val, list):
|
|
405
|
+
dtype_val = [dtype_val]
|
|
406
|
+
patterns = []
|
|
407
|
+
for val in dtype_val:
|
|
408
|
+
if "pattern" in val:
|
|
409
|
+
patterns.append(val["pattern"])
|
|
410
|
+
patterns.extend(
|
|
411
|
+
v["pattern"]
|
|
412
|
+
for v in val.values()
|
|
413
|
+
if isinstance(v, dict) and "pattern" in v
|
|
414
|
+
)
|
|
415
|
+
return patterns
|
|
416
|
+
|
|
417
|
+
def get_type_element_keys(self, data_type: str) -> list[str]:
|
|
418
|
+
"""Get the element keys that the data of a data type depends on.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
data_type : str
|
|
423
|
+
The data type.
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
list of str
|
|
428
|
+
The replacements in the patterns of the data type (including the
|
|
429
|
+
nested ones), in the order of ``replacements``.
|
|
430
|
+
|
|
431
|
+
"""
|
|
432
|
+
patterns = self._get_type_patterns(data_type)
|
|
433
|
+
return [
|
|
434
|
+
x
|
|
435
|
+
for x in self.replacements
|
|
436
|
+
if any(f"{{{x}}}" in pattern for pattern in patterns)
|
|
437
|
+
]
|
|
438
|
+
|
|
355
439
|
def get_element_keys(self) -> list[str]:
|
|
356
440
|
"""Get element keys.
|
|
357
441
|
|
|
358
442
|
For each item in the "element" tuple, this functions returns the
|
|
359
|
-
corresponding key
|
|
360
|
-
|
|
443
|
+
corresponding key: the replacements in the patterns of the data
|
|
444
|
+
types to grab (see :meth:`.get_type_element_keys`), e.g., without
|
|
445
|
+
``task`` if only anatomical data types are grabbed.
|
|
361
446
|
|
|
362
447
|
Returns
|
|
363
448
|
-------
|
|
364
449
|
list of str
|
|
365
|
-
The element keys
|
|
450
|
+
The element keys, in the order of ``replacements``.
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
keys = {
|
|
454
|
+
k for t in self.get_types() for k in self.get_type_element_keys(t)
|
|
455
|
+
}
|
|
456
|
+
return [x for x in self.replacements if x in keys]
|
|
457
|
+
|
|
458
|
+
def get_replacement_values(self) -> dict[str, list[str]]:
|
|
459
|
+
"""Get the values of the replacements to grab.
|
|
460
|
+
|
|
461
|
+
The values are given by ``replacements`` (as a dictionary) or by the
|
|
462
|
+
fields of the replacements (see ``_REPLACEMENT_FIELDS``).
|
|
463
|
+
|
|
464
|
+
Returns
|
|
465
|
+
-------
|
|
466
|
+
dict
|
|
467
|
+
The lists of values of the replacements to grab, by replacement.
|
|
468
|
+
The replacements without values (to grab all of them) are not
|
|
469
|
+
included.
|
|
366
470
|
|
|
367
471
|
"""
|
|
368
|
-
|
|
472
|
+
values = {}
|
|
473
|
+
if isinstance(self.replacements, dict):
|
|
474
|
+
values = {
|
|
475
|
+
k: list(v)
|
|
476
|
+
for k, v in self.replacements.items()
|
|
477
|
+
if v is not None
|
|
478
|
+
}
|
|
479
|
+
for replacement, field in self._REPLACEMENT_FIELDS.items():
|
|
480
|
+
field_values = getattr(self, field)
|
|
481
|
+
if field_values is not None:
|
|
482
|
+
values[replacement] = [
|
|
483
|
+
_value(x) for x in ensure_list(field_values)
|
|
484
|
+
]
|
|
485
|
+
return values
|
|
486
|
+
|
|
487
|
+
def _check_element(self, element: dict) -> None:
|
|
488
|
+
"""Check the element has values of the replacements to grab.
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
element : dict
|
|
493
|
+
The element, as given to :meth:`.get_item`.
|
|
494
|
+
|
|
495
|
+
Raises
|
|
496
|
+
------
|
|
497
|
+
ValueError
|
|
498
|
+
If a value of the element is not one of the values to grab.
|
|
499
|
+
|
|
500
|
+
"""
|
|
501
|
+
for key, values in self.get_replacement_values().items():
|
|
502
|
+
if key in element and _value(element[key]) not in values:
|
|
503
|
+
raise_error(
|
|
504
|
+
msg=(
|
|
505
|
+
f"The value `{element[key]}` of `{key}` is not one of "
|
|
506
|
+
f"the values to grab: {values}"
|
|
507
|
+
),
|
|
508
|
+
klass=ValueError,
|
|
509
|
+
)
|
|
369
510
|
|
|
370
511
|
def get_item(self, **element: dict) -> dict[str, dict]:
|
|
371
|
-
"""
|
|
512
|
+
"""Get the specified item from the dataset.
|
|
372
513
|
|
|
373
514
|
This method constructs a real path to the requested item's data, by
|
|
374
515
|
replacing the ``patterns`` with actual values passed via ``**element``.
|
|
@@ -454,8 +595,8 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
454
595
|
"""Implement fetching list of elements in the dataset.
|
|
455
596
|
|
|
456
597
|
It will use regex to search for "replacements" in the "patterns" and
|
|
457
|
-
|
|
458
|
-
list of elements that have all the required types.
|
|
598
|
+
join the results for each type on their common replacements i.e.,
|
|
599
|
+
build a list of elements that have all the required types.
|
|
459
600
|
|
|
460
601
|
Returns
|
|
461
602
|
-------
|
|
@@ -463,22 +604,10 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
463
604
|
The list of elements that can be grabbed in the dataset.
|
|
464
605
|
|
|
465
606
|
"""
|
|
607
|
+
# The elements found so far, as tuples of the values of `keys`
|
|
466
608
|
elements = None
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
# all of them.
|
|
470
|
-
replacement_in_patterns = [
|
|
471
|
-
np.sum([x in self.patterns[t_type] for x in self.replacements])
|
|
472
|
-
for t_type in self.types
|
|
473
|
-
]
|
|
474
|
-
order = np.argsort(replacement_in_patterns)
|
|
475
|
-
|
|
476
|
-
# Start from the one with the most replacements and then
|
|
477
|
-
# filter out elements based on the ones with less replacements.
|
|
478
|
-
for t_idx in reversed(order):
|
|
479
|
-
t_type = self.types[t_idx]
|
|
480
|
-
types_element = set()
|
|
481
|
-
|
|
609
|
+
keys = []
|
|
610
|
+
for t_type in self.get_types():
|
|
482
611
|
# Data type dictionary
|
|
483
612
|
patterns = self.patterns[t_type]
|
|
484
613
|
# Conditional for list dtype vals like Warp
|
|
@@ -496,46 +625,48 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
|
|
496
625
|
pattern = v["pattern"]
|
|
497
626
|
break
|
|
498
627
|
|
|
499
|
-
#
|
|
628
|
+
# Find the elements of the pattern
|
|
500
629
|
(
|
|
501
630
|
re_pattern,
|
|
502
631
|
glob_pattern,
|
|
503
|
-
|
|
632
|
+
t_keys,
|
|
504
633
|
) = self._replace_patterns_regex(pattern)
|
|
505
|
-
|
|
506
|
-
|
|
634
|
+
t_elements = set()
|
|
635
|
+
# glob.glob (unlike pathlib before Python 3.12) also matches
|
|
636
|
+
# the broken symbolic links, e.g., the files of a DataLad
|
|
637
|
+
# dataset that are not downloaded yet.
|
|
638
|
+
# TODO: go back to Path.glob once Python 3.11 is dropped (end
|
|
639
|
+
# of life in October 2027)
|
|
640
|
+
for fname in glob.glob(glob_pattern, root_dir=self.fulldir):
|
|
641
|
+
suffix = Path(fname).as_posix()
|
|
507
642
|
m = re.match(re_pattern, suffix)
|
|
508
643
|
if m is not None:
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
644
|
+
t_elements.add(tuple(m.group(k) for k in t_keys))
|
|
645
|
+
# Keep the elements with the values to grab
|
|
646
|
+
replacement_values = self.get_replacement_values()
|
|
647
|
+
restricted = [
|
|
648
|
+
(i, set(replacement_values[k]))
|
|
649
|
+
for i, k in enumerate(t_keys)
|
|
650
|
+
if k in replacement_values
|
|
651
|
+
]
|
|
652
|
+
t_elements = {
|
|
653
|
+
element
|
|
654
|
+
for element in t_elements
|
|
655
|
+
if all(element[i] in values for i, values in restricted)
|
|
656
|
+
}
|
|
657
|
+
# Keep the elements available for all the data types
|
|
518
658
|
if elements is None:
|
|
519
|
-
elements =
|
|
659
|
+
elements, keys = t_elements, t_keys
|
|
520
660
|
else:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
elements.intersection(types_element)
|
|
525
|
-
else:
|
|
526
|
-
t_repl_idx = [
|
|
527
|
-
i
|
|
528
|
-
for i, v in enumerate(self.replacements)
|
|
529
|
-
if v in t_replacements
|
|
530
|
-
]
|
|
531
|
-
new_elements = set()
|
|
532
|
-
for t_element in elements:
|
|
533
|
-
if (
|
|
534
|
-
tuple(np.array(t_element)[t_repl_idx])
|
|
535
|
-
in types_element
|
|
536
|
-
):
|
|
537
|
-
new_elements.add(t_element)
|
|
538
|
-
elements = new_elements
|
|
661
|
+
elements, keys = _join_elements(
|
|
662
|
+
elements, keys, t_elements, t_keys
|
|
663
|
+
)
|
|
539
664
|
if elements is None:
|
|
540
|
-
|
|
541
|
-
|
|
665
|
+
return []
|
|
666
|
+
# Sort the values as the element keys
|
|
667
|
+
element_keys = self.get_element_keys()
|
|
668
|
+
idx = [keys.index(k) for k in element_keys]
|
|
669
|
+
out = sorted(tuple(element[i] for i in idx) for element in elements)
|
|
670
|
+
if len(element_keys) == 1:
|
|
671
|
+
out = [element[0] for element in out]
|
|
672
|
+
return out
|