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
|
@@ -6,19 +6,36 @@
|
|
|
6
6
|
# License: AGPL
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
9
11
|
from contextlib import AbstractContextManager, nullcontext
|
|
12
|
+
from copy import deepcopy
|
|
10
13
|
from pathlib import Path
|
|
11
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, ClassVar
|
|
12
15
|
|
|
16
|
+
import nibabel as nib
|
|
17
|
+
import numpy as np
|
|
13
18
|
import pytest
|
|
14
19
|
from nibabel.filebasedimages import ImageFileError
|
|
15
20
|
from ruamel.yaml import YAML
|
|
16
21
|
|
|
17
22
|
import junifer.testing.registry # noqa: F401
|
|
18
|
-
from junifer.api import collect, list_elements, queue, reset, run
|
|
23
|
+
from junifer.api import collect, list_elements, parse_yaml, queue, reset, run
|
|
24
|
+
from junifer.api.decorators import register_marker
|
|
25
|
+
from junifer.datagrabber import DataType, PatternDataGrabber
|
|
19
26
|
from junifer.datagrabber.base import BaseDataGrabber
|
|
20
|
-
from junifer.
|
|
21
|
-
from junifer.
|
|
27
|
+
from junifer.markers import BaseMarker
|
|
28
|
+
from junifer.pipeline import (
|
|
29
|
+
MarkerCollection,
|
|
30
|
+
PipelineComponentRegistry,
|
|
31
|
+
WorkDirManager,
|
|
32
|
+
)
|
|
33
|
+
from junifer.storage import StorageType
|
|
34
|
+
from junifer.storage.base import BaseFeatureStorage
|
|
35
|
+
from junifer.typing import Dependencies, Elements, MarkerInOutMappings
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
pytestmark = pytest.mark.external
|
|
22
39
|
|
|
23
40
|
|
|
24
41
|
# Configure YAML class
|
|
@@ -31,10 +48,14 @@ yaml.indent(mapping=2, sequence=4, offset=2)
|
|
|
31
48
|
# Kept for parametrizing
|
|
32
49
|
_datagrabber = {
|
|
33
50
|
"kind": "PartlyCloudyTestingDataGrabber",
|
|
51
|
+
# Fewer subjects for faster tests
|
|
52
|
+
"n_subjects": 4,
|
|
34
53
|
}
|
|
35
54
|
_bids_ses_datagrabber = {
|
|
36
55
|
"kind": "PatternDataladDataGrabber",
|
|
37
|
-
"uri":
|
|
56
|
+
"uri": (
|
|
57
|
+
"https://cerebra.fz-juelich.de/junifer/datalad-example-bids-ses.git"
|
|
58
|
+
),
|
|
38
59
|
"types": ["T1w", "BOLD"],
|
|
39
60
|
"patterns": {
|
|
40
61
|
"T1w": {
|
|
@@ -62,7 +83,7 @@ def datagrabber() -> dict[str, str]:
|
|
|
62
83
|
|
|
63
84
|
|
|
64
85
|
@pytest.fixture
|
|
65
|
-
def markers() -> list[dict[str, str]]:
|
|
86
|
+
def markers() -> list[dict[str, list[str] | str]]:
|
|
66
87
|
"""Return markers as a list of dictionary."""
|
|
67
88
|
return [
|
|
68
89
|
{
|
|
@@ -84,18 +105,15 @@ def markers() -> list[dict[str, str]]:
|
|
|
84
105
|
def storage() -> dict[str, str]:
|
|
85
106
|
"""Return a storage as a dictionary."""
|
|
86
107
|
return {
|
|
87
|
-
"kind": "
|
|
108
|
+
"kind": "HDF5FeatureStorage",
|
|
88
109
|
}
|
|
89
110
|
|
|
90
111
|
|
|
91
112
|
@pytest.mark.parametrize(
|
|
92
113
|
"datagrabber, element, expect",
|
|
93
114
|
[
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
[("sub-01",)],
|
|
97
|
-
pytest.raises(RuntimeError, match="element selectors are invalid"),
|
|
98
|
-
),
|
|
115
|
+
# A complete element
|
|
116
|
+
(_datagrabber, [("sub-01",)], nullcontext()),
|
|
99
117
|
(
|
|
100
118
|
_datagrabber,
|
|
101
119
|
["sub-01"],
|
|
@@ -111,15 +129,16 @@ def storage() -> dict[str, str]:
|
|
|
111
129
|
[("sub-01", "ses-01")],
|
|
112
130
|
pytest.raises(ImageFileError, match="is not a gzip file"),
|
|
113
131
|
),
|
|
132
|
+
# Complete elements without data
|
|
114
133
|
(
|
|
115
134
|
_bids_ses_datagrabber,
|
|
116
135
|
[("sub-01", "ses-100")],
|
|
117
|
-
pytest.raises(RuntimeError, match="
|
|
136
|
+
pytest.raises(RuntimeError, match="Cannot access"),
|
|
118
137
|
),
|
|
119
138
|
(
|
|
120
139
|
_bids_ses_datagrabber,
|
|
121
140
|
[("sub-100", "ses-01")],
|
|
122
|
-
pytest.raises(RuntimeError, match="
|
|
141
|
+
pytest.raises(RuntimeError, match="Cannot access"),
|
|
123
142
|
),
|
|
124
143
|
],
|
|
125
144
|
)
|
|
@@ -150,7 +169,7 @@ def test_run_single_element(
|
|
|
150
169
|
|
|
151
170
|
"""
|
|
152
171
|
# Set storage
|
|
153
|
-
storage["uri"] = str((tmp_path / "out.
|
|
172
|
+
storage["uri"] = str((tmp_path / "out.hdf5").resolve())
|
|
154
173
|
# Run operations
|
|
155
174
|
with expect:
|
|
156
175
|
run(
|
|
@@ -161,7 +180,7 @@ def test_run_single_element(
|
|
|
161
180
|
elements=element,
|
|
162
181
|
)
|
|
163
182
|
# Check files
|
|
164
|
-
files = list(tmp_path.glob("*.
|
|
183
|
+
files = list(tmp_path.glob("*.hdf5"))
|
|
165
184
|
assert len(files) == 1
|
|
166
185
|
|
|
167
186
|
|
|
@@ -183,7 +202,7 @@ def test_run_single_element_with_preprocessing(
|
|
|
183
202
|
|
|
184
203
|
"""
|
|
185
204
|
# Set storage
|
|
186
|
-
storage["uri"] = str((tmp_path / "out.
|
|
205
|
+
storage["uri"] = str((tmp_path / "out.hdf5").resolve())
|
|
187
206
|
# Run operations
|
|
188
207
|
run(
|
|
189
208
|
workdir={"path": tmp_path, "cleanup": False},
|
|
@@ -201,17 +220,14 @@ def test_run_single_element_with_preprocessing(
|
|
|
201
220
|
elements=["sub-01"],
|
|
202
221
|
)
|
|
203
222
|
# Check files
|
|
204
|
-
files = list(tmp_path.glob("*.
|
|
223
|
+
files = list(tmp_path.glob("*.hdf5"))
|
|
205
224
|
assert len(files) == 1
|
|
206
225
|
|
|
207
226
|
|
|
208
227
|
@pytest.mark.parametrize(
|
|
209
228
|
"element, expect",
|
|
210
229
|
[
|
|
211
|
-
(
|
|
212
|
-
[("sub-01",), ("sub-03",)],
|
|
213
|
-
pytest.raises(RuntimeError, match="element selectors are invalid"),
|
|
214
|
-
),
|
|
230
|
+
([("sub-01",), ("sub-03",)], nullcontext()),
|
|
215
231
|
(["sub-01", "sub-03"], nullcontext()),
|
|
216
232
|
],
|
|
217
233
|
)
|
|
@@ -242,7 +258,7 @@ def test_run_multi_element_multi_output(
|
|
|
242
258
|
|
|
243
259
|
"""
|
|
244
260
|
# Set storage
|
|
245
|
-
storage["uri"] = str((tmp_path / "out.
|
|
261
|
+
storage["uri"] = str((tmp_path / "out.hdf5").resolve())
|
|
246
262
|
storage["single_output"] = False # type: ignore
|
|
247
263
|
# Run operations
|
|
248
264
|
with expect:
|
|
@@ -254,7 +270,7 @@ def test_run_multi_element_multi_output(
|
|
|
254
270
|
elements=element,
|
|
255
271
|
)
|
|
256
272
|
# Check files
|
|
257
|
-
files = list(tmp_path.glob("*.
|
|
273
|
+
files = list(tmp_path.glob("*.hdf5"))
|
|
258
274
|
assert len(files) == 2
|
|
259
275
|
|
|
260
276
|
|
|
@@ -279,7 +295,7 @@ def test_run_multi_element_single_output(
|
|
|
279
295
|
|
|
280
296
|
"""
|
|
281
297
|
# Set storage
|
|
282
|
-
storage["uri"] = str((tmp_path / "out.
|
|
298
|
+
storage["uri"] = str((tmp_path / "out.hdf5").resolve())
|
|
283
299
|
storage["single_output"] = True # type: ignore
|
|
284
300
|
# Run operations
|
|
285
301
|
run(
|
|
@@ -290,9 +306,9 @@ def test_run_multi_element_single_output(
|
|
|
290
306
|
elements=["sub-01", "sub-03"],
|
|
291
307
|
)
|
|
292
308
|
# Check files
|
|
293
|
-
files = list(tmp_path.glob("*.
|
|
309
|
+
files = list(tmp_path.glob("*.hdf5"))
|
|
294
310
|
assert len(files) == 1
|
|
295
|
-
assert files[0].name == "out.
|
|
311
|
+
assert files[0].name == "out.hdf5"
|
|
296
312
|
|
|
297
313
|
|
|
298
314
|
def test_run_and_collect(
|
|
@@ -316,7 +332,7 @@ def test_run_and_collect(
|
|
|
316
332
|
|
|
317
333
|
"""
|
|
318
334
|
# Set storage
|
|
319
|
-
uri = tmp_path / "out.
|
|
335
|
+
uri = tmp_path / "out.hdf5"
|
|
320
336
|
storage["uri"] = str(uri.resolve())
|
|
321
337
|
storage["single_output"] = False # type: ignore
|
|
322
338
|
# Run operations
|
|
@@ -328,13 +344,16 @@ def test_run_and_collect(
|
|
|
328
344
|
)
|
|
329
345
|
# Get datagrabber
|
|
330
346
|
dg = PipelineComponentRegistry().build_component_instance(
|
|
331
|
-
step="datagrabber",
|
|
347
|
+
step="datagrabber",
|
|
348
|
+
name=datagrabber["kind"],
|
|
349
|
+
baseclass=BaseDataGrabber,
|
|
350
|
+
init_params={k: v for k, v in datagrabber.items() if k != "kind"},
|
|
332
351
|
)
|
|
333
352
|
elements = dg.get_elements() # type: ignore
|
|
334
|
-
# This should create
|
|
335
|
-
files = list(tmp_path.glob("*.
|
|
353
|
+
# This should create one file per element
|
|
354
|
+
files = list(tmp_path.glob("*.hdf5"))
|
|
336
355
|
assert len(files) == len(elements)
|
|
337
|
-
# But the test.
|
|
356
|
+
# But the test.hdf5 file should not exist
|
|
338
357
|
assert not uri.exists()
|
|
339
358
|
# Collect in storage
|
|
340
359
|
collect(storage)
|
|
@@ -516,7 +535,7 @@ def test_queue_with_imports(
|
|
|
516
535
|
tmp_path: Path,
|
|
517
536
|
monkeypatch: pytest.MonkeyPatch,
|
|
518
537
|
caplog: pytest.LogCaptureFixture,
|
|
519
|
-
with_:
|
|
538
|
+
with_: str | list[str],
|
|
520
539
|
) -> None:
|
|
521
540
|
"""Test queue with `with` imports.
|
|
522
541
|
|
|
@@ -628,6 +647,143 @@ def test_queue_without_elements(
|
|
|
628
647
|
assert "Queue done" in caplog.text
|
|
629
648
|
|
|
630
649
|
|
|
650
|
+
def _workdir(tmp_path: Path, as_dict: bool) -> tuple[str | dict, Path]:
|
|
651
|
+
"""Get a working directory in the test directory.
|
|
652
|
+
|
|
653
|
+
Parameters
|
|
654
|
+
----------
|
|
655
|
+
tmp_path : pathlib.Path
|
|
656
|
+
The path to the test directory.
|
|
657
|
+
as_dict : bool
|
|
658
|
+
Whether to give the working directory as a dictionary or a path.
|
|
659
|
+
|
|
660
|
+
Returns
|
|
661
|
+
-------
|
|
662
|
+
str or dict
|
|
663
|
+
The working directory, as given to the functions.
|
|
664
|
+
pathlib.Path
|
|
665
|
+
The path to the working directory.
|
|
666
|
+
|
|
667
|
+
"""
|
|
668
|
+
path = tmp_path / "workdir"
|
|
669
|
+
workdir = {"path": str(path), "cleanup": True} if as_dict else str(path)
|
|
670
|
+
return workdir, path
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
@pytest.mark.parametrize("as_dict", [False, True])
|
|
674
|
+
@pytest.mark.parametrize("elements", [None, ["sub-01"]])
|
|
675
|
+
def test_queue_workdir(
|
|
676
|
+
tmp_path: Path,
|
|
677
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
678
|
+
datagrabber: dict[str, str],
|
|
679
|
+
elements: list[str] | None,
|
|
680
|
+
as_dict: bool,
|
|
681
|
+
) -> None:
|
|
682
|
+
"""Test queue sets up the working directory.
|
|
683
|
+
|
|
684
|
+
Parameters
|
|
685
|
+
----------
|
|
686
|
+
tmp_path : pathlib.Path
|
|
687
|
+
The path to the test directory.
|
|
688
|
+
monkeypatch : pytest.MonkeyPatch
|
|
689
|
+
The pytest.MonkeyPatch object.
|
|
690
|
+
datagrabber : dict
|
|
691
|
+
Testing datagrabber as dictionary.
|
|
692
|
+
elements : list of str or None
|
|
693
|
+
The parametrized elements to queue. If None, the elements are
|
|
694
|
+
listed using the datagrabber.
|
|
695
|
+
as_dict : bool
|
|
696
|
+
Whether to give the working directory as a dictionary or a path.
|
|
697
|
+
|
|
698
|
+
"""
|
|
699
|
+
workdir, path = _workdir(tmp_path, as_dict)
|
|
700
|
+
with monkeypatch.context() as m:
|
|
701
|
+
m.chdir(tmp_path)
|
|
702
|
+
queue(
|
|
703
|
+
config={"workdir": workdir, "datagrabber": datagrabber},
|
|
704
|
+
kind="HTCondor",
|
|
705
|
+
elements=elements,
|
|
706
|
+
)
|
|
707
|
+
assert WorkDirManager().workdir == path
|
|
708
|
+
assert path.is_dir()
|
|
709
|
+
# The working directory is written to the job configuration as given
|
|
710
|
+
config = YAML().load(tmp_path / "junifer_jobs/junifer_job/config.yaml")
|
|
711
|
+
assert config["workdir"] == workdir
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def test_queue_workdir_cleanup(
|
|
715
|
+
tmp_path: Path,
|
|
716
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
717
|
+
datagrabber: dict[str, str],
|
|
718
|
+
) -> None:
|
|
719
|
+
"""Test queued jobs clean up the working directory.
|
|
720
|
+
|
|
721
|
+
Disabling the cleanup only applies to queueing (e.g. to debug it), as
|
|
722
|
+
the jobs would otherwise leave the files of each element.
|
|
723
|
+
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
tmp_path : pathlib.Path
|
|
727
|
+
The path to the test directory.
|
|
728
|
+
monkeypatch : pytest.MonkeyPatch
|
|
729
|
+
The pytest.MonkeyPatch object.
|
|
730
|
+
datagrabber : dict
|
|
731
|
+
Testing datagrabber as dictionary.
|
|
732
|
+
|
|
733
|
+
"""
|
|
734
|
+
workdir = {"path": str(tmp_path / "workdir"), "cleanup": False}
|
|
735
|
+
with monkeypatch.context() as m:
|
|
736
|
+
m.chdir(tmp_path)
|
|
737
|
+
with pytest.warns(RuntimeWarning, match="will be set to True"):
|
|
738
|
+
queue(
|
|
739
|
+
config={"workdir": workdir, "datagrabber": datagrabber},
|
|
740
|
+
kind="HTCondor",
|
|
741
|
+
)
|
|
742
|
+
# Queueing does not clean up
|
|
743
|
+
assert WorkDirManager()._cleanup_dirs is False
|
|
744
|
+
# The jobs clean up
|
|
745
|
+
config = YAML().load(tmp_path / "junifer_jobs/junifer_job/config.yaml")
|
|
746
|
+
assert config["workdir"]["cleanup"] is True
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
@pytest.mark.parametrize("as_dict", [False, True])
|
|
750
|
+
def test_collect_workdir(tmp_path: Path, as_dict: bool) -> None:
|
|
751
|
+
"""Test collect sets up the working directory.
|
|
752
|
+
|
|
753
|
+
Parameters
|
|
754
|
+
----------
|
|
755
|
+
tmp_path : pathlib.Path
|
|
756
|
+
The path to the test directory.
|
|
757
|
+
as_dict : bool
|
|
758
|
+
Whether to give the working directory as a dictionary or a path.
|
|
759
|
+
|
|
760
|
+
"""
|
|
761
|
+
workdir, path = _workdir(tmp_path, as_dict)
|
|
762
|
+
storage = {"kind": "HDF5FeatureStorage", "uri": str(tmp_path / "out.hdf5")}
|
|
763
|
+
collect(storage, workdir=workdir)
|
|
764
|
+
assert WorkDirManager().workdir == path
|
|
765
|
+
assert path.is_dir()
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
@pytest.mark.parametrize("as_dict", [False, True])
|
|
769
|
+
def test_reset_workdir(tmp_path: Path, as_dict: bool) -> None:
|
|
770
|
+
"""Test reset sets up the working directory.
|
|
771
|
+
|
|
772
|
+
Parameters
|
|
773
|
+
----------
|
|
774
|
+
tmp_path : pathlib.Path
|
|
775
|
+
The path to the test directory.
|
|
776
|
+
as_dict : bool
|
|
777
|
+
Whether to give the working directory as a dictionary or a path.
|
|
778
|
+
|
|
779
|
+
"""
|
|
780
|
+
workdir, path = _workdir(tmp_path, as_dict)
|
|
781
|
+
storage = {"uri": str(tmp_path / "out.hdf5")}
|
|
782
|
+
reset(config={"workdir": workdir, "storage": storage})
|
|
783
|
+
assert WorkDirManager().workdir == path
|
|
784
|
+
assert path.is_dir()
|
|
785
|
+
|
|
786
|
+
|
|
631
787
|
def test_reset_run(
|
|
632
788
|
tmp_path: Path,
|
|
633
789
|
datagrabber: dict[str, str],
|
|
@@ -649,7 +805,7 @@ def test_reset_run(
|
|
|
649
805
|
|
|
650
806
|
"""
|
|
651
807
|
# Create storage
|
|
652
|
-
storage["uri"] = tmp_path / "test_reset_run.
|
|
808
|
+
storage["uri"] = tmp_path / "test_reset_run.hdf5" # type: ignore
|
|
653
809
|
# Run operation to generate files
|
|
654
810
|
run(
|
|
655
811
|
workdir=tmp_path,
|
|
@@ -700,7 +856,7 @@ def test_reset_queue(
|
|
|
700
856
|
with monkeypatch.context() as m:
|
|
701
857
|
m.chdir(tmp_path)
|
|
702
858
|
# Create storage
|
|
703
|
-
storage["uri"] = "test_reset_queue.
|
|
859
|
+
storage["uri"] = "test_reset_queue.hdf5"
|
|
704
860
|
# Set job name
|
|
705
861
|
if job_name is None:
|
|
706
862
|
job_name = "junifer_job"
|
|
@@ -742,7 +898,7 @@ def test_reset_queue(
|
|
|
742
898
|
)
|
|
743
899
|
def test_list_elements(
|
|
744
900
|
datagrabber: dict[str, str],
|
|
745
|
-
elements:
|
|
901
|
+
elements: list[tuple[str, ...]] | None,
|
|
746
902
|
) -> None:
|
|
747
903
|
"""Test elements listing.
|
|
748
904
|
|
|
@@ -756,3 +912,674 @@ def test_list_elements(
|
|
|
756
912
|
"""
|
|
757
913
|
listed_elements = list_elements(datagrabber, elements)
|
|
758
914
|
assert "sub-01" in listed_elements
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
@pytest.mark.parametrize("as_dict", [False, True, None])
|
|
918
|
+
def test_list_elements_workdir(
|
|
919
|
+
tmp_path: Path,
|
|
920
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
921
|
+
datagrabber: dict[str, str],
|
|
922
|
+
as_dict: bool | None,
|
|
923
|
+
) -> None:
|
|
924
|
+
"""Test elements listing sets up the working directory.
|
|
925
|
+
|
|
926
|
+
Parameters
|
|
927
|
+
----------
|
|
928
|
+
tmp_path : pathlib.Path
|
|
929
|
+
The path to the test directory.
|
|
930
|
+
monkeypatch : pytest.MonkeyPatch
|
|
931
|
+
The pytest.MonkeyPatch object.
|
|
932
|
+
datagrabber : dict
|
|
933
|
+
Testing datagrabber as dictionary.
|
|
934
|
+
as_dict : bool or None
|
|
935
|
+
Whether to give the working directory as a dictionary or a path.
|
|
936
|
+
If None, no working directory is given and the default is used.
|
|
937
|
+
|
|
938
|
+
"""
|
|
939
|
+
if as_dict is None:
|
|
940
|
+
# The default working directory is in the temporary directory
|
|
941
|
+
monkeypatch.setattr(tempfile, "tempdir", str(tmp_path))
|
|
942
|
+
workdir, path = None, tmp_path / "junifer"
|
|
943
|
+
else:
|
|
944
|
+
workdir, path = _workdir(tmp_path, as_dict)
|
|
945
|
+
listed_elements = list_elements(datagrabber, workdir=workdir)
|
|
946
|
+
assert "sub-01" in listed_elements
|
|
947
|
+
assert WorkDirManager().workdir == path
|
|
948
|
+
assert path.is_dir()
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
def test_parse_yaml_failure() -> None:
|
|
952
|
+
"""Test YAML parsing failure."""
|
|
953
|
+
with pytest.raises(ValueError, match="does not exist"):
|
|
954
|
+
parse_yaml("foo.yaml")
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def test_parse_yaml_empty_elements_failure(tmp_path: Path) -> None:
|
|
958
|
+
"""Test YAML parsing with empty elements failure.
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
tmp_path : pathlib.Path
|
|
963
|
+
The path to the test directory.
|
|
964
|
+
|
|
965
|
+
"""
|
|
966
|
+
# Write test file
|
|
967
|
+
fname = tmp_path / "test_parse_yaml_empty_elements_failure.yaml"
|
|
968
|
+
fname.write_text("elements:")
|
|
969
|
+
# Check test file
|
|
970
|
+
with pytest.raises(ValueError, match="elements key was defined"):
|
|
971
|
+
parse_yaml(fname)
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def test_parse_yaml_success(tmp_path: Path) -> None:
|
|
975
|
+
"""Test YAML parsing success.
|
|
976
|
+
|
|
977
|
+
Parameters
|
|
978
|
+
----------
|
|
979
|
+
tmp_path : pathlib.Path
|
|
980
|
+
The path to the test directory.
|
|
981
|
+
|
|
982
|
+
"""
|
|
983
|
+
# Write test file
|
|
984
|
+
fname = tmp_path / "test_parse_yaml_success.yaml"
|
|
985
|
+
fname.write_text("foo: bar")
|
|
986
|
+
# Check test file
|
|
987
|
+
contents = parse_yaml(fname)
|
|
988
|
+
assert "foo" in contents
|
|
989
|
+
assert contents["foo"] == "bar"
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def test_parse_yaml_success_with_module_autoload(tmp_path: Path) -> None:
|
|
993
|
+
"""Test YAML parsing with single module autoload success.
|
|
994
|
+
|
|
995
|
+
Parameters
|
|
996
|
+
----------
|
|
997
|
+
tmp_path : pathlib.Path
|
|
998
|
+
The path to the test directory.
|
|
999
|
+
|
|
1000
|
+
"""
|
|
1001
|
+
# Write test file
|
|
1002
|
+
fname = tmp_path / "test_parse_yaml_with_single_module_autoload.yaml"
|
|
1003
|
+
fname.write_text("foo: bar\nwith: numpy")
|
|
1004
|
+
# Check test file
|
|
1005
|
+
contents = parse_yaml(fname)
|
|
1006
|
+
assert "foo" in contents
|
|
1007
|
+
assert contents["foo"] == "bar"
|
|
1008
|
+
assert "with" in contents
|
|
1009
|
+
assert contents["with"] == ["numpy"]
|
|
1010
|
+
assert "numpy" in sys.modules
|
|
1011
|
+
assert "junifer.configs.wrong_config" not in sys.modules
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
def test_parse_yaml_failure_with_multi_module_autoload(tmp_path: Path) -> None:
|
|
1015
|
+
"""Test YAML parsing with multi module autoload failure.
|
|
1016
|
+
|
|
1017
|
+
Parameters
|
|
1018
|
+
----------
|
|
1019
|
+
tmp_path : pathlib.Path
|
|
1020
|
+
The path to the test directory.
|
|
1021
|
+
|
|
1022
|
+
"""
|
|
1023
|
+
# Write test file
|
|
1024
|
+
fname = tmp_path / "test_parse_yaml_with_multi_module_autoload.yaml"
|
|
1025
|
+
fname.write_text(
|
|
1026
|
+
"foo: bar\nwith:\n - numpy\n - junifer.testing.wrong_config"
|
|
1027
|
+
)
|
|
1028
|
+
# Check test file
|
|
1029
|
+
with pytest.raises(ImportError, match="wrong_config"):
|
|
1030
|
+
parse_yaml(fname)
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def test_parse_yaml_with_wrong_path(tmp_path: Path) -> None:
|
|
1034
|
+
"""Test YAML parsing with wrong paths in with.
|
|
1035
|
+
|
|
1036
|
+
Parameters
|
|
1037
|
+
----------
|
|
1038
|
+
tmp_path : pathlib.Path
|
|
1039
|
+
The path to the test directory.
|
|
1040
|
+
|
|
1041
|
+
"""
|
|
1042
|
+
t_tmp_path = tmp_path / "test_relative_with"
|
|
1043
|
+
# Write yaml that includes a relative path
|
|
1044
|
+
yaml_path = t_tmp_path / "yamls"
|
|
1045
|
+
yaml_path.mkdir(exist_ok=True, parents=True)
|
|
1046
|
+
yaml_fname = yaml_path / "test_parse_yaml_wrong_path.yaml"
|
|
1047
|
+
|
|
1048
|
+
yaml_fname.write_text("foo: bar\nwith:\n - missingt.py\n - scipy\n")
|
|
1049
|
+
|
|
1050
|
+
# Check test file
|
|
1051
|
+
with pytest.raises(ValueError, match="does not exist"):
|
|
1052
|
+
parse_yaml(yaml_fname)
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def test_parse_yaml_relative_path(tmp_path: Path) -> None:
|
|
1056
|
+
"""Test YAML parsing with relative paths in with.
|
|
1057
|
+
|
|
1058
|
+
Parameters
|
|
1059
|
+
----------
|
|
1060
|
+
tmp_path : pathlib.Path
|
|
1061
|
+
The path to the test directory.
|
|
1062
|
+
|
|
1063
|
+
"""
|
|
1064
|
+
t_tmp_path = tmp_path / "test_relative_with"
|
|
1065
|
+
|
|
1066
|
+
# Write .py to include
|
|
1067
|
+
py_path = t_tmp_path / "external"
|
|
1068
|
+
py_path.mkdir(exist_ok=True, parents=True)
|
|
1069
|
+
py_fname = py_path / "first.py"
|
|
1070
|
+
py_fname.write_text("import numpy as np\n")
|
|
1071
|
+
|
|
1072
|
+
# Write yaml that includes a relative path
|
|
1073
|
+
yaml_path = t_tmp_path / "yamls"
|
|
1074
|
+
yaml_path.mkdir(exist_ok=True, parents=True)
|
|
1075
|
+
yaml_fname = yaml_path / "test_parse_yaml_relative_path.yaml"
|
|
1076
|
+
|
|
1077
|
+
yaml_fname.write_text(
|
|
1078
|
+
"foo: bar\nwith:\n - ../external/first.py\n - scipy\n"
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
# Check test file
|
|
1082
|
+
parse_yaml(yaml_fname)
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
def test_parse_yaml_absolute_path(tmp_path: Path) -> None:
|
|
1086
|
+
"""Test YAML parsing with absolute paths in with.
|
|
1087
|
+
|
|
1088
|
+
Parameters
|
|
1089
|
+
----------
|
|
1090
|
+
tmp_path : pathlib.Path
|
|
1091
|
+
The path to the test directory.
|
|
1092
|
+
|
|
1093
|
+
"""
|
|
1094
|
+
t_tmp_path = tmp_path / "test_relative_with"
|
|
1095
|
+
|
|
1096
|
+
# Write .py to include
|
|
1097
|
+
py_path = t_tmp_path / "external"
|
|
1098
|
+
py_path.mkdir(exist_ok=True, parents=True)
|
|
1099
|
+
py_fname = py_path / "first.py"
|
|
1100
|
+
py_fname.write_text("import numpy as np\n")
|
|
1101
|
+
|
|
1102
|
+
# Write yaml that includes a relative path
|
|
1103
|
+
yaml_path = t_tmp_path / "yamls"
|
|
1104
|
+
yaml_path.mkdir(exist_ok=True, parents=True)
|
|
1105
|
+
yaml_fname = yaml_path / "test_parse_yaml_relative_path.yaml"
|
|
1106
|
+
|
|
1107
|
+
yaml_fname.write_text(
|
|
1108
|
+
f"foo: bar\nwith:\n - {py_fname.absolute()}\n - scipy\n"
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
# Check test file
|
|
1112
|
+
parse_yaml(yaml_fname)
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
def test_parse_yaml_multi_module_deps(tmp_path: Path) -> None:
|
|
1116
|
+
"""Test YAML parsing with multi-module import with deps.
|
|
1117
|
+
|
|
1118
|
+
Parameters
|
|
1119
|
+
----------
|
|
1120
|
+
tmp_path : pathlib.Path
|
|
1121
|
+
The path to the test directory.
|
|
1122
|
+
|
|
1123
|
+
"""
|
|
1124
|
+
t_tmp_path = tmp_path / "test_with_multi_module"
|
|
1125
|
+
|
|
1126
|
+
# Write .py to include
|
|
1127
|
+
py_path = t_tmp_path / "external"
|
|
1128
|
+
py_path.mkdir(exist_ok=True, parents=True)
|
|
1129
|
+
py_fname_1 = py_path / "first.py"
|
|
1130
|
+
py_fname_1.write_text(
|
|
1131
|
+
"import numpy as np\nfrom second import hej\n"
|
|
1132
|
+
"def junifer_module_deps(): return ['second.py']\n"
|
|
1133
|
+
)
|
|
1134
|
+
py_fname_2 = py_path / "second.py"
|
|
1135
|
+
py_fname_2.write_text("def hej(): print('hej')\n")
|
|
1136
|
+
|
|
1137
|
+
# Write yaml
|
|
1138
|
+
yaml_path = t_tmp_path / "yamls"
|
|
1139
|
+
yaml_path.mkdir(exist_ok=True, parents=True)
|
|
1140
|
+
yaml_fname = yaml_path / "test_parse_yaml_multi_module.yaml"
|
|
1141
|
+
|
|
1142
|
+
yaml_fname.write_text(
|
|
1143
|
+
"foo: bar\nwith:\n - ../external/first.py\n - scipy\n"
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
# Check test file
|
|
1147
|
+
parse_yaml(yaml_fname)
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
def test_parse_storage_uri_relative(tmp_path: Path) -> None:
|
|
1151
|
+
"""Test YAML parsing with storage and relative URI.
|
|
1152
|
+
|
|
1153
|
+
Parameters
|
|
1154
|
+
----------
|
|
1155
|
+
tmp_path : pathlib.Path
|
|
1156
|
+
The path to the test directory.
|
|
1157
|
+
|
|
1158
|
+
"""
|
|
1159
|
+
fname = tmp_path / "test_parse_yaml_with_storage_uri.yaml"
|
|
1160
|
+
fname.write_text("foo: bar\nwith: numpy\nstorage:\n uri: test.db\n")
|
|
1161
|
+
|
|
1162
|
+
contents = parse_yaml(fname)
|
|
1163
|
+
assert "foo" in contents
|
|
1164
|
+
assert contents["foo"] == "bar"
|
|
1165
|
+
assert "storage" in contents
|
|
1166
|
+
assert "uri" in contents["storage"]
|
|
1167
|
+
assert contents["storage"]["uri"] == str(tmp_path / "test.db")
|
|
1168
|
+
|
|
1169
|
+
fname = tmp_path / "test_parse_yaml_with_storage_uri.yaml"
|
|
1170
|
+
fname.write_text(
|
|
1171
|
+
"foo: bar\nwith: numpy\nstorage:\n uri: ../another/test.db\n"
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
contents = parse_yaml(fname)
|
|
1175
|
+
assert "foo" in contents
|
|
1176
|
+
assert contents["foo"] == "bar"
|
|
1177
|
+
assert "storage" in contents
|
|
1178
|
+
assert "uri" in contents["storage"]
|
|
1179
|
+
assert contents["storage"]["uri"] == str(
|
|
1180
|
+
(tmp_path / "../another/test.db").resolve()
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
fname = tmp_path / "test_parse_yaml_with_storage_uri.yaml"
|
|
1184
|
+
fname.write_text(
|
|
1185
|
+
"foo: bar\nwith: numpy\nstorage:\n uri: /absolute/test.db\n"
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
contents = parse_yaml(fname)
|
|
1189
|
+
assert "foo" in contents
|
|
1190
|
+
assert contents["foo"] == "bar"
|
|
1191
|
+
assert "storage" in contents
|
|
1192
|
+
assert "uri" in contents["storage"]
|
|
1193
|
+
assert contents["storage"]["uri"] == "/absolute/test.db"
|
|
1194
|
+
|
|
1195
|
+
# Just to trick coverage
|
|
1196
|
+
fname = tmp_path / "test_parse_yaml_with_storage_uri.yaml"
|
|
1197
|
+
fname.write_text("foo: bar\nwith: numpy\nstorage:\n kind: SomeStorage\n")
|
|
1198
|
+
|
|
1199
|
+
contents = parse_yaml(fname)
|
|
1200
|
+
assert "foo" in contents
|
|
1201
|
+
assert contents["foo"] == "bar"
|
|
1202
|
+
assert "storage" in contents
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
@pytest.mark.parametrize(
|
|
1206
|
+
"datadir, expected",
|
|
1207
|
+
[
|
|
1208
|
+
# Relative to the YAML file
|
|
1209
|
+
("data", "data"),
|
|
1210
|
+
("../other/data", "../other/data"),
|
|
1211
|
+
# Absolute
|
|
1212
|
+
("/absolute/data", "/absolute/data"),
|
|
1213
|
+
],
|
|
1214
|
+
)
|
|
1215
|
+
def test_parse_yaml_datadir(
|
|
1216
|
+
tmp_path: Path, datadir: str, expected: str
|
|
1217
|
+
) -> None:
|
|
1218
|
+
"""Test YAML parsing with the data directory of the datagrabber.
|
|
1219
|
+
|
|
1220
|
+
Parameters
|
|
1221
|
+
----------
|
|
1222
|
+
tmp_path : pathlib.Path
|
|
1223
|
+
The path to the test directory.
|
|
1224
|
+
datadir : str
|
|
1225
|
+
The parametrized data directory.
|
|
1226
|
+
expected : str
|
|
1227
|
+
The parametrized data directory, relative to the YAML file.
|
|
1228
|
+
|
|
1229
|
+
"""
|
|
1230
|
+
fname = tmp_path / "config.yaml"
|
|
1231
|
+
yaml.dump(
|
|
1232
|
+
{"datagrabber": {"kind": "DG", "datadir": datadir}}, stream=fname
|
|
1233
|
+
)
|
|
1234
|
+
contents = parse_yaml(fname)
|
|
1235
|
+
assert contents["datagrabber"]["datadir"] == str(
|
|
1236
|
+
(tmp_path / expected).resolve()
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
|
|
1240
|
+
def test_parse_yaml_datadir_multiple(tmp_path: Path) -> None:
|
|
1241
|
+
"""Test YAML parsing with the data directories of multiple datagrabbers.
|
|
1242
|
+
|
|
1243
|
+
Parameters
|
|
1244
|
+
----------
|
|
1245
|
+
tmp_path : pathlib.Path
|
|
1246
|
+
The path to the test directory.
|
|
1247
|
+
|
|
1248
|
+
"""
|
|
1249
|
+
fname = tmp_path / "config.yaml"
|
|
1250
|
+
datagrabber = {
|
|
1251
|
+
"kind": "MultipleDataGrabber",
|
|
1252
|
+
"datagrabbers": [
|
|
1253
|
+
{"kind": "DG", "datadir": "data"},
|
|
1254
|
+
{"kind": "DG", "datadir": "/absolute/data"},
|
|
1255
|
+
{"kind": "DG"},
|
|
1256
|
+
],
|
|
1257
|
+
}
|
|
1258
|
+
yaml.dump({"datagrabber": datagrabber}, stream=fname)
|
|
1259
|
+
contents = parse_yaml(fname)
|
|
1260
|
+
datagrabbers = contents["datagrabber"]["datagrabbers"]
|
|
1261
|
+
assert datagrabbers[0]["datadir"] == str(tmp_path / "data")
|
|
1262
|
+
assert datagrabbers[1]["datadir"] == "/absolute/data"
|
|
1263
|
+
assert "datadir" not in datagrabbers[2]
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
def test_parse_yaml_queue_venv_relative(tmp_path: Path) -> None:
|
|
1267
|
+
"""Test YAML parsing with relative venv queue.
|
|
1268
|
+
|
|
1269
|
+
Parameters
|
|
1270
|
+
----------
|
|
1271
|
+
tmp_path : pathlib.Path
|
|
1272
|
+
The path to the test directory.
|
|
1273
|
+
|
|
1274
|
+
"""
|
|
1275
|
+
fname = tmp_path / "test_parse_yaml_queue_venv_relative.yaml"
|
|
1276
|
+
fname.write_text("queue:\n env:\n kind: venv\n name: .venv\n")
|
|
1277
|
+
_ = parse_yaml(fname)
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
@pytest.mark.parametrize(
|
|
1281
|
+
"elements, expected, searches",
|
|
1282
|
+
[
|
|
1283
|
+
# Complete elements, without searching the dataset
|
|
1284
|
+
([("sub-01", "rest")], [("sub-01", "rest")], 0),
|
|
1285
|
+
(
|
|
1286
|
+
[("sub-01", "rest"), ("sub-02", "movie")],
|
|
1287
|
+
[("sub-01", "rest"), ("sub-02", "movie")],
|
|
1288
|
+
0,
|
|
1289
|
+
),
|
|
1290
|
+
# Partial elements: all the tasks of a subject
|
|
1291
|
+
(["sub-01"], [("sub-01", "movie"), ("sub-01", "rest")], 1),
|
|
1292
|
+
([("sub-01",)], [("sub-01", "movie"), ("sub-01", "rest")], 1),
|
|
1293
|
+
# Without duplicates
|
|
1294
|
+
(
|
|
1295
|
+
[("sub-01", "rest"), ("sub-01", "rest")],
|
|
1296
|
+
[("sub-01", "rest")],
|
|
1297
|
+
0,
|
|
1298
|
+
),
|
|
1299
|
+
# ("sub-01", "rest") matches both selectors
|
|
1300
|
+
(
|
|
1301
|
+
["sub-01", "rest"],
|
|
1302
|
+
[("sub-01", "movie"), ("sub-01", "rest"), ("sub-02", "rest")],
|
|
1303
|
+
1,
|
|
1304
|
+
),
|
|
1305
|
+
],
|
|
1306
|
+
)
|
|
1307
|
+
def test_run_element_selectors(
|
|
1308
|
+
tmp_path: Path,
|
|
1309
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1310
|
+
markers: list[dict[str, str]],
|
|
1311
|
+
elements: Elements,
|
|
1312
|
+
expected: list[tuple[str, str]],
|
|
1313
|
+
searches: int,
|
|
1314
|
+
) -> None:
|
|
1315
|
+
"""Test run function with complete and partial element selectors.
|
|
1316
|
+
|
|
1317
|
+
Parameters
|
|
1318
|
+
----------
|
|
1319
|
+
tmp_path : pathlib.Path
|
|
1320
|
+
The path to the test directory.
|
|
1321
|
+
monkeypatch : pytest.MonkeyPatch
|
|
1322
|
+
The pytest.MonkeyPatch object.
|
|
1323
|
+
markers : list of dict
|
|
1324
|
+
Testing markers as list of dictionary.
|
|
1325
|
+
elements : list of str or tuple
|
|
1326
|
+
The parametrized element selectors.
|
|
1327
|
+
expected : list of tuple of str
|
|
1328
|
+
The parametrized elements to compute.
|
|
1329
|
+
searches : int
|
|
1330
|
+
The parametrized number of searches of the elements of the dataset.
|
|
1331
|
+
|
|
1332
|
+
"""
|
|
1333
|
+
computed = []
|
|
1334
|
+
n_searches = _run_selectors(
|
|
1335
|
+
tmp_path, monkeypatch, markers, elements, computed
|
|
1336
|
+
)
|
|
1337
|
+
assert sorted(computed) == expected
|
|
1338
|
+
assert n_searches == searches
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
def test_run_element_selectors_invalid(
|
|
1342
|
+
tmp_path: Path,
|
|
1343
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1344
|
+
markers: list[dict[str, str]],
|
|
1345
|
+
) -> None:
|
|
1346
|
+
"""Test run function with partial selectors matching no element.
|
|
1347
|
+
|
|
1348
|
+
Parameters
|
|
1349
|
+
----------
|
|
1350
|
+
tmp_path : pathlib.Path
|
|
1351
|
+
The path to the test directory.
|
|
1352
|
+
monkeypatch : pytest.MonkeyPatch
|
|
1353
|
+
The pytest.MonkeyPatch object.
|
|
1354
|
+
markers : list of dict
|
|
1355
|
+
Testing markers as list of dictionary.
|
|
1356
|
+
|
|
1357
|
+
"""
|
|
1358
|
+
computed = []
|
|
1359
|
+
with pytest.raises(RuntimeError, match=r"invalid:\n\['sub-03'\]"):
|
|
1360
|
+
_run_selectors(
|
|
1361
|
+
tmp_path, monkeypatch, markers, ["sub-01", "sub-03"], computed
|
|
1362
|
+
)
|
|
1363
|
+
# Nothing is computed, not even the elements of the valid selectors
|
|
1364
|
+
assert computed == []
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
def test_run_element_selectors_sizes(
|
|
1368
|
+
tmp_path: Path,
|
|
1369
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1370
|
+
markers: list[dict[str, str]],
|
|
1371
|
+
) -> None:
|
|
1372
|
+
"""Test run function with selectors of different numbers of values.
|
|
1373
|
+
|
|
1374
|
+
Parameters
|
|
1375
|
+
----------
|
|
1376
|
+
tmp_path : pathlib.Path
|
|
1377
|
+
The path to the test directory.
|
|
1378
|
+
monkeypatch : pytest.MonkeyPatch
|
|
1379
|
+
The pytest.MonkeyPatch object.
|
|
1380
|
+
markers : list of dict
|
|
1381
|
+
Testing markers as list of dictionary.
|
|
1382
|
+
|
|
1383
|
+
"""
|
|
1384
|
+
computed = []
|
|
1385
|
+
with pytest.raises(ValueError, match="same number of values"):
|
|
1386
|
+
_run_selectors(
|
|
1387
|
+
tmp_path,
|
|
1388
|
+
monkeypatch,
|
|
1389
|
+
markers,
|
|
1390
|
+
["sub-01", ("sub-02", "rest")],
|
|
1391
|
+
computed,
|
|
1392
|
+
)
|
|
1393
|
+
assert computed == []
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
def _run_selectors(
|
|
1397
|
+
tmp_path: Path,
|
|
1398
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1399
|
+
markers: list[dict[str, str]],
|
|
1400
|
+
elements: Elements,
|
|
1401
|
+
computed: list[tuple[str, str]],
|
|
1402
|
+
) -> int:
|
|
1403
|
+
"""Run with element selectors, only recording the elements to compute.
|
|
1404
|
+
|
|
1405
|
+
The dataset has two subjects (``sub-01`` and ``sub-02``) with two tasks
|
|
1406
|
+
(``rest`` and ``movie``).
|
|
1407
|
+
|
|
1408
|
+
Parameters
|
|
1409
|
+
----------
|
|
1410
|
+
tmp_path : pathlib.Path
|
|
1411
|
+
The path to the test directory.
|
|
1412
|
+
monkeypatch : pytest.MonkeyPatch
|
|
1413
|
+
The pytest.MonkeyPatch object.
|
|
1414
|
+
markers : list of dict
|
|
1415
|
+
Testing markers as list of dictionary.
|
|
1416
|
+
elements : list of str or tuple
|
|
1417
|
+
The element selectors.
|
|
1418
|
+
computed : list of tuple of str
|
|
1419
|
+
The list to add the computed elements to.
|
|
1420
|
+
|
|
1421
|
+
Returns
|
|
1422
|
+
-------
|
|
1423
|
+
int
|
|
1424
|
+
The number of searches of the elements of the dataset.
|
|
1425
|
+
|
|
1426
|
+
"""
|
|
1427
|
+
datadir = tmp_path / "data"
|
|
1428
|
+
for subject in ("sub-01", "sub-02"):
|
|
1429
|
+
(datadir / subject).mkdir(parents=True)
|
|
1430
|
+
for task in ("rest", "movie"):
|
|
1431
|
+
(datadir / subject / f"{subject}_task-{task}_bold.nii").touch()
|
|
1432
|
+
n_searches = 0
|
|
1433
|
+
get_elements = PatternDataGrabber.get_elements
|
|
1434
|
+
|
|
1435
|
+
def fit(self: MarkerCollection, input: dict) -> None:
|
|
1436
|
+
element = input["BOLD"]["meta"]["element"]
|
|
1437
|
+
computed.append((element["subject"], element["task"]))
|
|
1438
|
+
|
|
1439
|
+
def counted_get_elements(self: PatternDataGrabber) -> Elements:
|
|
1440
|
+
nonlocal n_searches
|
|
1441
|
+
n_searches += 1
|
|
1442
|
+
return get_elements(self)
|
|
1443
|
+
|
|
1444
|
+
monkeypatch.setattr(MarkerCollection, "fit", fit)
|
|
1445
|
+
monkeypatch.setattr(
|
|
1446
|
+
PatternDataGrabber, "get_elements", counted_get_elements
|
|
1447
|
+
)
|
|
1448
|
+
run(
|
|
1449
|
+
workdir=tmp_path / "workdir",
|
|
1450
|
+
datagrabber={
|
|
1451
|
+
"kind": "PatternDataGrabber",
|
|
1452
|
+
"datadir": str(datadir),
|
|
1453
|
+
"types": ["BOLD"],
|
|
1454
|
+
"patterns": {
|
|
1455
|
+
"BOLD": {
|
|
1456
|
+
"pattern": "{subject}/{subject}_task-{task}_bold.nii",
|
|
1457
|
+
"space": "MNI152NLin6Asym",
|
|
1458
|
+
},
|
|
1459
|
+
},
|
|
1460
|
+
"replacements": ["subject", "task"],
|
|
1461
|
+
},
|
|
1462
|
+
markers=markers,
|
|
1463
|
+
storage={
|
|
1464
|
+
"kind": "HDF5FeatureStorage",
|
|
1465
|
+
"uri": str(tmp_path / "out.hdf5"),
|
|
1466
|
+
},
|
|
1467
|
+
elements=elements,
|
|
1468
|
+
)
|
|
1469
|
+
return n_searches
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
@register_marker
|
|
1473
|
+
class _ImageMean(BaseMarker):
|
|
1474
|
+
"""Marker storing the mean of an image, for BOLD and VBM_GM."""
|
|
1475
|
+
|
|
1476
|
+
_DEPENDENCIES: ClassVar[Dependencies] = {"numpy"}
|
|
1477
|
+
_MARKER_INOUT_MAPPINGS: ClassVar[MarkerInOutMappings] = {
|
|
1478
|
+
DataType.BOLD: {"mean": StorageType.Vector},
|
|
1479
|
+
DataType.VBM_GM: {"mean": StorageType.Vector},
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
def compute(self, input: dict, extra_input: dict | None = None) -> dict:
|
|
1483
|
+
data = np.asarray(input["data"].dataobj, dtype=float)
|
|
1484
|
+
return {
|
|
1485
|
+
"mean": {"data": np.array([[data.mean()]]), "col_names": ["mean"]}
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
@pytest.mark.parametrize("single_output", [True, False])
|
|
1490
|
+
@pytest.mark.parametrize(
|
|
1491
|
+
"storage_kind", ["HDF5FeatureStorage", "SQLiteFeatureStorage"]
|
|
1492
|
+
)
|
|
1493
|
+
def test_run_data_types_element_keys(
|
|
1494
|
+
tmp_path: Path, storage_kind: str, single_output: bool
|
|
1495
|
+
) -> None:
|
|
1496
|
+
"""Test run function storing data types with different element keys.
|
|
1497
|
+
|
|
1498
|
+
The BOLD data depends on the subject and the task, and the VBM data only
|
|
1499
|
+
on the subject, so it is stored once for each subject. The elements are
|
|
1500
|
+
run twice, as when trying a few elements and then running all of them.
|
|
1501
|
+
|
|
1502
|
+
Parameters
|
|
1503
|
+
----------
|
|
1504
|
+
tmp_path : pathlib.Path
|
|
1505
|
+
The path to the test directory.
|
|
1506
|
+
storage_kind : str
|
|
1507
|
+
The parametrized storage.
|
|
1508
|
+
single_output : bool
|
|
1509
|
+
The parametrized storage in a single file.
|
|
1510
|
+
|
|
1511
|
+
"""
|
|
1512
|
+
datadir = tmp_path / "data"
|
|
1513
|
+
# BOLD: 10 * subject + task (1: rest, 2: movie); VBM: subject
|
|
1514
|
+
for i, subject in enumerate(("sub-01", "sub-02"), start=1):
|
|
1515
|
+
(datadir / subject).mkdir(parents=True)
|
|
1516
|
+
nib.save(
|
|
1517
|
+
nib.Nifti1Image(np.full((2, 2, 2), float(i)), np.eye(4)),
|
|
1518
|
+
datadir / subject / f"{subject}_GM.nii",
|
|
1519
|
+
)
|
|
1520
|
+
for j, task in enumerate(("rest", "movie"), start=1):
|
|
1521
|
+
nib.save(
|
|
1522
|
+
nib.Nifti1Image(
|
|
1523
|
+
np.full((2, 2, 2, 3), 10.0 * i + j), np.eye(4)
|
|
1524
|
+
),
|
|
1525
|
+
datadir / subject / f"{subject}_task-{task}_bold.nii",
|
|
1526
|
+
)
|
|
1527
|
+
datagrabber = {
|
|
1528
|
+
"kind": "PatternDataGrabber",
|
|
1529
|
+
"datadir": str(datadir),
|
|
1530
|
+
"types": ["BOLD", "VBM_GM"],
|
|
1531
|
+
"patterns": {
|
|
1532
|
+
"BOLD": {
|
|
1533
|
+
"pattern": "{subject}/{subject}_task-{task}_bold.nii",
|
|
1534
|
+
"space": "native",
|
|
1535
|
+
},
|
|
1536
|
+
"VBM_GM": {
|
|
1537
|
+
"pattern": "{subject}/{subject}_GM.nii",
|
|
1538
|
+
"space": "native",
|
|
1539
|
+
},
|
|
1540
|
+
},
|
|
1541
|
+
"replacements": ["subject", "task"],
|
|
1542
|
+
}
|
|
1543
|
+
uri = tmp_path / "out" / "features.db"
|
|
1544
|
+
storage = {
|
|
1545
|
+
"kind": storage_kind,
|
|
1546
|
+
"uri": str(uri),
|
|
1547
|
+
"single_output": single_output,
|
|
1548
|
+
}
|
|
1549
|
+
# A few elements, and then all of them
|
|
1550
|
+
for elements in ([("sub-01", "rest")], None):
|
|
1551
|
+
run(
|
|
1552
|
+
workdir=tmp_path / "workdir",
|
|
1553
|
+
datagrabber=deepcopy(datagrabber),
|
|
1554
|
+
markers=[{"kind": "_ImageMean", "name": "mean"}],
|
|
1555
|
+
storage=deepcopy(storage),
|
|
1556
|
+
elements=elements,
|
|
1557
|
+
)
|
|
1558
|
+
if not single_output:
|
|
1559
|
+
collect(deepcopy(storage))
|
|
1560
|
+
|
|
1561
|
+
reader = PipelineComponentRegistry().build_component_instance(
|
|
1562
|
+
step="storage",
|
|
1563
|
+
name=storage_kind,
|
|
1564
|
+
baseclass=BaseFeatureStorage,
|
|
1565
|
+
init_params={"uri": uri},
|
|
1566
|
+
)
|
|
1567
|
+
features = {
|
|
1568
|
+
meta["name"]: md5 for md5, meta in reader.list_features().items()
|
|
1569
|
+
}
|
|
1570
|
+
bold = reader.read_df(feature_md5=features["BOLD_mean_mean"])
|
|
1571
|
+
vbm = reader.read_df(feature_md5=features["VBM_GM_mean_mean"])
|
|
1572
|
+
assert sorted(bold.index) == [
|
|
1573
|
+
("sub-01", "movie"),
|
|
1574
|
+
("sub-01", "rest"),
|
|
1575
|
+
("sub-02", "movie"),
|
|
1576
|
+
("sub-02", "rest"),
|
|
1577
|
+
]
|
|
1578
|
+
assert sorted(bold["mean"]) == [11, 12, 21, 22]
|
|
1579
|
+
# Once for each subject
|
|
1580
|
+
assert vbm.index.names == ["subject"]
|
|
1581
|
+
assert sorted(vbm.index.get_level_values("subject")) == [
|
|
1582
|
+
"sub-01",
|
|
1583
|
+
"sub-02",
|
|
1584
|
+
]
|
|
1585
|
+
assert sorted(vbm["mean"]) == [1, 2]
|