junifer 0.0.7.dev332__py3-none-any.whl → 0.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- junifer/_version.py +9 -19
- junifer/api/__init__.pyi +17 -2
- junifer/api/functions.py +222 -39
- junifer/api/queue_context/__init__.pyi +11 -3
- junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
- junifer/api/queue_context/htcondor_adapter.py +118 -181
- junifer/api/queue_context/queue_context_adapter.py +51 -7
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
- junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
- junifer/api/res/fsl/run_fsl_docker.sh +4 -0
- junifer/api/tests/test_functions.py +863 -36
- junifer/cli/__init__.py +1 -7
- junifer/cli/cli.py +41 -22
- junifer/cli/parser.py +23 -147
- junifer/cli/tests/data/gmd_mean.yaml +6 -6
- junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
- junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
- junifer/cli/tests/test_cli.py +120 -4
- junifer/cli/tests/test_cli_utils.py +23 -6
- junifer/cli/utils.py +14 -9
- junifer/configs/__init__.py +5 -0
- junifer/configs/__init__.pyi +0 -0
- junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
- junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
- junifer/configs/juseless/datagrabbers/ucla.py +111 -104
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
- junifer/configs/py.typed +0 -0
- junifer/conftest.py +63 -24
- junifer/data/_dispatch.py +9 -19
- junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
- junifer/data/coordinates/_coordinates.py +51 -6
- junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
- junifer/data/coordinates/tests/test_coordinates.py +15 -3
- junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
- junifer/data/maps/_ants_maps_warper.py +18 -12
- junifer/data/maps/_fsl_maps_warper.py +7 -3
- junifer/data/maps/_maps.py +12 -8
- junifer/data/maps/tests/test_maps.py +13 -21
- junifer/data/masks/_ants_mask_warper.py +18 -12
- junifer/data/masks/_fsl_mask_warper.py +7 -3
- junifer/data/masks/_masks.py +15 -12
- junifer/data/masks/tests/test_masks.py +20 -17
- junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
- junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
- junifer/data/parcellations/_parcellations.py +125 -91
- junifer/data/parcellations/tests/test_parcellations.py +122 -1
- junifer/data/template_spaces.py +117 -50
- junifer/data/tests/test_template_spaces.py +10 -7
- junifer/data/utils.py +10 -8
- junifer/datagrabber/__init__.pyi +38 -5
- junifer/datagrabber/aomic/__init__.pyi +8 -1
- junifer/datagrabber/aomic/_types.py +22 -0
- junifer/datagrabber/aomic/id1000.py +193 -183
- junifer/datagrabber/aomic/piop1.py +215 -259
- junifer/datagrabber/aomic/piop2.py +211 -244
- junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
- junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
- junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
- junifer/datagrabber/base.py +253 -79
- junifer/datagrabber/datalad_base.py +127 -76
- junifer/datagrabber/dmcc13_benchmark.py +204 -289
- junifer/datagrabber/hcp1200/__init__.pyi +7 -2
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
- junifer/datagrabber/hcp1200/hcp1200.py +151 -164
- junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
- junifer/datagrabber/multiple.py +183 -63
- junifer/datagrabber/pattern.py +358 -227
- junifer/datagrabber/pattern_datalad.py +84 -124
- junifer/datagrabber/pattern_validation_mixin.py +22 -50
- junifer/datagrabber/tests/test_base.py +17 -4
- junifer/datagrabber/tests/test_datalad_base.py +20 -27
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
- junifer/datagrabber/tests/test_multiple.py +293 -31
- junifer/datagrabber/tests/test_pattern.py +577 -18
- junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
- junifer/datareader/default.py +8 -5
- junifer/datareader/tests/test_default_reader.py +1 -1
- junifer/external/BrainPrint/.codecov.yml +2 -0
- junifer/external/BrainPrint/.codespellignore +2 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
- junifer/external/BrainPrint/.github/dependabot.yml +6 -0
- junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
- junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
- junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
- junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
- junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
- junifer/external/BrainPrint/.gitignore +123 -0
- junifer/external/BrainPrint/LICENSE +21 -0
- junifer/external/BrainPrint/README.md +108 -0
- junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
- junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
- junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
- junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
- junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
- junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
- junifer/external/BrainPrint/doc/Makefile +20 -0
- junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
- junifer/external/BrainPrint/doc/api/index.rst +11 -0
- junifer/external/BrainPrint/doc/conf.py +272 -0
- junifer/external/BrainPrint/doc/index.rst +140 -0
- junifer/external/BrainPrint/doc/links.inc +36 -0
- junifer/external/BrainPrint/doc/make.bat +35 -0
- junifer/external/BrainPrint/doc/references.bib +23 -0
- junifer/external/BrainPrint/pyproject.toml +170 -0
- junifer/external/BrainPrint/setup.py +3 -0
- junifer/external/__init__.py +5 -0
- junifer/external/__init__.pyi +0 -0
- junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
- junifer/external/py.typed +0 -0
- junifer/markers/__init__.pyi +6 -3
- junifer/markers/base.py +126 -76
- junifer/markers/brainprint.py +30 -39
- junifer/markers/complexity/__init__.py +20 -3
- junifer/markers/complexity/complexity_base.py +40 -41
- junifer/markers/complexity/hurst_exponent.py +22 -35
- junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
- junifer/markers/complexity/perm_entropy.py +22 -36
- junifer/markers/complexity/range_entropy.py +22 -35
- junifer/markers/complexity/range_entropy_auc.py +27 -36
- junifer/markers/complexity/sample_entropy.py +22 -36
- junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
- junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
- junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
- junifer/markers/complexity/tests/test_range_entropy.py +47 -36
- junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
- junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
- junifer/markers/complexity/weighted_perm_entropy.py +22 -36
- junifer/markers/ets_rss.py +35 -31
- junifer/markers/falff/__init__.pyi +7 -1
- junifer/markers/falff/_afni_falff.py +22 -9
- junifer/markers/falff/_junifer_falff.py +47 -31
- junifer/markers/falff/falff_base.py +80 -60
- junifer/markers/falff/falff_maps.py +17 -41
- junifer/markers/falff/falff_parcels.py +26 -51
- junifer/markers/falff/falff_spheres.py +34 -61
- junifer/markers/falff/tests/test_falff_maps.py +20 -23
- junifer/markers/falff/tests/test_falff_mask.py +99 -0
- junifer/markers/falff/tests/test_falff_parcels.py +14 -10
- junifer/markers/falff/tests/test_falff_spheres.py +14 -10
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
- junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
- junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
- junifer/markers/maps_aggregation.py +77 -55
- junifer/markers/parcel_aggregation.py +91 -70
- junifer/markers/reho/__init__.pyi +7 -1
- junifer/markers/reho/_afni_reho.py +23 -14
- junifer/markers/reho/_junifer_reho.py +112 -123
- junifer/markers/reho/reho_base.py +115 -37
- junifer/markers/reho/reho_maps.py +18 -32
- junifer/markers/reho/reho_parcels.py +26 -41
- junifer/markers/reho/reho_spheres.py +33 -54
- junifer/markers/reho/tests/test_junifer_reho.py +178 -0
- junifer/markers/reho/tests/test_reho_maps.py +27 -25
- junifer/markers/reho/tests/test_reho_parcels.py +28 -22
- junifer/markers/reho/tests/test_reho_spheres.py +16 -12
- junifer/markers/sphere_aggregation.py +88 -72
- junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
- junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
- junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
- junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
- junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
- junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
- junifer/markers/tests/test_brainprint.py +10 -9
- junifer/markers/tests/test_ets_rss.py +7 -3
- junifer/markers/tests/test_lost_regions.py +279 -0
- junifer/markers/tests/test_maps_aggregation.py +67 -114
- junifer/markers/tests/test_marker_utils.py +40 -1
- junifer/markers/tests/test_markers_base.py +275 -6
- junifer/markers/tests/test_parcel_aggregation.py +91 -53
- junifer/markers/tests/test_sphere_aggregation.py +53 -47
- junifer/markers/utils.py +267 -5
- junifer/onthefly/__init__.py +2 -3
- junifer/onthefly/__init__.pyi +4 -0
- junifer/onthefly/_brainprint.py +3 -5
- junifer/onthefly/py.typed +0 -0
- junifer/onthefly/read_transform.py +11 -9
- junifer/onthefly/tests/test_read_transform.py +7 -7
- junifer/pipeline/__init__.pyi +4 -2
- junifer/pipeline/marker_collection.py +14 -10
- junifer/pipeline/pipeline_component_registry.py +25 -6
- junifer/pipeline/pipeline_step_mixin.py +5 -12
- junifer/pipeline/tests/test_data_object_dumper.py +10 -10
- junifer/pipeline/tests/test_marker_collection.py +145 -168
- junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
- junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
- junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
- junifer/pipeline/tests/test_workdir_manager.py +39 -0
- junifer/pipeline/update_meta_mixin.py +13 -10
- junifer/pipeline/utils.py +22 -17
- junifer/pipeline/workdir_manager.py +41 -32
- junifer/preprocess/__init__.pyi +9 -4
- junifer/preprocess/_temporal_filter.py +34 -33
- junifer/preprocess/_temporal_slicer.py +17 -30
- junifer/preprocess/base.py +99 -55
- junifer/preprocess/confounds/__init__.pyi +10 -2
- junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
- junifer/preprocess/smoothing/__init__.pyi +2 -2
- junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
- junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
- junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
- junifer/preprocess/smoothing/smoothing.py +59 -40
- junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
- junifer/preprocess/tests/test_preprocess_base.py +17 -9
- junifer/preprocess/tests/test_temporal_filter.py +37 -16
- junifer/preprocess/tests/test_temporal_slicer.py +12 -12
- junifer/preprocess/warping/__init__.pyi +2 -2
- junifer/preprocess/warping/_ants_warper.py +56 -40
- junifer/preprocess/warping/_fsl_warper.py +49 -36
- junifer/preprocess/warping/_utils.py +106 -0
- junifer/preprocess/warping/space_warper.py +99 -98
- junifer/preprocess/warping/tests/test_space_warper.py +46 -41
- junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
- junifer/stats.py +46 -34
- junifer/storage/__init__.pyi +8 -4
- junifer/storage/base.py +129 -64
- junifer/storage/hdf5.py +307 -199
- junifer/storage/pandas_base.py +46 -24
- junifer/storage/sqlite.py +99 -62
- junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
- junifer/storage/tests/test_hdf5.py +147 -12
- junifer/storage/tests/test_repeated_storage.py +293 -0
- junifer/storage/tests/test_sqlite.py +9 -20
- junifer/storage/tests/test_storage_base.py +18 -21
- junifer/storage/tests/test_utils.py +83 -22
- junifer/storage/utils.py +58 -38
- junifer/testing/__init__.pyi +2 -2
- junifer/testing/datagrabbers.py +480 -44
- junifer/testing/registry.py +12 -0
- junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
- junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
- junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
- junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
- junifer/testing/tests/test_testing_registry.py +31 -0
- junifer/testing/tests/test_testing_utils.py +35 -0
- junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
- junifer/testing/utils.py +33 -1
- junifer/tests/test_stats.py +1 -5
- junifer/typing/__init__.pyi +2 -0
- junifer/typing/_typing.py +34 -19
- junifer/typing/py.typed +0 -0
- junifer/utils/__init__.pyi +10 -1
- junifer/utils/_config.py +1 -2
- junifer/utils/helpers.py +68 -2
- junifer/utils/logging.py +31 -7
- junifer/utils/tests/test_helpers.py +36 -1
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
- junifer-0.0.8.dist-info/RECORD +402 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
- junifer/cli/tests/test_parser.py +0 -280
- junifer/external/h5io/h5io/__init__.py +0 -15
- junifer/external/h5io/h5io/_h5io.py +0 -767
- junifer/external/h5io/h5io/_version.py +0 -1
- junifer/external/h5io/h5io/chunked_array.py +0 -94
- junifer/external/h5io/h5io/chunked_list.py +0 -65
- junifer/markers/complexity/__init__.pyi +0 -17
- junifer-0.0.7.dev332.dist-info/RECORD +0 -353
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
junifer/storage/pandas_base.py
CHANGED
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
from collections.abc import Sequence
|
|
9
|
-
from
|
|
10
|
-
from typing import ClassVar, Optional, Union
|
|
9
|
+
from typing import ClassVar
|
|
11
10
|
|
|
12
11
|
import numpy as np
|
|
13
12
|
import pandas as pd
|
|
14
13
|
|
|
15
14
|
from ..utils import raise_error
|
|
16
|
-
from .base import BaseFeatureStorage
|
|
15
|
+
from .base import BaseFeatureStorage, StorageType
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
__all__ = ["PandasBaseFeatureStorage"]
|
|
@@ -38,19 +37,12 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
38
37
|
|
|
39
38
|
"""
|
|
40
39
|
|
|
41
|
-
_STORAGE_TYPES: ClassVar[Sequence[
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
_STORAGE_TYPES: ClassVar[Sequence[StorageType]] = [
|
|
41
|
+
StorageType.Vector,
|
|
42
|
+
StorageType.Timeseries,
|
|
43
|
+
StorageType.Matrix,
|
|
45
44
|
]
|
|
46
45
|
|
|
47
|
-
def __init__(
|
|
48
|
-
self,
|
|
49
|
-
uri: Union[str, Path],
|
|
50
|
-
single_output: bool = True,
|
|
51
|
-
) -> None:
|
|
52
|
-
super().__init__(uri=uri, single_output=single_output)
|
|
53
|
-
|
|
54
46
|
def _meta_row(self, meta: dict, meta_md5: str) -> pd.DataFrame:
|
|
55
47
|
"""Convert the metadata to a pandas DataFrame.
|
|
56
48
|
|
|
@@ -75,8 +67,8 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
75
67
|
|
|
76
68
|
@staticmethod
|
|
77
69
|
def element_to_index(
|
|
78
|
-
element: dict, n_rows: int = 1, rows_col_name:
|
|
79
|
-
) ->
|
|
70
|
+
element: dict, n_rows: int = 1, rows_col_name: str | None = None
|
|
71
|
+
) -> pd.Index | pd.MultiIndex:
|
|
80
72
|
"""Convert the element metadata to index.
|
|
81
73
|
|
|
82
74
|
Parameters
|
|
@@ -124,7 +116,11 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
124
116
|
return index
|
|
125
117
|
|
|
126
118
|
def store_df(
|
|
127
|
-
self,
|
|
119
|
+
self,
|
|
120
|
+
meta_md5: str,
|
|
121
|
+
element: dict,
|
|
122
|
+
df: pd.DataFrame | pd.Series,
|
|
123
|
+
processed_element: dict | None = None,
|
|
128
124
|
) -> None:
|
|
129
125
|
"""Implement pandas DataFrame storing.
|
|
130
126
|
|
|
@@ -136,6 +132,10 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
136
132
|
The element as a dictionary.
|
|
137
133
|
df : pandas.DataFrame or pandas.Series
|
|
138
134
|
The pandas DataFrame or Series to store.
|
|
135
|
+
processed_element : dict or None, optional
|
|
136
|
+
The element being processed when the data was computed, used to
|
|
137
|
+
choose the file of each element if ``single_output=False``. If
|
|
138
|
+
None, ``element`` is used (default None).
|
|
139
139
|
|
|
140
140
|
Raises
|
|
141
141
|
------
|
|
@@ -153,9 +153,10 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
153
153
|
self,
|
|
154
154
|
meta_md5: str,
|
|
155
155
|
element: dict,
|
|
156
|
-
data:
|
|
157
|
-
col_names:
|
|
158
|
-
rows_col_name:
|
|
156
|
+
data: np.ndarray | list,
|
|
157
|
+
col_names: Sequence[str] | None = None,
|
|
158
|
+
rows_col_name: str | None = None,
|
|
159
|
+
processed_element: dict | None = None,
|
|
159
160
|
) -> None:
|
|
160
161
|
"""Store 2D data.
|
|
161
162
|
|
|
@@ -173,6 +174,10 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
173
174
|
The column name to use in case number of rows greater than 1.
|
|
174
175
|
If None and number of rows greater than 1, then the name will be
|
|
175
176
|
"idx" (default None).
|
|
177
|
+
processed_element : dict or None, optional
|
|
178
|
+
The element being processed when the data was computed, used to
|
|
179
|
+
choose the file of each element if ``single_output=False``. If
|
|
180
|
+
None, ``element`` is used (default None).
|
|
176
181
|
|
|
177
182
|
"""
|
|
178
183
|
# Convert element metadata to index
|
|
@@ -186,14 +191,20 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
186
191
|
index=idx, # type: ignore
|
|
187
192
|
)
|
|
188
193
|
# Store dataframe
|
|
189
|
-
self.store_df(
|
|
194
|
+
self.store_df(
|
|
195
|
+
meta_md5=meta_md5,
|
|
196
|
+
element=element,
|
|
197
|
+
df=df,
|
|
198
|
+
processed_element=processed_element,
|
|
199
|
+
)
|
|
190
200
|
|
|
191
201
|
def store_vector(
|
|
192
202
|
self,
|
|
193
203
|
meta_md5: str,
|
|
194
204
|
element: dict,
|
|
195
|
-
data:
|
|
196
|
-
col_names:
|
|
205
|
+
data: np.ndarray | list,
|
|
206
|
+
col_names: Sequence[str] | None = None,
|
|
207
|
+
processed_element: dict | None = None,
|
|
197
208
|
) -> None:
|
|
198
209
|
"""Store vector.
|
|
199
210
|
|
|
@@ -207,6 +218,10 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
207
218
|
The vector data to store.
|
|
208
219
|
col_names : list-like of str, optional
|
|
209
220
|
The column labels (default None).
|
|
221
|
+
processed_element : dict or None, optional
|
|
222
|
+
The element being processed when the data was computed, used to
|
|
223
|
+
choose the file of each element if ``single_output=False``. If
|
|
224
|
+
None, ``element`` is used (default None).
|
|
210
225
|
|
|
211
226
|
"""
|
|
212
227
|
if isinstance(data, list):
|
|
@@ -224,6 +239,7 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
224
239
|
element=element,
|
|
225
240
|
data=data,
|
|
226
241
|
col_names=col_names,
|
|
242
|
+
processed_element=processed_element,
|
|
227
243
|
)
|
|
228
244
|
|
|
229
245
|
def store_timeseries(
|
|
@@ -231,7 +247,8 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
231
247
|
meta_md5: str,
|
|
232
248
|
element: dict,
|
|
233
249
|
data: np.ndarray,
|
|
234
|
-
col_names:
|
|
250
|
+
col_names: Sequence[str] | None = None,
|
|
251
|
+
processed_element: dict | None = None,
|
|
235
252
|
) -> None:
|
|
236
253
|
"""Store timeseries.
|
|
237
254
|
|
|
@@ -245,6 +262,10 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
245
262
|
The timeseries data to store.
|
|
246
263
|
col_names : list-like of str, optional
|
|
247
264
|
The column labels (default None).
|
|
265
|
+
processed_element : dict or None, optional
|
|
266
|
+
The element being processed when the data was computed, used to
|
|
267
|
+
choose the file of each element if ``single_output=False``. If
|
|
268
|
+
None, ``element`` is used (default None).
|
|
248
269
|
|
|
249
270
|
"""
|
|
250
271
|
self._store_2d(
|
|
@@ -253,4 +274,5 @@ class PandasBaseFeatureStorage(BaseFeatureStorage):
|
|
|
253
274
|
data=data,
|
|
254
275
|
col_names=col_names,
|
|
255
276
|
rows_col_name="timepoint",
|
|
277
|
+
processed_element=processed_element,
|
|
256
278
|
)
|
junifer/storage/sqlite.py
CHANGED
|
@@ -5,28 +5,40 @@
|
|
|
5
5
|
# License: AGPL
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
8
|
+
from enum import StrEnum
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
11
10
|
|
|
12
11
|
import numpy as np
|
|
13
12
|
import pandas as pd
|
|
14
13
|
from pandas.core.base import NoNewAttributesMixin
|
|
15
14
|
from pandas.io.sql import pandasSQL_builder
|
|
16
15
|
from sqlalchemy import create_engine, inspect
|
|
16
|
+
from sqlalchemy.pool import NullPool
|
|
17
17
|
from tqdm import tqdm
|
|
18
18
|
|
|
19
19
|
from ..api.decorators import register_storage
|
|
20
|
-
from ..utils import
|
|
20
|
+
from ..utils import raise_error, warn_with_log
|
|
21
|
+
from .base import MatrixKind, logger
|
|
21
22
|
from .pandas_base import PandasBaseFeatureStorage
|
|
22
|
-
from .utils import
|
|
23
|
+
from .utils import (
|
|
24
|
+
element_to_prefix,
|
|
25
|
+
matrix_to_vector,
|
|
26
|
+
store_matrix_checks,
|
|
27
|
+
)
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
if TYPE_CHECKING:
|
|
26
31
|
from sqlalchemy.engine import Engine
|
|
27
32
|
|
|
28
33
|
|
|
29
|
-
__all__ = ["SQLiteFeatureStorage"]
|
|
34
|
+
__all__ = ["SQLiteFeatureStorage", "Upsert"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Upsert(StrEnum):
|
|
38
|
+
"""Accepted upsert value."""
|
|
39
|
+
|
|
40
|
+
Update = "update"
|
|
41
|
+
Ignore = "ignore"
|
|
30
42
|
|
|
31
43
|
|
|
32
44
|
@register_storage
|
|
@@ -44,9 +56,11 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
44
56
|
``uri`` and store all the elements in the same file. This behaviour
|
|
45
57
|
is only suitable for non-parallel executions. SQLite does not
|
|
46
58
|
support concurrency (default True).
|
|
47
|
-
upsert :
|
|
48
|
-
Upsert mode. If
|
|
49
|
-
|
|
59
|
+
upsert : :enum:`.Upsert`, optional
|
|
60
|
+
Upsert mode. If ``Upsert.Ignore`` is used, the existing elements are
|
|
61
|
+
ignored, so the elements that are already stored are not stored
|
|
62
|
+
again. If ``Upsert.Update``, the existing elements are updated
|
|
63
|
+
(default ``Upsert.Ignore``).
|
|
50
64
|
|
|
51
65
|
See Also
|
|
52
66
|
--------
|
|
@@ -55,27 +69,9 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
55
69
|
|
|
56
70
|
"""
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
self,
|
|
60
|
-
uri: Union[str, Path],
|
|
61
|
-
single_output: bool = True,
|
|
62
|
-
upsert: str = "update",
|
|
63
|
-
) -> None:
|
|
64
|
-
# Check and set upsert argument value
|
|
65
|
-
if upsert not in ["update", "ignore"]:
|
|
66
|
-
raise_error(
|
|
67
|
-
msg=(
|
|
68
|
-
"Invalid choice for `upsert`. "
|
|
69
|
-
"Must be either 'update' or 'ignore'."
|
|
70
|
-
)
|
|
71
|
-
)
|
|
72
|
-
self.upsert = upsert
|
|
73
|
-
super().__init__(
|
|
74
|
-
uri=uri,
|
|
75
|
-
single_output=single_output,
|
|
76
|
-
)
|
|
72
|
+
upsert: Upsert = Upsert.Ignore
|
|
77
73
|
|
|
78
|
-
def get_engine(self, element:
|
|
74
|
+
def get_engine(self, element: dict | None = None) -> "Engine":
|
|
79
75
|
"""Get engine.
|
|
80
76
|
|
|
81
77
|
Parameters
|
|
@@ -105,11 +101,11 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
105
101
|
prefix = element_to_prefix(element)
|
|
106
102
|
# Format URI for engine creation
|
|
107
103
|
uri = f"sqlite:///{self.uri.parent}/{prefix}{self.uri.name}"
|
|
108
|
-
return create_engine(uri, echo=False)
|
|
104
|
+
return create_engine(uri, echo=False, poolclass=NullPool)
|
|
109
105
|
|
|
110
106
|
def _save_upsert(
|
|
111
107
|
self,
|
|
112
|
-
df:
|
|
108
|
+
df: pd.DataFrame | pd.Series,
|
|
113
109
|
name: str,
|
|
114
110
|
engine: Optional["Engine"] = None,
|
|
115
111
|
if_exists: str = "append",
|
|
@@ -225,11 +221,9 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
225
221
|
|
|
226
222
|
def read(
|
|
227
223
|
self,
|
|
228
|
-
feature_name:
|
|
229
|
-
feature_md5:
|
|
230
|
-
) -> dict[
|
|
231
|
-
str, Union[str, list[Union[int, str, dict[str, str]]], np.ndarray]
|
|
232
|
-
]:
|
|
224
|
+
feature_name: str | None = None,
|
|
225
|
+
feature_md5: str | None = None,
|
|
226
|
+
) -> dict[str, str | list[int | str | dict[str, str]] | np.ndarray]:
|
|
233
227
|
"""Read stored feature.
|
|
234
228
|
|
|
235
229
|
Parameters
|
|
@@ -261,8 +255,8 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
261
255
|
|
|
262
256
|
def read_df(
|
|
263
257
|
self,
|
|
264
|
-
feature_name:
|
|
265
|
-
feature_md5:
|
|
258
|
+
feature_name: str | None = None,
|
|
259
|
+
feature_md5: str | None = None,
|
|
266
260
|
) -> pd.DataFrame:
|
|
267
261
|
"""Implement feature reading into a pandas DataFrame.
|
|
268
262
|
|
|
@@ -347,7 +341,13 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
347
341
|
df = df.set_index(index_names)
|
|
348
342
|
return df
|
|
349
343
|
|
|
350
|
-
def store_metadata(
|
|
344
|
+
def store_metadata(
|
|
345
|
+
self,
|
|
346
|
+
meta_md5: str,
|
|
347
|
+
element: dict,
|
|
348
|
+
meta: dict,
|
|
349
|
+
processed_element: dict | None = None,
|
|
350
|
+
) -> None:
|
|
351
351
|
"""Implement metadata storing in the storage.
|
|
352
352
|
|
|
353
353
|
Parameters
|
|
@@ -358,10 +358,15 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
358
358
|
The element as a dictionary.
|
|
359
359
|
meta : dict
|
|
360
360
|
The metadata as a dictionary.
|
|
361
|
+
processed_element : dict or None, optional
|
|
362
|
+
The element being processed when the data was computed, used to
|
|
363
|
+
choose the file of each element if ``single_output=False``. If
|
|
364
|
+
None, ``element`` is used (default None).
|
|
361
365
|
|
|
362
366
|
"""
|
|
363
367
|
# Get sqlalchemy engine
|
|
364
|
-
|
|
368
|
+
# The file of the element being processed (if single_output=False)
|
|
369
|
+
engine = self.get_engine(element=processed_element or element)
|
|
365
370
|
table_name = f"meta_{meta_md5}"
|
|
366
371
|
if table_name not in inspect(engine).get_table_names():
|
|
367
372
|
# Convert metadata to dataframe
|
|
@@ -370,7 +375,11 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
370
375
|
self._save_upsert(meta_df, "meta", engine)
|
|
371
376
|
|
|
372
377
|
def store_df(
|
|
373
|
-
self,
|
|
378
|
+
self,
|
|
379
|
+
meta_md5: str,
|
|
380
|
+
element: dict,
|
|
381
|
+
df: pd.DataFrame | pd.Series,
|
|
382
|
+
processed_element: dict | None = None,
|
|
374
383
|
) -> None:
|
|
375
384
|
"""Implement pandas DataFrame storing.
|
|
376
385
|
|
|
@@ -382,6 +391,10 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
382
391
|
The element as a dictionary.
|
|
383
392
|
df : pandas.DataFrame or pandas.Series
|
|
384
393
|
The pandas DataFrame or Series to store.
|
|
394
|
+
processed_element : dict or None, optional
|
|
395
|
+
The element being processed when the data was computed, used to
|
|
396
|
+
choose the file of each element if ``single_output=False``. If
|
|
397
|
+
None, ``element`` is used (default None).
|
|
385
398
|
|
|
386
399
|
Raises
|
|
387
400
|
------
|
|
@@ -416,7 +429,8 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
416
429
|
|
|
417
430
|
table_name = f"meta_{meta_md5}"
|
|
418
431
|
# Get sqlalchemy engine
|
|
419
|
-
|
|
432
|
+
# The file of the element being processed (if single_output=False)
|
|
433
|
+
engine = self.get_engine(element=processed_element or element)
|
|
420
434
|
# Save data
|
|
421
435
|
self._save_upsert(df, table_name, engine)
|
|
422
436
|
|
|
@@ -425,10 +439,11 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
425
439
|
meta_md5: str,
|
|
426
440
|
element: dict,
|
|
427
441
|
data: np.ndarray,
|
|
428
|
-
col_names:
|
|
429
|
-
row_names:
|
|
430
|
-
matrix_kind:
|
|
442
|
+
col_names: list[str] | None = None,
|
|
443
|
+
row_names: list[str] | None = None,
|
|
444
|
+
matrix_kind: MatrixKind = MatrixKind.Full,
|
|
431
445
|
diagonal: bool = True,
|
|
446
|
+
processed_element: dict | None = None,
|
|
432
447
|
) -> None:
|
|
433
448
|
"""Store matrix.
|
|
434
449
|
|
|
@@ -440,23 +455,19 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
440
455
|
The element as a dictionary.
|
|
441
456
|
data : numpy.ndarray
|
|
442
457
|
The matrix data to store.
|
|
443
|
-
meta : dict
|
|
444
|
-
The metadata as a dictionary.
|
|
445
458
|
col_names : list-like of str, optional
|
|
446
459
|
The column labels (default None).
|
|
447
460
|
row_names : list-like of str, optional
|
|
448
461
|
The row labels (optional None).
|
|
449
|
-
matrix_kind :
|
|
450
|
-
The kind
|
|
451
|
-
|
|
452
|
-
* ``triu`` : store upper triangular only
|
|
453
|
-
* ``tril`` : store lower triangular
|
|
454
|
-
* ``full`` : full matrix
|
|
455
|
-
|
|
456
|
-
(default "full").
|
|
462
|
+
matrix_kind : :enum:`.MatrixKind`, optional
|
|
463
|
+
The matrix kind (default ``MatrixKind.Full``).
|
|
457
464
|
diagonal : bool, optional
|
|
458
|
-
Whether to store the diagonal. If ``matrix_kind
|
|
459
|
-
this to False will raise an error (default True).
|
|
465
|
+
Whether to store the diagonal. If ``matrix_kind=MatrixKind.Full``,
|
|
466
|
+
setting this to False will raise an error (default True).
|
|
467
|
+
processed_element : dict or None, optional
|
|
468
|
+
The element being processed when the data was computed, used to
|
|
469
|
+
choose the file of each element if ``single_output=False``. If
|
|
470
|
+
None, ``element`` is used (default None).
|
|
460
471
|
|
|
461
472
|
"""
|
|
462
473
|
# Row data validation
|
|
@@ -508,7 +519,12 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
508
519
|
data_df.index.names = new_names
|
|
509
520
|
|
|
510
521
|
# Store dataframe
|
|
511
|
-
self.store_df(
|
|
522
|
+
self.store_df(
|
|
523
|
+
meta_md5=meta_md5,
|
|
524
|
+
element=element,
|
|
525
|
+
df=data_df,
|
|
526
|
+
processed_element=processed_element,
|
|
527
|
+
)
|
|
512
528
|
|
|
513
529
|
def collect(self) -> None:
|
|
514
530
|
"""Implement data collection.
|
|
@@ -526,9 +542,10 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
526
542
|
)
|
|
527
543
|
logger.info(f"Collecting data from {self.uri.parent}/*{self.uri.name}")
|
|
528
544
|
# Create new instance
|
|
529
|
-
out_storage = SQLiteFeatureStorage(uri=self.uri, upsert=
|
|
545
|
+
out_storage = SQLiteFeatureStorage(uri=self.uri, upsert=Upsert.Ignore)
|
|
530
546
|
# Glob files
|
|
531
|
-
files
|
|
547
|
+
# Only the files of the elements, not the collected file
|
|
548
|
+
files = self.uri.parent.glob(f"*_{self.uri.name}")
|
|
532
549
|
for elem in tqdm(files, desc="file"):
|
|
533
550
|
logger.debug(f"Reading from {elem.absolute()!s}")
|
|
534
551
|
in_storage = SQLiteFeatureStorage(uri=elem)
|
|
@@ -539,6 +556,7 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
539
556
|
)
|
|
540
557
|
# Save metadata
|
|
541
558
|
out_storage._save_upsert(t_meta_df, "meta")
|
|
559
|
+
in_features = in_storage.list_features()
|
|
542
560
|
# Save dataframes
|
|
543
561
|
for meta_md5 in tqdm(t_meta_df.index, desc="feature"):
|
|
544
562
|
logger.debug(f"Collecting feature {meta_md5}")
|
|
@@ -546,8 +564,27 @@ class SQLiteFeatureStorage(PandasBaseFeatureStorage):
|
|
|
546
564
|
# properly
|
|
547
565
|
table_name = f"meta_{meta_md5}"
|
|
548
566
|
t_df = in_storage.read_df(feature_md5=meta_md5)
|
|
567
|
+
# Only the files of single elements can be collected
|
|
568
|
+
element_keys = in_features[meta_md5]["_element_keys"]
|
|
569
|
+
n_elements = len(
|
|
570
|
+
t_df.index.to_frame(index=False)[
|
|
571
|
+
element_keys
|
|
572
|
+
].drop_duplicates()
|
|
573
|
+
)
|
|
574
|
+
if n_elements != 1:
|
|
575
|
+
raise_error(
|
|
576
|
+
msg=(
|
|
577
|
+
f"The file {elem} has {n_elements} elements for "
|
|
578
|
+
f"the feature {meta_md5}, but only the files with "
|
|
579
|
+
"one element (as stored for each element with "
|
|
580
|
+
"`single_output=False`) can be collected"
|
|
581
|
+
),
|
|
582
|
+
klass=RuntimeError,
|
|
583
|
+
)
|
|
549
584
|
# Save data
|
|
550
|
-
|
|
585
|
+
# Skip the elements already collected (e.g., the data of a
|
|
586
|
+
# subject in the files of each of its tasks)
|
|
587
|
+
out_storage._save_upsert(t_df, table_name)
|
|
551
588
|
|
|
552
589
|
|
|
553
590
|
# TODO: refactor
|
|
@@ -582,7 +619,7 @@ def _generate_update_statements(table, index_col, rows_to_update):
|
|
|
582
619
|
for i, (_, keys) in enumerate(pk_indb.iterrows()):
|
|
583
620
|
stmt = (
|
|
584
621
|
table.update()
|
|
585
|
-
.where(and_(col == keys[j] for j, col in enumerate(pk_cols))) # type: ignore
|
|
622
|
+
.where(and_(col == keys.iloc[j] for j, col in enumerate(pk_cols))) # type: ignore
|
|
586
623
|
.values(new_records[i])
|
|
587
624
|
)
|
|
588
625
|
stmts.append(stmt)
|
|
Binary file
|
|
@@ -10,6 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
import h5py
|
|
11
11
|
import numpy as np
|
|
12
12
|
import pytest
|
|
13
|
+
from h5io import read_hdf5
|
|
13
14
|
from numpy.testing import assert_array_equal
|
|
14
15
|
from pandas.testing import assert_frame_equal
|
|
15
16
|
|
|
@@ -21,18 +22,6 @@ from junifer.storage.utils import (
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
def test_get_valid_inputs() -> None:
|
|
25
|
-
"""Test valid inputs."""
|
|
26
|
-
storage = HDF5FeatureStorage(uri="/tmp")
|
|
27
|
-
assert set(storage.get_valid_inputs()) == {
|
|
28
|
-
"matrix",
|
|
29
|
-
"vector",
|
|
30
|
-
"timeseries",
|
|
31
|
-
"scalar_table",
|
|
32
|
-
"timeseries_2d",
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
25
|
def test_single_output(tmp_path: Path) -> None:
|
|
37
26
|
"""Test single output setup.
|
|
38
27
|
|
|
@@ -1178,3 +1167,149 @@ def test_collect_error_single_output() -> None:
|
|
|
1178
1167
|
):
|
|
1179
1168
|
storage = HDF5FeatureStorage(uri="/tmp", single_output=True)
|
|
1180
1169
|
storage.collect()
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
def test_read_collected_with_h5io_fork() -> None:
|
|
1173
|
+
"""Test reading features collected with junifer's fork of h5io.
|
|
1174
|
+
|
|
1175
|
+
The file was collected from three elements (``sub-01`` to ``sub-03``),
|
|
1176
|
+
each with a vector, a matrix and a timeseries computed from the element
|
|
1177
|
+
index ``i``.
|
|
1178
|
+
|
|
1179
|
+
"""
|
|
1180
|
+
uri = Path(__file__).parent / "data" / "collected_h5io_fork.hdf5"
|
|
1181
|
+
storage = HDF5FeatureStorage(uri=uri)
|
|
1182
|
+
md5s = {meta["name"]: md5 for md5, meta in storage.list_features().items()}
|
|
1183
|
+
elements = [{"subject": f"sub-0{i}"} for i in range(1, 4)]
|
|
1184
|
+
# The vector data is stored as a "chunkedarray"
|
|
1185
|
+
vector = storage.read(feature_md5=md5s["BOLD_vec"])
|
|
1186
|
+
assert vector["element"] == elements
|
|
1187
|
+
assert vector["data"].dtype == np.float32
|
|
1188
|
+
for i in range(3):
|
|
1189
|
+
assert_array_equal(
|
|
1190
|
+
vector["data"][:, i], np.float32(np.array([0.1, 0.2]) + i)
|
|
1191
|
+
)
|
|
1192
|
+
# The matrix data is stored as a "chunkedarray"
|
|
1193
|
+
matrix = storage.read(feature_md5=md5s["BOLD_mat"])
|
|
1194
|
+
assert matrix["element"] == elements
|
|
1195
|
+
for i in range(3):
|
|
1196
|
+
assert_array_equal(
|
|
1197
|
+
matrix["data"][..., i], np.array([[1.0, 0.5], [0.5, 1.0]]) + i
|
|
1198
|
+
)
|
|
1199
|
+
# The timeseries data is stored as a "chunkedlist"
|
|
1200
|
+
timeseries = storage.read(feature_md5=md5s["BOLD_ts"])
|
|
1201
|
+
assert timeseries["element"] == elements
|
|
1202
|
+
assert len(timeseries["data"]) == 3
|
|
1203
|
+
for i in range(3):
|
|
1204
|
+
assert_array_equal(
|
|
1205
|
+
timeseries["data"][i], np.arange(6.0).reshape(3, 2) + i
|
|
1206
|
+
)
|
|
1207
|
+
assert storage.read_df(feature_md5=md5s["BOLD_ts"]).shape == (9, 2)
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
def _store_and_collect(uri: Path, force_float32: bool) -> HDF5FeatureStorage:
|
|
1211
|
+
"""Store a vector and a timeseries for three elements and collect them.
|
|
1212
|
+
|
|
1213
|
+
Parameters
|
|
1214
|
+
----------
|
|
1215
|
+
uri : pathlib.Path
|
|
1216
|
+
The path to the collected file.
|
|
1217
|
+
force_float32 : bool
|
|
1218
|
+
Whether to store the data as float32.
|
|
1219
|
+
|
|
1220
|
+
Returns
|
|
1221
|
+
-------
|
|
1222
|
+
HDF5FeatureStorage
|
|
1223
|
+
The storage of the collected file.
|
|
1224
|
+
|
|
1225
|
+
"""
|
|
1226
|
+
storage = HDF5FeatureStorage(
|
|
1227
|
+
uri=uri,
|
|
1228
|
+
single_output=False,
|
|
1229
|
+
force_float32=force_float32,
|
|
1230
|
+
chunk_size=2,
|
|
1231
|
+
)
|
|
1232
|
+
for i in range(3):
|
|
1233
|
+
meta = {
|
|
1234
|
+
"element": {"subject": f"sub-0{i}"},
|
|
1235
|
+
"dependencies": [],
|
|
1236
|
+
"type": "BOLD",
|
|
1237
|
+
}
|
|
1238
|
+
storage.store(
|
|
1239
|
+
kind="vector",
|
|
1240
|
+
meta={**meta, "marker": {"name": "vec"}},
|
|
1241
|
+
data=np.array([[0.1, 0.2]]) + i,
|
|
1242
|
+
col_names=["a", "b"],
|
|
1243
|
+
)
|
|
1244
|
+
storage.store(
|
|
1245
|
+
kind="timeseries",
|
|
1246
|
+
meta={**meta, "marker": {"name": "ts"}},
|
|
1247
|
+
data=np.arange(6.0).reshape(3, 2) + i,
|
|
1248
|
+
col_names=["a", "b"],
|
|
1249
|
+
)
|
|
1250
|
+
storage.collect()
|
|
1251
|
+
return HDF5FeatureStorage(uri=uri)
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
@pytest.mark.parametrize(
|
|
1255
|
+
"force_float32, dtype", [(True, np.float32), (False, np.float64)]
|
|
1256
|
+
)
|
|
1257
|
+
def test_collect_dtype(
|
|
1258
|
+
tmp_path: Path, force_float32: bool, dtype: np.dtype
|
|
1259
|
+
) -> None:
|
|
1260
|
+
"""Test collected data keeps its dtype.
|
|
1261
|
+
|
|
1262
|
+
Parameters
|
|
1263
|
+
----------
|
|
1264
|
+
tmp_path : pathlib.Path
|
|
1265
|
+
The path to the test directory.
|
|
1266
|
+
force_float32 : bool
|
|
1267
|
+
The parametrized casting of the data to float32.
|
|
1268
|
+
dtype : numpy.dtype
|
|
1269
|
+
The parametrized dtype of the collected data.
|
|
1270
|
+
|
|
1271
|
+
"""
|
|
1272
|
+
storage = _store_and_collect(tmp_path / "out.hdf5", force_float32)
|
|
1273
|
+
md5s = {meta["name"]: md5 for md5, meta in storage.list_features().items()}
|
|
1274
|
+
vector = storage.read(feature_md5=md5s["BOLD_vec"])
|
|
1275
|
+
timeseries = storage.read(feature_md5=md5s["BOLD_ts"])
|
|
1276
|
+
assert vector["data"].dtype == dtype
|
|
1277
|
+
assert all(x.dtype == dtype for x in timeseries["data"])
|
|
1278
|
+
# The elements are collected in any order
|
|
1279
|
+
for i_element, element in enumerate(vector["element"]):
|
|
1280
|
+
i = int(element["subject"][-1])
|
|
1281
|
+
expected = np.array([0.1, 0.2]) + i
|
|
1282
|
+
assert_array_equal(
|
|
1283
|
+
vector["data"][:, i_element], expected.astype(dtype)
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def test_collect_h5io_types(tmp_path: Path) -> None:
|
|
1288
|
+
"""Test collected data is stored with h5io types and can be re-collected.
|
|
1289
|
+
|
|
1290
|
+
Parameters
|
|
1291
|
+
----------
|
|
1292
|
+
tmp_path : pathlib.Path
|
|
1293
|
+
The path to the test directory.
|
|
1294
|
+
|
|
1295
|
+
"""
|
|
1296
|
+
uri = tmp_path / "out.hdf5"
|
|
1297
|
+
storage = _store_and_collect(uri, force_float32=False)
|
|
1298
|
+
collected = {
|
|
1299
|
+
md5: read_hdf5(uri, title=md5) for md5 in storage.list_features()
|
|
1300
|
+
}
|
|
1301
|
+
# The data is only in the features
|
|
1302
|
+
with h5py.File(uri, mode="r") as fid:
|
|
1303
|
+
assert set(fid.keys()) == {"meta", *collected.keys()}
|
|
1304
|
+
# Collect again
|
|
1305
|
+
_store_and_collect(uri, force_float32=False)
|
|
1306
|
+
for md5, feature in collected.items():
|
|
1307
|
+
recollected = read_hdf5(uri, title=md5)
|
|
1308
|
+
assert recollected["element"] == feature["element"]
|
|
1309
|
+
if isinstance(feature["data"], list):
|
|
1310
|
+
for new, old in zip(
|
|
1311
|
+
recollected["data"], feature["data"], strict=True
|
|
1312
|
+
):
|
|
1313
|
+
assert_array_equal(new, old)
|
|
1314
|
+
else:
|
|
1315
|
+
assert_array_equal(recollected["data"], feature["data"])
|