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
|
@@ -7,14 +7,41 @@
|
|
|
7
7
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
|
8
8
|
# License: AGPL
|
|
9
9
|
|
|
10
|
-
from
|
|
10
|
+
from collections.abc import Iterator
|
|
11
|
+
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
import pytest
|
|
14
|
+
from pydantic import AnyUrl
|
|
13
15
|
|
|
14
16
|
from junifer.datagrabber import DataladAOMICPIOP2
|
|
17
|
+
from junifer.testing import config_override
|
|
15
18
|
|
|
16
19
|
|
|
17
|
-
URI =
|
|
20
|
+
URI = AnyUrl(
|
|
21
|
+
"https://cerebra.fz-juelich.de/junifer/datalad-example-aomicpiop2"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="module")
|
|
26
|
+
def aomic_datadir(tmp_path_factory) -> Iterator[Path]:
|
|
27
|
+
"""Return the path to the AOMIC data directory.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
tmp_path_factory : pathlib.Path
|
|
32
|
+
The path to the test directory.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
pathlib.Path
|
|
37
|
+
The path to the AOMIC data directory.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
datadir = tmp_path_factory.mktemp("aomic_data")
|
|
41
|
+
datadir.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
dg = DataladAOMICPIOP2(uri=URI, types=["T1w"], datadir=datadir)
|
|
43
|
+
with config_override("datagrabber.skipidcheck", True), dg:
|
|
44
|
+
yield datadir
|
|
18
45
|
|
|
19
46
|
|
|
20
47
|
@pytest.mark.parametrize(
|
|
@@ -23,18 +50,11 @@ URI = "https://gin.g-node.org/juaml/datalad-example-aomicpiop2"
|
|
|
23
50
|
(
|
|
24
51
|
"BOLD",
|
|
25
52
|
["confounds", "mask", "reference"],
|
|
26
|
-
|
|
53
|
+
"restingstate",
|
|
27
54
|
"MNI152NLin2009cAsym",
|
|
28
55
|
),
|
|
29
|
-
("BOLD", ["confounds", "mask", "reference"], None, "native"),
|
|
30
56
|
(
|
|
31
|
-
"BOLD",
|
|
32
|
-
["confounds", "mask", "reference"],
|
|
33
|
-
["restingstate"],
|
|
34
|
-
"MNI152NLin2009cAsym",
|
|
35
|
-
),
|
|
36
|
-
(
|
|
37
|
-
"BOLD",
|
|
57
|
+
["BOLD"],
|
|
38
58
|
["confounds", "mask", "reference"],
|
|
39
59
|
["restingstate", "stopsignal"],
|
|
40
60
|
"MNI152NLin2009cAsym",
|
|
@@ -46,72 +66,79 @@ URI = "https://gin.g-node.org/juaml/datalad-example-aomicpiop2"
|
|
|
46
66
|
"MNI152NLin2009cAsym",
|
|
47
67
|
),
|
|
48
68
|
(
|
|
49
|
-
"BOLD",
|
|
69
|
+
["BOLD"],
|
|
50
70
|
["confounds", "mask", "reference"],
|
|
51
|
-
|
|
71
|
+
"workingmemory",
|
|
52
72
|
"MNI152NLin2009cAsym",
|
|
53
73
|
),
|
|
54
|
-
("T1w", ["mask"],
|
|
55
|
-
("T1w", ["mask"],
|
|
56
|
-
("VBM_CSF", None,
|
|
57
|
-
("VBM_CSF", None,
|
|
58
|
-
("VBM_GM", None,
|
|
59
|
-
("VBM_GM", None,
|
|
60
|
-
("VBM_WM", None,
|
|
61
|
-
("VBM_WM", None,
|
|
62
|
-
("DWI", None,
|
|
63
|
-
("FreeSurfer", None,
|
|
74
|
+
(["T1w"], ["mask"], "restingstate", "MNI152NLin2009cAsym"),
|
|
75
|
+
("T1w", ["mask"], ["restingstate"], "native"),
|
|
76
|
+
(["VBM_CSF"], None, "restingstate", "MNI152NLin2009cAsym"),
|
|
77
|
+
("VBM_CSF", None, ["restingstate"], "native"),
|
|
78
|
+
(["VBM_GM"], None, "restingstate", "MNI152NLin2009cAsym"),
|
|
79
|
+
("VBM_GM", None, ["restingstate"], "native"),
|
|
80
|
+
(["VBM_WM"], None, "restingstate", "MNI152NLin2009cAsym"),
|
|
81
|
+
("VBM_WM", None, ["restingstate"], "native"),
|
|
82
|
+
(["DWI"], None, "restingstate", "MNI152NLin2009cAsym"),
|
|
83
|
+
(["FreeSurfer"], None, ["restingstate"], "MNI152NLin2009cAsym"),
|
|
64
84
|
],
|
|
65
85
|
)
|
|
66
86
|
def test_DataladAOMICPIOP2(
|
|
67
|
-
type_: str,
|
|
68
|
-
nested_types:
|
|
69
|
-
tasks:
|
|
87
|
+
type_: str | list[str],
|
|
88
|
+
nested_types: list[str] | None,
|
|
89
|
+
tasks: str | list[str],
|
|
70
90
|
space: str,
|
|
91
|
+
aomic_datadir: Path,
|
|
71
92
|
) -> None:
|
|
72
93
|
"""Test DataladAOMICPIOP2 DataGrabber.
|
|
73
94
|
|
|
74
95
|
Parameters
|
|
75
96
|
----------
|
|
76
|
-
type_ : str
|
|
97
|
+
type_ : str or list of str
|
|
77
98
|
The parametrized type.
|
|
78
99
|
nested_types : list of str or None
|
|
79
100
|
The parametrized nested types.
|
|
80
|
-
tasks : list of str
|
|
101
|
+
tasks : str or list of str
|
|
81
102
|
The parametrized task values.
|
|
82
103
|
space: str
|
|
83
104
|
The parametrized space.
|
|
105
|
+
aomic_datadir: Path
|
|
106
|
+
The path to the AOMIC data directory.
|
|
84
107
|
|
|
85
108
|
"""
|
|
86
|
-
dg = DataladAOMICPIOP2(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
dg = DataladAOMICPIOP2(
|
|
110
|
+
uri=URI, types=type_, tasks=tasks, space=space, datadir=aomic_datadir
|
|
111
|
+
)
|
|
90
112
|
with dg:
|
|
91
|
-
# Get all elements
|
|
92
113
|
all_elements = dg.get_elements()
|
|
93
|
-
# Get test element
|
|
94
114
|
test_element = all_elements[0]
|
|
95
|
-
# Get test element data
|
|
96
115
|
out = dg[test_element]
|
|
116
|
+
# Elements only have the subject if no data type of the tasks is
|
|
117
|
+
# grabbed
|
|
118
|
+
if not isinstance(test_element, tuple):
|
|
119
|
+
test_element = (test_element,)
|
|
97
120
|
# Assert data type
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
assert
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
if isinstance(type_, str):
|
|
122
|
+
type_ = [type_]
|
|
123
|
+
for t in type_:
|
|
124
|
+
assert t in out
|
|
125
|
+
# Check task name if BOLD
|
|
126
|
+
if t == "BOLD":
|
|
127
|
+
assert test_element[1] in out[t]["path"].name
|
|
128
|
+
assert out[t]["path"].exists()
|
|
129
|
+
assert out[t]["path"].is_file()
|
|
130
|
+
# Asserts data type metadata
|
|
131
|
+
assert "meta" in out[t]
|
|
132
|
+
meta = out[t]["meta"]
|
|
133
|
+
assert "element" in meta
|
|
134
|
+
assert "subject" in meta["element"]
|
|
135
|
+
assert test_element[0] == meta["element"]["subject"]
|
|
136
|
+
assert list(meta["element"]) == dg.get_element_keys()
|
|
137
|
+
# Assert nested data type if not None
|
|
138
|
+
if nested_types is not None:
|
|
139
|
+
for nested_type in nested_types:
|
|
140
|
+
assert out[t][nested_type]["path"].exists()
|
|
141
|
+
assert out[t][nested_type]["path"].is_file()
|
|
115
142
|
|
|
116
143
|
|
|
117
144
|
@pytest.mark.parametrize(
|
|
@@ -130,7 +157,8 @@ def test_DataladAOMICPIOP2(
|
|
|
130
157
|
],
|
|
131
158
|
)
|
|
132
159
|
def test_DataladAOMICPIOP2_partial_data_access(
|
|
133
|
-
types:
|
|
160
|
+
types: str | list[str],
|
|
161
|
+
aomic_datadir: Path,
|
|
134
162
|
) -> None:
|
|
135
163
|
"""Test DataladAOMICPIOP2 DataGrabber partial data access.
|
|
136
164
|
|
|
@@ -138,42 +166,17 @@ def test_DataladAOMICPIOP2_partial_data_access(
|
|
|
138
166
|
----------
|
|
139
167
|
types : str or list of str
|
|
140
168
|
The parametrized types.
|
|
169
|
+
aomic_datadir : pathlib.Path
|
|
170
|
+
The path to the AOMIC data directory.
|
|
141
171
|
|
|
142
172
|
"""
|
|
143
|
-
dg = DataladAOMICPIOP2(types=types)
|
|
144
|
-
# Set URI to Gin
|
|
145
|
-
dg.uri = URI
|
|
146
|
-
|
|
173
|
+
dg = DataladAOMICPIOP2(uri=URI, types=types, datadir=aomic_datadir)
|
|
147
174
|
with dg:
|
|
148
|
-
# Get all elements
|
|
149
175
|
all_elements = dg.get_elements()
|
|
150
|
-
# Get test element
|
|
151
176
|
test_element = all_elements[0]
|
|
152
|
-
# Get test element data
|
|
153
177
|
out = dg[test_element]
|
|
154
178
|
# Assert data type
|
|
155
|
-
if isinstance(types,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
assert types in out
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def test_DataladAOMICPIOP2_incorrect_data_type() -> None:
|
|
163
|
-
"""Test DataladAOMICPIOP2 DataGrabber incorrect data type."""
|
|
164
|
-
with pytest.raises(
|
|
165
|
-
ValueError, match="`patterns` must contain all `types`"
|
|
166
|
-
):
|
|
167
|
-
_ = DataladAOMICPIOP2(types="Vesta")
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def test_DataladAOMICPIOP2_invalid_tasks():
|
|
171
|
-
"""Test DataladAOMICIDPIOP2 DataGrabber invalid tasks."""
|
|
172
|
-
with pytest.raises(
|
|
173
|
-
ValueError,
|
|
174
|
-
match=(
|
|
175
|
-
"thisisnotarealtask is not a valid task in "
|
|
176
|
-
"the AOMIC PIOP2 dataset!"
|
|
177
|
-
),
|
|
178
|
-
):
|
|
179
|
-
DataladAOMICPIOP2(tasks="thisisnotarealtask")
|
|
179
|
+
if isinstance(types, str):
|
|
180
|
+
types = [types]
|
|
181
|
+
for t_ in types:
|
|
182
|
+
assert t_ in out
|
junifer/datagrabber/base.py
CHANGED
|
@@ -7,57 +7,140 @@
|
|
|
7
7
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
9
|
from collections.abc import Iterator
|
|
10
|
+
from enum import Enum
|
|
10
11
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
12
|
+
from typing import Annotated, Any
|
|
13
|
+
|
|
14
|
+
import structlog
|
|
15
|
+
from aenum import Enum as AEnum
|
|
16
|
+
from pydantic import BaseModel, BeforeValidator, ConfigDict
|
|
12
17
|
|
|
13
18
|
from ..pipeline import UpdateMetaMixin
|
|
14
19
|
from ..typing import Element, Elements
|
|
15
|
-
from ..utils import
|
|
20
|
+
from ..utils import ensure_list, raise_error
|
|
16
21
|
|
|
17
22
|
|
|
18
|
-
__all__ = ["BaseDataGrabber"]
|
|
23
|
+
__all__ = ["BaseDataGrabber", "DataType"]
|
|
19
24
|
|
|
25
|
+
_log = structlog.get_logger("junifer")
|
|
26
|
+
logger = _log.bind(pkg="datagrabber", step="datagrabber")
|
|
20
27
|
|
|
21
|
-
class BaseDataGrabber(ABC, UpdateMetaMixin):
|
|
22
|
-
"""Abstract base class for DataGrabber.
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
class DataType(str, AEnum):
|
|
30
|
+
"""Accepted data type."""
|
|
31
|
+
|
|
32
|
+
T1w = "T1w"
|
|
33
|
+
T2w = "T2w"
|
|
34
|
+
BOLD = "BOLD"
|
|
35
|
+
Warp = "Warp"
|
|
36
|
+
VBM_GM = "VBM_GM"
|
|
37
|
+
VBM_WM = "VBM_WM"
|
|
38
|
+
VBM_CSF = "VBM_CSF"
|
|
39
|
+
FALFF = "fALFF"
|
|
40
|
+
GCOR = "GCOR"
|
|
41
|
+
LCOR = "LCOR"
|
|
42
|
+
DWI = "DWI"
|
|
43
|
+
FreeSurfer = "FreeSurfer"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _matches(element: Element, selector: Element) -> bool:
|
|
47
|
+
"""Check whether an element matches a selector.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
element : ``Element``
|
|
52
|
+
The element.
|
|
53
|
+
selector : ``Element``
|
|
54
|
+
The partial or complete element selector: a value (the element must
|
|
55
|
+
have it) or a tuple of values (the element must have all of them).
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
bool
|
|
60
|
+
Whether the element matches the selector.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
if not isinstance(element, tuple):
|
|
64
|
+
element = (element,)
|
|
65
|
+
if isinstance(selector, tuple):
|
|
66
|
+
return set(selector).issubset(element)
|
|
67
|
+
return selector in element
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _selectors_as_tuples(selection: Elements) -> list[tuple]:
|
|
71
|
+
"""Get the element selectors as tuples, checking they have the same size.
|
|
26
72
|
|
|
27
73
|
Parameters
|
|
28
74
|
----------
|
|
29
|
-
|
|
30
|
-
The
|
|
31
|
-
|
|
32
|
-
|
|
75
|
+
selection : ``Elements``
|
|
76
|
+
The list of partial or complete element selectors.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
list of tuple
|
|
81
|
+
The selectors, as tuples (a single value is a tuple of one value).
|
|
33
82
|
|
|
34
83
|
Raises
|
|
35
84
|
------
|
|
36
|
-
|
|
37
|
-
If
|
|
85
|
+
ValueError
|
|
86
|
+
If the selectors have different numbers of values.
|
|
38
87
|
|
|
39
88
|
"""
|
|
89
|
+
selectors = [s if isinstance(s, tuple) else (s,) for s in selection]
|
|
90
|
+
if len({len(selector) for selector in selectors}) > 1:
|
|
91
|
+
raise_error(
|
|
92
|
+
msg=(
|
|
93
|
+
"The element selectors must have the same number of values: "
|
|
94
|
+
f"{selection}"
|
|
95
|
+
),
|
|
96
|
+
klass=ValueError,
|
|
97
|
+
)
|
|
98
|
+
return selectors
|
|
40
99
|
|
|
41
|
-
def __init__(self, types: list[str], datadir: Union[str, Path]) -> None:
|
|
42
|
-
# Validate types
|
|
43
|
-
if not isinstance(types, list):
|
|
44
|
-
raise_error(msg="`types` must be a list", klass=TypeError)
|
|
45
|
-
if any(not isinstance(x, str) for x in types):
|
|
46
|
-
raise_error(
|
|
47
|
-
msg="`types` must be a list of strings", klass=TypeError
|
|
48
|
-
)
|
|
49
|
-
self.types = types
|
|
50
100
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
101
|
+
class BaseDataGrabber(BaseModel, ABC, UpdateMetaMixin):
|
|
102
|
+
"""Abstract base class for data fetcher.
|
|
103
|
+
|
|
104
|
+
For every datagrabber, one needs to provide a concrete
|
|
105
|
+
implementation of this abstract class.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
types : :enum:`.DataType` or list of variants
|
|
110
|
+
The data type(s) to grab.
|
|
111
|
+
datadir : pathlib.Path
|
|
112
|
+
The path where the data is or will be stored.
|
|
113
|
+
|
|
114
|
+
"""
|
|
55
115
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
116
|
+
model_config = ConfigDict(extra="forbid", use_enum_values=True)
|
|
117
|
+
|
|
118
|
+
types: Annotated[
|
|
119
|
+
DataType | list[DataType],
|
|
120
|
+
BeforeValidator(ensure_list),
|
|
121
|
+
]
|
|
122
|
+
datadir: Path
|
|
123
|
+
|
|
124
|
+
def model_post_init(self, context: Any): # noqa: D102
|
|
125
|
+
# Run extra validation for datagrabbers and fail early if needed
|
|
126
|
+
self.validate_datagrabber_params()
|
|
127
|
+
# Convert to correct data type
|
|
128
|
+
self.types = [
|
|
129
|
+
DataType(t) if isinstance(t, str) else t for t in self.types
|
|
130
|
+
]
|
|
131
|
+
logger.info(
|
|
132
|
+
f"Parameters: {self.model_dump(mode='json')}",
|
|
133
|
+
component=self.__class__.__name__,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def validate_datagrabber_params(self) -> None:
|
|
137
|
+
"""Run extra logical validation for datagrabber.
|
|
138
|
+
|
|
139
|
+
Subclasses can override to provide validation.
|
|
140
|
+
"""
|
|
141
|
+
pass
|
|
59
142
|
|
|
60
|
-
def __iter__(self) -> Iterator:
|
|
143
|
+
def __iter__(self) -> Iterator[Elements]:
|
|
61
144
|
"""Enable iterable support.
|
|
62
145
|
|
|
63
146
|
Yields
|
|
@@ -73,7 +156,7 @@ class BaseDataGrabber(ABC, UpdateMetaMixin):
|
|
|
73
156
|
|
|
74
157
|
Parameters
|
|
75
158
|
----------
|
|
76
|
-
element :
|
|
159
|
+
element : `Element`
|
|
77
160
|
The element to be indexed.
|
|
78
161
|
|
|
79
162
|
Returns
|
|
@@ -83,28 +166,66 @@ class BaseDataGrabber(ABC, UpdateMetaMixin):
|
|
|
83
166
|
specified element.
|
|
84
167
|
|
|
85
168
|
"""
|
|
169
|
+
# Convert element to tuple if not already and extract enum values if
|
|
170
|
+
# present
|
|
171
|
+
element = (
|
|
172
|
+
(element,)
|
|
173
|
+
if not isinstance(element, tuple)
|
|
174
|
+
else tuple(i.value if isinstance(i, Enum) else i for i in element)
|
|
175
|
+
)
|
|
86
176
|
logger.info(f"Getting element {element}")
|
|
87
|
-
# Convert element to tuple if not already
|
|
88
|
-
if not isinstance(element, tuple):
|
|
89
|
-
element = (element,)
|
|
90
177
|
# Zip through element keys and actual values to construct element
|
|
91
178
|
# access dictionary
|
|
92
|
-
named_element: dict = dict(
|
|
179
|
+
named_element: dict = dict(
|
|
180
|
+
zip(self.get_element_keys(), element, strict=False)
|
|
181
|
+
)
|
|
93
182
|
logger.debug(f"Named element: {named_element}")
|
|
183
|
+
self._check_element(named_element)
|
|
94
184
|
# Fetch element
|
|
95
185
|
out = self.get_item(**named_element)
|
|
96
|
-
# Update metadata
|
|
97
|
-
|
|
186
|
+
# Update metadata: the element being processed and the keys of the
|
|
187
|
+
# element that the data of each data type depends on
|
|
188
|
+
for t_type, t_val in out.items():
|
|
98
189
|
self.update_meta(t_val, "datagrabber")
|
|
190
|
+
element_keys = self.get_type_element_keys(t_type)
|
|
99
191
|
# Conditional for list dtype vals like Warp
|
|
100
|
-
if isinstance(t_val, list):
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
else:
|
|
104
|
-
t_val["meta"]["element"] = named_element
|
|
192
|
+
for entry in t_val if isinstance(t_val, list) else [t_val]:
|
|
193
|
+
entry["meta"]["element"] = named_element
|
|
194
|
+
entry["meta"]["_element_keys"] = element_keys
|
|
105
195
|
|
|
106
196
|
return out
|
|
107
197
|
|
|
198
|
+
def get_type_element_keys(self, data_type: str) -> list[str]:
|
|
199
|
+
"""Get the element keys that the data of a data type depends on.
|
|
200
|
+
|
|
201
|
+
The element keys (see :meth:`.get_element_keys`) are the keys that
|
|
202
|
+
the data types to grab depend on.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
data_type : str
|
|
207
|
+
The data type.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
list of str
|
|
212
|
+
The element keys, all of them unless a subclass knows which ones
|
|
213
|
+
the data type depends on (e.g., only the subject for data that is
|
|
214
|
+
the same for all the tasks of a subject).
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
return self.get_element_keys()
|
|
218
|
+
|
|
219
|
+
def _check_element(self, element: dict) -> None:
|
|
220
|
+
"""Check the element can be grabbed.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
element : dict
|
|
225
|
+
The element, as given to :meth:`.get_item`.
|
|
226
|
+
|
|
227
|
+
"""
|
|
228
|
+
|
|
108
229
|
def __enter__(self) -> "BaseDataGrabber":
|
|
109
230
|
"""Context entry."""
|
|
110
231
|
return self
|
|
@@ -119,67 +240,120 @@ class BaseDataGrabber(ABC, UpdateMetaMixin):
|
|
|
119
240
|
Returns
|
|
120
241
|
-------
|
|
121
242
|
list of str
|
|
122
|
-
The
|
|
243
|
+
The data type(s) to grab.
|
|
123
244
|
|
|
124
245
|
"""
|
|
125
|
-
return self.types
|
|
246
|
+
return [x.value if isinstance(x, Enum) else x for x in self.types]
|
|
126
247
|
|
|
127
248
|
@property
|
|
128
|
-
def
|
|
129
|
-
"""Get data directory path.
|
|
249
|
+
def fulldir(self) -> Path:
|
|
250
|
+
"""Get complete data directory path.
|
|
130
251
|
|
|
131
252
|
Returns
|
|
132
253
|
-------
|
|
133
254
|
pathlib.Path
|
|
134
|
-
|
|
255
|
+
Complete path to the data directory.
|
|
256
|
+
Can be overridden by subclasses.
|
|
135
257
|
|
|
136
258
|
"""
|
|
137
|
-
return self.
|
|
259
|
+
return self.datadir
|
|
138
260
|
|
|
139
261
|
def filter(self, selection: Elements) -> Iterator:
|
|
140
262
|
"""Filter elements to be grabbed.
|
|
141
263
|
|
|
142
264
|
Parameters
|
|
143
265
|
----------
|
|
144
|
-
selection :
|
|
145
|
-
The list of partial or complete element selectors to filter using
|
|
266
|
+
selection : ``Elements``
|
|
267
|
+
The list of partial or complete element selectors to filter using,
|
|
268
|
+
with the same number of values.
|
|
146
269
|
|
|
147
270
|
Yields
|
|
148
271
|
------
|
|
149
272
|
object
|
|
150
273
|
An element that can be indexed by the DataGrabber.
|
|
151
274
|
|
|
275
|
+
Raises
|
|
276
|
+
------
|
|
277
|
+
ValueError
|
|
278
|
+
If the selectors have different numbers of values.
|
|
279
|
+
|
|
152
280
|
"""
|
|
281
|
+
selectors = _selectors_as_tuples(selection)
|
|
282
|
+
for element in self.get_elements():
|
|
283
|
+
if any(_matches(element, selector) for selector in selectors):
|
|
284
|
+
yield element
|
|
285
|
+
|
|
286
|
+
def select_elements(self, selection: Elements) -> list[Element]:
|
|
287
|
+
"""Select the elements to grab.
|
|
288
|
+
|
|
289
|
+
All the selectors must have the same number of values (a single value
|
|
290
|
+
counts as a tuple of one value), so they are either all complete
|
|
291
|
+
elements or all partial selectors:
|
|
292
|
+
|
|
293
|
+
* Complete elements have a value for each element key, in the order of
|
|
294
|
+
:meth:`.get_element_keys`, e.g., ``("sub-01", "rest")`` for the keys
|
|
295
|
+
``["subject", "task"]``. They are the elements to grab, so the
|
|
296
|
+
elements of the dataset are not needed (they are not searched for).
|
|
297
|
+
Repeated elements are only returned once.
|
|
298
|
+
* Partial selectors have fewer values, e.g., ``"sub-01"`` or
|
|
299
|
+
``("sub-01",)``. They select the elements of the dataset that match
|
|
300
|
+
them, i.e., that have all their values for any key (see
|
|
301
|
+
:meth:`.filter`). The elements of the dataset are searched for once,
|
|
302
|
+
and each element is returned once, even if several selectors match
|
|
303
|
+
it: with ``["sub-01", "rest"]``, the element ``("sub-01", "rest")``
|
|
304
|
+
matches both selectors and is returned once, together with the other
|
|
305
|
+
tasks of ``sub-01`` and the ``rest`` task of the other subjects.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
selection : ``Elements``
|
|
310
|
+
The list of partial or complete element selectors.
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
list of tuple
|
|
315
|
+
The selected elements, without duplicates.
|
|
316
|
+
|
|
317
|
+
Raises
|
|
318
|
+
------
|
|
319
|
+
ValueError
|
|
320
|
+
If the selectors have different numbers of values.
|
|
321
|
+
RuntimeError
|
|
322
|
+
If a partial selector does not match any element.
|
|
153
323
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
324
|
+
"""
|
|
325
|
+
selectors = _selectors_as_tuples(selection)
|
|
326
|
+
if not selectors:
|
|
327
|
+
return []
|
|
328
|
+
# Complete elements: the selectors are the elements, without the
|
|
329
|
+
# repeated ones
|
|
330
|
+
if len(selectors[0]) == len(self.get_element_keys()):
|
|
331
|
+
# Remove the repeated selectors, keeping the order
|
|
332
|
+
return list(dict.fromkeys(selectors))
|
|
333
|
+
# Partial selectors: the elements of the dataset that match them, in a
|
|
334
|
+
# single pass over the elements. Each element is checked once, so it
|
|
335
|
+
# is returned once even if several selectors match it
|
|
336
|
+
selected = []
|
|
337
|
+
matched = set()
|
|
338
|
+
for element in self.get_elements():
|
|
339
|
+
t_matched = [
|
|
340
|
+
i
|
|
341
|
+
for i, selector in enumerate(selectors)
|
|
342
|
+
if _matches(element, selector)
|
|
343
|
+
]
|
|
344
|
+
if t_matched:
|
|
345
|
+
selected.append(element)
|
|
346
|
+
matched.update(t_matched)
|
|
347
|
+
# The selectors that no element matches
|
|
348
|
+
invalid = [
|
|
349
|
+
given for i, given in enumerate(selection) if i not in matched
|
|
350
|
+
]
|
|
351
|
+
if invalid:
|
|
352
|
+
raise_error(
|
|
353
|
+
msg=f"The following element selectors are invalid:\n{invalid}",
|
|
354
|
+
klass=RuntimeError,
|
|
355
|
+
)
|
|
356
|
+
return selected
|
|
183
357
|
|
|
184
358
|
@abstractmethod
|
|
185
359
|
def get_element_keys(self) -> list[str]:
|