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/hdf5.py
CHANGED
|
@@ -4,25 +4,22 @@
|
|
|
4
4
|
# Federico Raimondo <f.raimondo@fz-juelich.de>
|
|
5
5
|
# License: AGPL
|
|
6
6
|
|
|
7
|
+
import io
|
|
7
8
|
from collections import defaultdict
|
|
8
9
|
from collections.abc import Sequence
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import Any, ClassVar,
|
|
11
|
+
from typing import Any, ClassVar, Literal
|
|
11
12
|
|
|
13
|
+
import h5py
|
|
12
14
|
import numpy as np
|
|
13
15
|
import pandas as pd
|
|
16
|
+
from h5io import read_hdf5, write_hdf5
|
|
17
|
+
from pydantic import PositiveInt
|
|
14
18
|
from tqdm import tqdm
|
|
15
19
|
|
|
16
20
|
from ..api.decorators import register_storage
|
|
17
|
-
from ..
|
|
18
|
-
|
|
19
|
-
ChunkedList,
|
|
20
|
-
has_hdf5,
|
|
21
|
-
read_hdf5,
|
|
22
|
-
write_hdf5,
|
|
23
|
-
)
|
|
24
|
-
from ..utils import logger, raise_error
|
|
25
|
-
from .base import BaseFeatureStorage
|
|
21
|
+
from ..utils import raise_error
|
|
22
|
+
from .base import BaseFeatureStorage, MatrixKind, StorageType, logger
|
|
26
23
|
from .utils import (
|
|
27
24
|
element_to_prefix,
|
|
28
25
|
matrix_to_vector,
|
|
@@ -35,21 +32,135 @@ from .utils import (
|
|
|
35
32
|
__all__ = ["HDF5FeatureStorage"]
|
|
36
33
|
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
# Types of the data of collected features in files written with junifer's
|
|
36
|
+
# fork of h5io, and the h5io types they are stored as
|
|
37
|
+
_LEGACY_TYPES = {"chunkedarray": "ndarray", "chunkedlist": "list"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _node_type(node: h5py.Group | h5py.Dataset) -> str:
|
|
41
|
+
"""Get the h5io type of a node.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
node : h5py.Group or h5py.Dataset
|
|
46
|
+
The node.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
str
|
|
51
|
+
The type.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
title = node.attrs["TITLE"]
|
|
55
|
+
return title.decode() if isinstance(title, bytes) else title
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _has_hdf5(fname: str, title: str) -> bool:
|
|
59
|
+
"""Check whether a title is in an HDF5 file.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
fname : str
|
|
64
|
+
The HDF5 file.
|
|
65
|
+
title : str
|
|
66
|
+
The title.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
bool
|
|
71
|
+
Whether the title is in the file.
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
with h5py.File(fname, mode="r") as fid:
|
|
75
|
+
return title in fid
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _read_hdf5(fname: str, title: str) -> Any:
|
|
79
|
+
"""Read a title of an HDF5 file.
|
|
80
|
+
|
|
81
|
+
Features collected with junifer's fork of h5io have their data stored with
|
|
82
|
+
types that h5io does not know (see ``_LEGACY_TYPES``). To read them, the
|
|
83
|
+
feature is copied in memory with the h5io types.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
fname : str
|
|
88
|
+
The HDF5 file.
|
|
89
|
+
title : str
|
|
90
|
+
The title.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
Any
|
|
95
|
+
The data.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
with h5py.File(fname, mode="r") as fid:
|
|
99
|
+
node = fid.get(title)
|
|
100
|
+
data = node.get("key_data") if isinstance(node, h5py.Group) else None
|
|
101
|
+
if data is not None and _node_type(data) in _LEGACY_TYPES:
|
|
102
|
+
with h5py.File(io.BytesIO(), mode="w") as copy:
|
|
103
|
+
fid.copy(node, copy, name=title)
|
|
104
|
+
copy_data = copy[title]["key_data"]
|
|
105
|
+
copy_data.attrs["TITLE"] = _LEGACY_TYPES[_node_type(data)]
|
|
106
|
+
return read_hdf5(copy, title=title, slash="ignore")
|
|
107
|
+
return read_hdf5(fname, title=title, slash="ignore")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _read_elements(fname: str, feature_md5s: list[str]) -> dict[str, list]:
|
|
111
|
+
"""Get the elements that a file has for each feature, without the data.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
fname : str
|
|
116
|
+
The HDF5 file.
|
|
117
|
+
feature_md5s : list of str
|
|
118
|
+
The MD5 of the feature to check.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
dict
|
|
123
|
+
The elements present in the file for each feature (MD5 -> list of
|
|
124
|
+
elements). Each element is a tuple of its values, e.g.,
|
|
125
|
+
``("sub-01",)``, as all the elements of a feature have the same keys.
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
out = {}
|
|
129
|
+
with h5py.File(fname, mode="r") as fid:
|
|
130
|
+
for feature_md5 in feature_md5s:
|
|
131
|
+
# Copy only the elements of the feature, not its data
|
|
132
|
+
with h5py.File(io.BytesIO(), mode="w") as copy:
|
|
133
|
+
fid.copy(fid[feature_md5]["key_element"], copy, name="element")
|
|
134
|
+
elements = read_hdf5(copy, title="element", slash="ignore")
|
|
135
|
+
out[feature_md5] = [tuple(e.values()) for e in elements]
|
|
136
|
+
return out
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _write_chunk(
|
|
140
|
+
fname: str,
|
|
141
|
+
key: str,
|
|
39
142
|
chunk_data: list[np.ndarray],
|
|
40
|
-
kind:
|
|
143
|
+
kind: StorageType,
|
|
41
144
|
element_count: int,
|
|
42
145
|
chunk_size: int,
|
|
43
146
|
i_chunk: int,
|
|
44
|
-
) ->
|
|
45
|
-
"""
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Write a chunk of the data of the elements.
|
|
149
|
+
|
|
150
|
+
The data is written in a dataset (``ndarray`` in h5io) for vectors and
|
|
151
|
+
matrices, with the elements in the last dimension, or in a group of
|
|
152
|
+
datasets (``list`` in h5io) for the other kinds.
|
|
46
153
|
|
|
47
154
|
Parameters
|
|
48
155
|
----------
|
|
156
|
+
fname : str
|
|
157
|
+
The HDF5 file.
|
|
158
|
+
key : str
|
|
159
|
+
The name of the dataset or group to write to.
|
|
49
160
|
chunk_data : list of numpy.ndarray
|
|
50
|
-
The data
|
|
51
|
-
kind :
|
|
52
|
-
The kind of data
|
|
161
|
+
The data of the elements in the chunk.
|
|
162
|
+
kind : :enum:`.StorageType`
|
|
163
|
+
The kind of data.
|
|
53
164
|
element_count : int
|
|
54
165
|
The total number of elements.
|
|
55
166
|
chunk_size : int
|
|
@@ -57,49 +168,29 @@ def _create_chunk(
|
|
|
57
168
|
i_chunk : int
|
|
58
169
|
The chunk index.
|
|
59
170
|
|
|
60
|
-
Returns
|
|
61
|
-
-------
|
|
62
|
-
ChunkedArray or ChunkedList
|
|
63
|
-
The chunked array or list.
|
|
64
|
-
|
|
65
|
-
Raises
|
|
66
|
-
------
|
|
67
|
-
ValueError
|
|
68
|
-
If `kind` is not one of ['vector', 'matrix', 'timeseries',
|
|
69
|
-
'scalar_table'].
|
|
70
|
-
|
|
71
171
|
"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
offset=i_chunk * chunk_size,
|
|
95
|
-
)
|
|
96
|
-
else:
|
|
97
|
-
raise_error(
|
|
98
|
-
f"Invalid kind: {kind}. "
|
|
99
|
-
"Must be one of ['vector', 'matrix', 'timeseries', "
|
|
100
|
-
"'timeseries_2d', 'scalar_table']."
|
|
101
|
-
)
|
|
102
|
-
return out
|
|
172
|
+
offset = i_chunk * chunk_size
|
|
173
|
+
with h5py.File(fname, mode="a") as fid:
|
|
174
|
+
if kind in ["vector", "matrix"]:
|
|
175
|
+
features_data = np.concatenate(chunk_data, axis=-1)
|
|
176
|
+
if key not in fid:
|
|
177
|
+
dataset = fid.create_dataset(
|
|
178
|
+
key,
|
|
179
|
+
shape=(*features_data.shape[:-1], element_count),
|
|
180
|
+
dtype=features_data.dtype,
|
|
181
|
+
chunks=(*features_data.shape[:-1], chunk_size),
|
|
182
|
+
)
|
|
183
|
+
dataset.attrs["TITLE"] = "ndarray"
|
|
184
|
+
n_elements = features_data.shape[-1]
|
|
185
|
+
fid[key][..., offset : offset + n_elements] = features_data
|
|
186
|
+
elif kind in ["timeseries", "scalar_table", "timeseries_2d"]:
|
|
187
|
+
group = fid.require_group(key)
|
|
188
|
+
group.attrs["TITLE"] = "list"
|
|
189
|
+
for i, element_data in enumerate(chunk_data):
|
|
190
|
+
dataset = group.create_dataset(
|
|
191
|
+
f"idx_{offset + i}", data=element_data
|
|
192
|
+
)
|
|
193
|
+
dataset.attrs["TITLE"] = "ndarray"
|
|
103
194
|
|
|
104
195
|
|
|
105
196
|
@register_storage
|
|
@@ -108,7 +199,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
108
199
|
|
|
109
200
|
Parameters
|
|
110
201
|
----------
|
|
111
|
-
uri :
|
|
202
|
+
uri : pathlib.Path
|
|
112
203
|
The path to the file to be used.
|
|
113
204
|
single_output : bool, optional
|
|
114
205
|
If False, will create one HDF5 file per element. The name
|
|
@@ -124,7 +215,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
124
215
|
force_float32 : bool, optional
|
|
125
216
|
Whether to force casting of numpy.ndarray values to float32 if float64
|
|
126
217
|
values are found (default True).
|
|
127
|
-
chunk_size : int, optional
|
|
218
|
+
chunk_size : positive int, optional
|
|
128
219
|
The chunk size to use when collecting data from element files in
|
|
129
220
|
:meth:`.collect`. If the file count is smaller than the value, the
|
|
130
221
|
minimum is used (default 100).
|
|
@@ -135,34 +226,21 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
135
226
|
|
|
136
227
|
"""
|
|
137
228
|
|
|
138
|
-
_STORAGE_TYPES: ClassVar[Sequence[
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
229
|
+
_STORAGE_TYPES: ClassVar[Sequence[StorageType]] = [
|
|
230
|
+
StorageType.Vector,
|
|
231
|
+
StorageType.Timeseries,
|
|
232
|
+
StorageType.Matrix,
|
|
233
|
+
StorageType.ScalarTable,
|
|
234
|
+
StorageType.Timeseries2D,
|
|
144
235
|
]
|
|
145
236
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
overwrite: Union[bool, str] = "update",
|
|
151
|
-
compression: int = 7,
|
|
152
|
-
force_float32: bool = True,
|
|
153
|
-
chunk_size: int = 100,
|
|
154
|
-
) -> None:
|
|
155
|
-
self.overwrite = overwrite
|
|
156
|
-
self.compression = compression
|
|
157
|
-
self.force_float32 = force_float32
|
|
158
|
-
self.chunk_size = chunk_size
|
|
159
|
-
super().__init__(
|
|
160
|
-
uri=uri,
|
|
161
|
-
single_output=single_output,
|
|
162
|
-
)
|
|
237
|
+
overwrite: bool | str = "update"
|
|
238
|
+
compression: Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] = 7
|
|
239
|
+
force_float32: bool = True
|
|
240
|
+
chunk_size: PositiveInt = 100
|
|
163
241
|
|
|
164
|
-
def _fetch_correct_uri_for_io(self, element:
|
|
165
|
-
"""Return proper URI for I/O based on
|
|
242
|
+
def _fetch_correct_uri_for_io(self, element: dict | None) -> str:
|
|
243
|
+
"""Return proper URI for I/O based on `element`.
|
|
166
244
|
|
|
167
245
|
If ``element`` is None, will return ``self.uri``.
|
|
168
246
|
|
|
@@ -197,7 +275,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
197
275
|
return f"{self.uri.parent}/{prefix}{self.uri.name}"
|
|
198
276
|
|
|
199
277
|
def _read_metadata(
|
|
200
|
-
self, element:
|
|
278
|
+
self, element: dict[str, str] | None = None
|
|
201
279
|
) -> dict[str, dict[str, Any]]:
|
|
202
280
|
"""Read metadata (should not be called directly).
|
|
203
281
|
|
|
@@ -231,7 +309,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
231
309
|
)
|
|
232
310
|
|
|
233
311
|
# Check if group is found in the storage
|
|
234
|
-
if not
|
|
312
|
+
if not _has_hdf5(fname=uri, title="meta"):
|
|
235
313
|
raise_error(
|
|
236
314
|
f"Invalid junifer HDF5 file at: {uri}",
|
|
237
315
|
klass=RuntimeError,
|
|
@@ -239,11 +317,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
239
317
|
|
|
240
318
|
# Read metadata
|
|
241
319
|
logger.debug(f"Loading HDF5 metadata from: {uri}")
|
|
242
|
-
metadata =
|
|
243
|
-
fname=uri,
|
|
244
|
-
title="meta",
|
|
245
|
-
slash="ignore",
|
|
246
|
-
)
|
|
320
|
+
metadata = _read_hdf5(fname=uri, title="meta")
|
|
247
321
|
logger.debug(f"Loaded HDF5 metadata from: {uri}")
|
|
248
322
|
|
|
249
323
|
return metadata
|
|
@@ -260,15 +334,11 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
260
334
|
|
|
261
335
|
"""
|
|
262
336
|
# Read metadata
|
|
263
|
-
metadata =
|
|
264
|
-
fname=str(self.uri.resolve()),
|
|
265
|
-
title="meta",
|
|
266
|
-
slash="ignore",
|
|
267
|
-
)
|
|
337
|
+
metadata = _read_hdf5(fname=str(self.uri.resolve()), title="meta")
|
|
268
338
|
return metadata
|
|
269
339
|
|
|
270
340
|
def _read_data(
|
|
271
|
-
self, md5: str, element:
|
|
341
|
+
self, md5: str, element: dict[str, str] | None = None
|
|
272
342
|
) -> dict[str, Any]:
|
|
273
343
|
"""Read data (should not be called directly).
|
|
274
344
|
|
|
@@ -276,7 +346,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
276
346
|
----------
|
|
277
347
|
md5 : str
|
|
278
348
|
The MD5 used as the HDF5 group name.
|
|
279
|
-
element : dict, optional
|
|
349
|
+
element : dict or None, optional
|
|
280
350
|
The element as dictionary (default None).
|
|
281
351
|
|
|
282
352
|
Returns
|
|
@@ -304,7 +374,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
304
374
|
)
|
|
305
375
|
|
|
306
376
|
# Check if group is found in the storage
|
|
307
|
-
if not
|
|
377
|
+
if not _has_hdf5(fname=uri, title=md5):
|
|
308
378
|
raise_error(
|
|
309
379
|
f"{md5} not found in HDF5 file at: {uri}",
|
|
310
380
|
klass=RuntimeError,
|
|
@@ -312,22 +382,16 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
312
382
|
|
|
313
383
|
# Read data
|
|
314
384
|
logger.debug(f"Loading HDF5 data for {md5} from: {uri}")
|
|
315
|
-
data =
|
|
316
|
-
fname=uri,
|
|
317
|
-
title=md5,
|
|
318
|
-
slash="ignore",
|
|
319
|
-
)
|
|
385
|
+
data = _read_hdf5(fname=uri, title=md5)
|
|
320
386
|
logger.debug(f"Loaded HDF5 data for {md5} from: {uri}")
|
|
321
387
|
|
|
322
388
|
return data
|
|
323
389
|
|
|
324
390
|
def read(
|
|
325
391
|
self,
|
|
326
|
-
feature_name:
|
|
327
|
-
feature_md5:
|
|
328
|
-
) -> dict[
|
|
329
|
-
str, Union[str, list[Union[int, str, dict[str, str]]], np.ndarray]
|
|
330
|
-
]:
|
|
392
|
+
feature_name: str | None = None,
|
|
393
|
+
feature_md5: str | None = None,
|
|
394
|
+
) -> dict[str, str | list[int | str | dict[str, str]] | np.ndarray]:
|
|
331
395
|
"""Read stored feature.
|
|
332
396
|
|
|
333
397
|
Parameters
|
|
@@ -371,11 +435,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
371
435
|
)
|
|
372
436
|
)
|
|
373
437
|
# Parameter check pass; read metadata
|
|
374
|
-
metadata =
|
|
375
|
-
fname=str(self.uri.resolve()),
|
|
376
|
-
title="meta",
|
|
377
|
-
slash="ignore",
|
|
378
|
-
)
|
|
438
|
+
metadata = _read_hdf5(fname=str(self.uri.resolve()), title="meta")
|
|
379
439
|
# Initialize MD5 variable
|
|
380
440
|
md5: str = ""
|
|
381
441
|
|
|
@@ -383,7 +443,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
383
443
|
if feature_md5:
|
|
384
444
|
logger.debug(
|
|
385
445
|
f"Validating feature MD5 '{feature_md5}' in metadata "
|
|
386
|
-
f"for: {self.uri.resolve()} ..."
|
|
446
|
+
f"for: {self.uri.resolve()} ..."
|
|
387
447
|
)
|
|
388
448
|
# Validate MD5
|
|
389
449
|
if feature_md5 in metadata:
|
|
@@ -423,17 +483,13 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
423
483
|
md5 = feature_name_duplicates_with_different_md5[0]
|
|
424
484
|
|
|
425
485
|
# Read data from HDF5
|
|
426
|
-
hdf_data =
|
|
427
|
-
fname=str(self.uri.resolve()),
|
|
428
|
-
title=md5,
|
|
429
|
-
slash="ignore",
|
|
430
|
-
)
|
|
486
|
+
hdf_data = _read_hdf5(fname=str(self.uri.resolve()), title=md5)
|
|
431
487
|
return hdf_data
|
|
432
488
|
|
|
433
489
|
def read_df(
|
|
434
490
|
self,
|
|
435
|
-
feature_name:
|
|
436
|
-
feature_md5:
|
|
491
|
+
feature_name: str | None = None,
|
|
492
|
+
feature_md5: str | None = None,
|
|
437
493
|
) -> pd.DataFrame:
|
|
438
494
|
"""Read feature into a pandas.DataFrame.
|
|
439
495
|
|
|
@@ -613,6 +669,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
613
669
|
meta_md5: str,
|
|
614
670
|
element: dict[str, str],
|
|
615
671
|
meta: dict[str, Any],
|
|
672
|
+
processed_element: dict[str, str] | None = None,
|
|
616
673
|
) -> None:
|
|
617
674
|
"""Store metadata.
|
|
618
675
|
|
|
@@ -629,15 +686,20 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
629
686
|
The element as a dictionary.
|
|
630
687
|
meta : dict
|
|
631
688
|
The metadata as a dictionary.
|
|
689
|
+
processed_element : dict or None, optional
|
|
690
|
+
The element being processed when the data was computed, used to
|
|
691
|
+
choose the file of each element if ``single_output=False``. If
|
|
692
|
+
None, ``element`` is used (default None).
|
|
632
693
|
|
|
633
694
|
"""
|
|
634
|
-
# Get correct URI for element;
|
|
695
|
+
# Get correct URI for the element being processed;
|
|
635
696
|
# is different from uri if single_output is False
|
|
636
|
-
|
|
697
|
+
file_element = processed_element or element
|
|
698
|
+
uri = self._fetch_correct_uri_for_io(element=file_element)
|
|
637
699
|
|
|
638
700
|
# Check if file exists, then read metadata else create empty dictionary
|
|
639
701
|
if Path(uri).exists():
|
|
640
|
-
metadata = self._read_metadata(element=
|
|
702
|
+
metadata = self._read_metadata(element=file_element)
|
|
641
703
|
else:
|
|
642
704
|
logger.debug(f"Creating new file at {uri} ...")
|
|
643
705
|
metadata = {}
|
|
@@ -670,10 +732,11 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
670
732
|
|
|
671
733
|
def _store_data(
|
|
672
734
|
self,
|
|
673
|
-
kind:
|
|
735
|
+
kind: StorageType,
|
|
674
736
|
meta_md5: str,
|
|
675
737
|
element: list[dict[str, str]],
|
|
676
738
|
data: np.ndarray,
|
|
739
|
+
processed_element: dict[str, str] | None = None,
|
|
677
740
|
**kwargs: Any,
|
|
678
741
|
) -> None:
|
|
679
742
|
"""Store data.
|
|
@@ -685,7 +748,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
685
748
|
|
|
686
749
|
Parameters
|
|
687
750
|
----------
|
|
688
|
-
kind :
|
|
751
|
+
kind : :enum:`.StorageType`
|
|
689
752
|
The storage kind.
|
|
690
753
|
meta_md5 : str
|
|
691
754
|
The metadata MD5 hash.
|
|
@@ -693,18 +756,23 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
693
756
|
The element as list of dictionary.
|
|
694
757
|
data : numpy.ndarray
|
|
695
758
|
The data to store.
|
|
759
|
+
processed_element : dict or None, optional
|
|
760
|
+
The element being processed when the data was computed, used to
|
|
761
|
+
choose the file of each element if ``single_output=False``. If
|
|
762
|
+
None, ``element`` is used (default None).
|
|
696
763
|
**kwargs : dict
|
|
697
764
|
Keyword arguments passed from the calling method.
|
|
698
765
|
|
|
699
766
|
"""
|
|
700
|
-
# Get correct URI for element;
|
|
767
|
+
# Get correct URI for the element being processed;
|
|
701
768
|
# is different from uri if single_output is False
|
|
702
|
-
|
|
769
|
+
file_element = processed_element or element[0]
|
|
770
|
+
uri = self._fetch_correct_uri_for_io(element=file_element)
|
|
703
771
|
|
|
704
772
|
# Check if MD5 exists, then read data else create empty dictionary
|
|
705
773
|
# File should be present here already
|
|
706
|
-
if
|
|
707
|
-
stored_data = self._read_data(md5=meta_md5, element=
|
|
774
|
+
if _has_hdf5(fname=uri, title=meta_md5):
|
|
775
|
+
stored_data = self._read_data(md5=meta_md5, element=file_element)
|
|
708
776
|
else:
|
|
709
777
|
logger.debug(f"Creating new data map for {meta_md5} ...")
|
|
710
778
|
stored_data = {}
|
|
@@ -814,13 +882,14 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
814
882
|
def store_matrix(
|
|
815
883
|
self,
|
|
816
884
|
meta_md5: str,
|
|
817
|
-
element: dict
|
|
885
|
+
element: dict,
|
|
818
886
|
data: np.ndarray,
|
|
819
|
-
col_names:
|
|
820
|
-
row_names:
|
|
821
|
-
matrix_kind:
|
|
887
|
+
col_names: Sequence[str] | None = None,
|
|
888
|
+
row_names: Sequence[str] | None = None,
|
|
889
|
+
matrix_kind: MatrixKind = MatrixKind.Full,
|
|
822
890
|
diagonal: bool = True,
|
|
823
891
|
row_header_col_name: str = "ROI",
|
|
892
|
+
processed_element: dict[str, str] | None = None,
|
|
824
893
|
) -> None:
|
|
825
894
|
"""Store matrix.
|
|
826
895
|
|
|
@@ -839,28 +908,17 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
839
908
|
The column labels (default None).
|
|
840
909
|
row_names : list-like of str, optional
|
|
841
910
|
The row labels (default None).
|
|
842
|
-
matrix_kind :
|
|
843
|
-
The kind
|
|
844
|
-
|
|
845
|
-
* ``triu`` : store upper triangular only
|
|
846
|
-
* ``tril`` : store lower triangular
|
|
847
|
-
* ``full`` : full matrix
|
|
848
|
-
|
|
849
|
-
(default "full").
|
|
911
|
+
matrix_kind : :enum:`.MatrixKind`, optional
|
|
912
|
+
The matrix kind (default ``MatrixKind.Full``).
|
|
850
913
|
diagonal : bool, optional
|
|
851
|
-
Whether to store the diagonal. If ``matrix_kind
|
|
914
|
+
Whether to store the diagonal. If ``matrix_kind=MatrixKind.Full``,
|
|
852
915
|
setting this to False will raise an error (default True).
|
|
853
916
|
row_header_col_name : str, optional
|
|
854
917
|
The column name for the row header column (default "ROI").
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
If invalid ``matrix_kind`` is provided, ``diagonal = False``
|
|
860
|
-
for ``matrix_kind = "full"``, non-square data is provided
|
|
861
|
-
for ``matrix_kind = {"triu", "tril"}``, length of ``row_names``
|
|
862
|
-
do not match data row count, or length of ``col_names`` do not
|
|
863
|
-
match data column count.
|
|
918
|
+
processed_element : dict or None, optional
|
|
919
|
+
The element being processed when the data was computed, used to
|
|
920
|
+
choose the file of each element if ``single_output=False``. If
|
|
921
|
+
None, ``element`` is used (default None).
|
|
864
922
|
|
|
865
923
|
"""
|
|
866
924
|
# Row data validation
|
|
@@ -879,9 +937,10 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
879
937
|
)
|
|
880
938
|
# Store
|
|
881
939
|
self._store_data(
|
|
882
|
-
kind=
|
|
940
|
+
kind=StorageType.Matrix,
|
|
883
941
|
meta_md5=meta_md5,
|
|
884
942
|
element=[element], # convert to list
|
|
943
|
+
processed_element=processed_element,
|
|
885
944
|
data=data[:, :, np.newaxis], # convert to 3D
|
|
886
945
|
column_headers=col_names,
|
|
887
946
|
row_headers=row_names,
|
|
@@ -893,9 +952,10 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
893
952
|
def store_vector(
|
|
894
953
|
self,
|
|
895
954
|
meta_md5: str,
|
|
896
|
-
element: dict,
|
|
897
|
-
data:
|
|
898
|
-
col_names:
|
|
955
|
+
element: dict[str, str],
|
|
956
|
+
data: np.ndarray | list,
|
|
957
|
+
col_names: Sequence[str] | None = None,
|
|
958
|
+
processed_element: dict[str, str] | None = None,
|
|
899
959
|
) -> None:
|
|
900
960
|
"""Store vector.
|
|
901
961
|
|
|
@@ -909,6 +969,10 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
909
969
|
The vector data to store.
|
|
910
970
|
col_names : list-like of str, optional
|
|
911
971
|
The column labels (default None).
|
|
972
|
+
processed_element : dict or None, optional
|
|
973
|
+
The element being processed when the data was computed, used to
|
|
974
|
+
choose the file of each element if ``single_output=False``. If
|
|
975
|
+
None, ``element`` is used (default None).
|
|
912
976
|
|
|
913
977
|
"""
|
|
914
978
|
if isinstance(data, list):
|
|
@@ -926,9 +990,10 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
926
990
|
processed_data = data.ravel()
|
|
927
991
|
|
|
928
992
|
self._store_data(
|
|
929
|
-
kind=
|
|
993
|
+
kind=StorageType.Vector,
|
|
930
994
|
meta_md5=meta_md5,
|
|
931
995
|
element=[element], # convert to list
|
|
996
|
+
processed_element=processed_element,
|
|
932
997
|
data=processed_data[:, np.newaxis], # convert to 2D
|
|
933
998
|
column_headers=col_names,
|
|
934
999
|
)
|
|
@@ -938,7 +1003,8 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
938
1003
|
meta_md5: str,
|
|
939
1004
|
element: dict,
|
|
940
1005
|
data: np.ndarray,
|
|
941
|
-
col_names:
|
|
1006
|
+
col_names: Sequence[str] | None = None,
|
|
1007
|
+
processed_element: dict[str, str] | None = None,
|
|
942
1008
|
) -> None:
|
|
943
1009
|
"""Store timeseries.
|
|
944
1010
|
|
|
@@ -952,12 +1018,17 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
952
1018
|
The timeseries data to store.
|
|
953
1019
|
col_names : list-like of str, optional
|
|
954
1020
|
The column labels (default None).
|
|
1021
|
+
processed_element : dict or None, optional
|
|
1022
|
+
The element being processed when the data was computed, used to
|
|
1023
|
+
choose the file of each element if ``single_output=False``. If
|
|
1024
|
+
None, ``element`` is used (default None).
|
|
955
1025
|
|
|
956
1026
|
"""
|
|
957
1027
|
self._store_data(
|
|
958
|
-
kind=
|
|
1028
|
+
kind=StorageType.Timeseries,
|
|
959
1029
|
meta_md5=meta_md5,
|
|
960
1030
|
element=[element], # convert to list
|
|
1031
|
+
processed_element=processed_element,
|
|
961
1032
|
data=[data], # convert to list
|
|
962
1033
|
column_headers=col_names,
|
|
963
1034
|
row_header_column_name="timepoint",
|
|
@@ -968,8 +1039,9 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
968
1039
|
meta_md5: str,
|
|
969
1040
|
element: dict,
|
|
970
1041
|
data: np.ndarray,
|
|
971
|
-
col_names:
|
|
972
|
-
row_names:
|
|
1042
|
+
col_names: Sequence[str] | None = None,
|
|
1043
|
+
row_names: Sequence[str] | None = None,
|
|
1044
|
+
processed_element: dict[str, str] | None = None,
|
|
973
1045
|
) -> None:
|
|
974
1046
|
"""Store 2D timeseries.
|
|
975
1047
|
|
|
@@ -985,6 +1057,10 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
985
1057
|
The column labels (default None).
|
|
986
1058
|
row_names : list-like of str, optional
|
|
987
1059
|
The row labels (default None).
|
|
1060
|
+
processed_element : dict or None, optional
|
|
1061
|
+
The element being processed when the data was computed, used to
|
|
1062
|
+
choose the file of each element if ``single_output=False``. If
|
|
1063
|
+
None, ``element`` is used (default None).
|
|
988
1064
|
|
|
989
1065
|
"""
|
|
990
1066
|
store_timeseries_2d_checks(
|
|
@@ -993,9 +1069,10 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
993
1069
|
col_names_len=len(col_names) if col_names is not None else 0,
|
|
994
1070
|
)
|
|
995
1071
|
self._store_data(
|
|
996
|
-
kind=
|
|
1072
|
+
kind=StorageType.Timeseries2D,
|
|
997
1073
|
meta_md5=meta_md5,
|
|
998
1074
|
element=[element], # convert to list
|
|
1075
|
+
processed_element=processed_element,
|
|
999
1076
|
data=[data], # convert to list
|
|
1000
1077
|
column_headers=col_names,
|
|
1001
1078
|
row_headers=row_names,
|
|
@@ -1006,9 +1083,10 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1006
1083
|
meta_md5: str,
|
|
1007
1084
|
element: dict,
|
|
1008
1085
|
data: np.ndarray,
|
|
1009
|
-
col_names:
|
|
1010
|
-
row_names:
|
|
1011
|
-
row_header_col_name:
|
|
1086
|
+
col_names: Sequence[str] | None = None,
|
|
1087
|
+
row_names: Sequence[str] | None = None,
|
|
1088
|
+
row_header_col_name: str | None = "feature",
|
|
1089
|
+
processed_element: dict[str, str] | None = None,
|
|
1012
1090
|
) -> None:
|
|
1013
1091
|
"""Store table with scalar values.
|
|
1014
1092
|
|
|
@@ -1026,12 +1104,17 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1026
1104
|
The row labels (default None).
|
|
1027
1105
|
row_header_col_name : str, optional
|
|
1028
1106
|
The column name for the row header column (default "feature").
|
|
1107
|
+
processed_element : dict or None, optional
|
|
1108
|
+
The element being processed when the data was computed, used to
|
|
1109
|
+
choose the file of each element if ``single_output=False``. If
|
|
1110
|
+
None, ``element`` is used (default None).
|
|
1029
1111
|
|
|
1030
1112
|
"""
|
|
1031
1113
|
self._store_data(
|
|
1032
|
-
kind=
|
|
1114
|
+
kind=StorageType.ScalarTable,
|
|
1033
1115
|
meta_md5=meta_md5,
|
|
1034
1116
|
element=[element], # convert to list
|
|
1117
|
+
processed_element=processed_element,
|
|
1035
1118
|
data=[data], # convert to list
|
|
1036
1119
|
column_headers=col_names,
|
|
1037
1120
|
row_headers=row_names,
|
|
@@ -1065,8 +1148,12 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1065
1148
|
logger.info(
|
|
1066
1149
|
f"Collecting metadata from {self.uri.parent}/*_{self.uri.name}"
|
|
1067
1150
|
)
|
|
1068
|
-
# Collect element files per feature MD5
|
|
1151
|
+
# Collect element files per feature MD5, only the ones with elements
|
|
1152
|
+
# of the feature that are not in other files: the data of an element
|
|
1153
|
+
# can be in the files of several elements being processed (e.g., the
|
|
1154
|
+
# data of a subject in the files of each of its tasks)
|
|
1069
1155
|
elements_per_feature_md5 = defaultdict(list)
|
|
1156
|
+
seen_elements = defaultdict(set)
|
|
1070
1157
|
out_metadata = {}
|
|
1071
1158
|
for file_ in tqdm(
|
|
1072
1159
|
self.uri.parent.glob(f"*_{self.uri.name}"), desc="file-metadata"
|
|
@@ -1083,7 +1170,28 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1083
1170
|
out_metadata.update(in_metadata)
|
|
1084
1171
|
|
|
1085
1172
|
# Update element files for found MD5s
|
|
1086
|
-
|
|
1173
|
+
file_elements = _read_elements(str(file_), list(in_metadata))
|
|
1174
|
+
for feature_md5, elements in file_elements.items():
|
|
1175
|
+
# Only the files of single elements can be collected
|
|
1176
|
+
if len(elements) != 1:
|
|
1177
|
+
raise_error(
|
|
1178
|
+
msg=(
|
|
1179
|
+
f"The file {file_} has {len(elements)} elements "
|
|
1180
|
+
f"for the feature {feature_md5}, but only the "
|
|
1181
|
+
"files with one element (as stored for each "
|
|
1182
|
+
"element with `single_output=False`) can be "
|
|
1183
|
+
"collected"
|
|
1184
|
+
),
|
|
1185
|
+
klass=RuntimeError,
|
|
1186
|
+
)
|
|
1187
|
+
(element,) = elements
|
|
1188
|
+
if element in seen_elements[feature_md5]:
|
|
1189
|
+
logger.debug(
|
|
1190
|
+
f"Skipping {file_} for {feature_md5}: its element "
|
|
1191
|
+
"is in another file"
|
|
1192
|
+
)
|
|
1193
|
+
continue
|
|
1194
|
+
seen_elements[feature_md5].add(element)
|
|
1087
1195
|
elements_per_feature_md5[feature_md5].append(file_)
|
|
1088
1196
|
|
|
1089
1197
|
logger.info("Writing metadata to HDF5 file ...")
|
|
@@ -1096,7 +1204,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1096
1204
|
|
|
1097
1205
|
# Run loop to collect data per feature per file
|
|
1098
1206
|
logger.info(
|
|
1099
|
-
f"Collecting data from {self.uri.parent}/*_{self.uri.name}"
|
|
1207
|
+
f"Collecting data from {self.uri.parent}/*_{self.uri.name}"
|
|
1100
1208
|
)
|
|
1101
1209
|
logger.info(f"Will collect {len(elements_per_feature_md5)} features.")
|
|
1102
1210
|
|
|
@@ -1119,6 +1227,13 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1119
1227
|
elements = []
|
|
1120
1228
|
static_data = None
|
|
1121
1229
|
kind = None
|
|
1230
|
+
# The data is written in chunks to a temporary key (h5io would
|
|
1231
|
+
# delete the previous chunks), and moved to the feature at the end
|
|
1232
|
+
fname = str(self.uri.resolve())
|
|
1233
|
+
data_key = f"collecting_{feature_md5}"
|
|
1234
|
+
with h5py.File(fname, mode="a") as fid:
|
|
1235
|
+
if data_key in fid:
|
|
1236
|
+
del fid[data_key]
|
|
1122
1237
|
for file_ in tqdm(element_files, desc="file-data"):
|
|
1123
1238
|
logger.debug(
|
|
1124
1239
|
f"Reading feature MD5: '{feature_md5}' "
|
|
@@ -1126,11 +1241,7 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1126
1241
|
)
|
|
1127
1242
|
|
|
1128
1243
|
# Read the data
|
|
1129
|
-
t_data =
|
|
1130
|
-
fname=str(file_),
|
|
1131
|
-
title=feature_md5,
|
|
1132
|
-
slash="ignore",
|
|
1133
|
-
)
|
|
1244
|
+
t_data = _read_hdf5(fname=str(file_), title=feature_md5)
|
|
1134
1245
|
if i_file == 0:
|
|
1135
1246
|
# Store the "static" data
|
|
1136
1247
|
static_data = {
|
|
@@ -1150,35 +1261,32 @@ class HDF5FeatureStorage(BaseFeatureStorage):
|
|
|
1150
1261
|
i_file += 1
|
|
1151
1262
|
if (i_file % t_chunk_size == 0) or i_file == element_count:
|
|
1152
1263
|
# If we have reached the chunk size or the end of the
|
|
1153
|
-
# elements, write the data
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
# Write data in chunks to avoid memory usage spikes
|
|
1159
|
-
# Start with the case for 2D
|
|
1160
|
-
# Write chunked array
|
|
1161
|
-
to_write["data"] = _create_chunk(
|
|
1264
|
+
# elements, write the data in chunks to avoid memory
|
|
1265
|
+
# usage spikes
|
|
1266
|
+
_write_chunk(
|
|
1267
|
+
fname=fname,
|
|
1268
|
+
key=data_key,
|
|
1162
1269
|
chunk_data=chunk_data,
|
|
1163
1270
|
kind=kind,
|
|
1164
1271
|
element_count=element_count,
|
|
1165
1272
|
chunk_size=t_chunk_size,
|
|
1166
1273
|
i_chunk=i_chunk,
|
|
1167
1274
|
)
|
|
1168
|
-
if i_file == element_count:
|
|
1169
|
-
to_write["element"] = elements
|
|
1170
|
-
|
|
1171
|
-
# Write to HDF5
|
|
1172
|
-
write_hdf5(
|
|
1173
|
-
fname=str(self.uri.resolve()),
|
|
1174
|
-
data=to_write,
|
|
1175
|
-
overwrite="update",
|
|
1176
|
-
compression=0,
|
|
1177
|
-
title=feature_md5,
|
|
1178
|
-
slash="error",
|
|
1179
|
-
use_json=False,
|
|
1180
|
-
)
|
|
1181
1275
|
|
|
1182
1276
|
# Increment counters and data
|
|
1183
1277
|
i_chunk += 1
|
|
1184
1278
|
chunk_data = []
|
|
1279
|
+
|
|
1280
|
+
# Write the feature without the data
|
|
1281
|
+
write_hdf5(
|
|
1282
|
+
fname=fname,
|
|
1283
|
+
data={**static_data, "element": elements},
|
|
1284
|
+
overwrite="update",
|
|
1285
|
+
compression=0,
|
|
1286
|
+
title=feature_md5,
|
|
1287
|
+
slash="error",
|
|
1288
|
+
use_json=False,
|
|
1289
|
+
)
|
|
1290
|
+
# Move the data to the feature (as written by h5io)
|
|
1291
|
+
with h5py.File(fname, mode="a") as fid:
|
|
1292
|
+
fid.move(data_key, f"{feature_md5}/key_data")
|