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
|
@@ -1,767 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Authors: Eric Larson <larson.eric.d@gmail.com>
|
|
3
|
-
#
|
|
4
|
-
# License: BSD (3-clause)
|
|
5
|
-
|
|
6
|
-
import datetime
|
|
7
|
-
import json
|
|
8
|
-
import tempfile
|
|
9
|
-
from shutil import rmtree
|
|
10
|
-
from os import path as op
|
|
11
|
-
from io import UnsupportedOperation
|
|
12
|
-
|
|
13
|
-
import numpy as np
|
|
14
|
-
|
|
15
|
-
from .chunked_array import ChunkedArray
|
|
16
|
-
from .chunked_list import ChunkedList
|
|
17
|
-
|
|
18
|
-
special_chars = {'{FWDSLASH}': '/'}
|
|
19
|
-
tab_str = '----'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _import_sparse():
|
|
23
|
-
try:
|
|
24
|
-
from scipy import sparse
|
|
25
|
-
except ImportError:
|
|
26
|
-
sparse = None
|
|
27
|
-
return sparse
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
##############################################################################
|
|
31
|
-
# WRITING
|
|
32
|
-
|
|
33
|
-
def _check_h5py():
|
|
34
|
-
"""Helper to check if h5py is installed"""
|
|
35
|
-
try:
|
|
36
|
-
import h5py
|
|
37
|
-
except ImportError:
|
|
38
|
-
raise ImportError('the h5py module is required to use HDF5 I/O')
|
|
39
|
-
return h5py
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _create_titled_group(root, key, title):
|
|
43
|
-
"""Helper to create a titled group in h5py"""
|
|
44
|
-
out = root.create_group(key)
|
|
45
|
-
out.attrs['TITLE'] = title
|
|
46
|
-
return out
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _create_titled_dataset(root, key, title, data, comp_kw=None):
|
|
50
|
-
"""Helper to create a titled dataset in h5py"""
|
|
51
|
-
comp_kw = {} if comp_kw is None else comp_kw
|
|
52
|
-
out = root.create_dataset(key, data=data, **comp_kw)
|
|
53
|
-
out.attrs['TITLE'] = title
|
|
54
|
-
return out
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _create_pandas_dataset(fname, root, key, title, data):
|
|
58
|
-
h5py = _check_h5py()
|
|
59
|
-
rootpath = '/'.join([root, key])
|
|
60
|
-
data.to_hdf(fname, rootpath)
|
|
61
|
-
with h5py.File(fname, mode='a') as fid:
|
|
62
|
-
fid[rootpath].attrs['TITLE'] = 'pd_dataframe'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _create_write_chunked_array(root, key, name, value):
|
|
66
|
-
if key not in root:
|
|
67
|
-
dset = root.create_dataset(key, value.shape, chunks=value.chunk_size)
|
|
68
|
-
dset.attrs['TITLE'] = name
|
|
69
|
-
else:
|
|
70
|
-
dset = root[key]
|
|
71
|
-
stored_shape = dset.shape
|
|
72
|
-
stored_chunk_size = dset.chunks
|
|
73
|
-
if np.all(stored_shape != value.shape):
|
|
74
|
-
raise ValueError(
|
|
75
|
-
f"The stored shape ({stored_shape}) does not match the given "
|
|
76
|
-
f"shape ({value.shape})."
|
|
77
|
-
)
|
|
78
|
-
if np.all(stored_chunk_size != value.chunk_size):
|
|
79
|
-
raise ValueError(
|
|
80
|
-
f"The stored chunk size ({stored_chunk_size}) does not match "
|
|
81
|
-
f"the given chunk size ({value.chunk_size})."
|
|
82
|
-
)
|
|
83
|
-
dset[value.chunkslice] = value.chunkdata
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def write_hdf5(fname, data, overwrite=False, compression=4,
|
|
87
|
-
title='h5io', slash='error', use_json=False):
|
|
88
|
-
"""Write python object to HDF5 format using h5py.
|
|
89
|
-
|
|
90
|
-
Parameters
|
|
91
|
-
----------
|
|
92
|
-
fname : str
|
|
93
|
-
Filename to use.
|
|
94
|
-
data : object
|
|
95
|
-
Object to write. Can be of any of these types:
|
|
96
|
-
|
|
97
|
-
{ndarray, dict, list, tuple, int, float, str, datetime, timezone}
|
|
98
|
-
|
|
99
|
-
Note that dict objects must only have ``str`` keys. It is recommended
|
|
100
|
-
to use ndarrays where possible, as it is handled most efficiently.
|
|
101
|
-
overwrite : True | False | 'update'
|
|
102
|
-
If True, overwrite file (if it exists). If 'update', appends the title
|
|
103
|
-
to the file (or replace value if title exists).
|
|
104
|
-
compression : int
|
|
105
|
-
Compression level to use (0-9) to compress data using gzip.
|
|
106
|
-
title : str
|
|
107
|
-
The top-level directory name to use. Typically it is useful to make
|
|
108
|
-
this your package name, e.g. ``'mnepython'``.
|
|
109
|
-
slash : 'error' | 'replace'
|
|
110
|
-
Whether to replace forward-slashes ('/') in any key found nested within
|
|
111
|
-
keys in data. This does not apply to the top level name (title).
|
|
112
|
-
If 'error', '/' is not allowed in any lower-level keys.
|
|
113
|
-
use_json : bool
|
|
114
|
-
To accelerate the read and write performance of small dictionaries and
|
|
115
|
-
lists they can be combined to JSON objects and stored as strings.
|
|
116
|
-
"""
|
|
117
|
-
h5py = _check_h5py()
|
|
118
|
-
if isinstance(fname, str):
|
|
119
|
-
mode = 'w'
|
|
120
|
-
if op.isfile(fname):
|
|
121
|
-
if isinstance(overwrite, str):
|
|
122
|
-
if overwrite != 'update':
|
|
123
|
-
raise ValueError('overwrite must be "update" or a bool')
|
|
124
|
-
mode = 'a'
|
|
125
|
-
elif not overwrite:
|
|
126
|
-
raise IOError(
|
|
127
|
-
'file "%s" exists, use overwrite=True to overwrite' % fname
|
|
128
|
-
)
|
|
129
|
-
elif isinstance(fname, h5py.File):
|
|
130
|
-
if fname.mode == 'r':
|
|
131
|
-
raise UnsupportedOperation('not writable')
|
|
132
|
-
else:
|
|
133
|
-
raise ValueError(f'fname must be str or h5py.File, got {type(fname)}')
|
|
134
|
-
if not isinstance(title, str):
|
|
135
|
-
raise ValueError('title must be a string')
|
|
136
|
-
comp_kw = dict()
|
|
137
|
-
if compression > 0:
|
|
138
|
-
comp_kw = dict(compression='gzip', compression_opts=compression)
|
|
139
|
-
|
|
140
|
-
def _write(fid, cleanup_data):
|
|
141
|
-
# if title in fid and not isinstance(data, ChunkedArray):
|
|
142
|
-
# del fid[title]
|
|
143
|
-
_triage_write(title, data, fid, comp_kw, str(type(data)),
|
|
144
|
-
cleanup_data, slash=slash, title=title,
|
|
145
|
-
use_json=use_json)
|
|
146
|
-
cleanup_data = []
|
|
147
|
-
if isinstance(fname, h5py.File):
|
|
148
|
-
_write(fname, cleanup_data)
|
|
149
|
-
else:
|
|
150
|
-
with h5py.File(fname, mode=mode) as fid:
|
|
151
|
-
_write(fid, cleanup_data)
|
|
152
|
-
|
|
153
|
-
# Will not be empty if any extra data to be written
|
|
154
|
-
for data in cleanup_data:
|
|
155
|
-
# In case different extra I/O needs different inputs
|
|
156
|
-
title = list(data.keys())[0]
|
|
157
|
-
if title in ['pd_dataframe', 'pd_series']:
|
|
158
|
-
rootname, key, value = data[title]
|
|
159
|
-
_create_pandas_dataset(fname, rootname, key, title, value)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _get_sub_root(root, key, title):
|
|
163
|
-
"""Helper to get a sub-root in h5py"""
|
|
164
|
-
if key not in root:
|
|
165
|
-
sub_root = _create_titled_group(root, key, title)
|
|
166
|
-
else:
|
|
167
|
-
sub_root = root[key]
|
|
168
|
-
if sub_root.attrs["TITLE"] != title:
|
|
169
|
-
# If the title is wrong, delete the old one and create a new one
|
|
170
|
-
del root[key]
|
|
171
|
-
sub_root = _create_titled_group(root, key, title)
|
|
172
|
-
return sub_root
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def _triage_write(key, value, root, comp_kw, where,
|
|
176
|
-
cleanup_data, slash='error', title=None,
|
|
177
|
-
use_json=False):
|
|
178
|
-
sparse = _import_sparse()
|
|
179
|
-
if key != title and '/' in key:
|
|
180
|
-
if slash == 'error':
|
|
181
|
-
raise ValueError('Found a key with "/", '
|
|
182
|
-
'this is not allowed if slash == error')
|
|
183
|
-
elif slash == 'replace':
|
|
184
|
-
# Auto-replace keys with proper values
|
|
185
|
-
for key_spec, val_spec in special_chars.items():
|
|
186
|
-
key = key.replace(val_spec, key_spec)
|
|
187
|
-
else:
|
|
188
|
-
raise ValueError("slash must be one of ['error', 'replace'")
|
|
189
|
-
|
|
190
|
-
if use_json and isinstance(value, (list, dict)) and \
|
|
191
|
-
_json_compatible(value, slash=slash):
|
|
192
|
-
value = np.frombuffer(json.dumps(value).encode('utf-8'), np.uint8)
|
|
193
|
-
if key in root:
|
|
194
|
-
del root[key]
|
|
195
|
-
_create_titled_dataset(root, key, 'json', value, comp_kw)
|
|
196
|
-
elif isinstance(value, dict):
|
|
197
|
-
sub_root = _get_sub_root(root, key, 'dict')
|
|
198
|
-
for key, sub_value in value.items():
|
|
199
|
-
if not isinstance(key, str):
|
|
200
|
-
raise TypeError('All dict keys must be strings')
|
|
201
|
-
_triage_write(
|
|
202
|
-
'key_{0}'.format(key), sub_value, sub_root, comp_kw,
|
|
203
|
-
where + '["%s"]' % key, cleanup_data=cleanup_data, slash=slash)
|
|
204
|
-
# Delete any keys that are no longer in the dict
|
|
205
|
-
for key, subnode in sub_root.items():
|
|
206
|
-
if slash == 'replace':
|
|
207
|
-
for key_spec, val_spec in special_chars.items():
|
|
208
|
-
key = key.replace(key_spec, val_spec)
|
|
209
|
-
if key[4:] not in value:
|
|
210
|
-
del sub_root[key]
|
|
211
|
-
elif isinstance(value, (list, tuple)):
|
|
212
|
-
title = 'list' if isinstance(value, list) else 'tuple'
|
|
213
|
-
sub_root = _get_sub_root(root, key, title)
|
|
214
|
-
for vi, sub_value in enumerate(value):
|
|
215
|
-
_triage_write(
|
|
216
|
-
'idx_{0}'.format(vi), sub_value, sub_root, comp_kw,
|
|
217
|
-
where + '[%s]' % vi, cleanup_data=cleanup_data, slash=slash)
|
|
218
|
-
# Delete any keys that are no longer in the list
|
|
219
|
-
ii = len(value)
|
|
220
|
-
while True:
|
|
221
|
-
t_key = 'idx_{0}'.format(ii)
|
|
222
|
-
subnode = sub_root.get(t_key, None)
|
|
223
|
-
if subnode is None:
|
|
224
|
-
break
|
|
225
|
-
del sub_root[t_key]
|
|
226
|
-
elif isinstance(value, ChunkedList):
|
|
227
|
-
sub_root = _get_sub_root(root, key, 'chunkedlist')
|
|
228
|
-
if 'SIZE' not in sub_root.attrs:
|
|
229
|
-
sub_root.attrs["SIZE"] = value.size
|
|
230
|
-
elif sub_root.attrs["SIZE"] != value.size:
|
|
231
|
-
# Size mismatch, asume it's a new list
|
|
232
|
-
del root[key]
|
|
233
|
-
sub_root = _get_sub_root(root, key, 'chunkedlist')
|
|
234
|
-
sub_root.attrs["SIZE"] = value.size
|
|
235
|
-
|
|
236
|
-
for vi, sub_value in value.enumerate:
|
|
237
|
-
_triage_write(
|
|
238
|
-
'idx_{0}'.format(vi), sub_value, sub_root, comp_kw,
|
|
239
|
-
where + '[%s]' % vi, cleanup_data=cleanup_data, slash=slash)
|
|
240
|
-
|
|
241
|
-
elif isinstance(value, type(None)):
|
|
242
|
-
if key in root:
|
|
243
|
-
del root[key]
|
|
244
|
-
_create_titled_dataset(root, key, 'None', [False])
|
|
245
|
-
elif isinstance(value, (int, float)):
|
|
246
|
-
if key in root:
|
|
247
|
-
del root[key]
|
|
248
|
-
if isinstance(value, int):
|
|
249
|
-
title = 'int'
|
|
250
|
-
else: # isinstance(value, float):
|
|
251
|
-
title = 'float'
|
|
252
|
-
_create_titled_dataset(root, key, title, np.atleast_1d(value))
|
|
253
|
-
elif isinstance(value, datetime.datetime):
|
|
254
|
-
title = 'datetime'
|
|
255
|
-
value = np.frombuffer(value.isoformat().encode('utf-8'), np.uint8)
|
|
256
|
-
if key in root:
|
|
257
|
-
del root[key]
|
|
258
|
-
_create_titled_dataset(root, key, title, value)
|
|
259
|
-
elif isinstance(value, datetime.timezone):
|
|
260
|
-
title = 'timezone' # the __repr__ is complete
|
|
261
|
-
value = np.frombuffer(repr(value).encode('utf-8'), np.uint8)
|
|
262
|
-
if key in root:
|
|
263
|
-
del root[key]
|
|
264
|
-
_create_titled_dataset(root, key, title, value)
|
|
265
|
-
elif isinstance(value, (np.integer, np.floating, np.bool_)):
|
|
266
|
-
title = 'np_{0}'.format(value.__class__.__name__)
|
|
267
|
-
if key in root:
|
|
268
|
-
del root[key]
|
|
269
|
-
_create_titled_dataset(root, key, title, np.atleast_1d(value))
|
|
270
|
-
elif isinstance(value, str):
|
|
271
|
-
if isinstance(value, str): # unicode
|
|
272
|
-
value = np.frombuffer(value.encode('utf-8'), np.uint8)
|
|
273
|
-
title = 'unicode'
|
|
274
|
-
else:
|
|
275
|
-
value = np.frombuffer(value.encode('ASCII'), np.uint8)
|
|
276
|
-
title = 'ascii'
|
|
277
|
-
if key in root:
|
|
278
|
-
del root[key]
|
|
279
|
-
_create_titled_dataset(root, key, title, value, comp_kw)
|
|
280
|
-
elif isinstance(value, np.ndarray):
|
|
281
|
-
if not (value.dtype == np.dtype('object') and
|
|
282
|
-
len(set([sub.dtype for sub in value])) == 1):
|
|
283
|
-
if key in root:
|
|
284
|
-
del root[key]
|
|
285
|
-
_create_titled_dataset(root, key, 'ndarray', value)
|
|
286
|
-
else:
|
|
287
|
-
ma_index, ma_data = multiarray_dump(value)
|
|
288
|
-
if key in root:
|
|
289
|
-
del root[key]
|
|
290
|
-
sub_root = _create_titled_group(root, key, 'multiarray')
|
|
291
|
-
_create_titled_dataset(sub_root, 'index', 'ndarray', ma_index)
|
|
292
|
-
_create_titled_dataset(sub_root, 'data', 'ndarray', ma_data)
|
|
293
|
-
elif sparse is not None and isinstance(value, sparse.csc_matrix):
|
|
294
|
-
if key in root:
|
|
295
|
-
del root[key]
|
|
296
|
-
sub_root = _create_titled_group(root, key, 'csc_matrix')
|
|
297
|
-
_triage_write('data', value.data, sub_root, comp_kw,
|
|
298
|
-
where + '.csc_matrix_data', cleanup_data=cleanup_data,
|
|
299
|
-
slash=slash)
|
|
300
|
-
_triage_write('indices', value.indices, sub_root, comp_kw,
|
|
301
|
-
where + '.csc_matrix_indices', cleanup_data=cleanup_data,
|
|
302
|
-
slash=slash)
|
|
303
|
-
_triage_write('indptr', value.indptr, sub_root, comp_kw,
|
|
304
|
-
where + '.csc_matrix_indptr', cleanup_data=cleanup_data,
|
|
305
|
-
slash=slash)
|
|
306
|
-
elif sparse is not None and isinstance(value, sparse.csr_matrix):
|
|
307
|
-
if key in root:
|
|
308
|
-
del root[key]
|
|
309
|
-
sub_root = _create_titled_group(root, key, 'csr_matrix')
|
|
310
|
-
_triage_write('data', value.data, sub_root, comp_kw,
|
|
311
|
-
where + '.csr_matrix_data', cleanup_data=cleanup_data,
|
|
312
|
-
slash=slash)
|
|
313
|
-
_triage_write('indices', value.indices, sub_root, comp_kw,
|
|
314
|
-
where + '.csr_matrix_indices', cleanup_data=cleanup_data,
|
|
315
|
-
slash=slash)
|
|
316
|
-
_triage_write('indptr', value.indptr, sub_root, comp_kw,
|
|
317
|
-
where + '.csr_matrix_indptr', cleanup_data=cleanup_data,
|
|
318
|
-
slash=slash)
|
|
319
|
-
_triage_write('shape', value.shape, sub_root, comp_kw,
|
|
320
|
-
where + '.csr_matrix_shape', cleanup_data=cleanup_data,
|
|
321
|
-
slash=slash)
|
|
322
|
-
elif isinstance(value, ChunkedArray):
|
|
323
|
-
_create_write_chunked_array(root, key, 'chunkedarray', value)
|
|
324
|
-
else:
|
|
325
|
-
try:
|
|
326
|
-
from pandas import DataFrame, Series
|
|
327
|
-
except ImportError:
|
|
328
|
-
pass
|
|
329
|
-
else:
|
|
330
|
-
if isinstance(value, (DataFrame, Series)):
|
|
331
|
-
if isinstance(value, DataFrame):
|
|
332
|
-
title = 'pd_dataframe'
|
|
333
|
-
else:
|
|
334
|
-
title = 'pd_series'
|
|
335
|
-
rootname = root.name
|
|
336
|
-
cleanup_data.append({title: (rootname, key, value)})
|
|
337
|
-
return
|
|
338
|
-
|
|
339
|
-
err_str = 'unsupported type %s (in %s)' % (type(value), where)
|
|
340
|
-
raise TypeError(err_str)
|
|
341
|
-
|
|
342
|
-
##############################################################################
|
|
343
|
-
# READING
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
def read_hdf5(fname, title='h5io', slash='ignore'):
|
|
347
|
-
"""Read python object from HDF5 format using h5py
|
|
348
|
-
|
|
349
|
-
Parameters
|
|
350
|
-
----------
|
|
351
|
-
fname : str
|
|
352
|
-
File to load.
|
|
353
|
-
title : str
|
|
354
|
-
The top-level directory name to use. Typically it is useful to make
|
|
355
|
-
this your package name, e.g. ``'mnepython'``.
|
|
356
|
-
slash : 'ignore' | 'replace'
|
|
357
|
-
Whether to replace the string {FWDSLASH} with the value /. This does
|
|
358
|
-
not apply to the top level name (title). If 'ignore', nothing will be
|
|
359
|
-
replaced.
|
|
360
|
-
|
|
361
|
-
Returns
|
|
362
|
-
-------
|
|
363
|
-
data : object
|
|
364
|
-
The loaded data. Can be of any type supported by ``write_hdf5``.
|
|
365
|
-
"""
|
|
366
|
-
h5py = _check_h5py()
|
|
367
|
-
if isinstance(fname, str):
|
|
368
|
-
if not op.isfile(fname):
|
|
369
|
-
raise IOError('file "%s" not found' % fname)
|
|
370
|
-
elif isinstance(fname, h5py.File):
|
|
371
|
-
if fname.mode == 'w':
|
|
372
|
-
raise UnsupportedOperation(
|
|
373
|
-
'file must not be opened be opened with "w"'
|
|
374
|
-
)
|
|
375
|
-
print(fname.mode)
|
|
376
|
-
else:
|
|
377
|
-
raise ValueError(f'fname must be str or h5py.File, got {type(fname)}')
|
|
378
|
-
if not isinstance(title, str):
|
|
379
|
-
raise ValueError('title must be a string')
|
|
380
|
-
|
|
381
|
-
def _read(fid):
|
|
382
|
-
if title not in fid:
|
|
383
|
-
raise ValueError('no "%s" data found' % title)
|
|
384
|
-
if isinstance(fid[title], h5py.Group):
|
|
385
|
-
if 'TITLE' not in fid[title].attrs:
|
|
386
|
-
raise ValueError('no "%s" data found' % title)
|
|
387
|
-
return _triage_read(fid[title], slash=slash)
|
|
388
|
-
if isinstance(fname, h5py.File):
|
|
389
|
-
return _read(fname)
|
|
390
|
-
else:
|
|
391
|
-
with h5py.File(fname, mode='r') as fid:
|
|
392
|
-
return _read(fid)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
def _triage_read(node, slash='ignore'):
|
|
396
|
-
if slash not in ['ignore', 'replace']:
|
|
397
|
-
raise ValueError("slash must be one of 'replace', 'ignore'")
|
|
398
|
-
h5py = _check_h5py()
|
|
399
|
-
sparse = _import_sparse()
|
|
400
|
-
type_str = node.attrs['TITLE']
|
|
401
|
-
if isinstance(type_str, bytes):
|
|
402
|
-
type_str = type_str.decode()
|
|
403
|
-
if isinstance(node, h5py.Group):
|
|
404
|
-
if type_str == 'dict':
|
|
405
|
-
data = dict()
|
|
406
|
-
for key, subnode in node.items():
|
|
407
|
-
if slash == 'replace':
|
|
408
|
-
for key_spec, val_spec in special_chars.items():
|
|
409
|
-
key = key.replace(key_spec, val_spec)
|
|
410
|
-
data[key[4:]] = _triage_read(subnode, slash=slash)
|
|
411
|
-
elif type_str in ['list', 'tuple']:
|
|
412
|
-
data = list()
|
|
413
|
-
ii = 0
|
|
414
|
-
while True:
|
|
415
|
-
subnode = node.get('idx_{0}'.format(ii), None)
|
|
416
|
-
if subnode is None:
|
|
417
|
-
break
|
|
418
|
-
data.append(_triage_read(subnode, slash=slash))
|
|
419
|
-
ii += 1
|
|
420
|
-
assert len(data) == ii
|
|
421
|
-
data = tuple(data) if type_str == 'tuple' else data
|
|
422
|
-
return data
|
|
423
|
-
elif type_str == 'chunkedlist':
|
|
424
|
-
data = list()
|
|
425
|
-
ii = 0
|
|
426
|
-
n_elems = node.attrs['SIZE']
|
|
427
|
-
for ii in range(n_elems):
|
|
428
|
-
subnode = node.get('idx_{0}'.format(ii), None)
|
|
429
|
-
if subnode is None:
|
|
430
|
-
data.append(None)
|
|
431
|
-
else:
|
|
432
|
-
data.append(_triage_read(subnode, slash=slash))
|
|
433
|
-
assert len(data) == n_elems
|
|
434
|
-
elif type_str == 'csc_matrix':
|
|
435
|
-
if sparse is None:
|
|
436
|
-
raise RuntimeError('scipy must be installed to read this data')
|
|
437
|
-
data = sparse.csc_matrix((_triage_read(node['data'], slash=slash),
|
|
438
|
-
_triage_read(node['indices'],
|
|
439
|
-
slash=slash),
|
|
440
|
-
_triage_read(node['indptr'],
|
|
441
|
-
slash=slash)))
|
|
442
|
-
elif type_str == 'csr_matrix':
|
|
443
|
-
if sparse is None:
|
|
444
|
-
raise RuntimeError('scipy must be installed to read this data')
|
|
445
|
-
data = sparse.csr_matrix((_triage_read(node['data'], slash=slash),
|
|
446
|
-
_triage_read(node['indices'],
|
|
447
|
-
slash=slash),
|
|
448
|
-
_triage_read(node['indptr'],
|
|
449
|
-
slash=slash)),
|
|
450
|
-
shape=_triage_read(node['shape']))
|
|
451
|
-
elif type_str in ['pd_dataframe', 'pd_series']:
|
|
452
|
-
from pandas import read_hdf, HDFStore
|
|
453
|
-
rootname = node.name
|
|
454
|
-
filename = node.file.filename
|
|
455
|
-
with HDFStore(filename, 'r') as tmpf:
|
|
456
|
-
data = read_hdf(tmpf, rootname)
|
|
457
|
-
elif type_str == 'multiarray':
|
|
458
|
-
ma_index = _triage_read(node.get('index', None), slash=slash)
|
|
459
|
-
ma_data = _triage_read(node.get('data', None), slash=slash)
|
|
460
|
-
data = multiarray_load(ma_index, ma_data)
|
|
461
|
-
else:
|
|
462
|
-
raise NotImplementedError('Unknown group type: {0}'
|
|
463
|
-
''.format(type_str))
|
|
464
|
-
elif type_str in ('ndarray', 'chunkedarray'):
|
|
465
|
-
data = np.array(node)
|
|
466
|
-
elif type_str in ('int', 'float'):
|
|
467
|
-
cast = int if type_str == 'int' else float
|
|
468
|
-
data = cast(np.array(node)[0])
|
|
469
|
-
elif type_str == 'datetime':
|
|
470
|
-
data = str(np.array(node).tobytes().decode('utf-8'))
|
|
471
|
-
data = datetime.datetime.fromisoformat(data)
|
|
472
|
-
elif type_str == 'timezone':
|
|
473
|
-
data = eval(str(np.array(node).tobytes().decode('utf-8')),
|
|
474
|
-
{'datetime': datetime})
|
|
475
|
-
elif type_str.startswith('np_'):
|
|
476
|
-
np_type = type_str.split('_')[1]
|
|
477
|
-
cast = getattr(np, np_type) if np_type != 'bool' else bool
|
|
478
|
-
data = np.array(node)[0].astype(cast)
|
|
479
|
-
elif type_str in ('unicode', 'ascii', 'str'): # 'str' for backward compat
|
|
480
|
-
decoder = 'utf-8' if type_str == 'unicode' else 'ASCII'
|
|
481
|
-
data = str(np.array(node).tobytes().decode(decoder))
|
|
482
|
-
elif type_str == 'json':
|
|
483
|
-
node_unicode = str(np.array(node).tobytes().decode('utf-8'))
|
|
484
|
-
data = json.loads(node_unicode)
|
|
485
|
-
elif type_str == 'None':
|
|
486
|
-
data = None
|
|
487
|
-
else:
|
|
488
|
-
raise TypeError('Unknown node type: {0}'.format(type_str))
|
|
489
|
-
return data
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
# ############################################################################
|
|
493
|
-
# UTILITIES
|
|
494
|
-
|
|
495
|
-
def _sort_keys(x):
|
|
496
|
-
"""Sort and return keys of dict"""
|
|
497
|
-
keys = list(x.keys()) # note: not thread-safe
|
|
498
|
-
idx = np.argsort([str(k) for k in keys])
|
|
499
|
-
keys = [keys[ii] for ii in idx]
|
|
500
|
-
return keys
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
def object_diff(a, b, pre=''):
|
|
504
|
-
"""Compute all differences between two python variables
|
|
505
|
-
|
|
506
|
-
Parameters
|
|
507
|
-
----------
|
|
508
|
-
a : object
|
|
509
|
-
Currently supported: dict, list, tuple, ndarray, int, str, bytes,
|
|
510
|
-
float.
|
|
511
|
-
b : object
|
|
512
|
-
Must be same type as x1.
|
|
513
|
-
pre : str
|
|
514
|
-
String to prepend to each line.
|
|
515
|
-
|
|
516
|
-
Returns
|
|
517
|
-
-------
|
|
518
|
-
diffs : str
|
|
519
|
-
A string representation of the differences.
|
|
520
|
-
"""
|
|
521
|
-
sparse = _import_sparse()
|
|
522
|
-
try:
|
|
523
|
-
from pandas import DataFrame, Series
|
|
524
|
-
except ImportError:
|
|
525
|
-
DataFrame = Series = type(None)
|
|
526
|
-
|
|
527
|
-
out = ''
|
|
528
|
-
if type(a) != type(b):
|
|
529
|
-
out += pre + ' type mismatch (%s, %s)\n' % (type(a), type(b))
|
|
530
|
-
elif isinstance(a, dict):
|
|
531
|
-
k1s = _sort_keys(a)
|
|
532
|
-
k2s = _sort_keys(b)
|
|
533
|
-
m1 = set(k2s) - set(k1s)
|
|
534
|
-
if len(m1):
|
|
535
|
-
out += pre + ' x1 missing keys %s\n' % (m1)
|
|
536
|
-
for key in k1s:
|
|
537
|
-
if key not in k2s:
|
|
538
|
-
out += pre + ' x2 missing key %s\n' % key
|
|
539
|
-
else:
|
|
540
|
-
out += object_diff(a[key], b[key], pre + 'd1[%s]' % repr(key))
|
|
541
|
-
elif isinstance(a, (list, tuple)):
|
|
542
|
-
if len(a) != len(b):
|
|
543
|
-
out += pre + ' length mismatch (%s, %s)\n' % (len(a), len(b))
|
|
544
|
-
else:
|
|
545
|
-
for xx1, xx2 in zip(a, b):
|
|
546
|
-
out += object_diff(xx1, xx2, pre='')
|
|
547
|
-
elif isinstance(a, (str, int, float, bytes)):
|
|
548
|
-
if a != b:
|
|
549
|
-
out += pre + ' value mismatch (%s, %s)\n' % (a, b)
|
|
550
|
-
elif a is None:
|
|
551
|
-
pass # b must be None due to our type checking
|
|
552
|
-
elif isinstance(a, np.ndarray):
|
|
553
|
-
if not np.array_equal(a, b):
|
|
554
|
-
out += pre + ' array mismatch\n'
|
|
555
|
-
elif sparse is not None and sparse.isspmatrix(a):
|
|
556
|
-
# sparsity and sparse type of b vs a already checked above by type()
|
|
557
|
-
if b.shape != a.shape:
|
|
558
|
-
out += pre + (' sparse matrix a and b shape mismatch'
|
|
559
|
-
'(%s vs %s)' % (a.shape, b.shape))
|
|
560
|
-
else:
|
|
561
|
-
c = a - b
|
|
562
|
-
c.eliminate_zeros()
|
|
563
|
-
if c.nnz > 0:
|
|
564
|
-
out += pre + (' sparse matrix a and b differ on %s '
|
|
565
|
-
'elements' % c.nnz)
|
|
566
|
-
elif isinstance(a, (DataFrame, Series)):
|
|
567
|
-
if b.shape != a.shape:
|
|
568
|
-
out += pre + (' pandas values a and b shape mismatch'
|
|
569
|
-
'(%s vs %s)' % (a.shape, b.shape))
|
|
570
|
-
else:
|
|
571
|
-
c = a.values - b.values
|
|
572
|
-
nzeros = np.sum(c != 0)
|
|
573
|
-
if nzeros > 0:
|
|
574
|
-
out += pre + (' pandas values a and b differ on %s '
|
|
575
|
-
'elements' % nzeros)
|
|
576
|
-
else:
|
|
577
|
-
raise RuntimeError(pre + ': unsupported type %s (%s)' % (type(a), a))
|
|
578
|
-
return out
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
class _TempDir(str):
|
|
582
|
-
"""Class for creating and auto-destroying temp dir
|
|
583
|
-
|
|
584
|
-
This is designed to be used with testing modules. Instances should be
|
|
585
|
-
defined inside test functions. Instances defined at module level can not
|
|
586
|
-
guarantee proper destruction of the temporary directory.
|
|
587
|
-
|
|
588
|
-
When used at module level, the current use of the __del__() method for
|
|
589
|
-
cleanup can fail because the rmtree function may be cleaned up before this
|
|
590
|
-
object (an alternative could be using the atexit module instead).
|
|
591
|
-
"""
|
|
592
|
-
def __new__(self):
|
|
593
|
-
new = str.__new__(self, tempfile.mkdtemp())
|
|
594
|
-
return new
|
|
595
|
-
|
|
596
|
-
def __init__(self):
|
|
597
|
-
self._path = self.__str__()
|
|
598
|
-
|
|
599
|
-
def __del__(self):
|
|
600
|
-
rmtree(self._path, ignore_errors=True)
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
def _list_file_contents(h5file):
|
|
604
|
-
if 'h5io' not in h5file.keys():
|
|
605
|
-
raise ValueError('h5file must contain h5io data')
|
|
606
|
-
|
|
607
|
-
# Set up useful variables for later
|
|
608
|
-
h5file = h5file['h5io']
|
|
609
|
-
root_title = h5file.attrs['TITLE']
|
|
610
|
-
n_space = np.max([(len(key), len(val.attrs['TITLE']))
|
|
611
|
-
for key, val in h5file.items()]) + 2
|
|
612
|
-
|
|
613
|
-
# Create print strings
|
|
614
|
-
strs = ['Root type: %s | Items: %s\n' % (root_title, len(h5file))]
|
|
615
|
-
for key, data in h5file.items():
|
|
616
|
-
type_str = data.attrs['TITLE']
|
|
617
|
-
str_format = '%%-%ss' % n_space
|
|
618
|
-
if type_str == 'ndarray':
|
|
619
|
-
desc = 'Shape: %s'
|
|
620
|
-
desc_val = data.shape
|
|
621
|
-
elif type_str in ['pd_dataframe', 'pd_series']:
|
|
622
|
-
desc = 'Shape: %s'
|
|
623
|
-
desc_val = data['values'].shape
|
|
624
|
-
elif type_str in ('unicode', 'ascii', 'str'):
|
|
625
|
-
desc = 'Text: %s'
|
|
626
|
-
decoder = 'utf-8' if type_str == 'unicode' else 'ASCII'
|
|
627
|
-
data = str(np.array(data).tobytes().decode(decoder))
|
|
628
|
-
desc_val = data[:10] + '...' if len(data) > 10 else data
|
|
629
|
-
else:
|
|
630
|
-
desc = 'Items: %s'
|
|
631
|
-
desc_val = len(data)
|
|
632
|
-
this_str = ('%%s Key: %s | Type: %s | ' + desc) % (
|
|
633
|
-
str_format, str_format, str_format)
|
|
634
|
-
this_str = this_str % (tab_str, key, type_str, desc_val)
|
|
635
|
-
strs.append(this_str)
|
|
636
|
-
out_str = '\n'.join(strs)
|
|
637
|
-
print(out_str)
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
def list_file_contents(h5file):
|
|
641
|
-
"""List the contents of an h5io file.
|
|
642
|
-
|
|
643
|
-
This will list the root and one-level-deep contents of the file.
|
|
644
|
-
|
|
645
|
-
Parameters
|
|
646
|
-
----------
|
|
647
|
-
h5file : str
|
|
648
|
-
The path to an h5io hdf5 file.
|
|
649
|
-
"""
|
|
650
|
-
h5py = _check_h5py()
|
|
651
|
-
err = 'h5file must be an h5py File object, not {0}'
|
|
652
|
-
if isinstance(h5file, str):
|
|
653
|
-
with h5py.File(h5file, 'r') as f:
|
|
654
|
-
_list_file_contents(f)
|
|
655
|
-
else:
|
|
656
|
-
if not isinstance(h5file, h5py.File):
|
|
657
|
-
raise TypeError(err.format(type(h5file)))
|
|
658
|
-
_list_file_contents(h5file)
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
def _json_compatible(obj, slash='error'):
|
|
662
|
-
if isinstance(obj, (str, int, float, bool, type(None))):
|
|
663
|
-
return True
|
|
664
|
-
elif isinstance(obj, list):
|
|
665
|
-
return all([_json_compatible(item) for item in obj])
|
|
666
|
-
elif isinstance(obj, dict):
|
|
667
|
-
_check_keys_in_dict(obj, slash=slash)
|
|
668
|
-
return all([_json_compatible(item) for item in obj.values()])
|
|
669
|
-
else:
|
|
670
|
-
return False
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
def _check_keys_in_dict(obj, slash='error'):
|
|
674
|
-
repl = list()
|
|
675
|
-
for key in obj.keys():
|
|
676
|
-
if '/' in key:
|
|
677
|
-
key_prev = key
|
|
678
|
-
if slash == 'error':
|
|
679
|
-
raise ValueError('Found a key with "/", '
|
|
680
|
-
'this is not allowed if slash == error')
|
|
681
|
-
elif slash == 'replace':
|
|
682
|
-
# Auto-replace keys with proper values
|
|
683
|
-
for key_spec, val_spec in special_chars.items():
|
|
684
|
-
key = key.replace(val_spec, key_spec)
|
|
685
|
-
repl.append((key, key_prev))
|
|
686
|
-
else:
|
|
687
|
-
raise ValueError("slash must be one of ['error', 'replace'")
|
|
688
|
-
for key, key_prev in repl:
|
|
689
|
-
obj[key] = obj.pop(key_prev)
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
##############################################################################
|
|
693
|
-
# Arrays with mixed dimensions
|
|
694
|
-
def _validate_object_array(array):
|
|
695
|
-
if not (array.dtype == np.dtype('object') and
|
|
696
|
-
len(set([sub.dtype for sub in array])) == 1):
|
|
697
|
-
raise TypeError('unsupported array type')
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
def _shape_list(array):
|
|
701
|
-
return [np.shape(sub) for sub in array]
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
def _validate_sub_shapes(shape_lst):
|
|
705
|
-
if not all([shape_lst[0][1:] == t[1:] for t in shape_lst]):
|
|
706
|
-
raise ValueError('shape does not match!')
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
def _array_index(shape_lst):
|
|
710
|
-
return [t[0] for t in shape_lst]
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
def _index_sum(index_lst):
|
|
714
|
-
index_sum_lst = []
|
|
715
|
-
for step in index_lst:
|
|
716
|
-
if index_sum_lst != []:
|
|
717
|
-
index_sum_lst.append(index_sum_lst[-1] + step)
|
|
718
|
-
else:
|
|
719
|
-
index_sum_lst.append(step)
|
|
720
|
-
return index_sum_lst
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
def _merge_array(array):
|
|
724
|
-
merged_lst = []
|
|
725
|
-
for sub in array:
|
|
726
|
-
merged_lst += sub.tolist()
|
|
727
|
-
return np.array(merged_lst)
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
def multiarray_dump(array):
|
|
731
|
-
_validate_object_array(array)
|
|
732
|
-
shape_lst = _shape_list(array)
|
|
733
|
-
_validate_sub_shapes(shape_lst=shape_lst)
|
|
734
|
-
index_sum = _index_sum(index_lst=_array_index(shape_lst=shape_lst))
|
|
735
|
-
return index_sum, _merge_array(array=array)
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
def multiarray_load(index, array_merged):
|
|
739
|
-
array_restore = []
|
|
740
|
-
i_prev = 0
|
|
741
|
-
for i in index[:-1]:
|
|
742
|
-
array_restore.append(array_merged[i_prev:i])
|
|
743
|
-
i_prev = i
|
|
744
|
-
array_restore.append(array_merged[i_prev:])
|
|
745
|
-
return np.array(array_restore, dtype=object)
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
def has_hdf5(fname, title="h5io"):
|
|
749
|
-
h5py = _check_h5py()
|
|
750
|
-
if isinstance(fname, str):
|
|
751
|
-
if not op.isfile(fname):
|
|
752
|
-
raise IOError('file "%s" not found' % fname)
|
|
753
|
-
elif isinstance(fname, h5py.File):
|
|
754
|
-
if fname.mode == 'w':
|
|
755
|
-
raise UnsupportedOperation(
|
|
756
|
-
'file must not be opened be opened with "w"'
|
|
757
|
-
)
|
|
758
|
-
print(fname.mode)
|
|
759
|
-
else:
|
|
760
|
-
raise ValueError(f'fname must be str or h5py.File, got {type(fname)}')
|
|
761
|
-
if not isinstance(title, str):
|
|
762
|
-
raise ValueError('title must be a string')
|
|
763
|
-
if isinstance(fname, h5py.File):
|
|
764
|
-
return title in fname
|
|
765
|
-
else:
|
|
766
|
-
with h5py.File(fname, mode='r') as fid:
|
|
767
|
-
return title in fid
|