oscura 0.0.1__py3-none-any.whl → 0.1.1__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.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"""HDF5 file loader for waveform data.
|
|
2
|
+
|
|
3
|
+
This module provides loading of waveform data from HDF5 (.h5) files
|
|
4
|
+
with automatic dataset discovery and attribute-based metadata extraction.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.loaders.hdf5_loader import load_hdf5
|
|
9
|
+
>>> trace = load_hdf5("data.h5")
|
|
10
|
+
>>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from oscura.core.exceptions import FormatError, LoaderError
|
|
21
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from os import PathLike
|
|
25
|
+
|
|
26
|
+
# Try to import h5py
|
|
27
|
+
try:
|
|
28
|
+
import h5py
|
|
29
|
+
|
|
30
|
+
H5PY_AVAILABLE = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
H5PY_AVAILABLE = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Common dataset names for waveform data
|
|
36
|
+
DATASET_NAMES = [
|
|
37
|
+
"data",
|
|
38
|
+
"waveform",
|
|
39
|
+
"signal",
|
|
40
|
+
"samples",
|
|
41
|
+
"voltage",
|
|
42
|
+
"trace",
|
|
43
|
+
"ch1",
|
|
44
|
+
"ch2",
|
|
45
|
+
"channel1",
|
|
46
|
+
"channel2",
|
|
47
|
+
"analog",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
# Common attribute names for sample rate
|
|
51
|
+
SAMPLE_RATE_ATTRS = [
|
|
52
|
+
"sample_rate",
|
|
53
|
+
"samplerate",
|
|
54
|
+
"sampling_rate",
|
|
55
|
+
"fs",
|
|
56
|
+
"rate",
|
|
57
|
+
"sample_interval",
|
|
58
|
+
"dt",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class HDF5MmapTrace:
|
|
63
|
+
"""Memory-mapped waveform trace backed by HDF5 dataset.
|
|
64
|
+
|
|
65
|
+
Provides lazy access to HDF5 dataset without loading into memory.
|
|
66
|
+
Useful for huge files that don't fit in RAM.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
file_path: Path to the HDF5 file.
|
|
70
|
+
dataset_path: Path to dataset within HDF5 file.
|
|
71
|
+
sample_rate: Sample rate in Hz.
|
|
72
|
+
length: Number of samples in the trace.
|
|
73
|
+
metadata: TraceMetadata object.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> trace = HDF5MmapTrace("huge.h5", "/data", metadata)
|
|
77
|
+
>>> # Access subset without loading entire file
|
|
78
|
+
>>> subset = trace[1000:2000]
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
file_path: str | Path,
|
|
84
|
+
dataset_path: str,
|
|
85
|
+
metadata: TraceMetadata,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Initialize HDF5 memory-mapped trace.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
file_path: Path to HDF5 file.
|
|
91
|
+
dataset_path: Path to dataset within file (e.g., "/data").
|
|
92
|
+
metadata: TraceMetadata with sample rate and other info.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
LoaderError: If file not found or invalid.
|
|
96
|
+
"""
|
|
97
|
+
self._file_path = Path(file_path)
|
|
98
|
+
self._dataset_path = dataset_path
|
|
99
|
+
self._metadata = metadata
|
|
100
|
+
self._h5_file: h5py.File | None = None
|
|
101
|
+
self._dataset: h5py.Dataset | None = None
|
|
102
|
+
|
|
103
|
+
if not self._file_path.exists():
|
|
104
|
+
raise LoaderError(f"File not found: {self._file_path}")
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def sample_rate(self) -> float:
|
|
108
|
+
"""Sample rate in Hz."""
|
|
109
|
+
return self._metadata.sample_rate
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def length(self) -> int:
|
|
113
|
+
"""Number of samples."""
|
|
114
|
+
self._ensure_open()
|
|
115
|
+
assert self._dataset is not None
|
|
116
|
+
return len(self._dataset)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def metadata(self) -> TraceMetadata:
|
|
120
|
+
"""Trace metadata."""
|
|
121
|
+
return self._metadata
|
|
122
|
+
|
|
123
|
+
def _ensure_open(self) -> None:
|
|
124
|
+
"""Ensure HDF5 file is open."""
|
|
125
|
+
if self._h5_file is None or self._dataset is None:
|
|
126
|
+
self._h5_file = h5py.File(self._file_path, "r")
|
|
127
|
+
self._dataset = self._h5_file[self._dataset_path]
|
|
128
|
+
|
|
129
|
+
def __getitem__(self, key: int | slice) -> np.ndarray[Any, Any]:
|
|
130
|
+
"""Access data by index or slice.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
key: Index or slice.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Numpy array of data.
|
|
137
|
+
"""
|
|
138
|
+
self._ensure_open()
|
|
139
|
+
assert self._dataset is not None
|
|
140
|
+
data = self._dataset[key]
|
|
141
|
+
return np.asarray(data, dtype=np.float64)
|
|
142
|
+
|
|
143
|
+
def __len__(self) -> int:
|
|
144
|
+
"""Return number of samples."""
|
|
145
|
+
return self.length
|
|
146
|
+
|
|
147
|
+
def close(self) -> None:
|
|
148
|
+
"""Close HDF5 file handle."""
|
|
149
|
+
if self._h5_file is not None:
|
|
150
|
+
self._h5_file.close()
|
|
151
|
+
self._h5_file = None
|
|
152
|
+
self._dataset = None
|
|
153
|
+
|
|
154
|
+
def __del__(self) -> None:
|
|
155
|
+
"""Cleanup on deletion."""
|
|
156
|
+
self.close()
|
|
157
|
+
|
|
158
|
+
def __enter__(self) -> HDF5MmapTrace:
|
|
159
|
+
"""Context manager entry."""
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
163
|
+
"""Context manager exit."""
|
|
164
|
+
self.close()
|
|
165
|
+
|
|
166
|
+
def __repr__(self) -> str:
|
|
167
|
+
"""String representation."""
|
|
168
|
+
return (
|
|
169
|
+
f"HDF5MmapTrace("
|
|
170
|
+
f"file={self._file_path.name}, "
|
|
171
|
+
f"dataset={self._dataset_path}, "
|
|
172
|
+
f"sample_rate={self.sample_rate:.2e} Hz, "
|
|
173
|
+
f"length={self.length:,} samples)"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def load_hdf5(
|
|
178
|
+
path: str | PathLike[str],
|
|
179
|
+
*,
|
|
180
|
+
dataset: str | None = None,
|
|
181
|
+
channel: str | int | None = None,
|
|
182
|
+
sample_rate: float | None = None,
|
|
183
|
+
mmap: bool = False,
|
|
184
|
+
) -> WaveformTrace | HDF5MmapTrace:
|
|
185
|
+
"""Load waveform data from an HDF5 file.
|
|
186
|
+
|
|
187
|
+
Loads waveform data and metadata from HDF5 files. Automatically
|
|
188
|
+
discovers datasets and extracts sample rate from attributes.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
path: Path to the HDF5 file.
|
|
192
|
+
dataset: Specific dataset path to load. If None, auto-detects.
|
|
193
|
+
channel: Alias for dataset (for API consistency with other loaders).
|
|
194
|
+
sample_rate: Override sample rate (if not found in attributes).
|
|
195
|
+
mmap: If True, return memory-mapped trace for large files.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
WaveformTrace containing the waveform data and metadata.
|
|
199
|
+
If mmap=True, returns HDF5MmapTrace instead.
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
LoaderError: If the file cannot be loaded.
|
|
203
|
+
FormatError: If no valid waveform data is found.
|
|
204
|
+
|
|
205
|
+
Example:
|
|
206
|
+
>>> trace = load_hdf5("data.h5")
|
|
207
|
+
>>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
|
|
208
|
+
|
|
209
|
+
>>> # Load specific dataset
|
|
210
|
+
>>> trace = load_hdf5("multi.h5", dataset="/measurements/ch1")
|
|
211
|
+
|
|
212
|
+
>>> # Load as memory-mapped for large files
|
|
213
|
+
>>> trace = load_hdf5("huge_data.h5", mmap=True)
|
|
214
|
+
"""
|
|
215
|
+
if not H5PY_AVAILABLE:
|
|
216
|
+
raise LoaderError(
|
|
217
|
+
"HDF5 support not available",
|
|
218
|
+
details="h5py package is required for HDF5 loading",
|
|
219
|
+
fix_hint="Install h5py: pip install h5py",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
path = Path(path)
|
|
223
|
+
|
|
224
|
+
if not path.exists():
|
|
225
|
+
raise LoaderError(
|
|
226
|
+
"File not found",
|
|
227
|
+
file_path=str(path),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Use channel as dataset if dataset not specified
|
|
231
|
+
if dataset is None and channel is not None:
|
|
232
|
+
dataset = str(channel)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
with h5py.File(path, "r") as f:
|
|
236
|
+
# Find dataset
|
|
237
|
+
if dataset is not None:
|
|
238
|
+
if dataset in f:
|
|
239
|
+
ds = f[dataset]
|
|
240
|
+
else:
|
|
241
|
+
# Try to find by name
|
|
242
|
+
ds = _find_dataset_by_name(f, dataset)
|
|
243
|
+
if ds is None:
|
|
244
|
+
available = list_datasets(path)
|
|
245
|
+
raise FormatError(
|
|
246
|
+
f"Dataset not found: {dataset}",
|
|
247
|
+
file_path=str(path),
|
|
248
|
+
expected=dataset,
|
|
249
|
+
got=f"Available: {', '.join(available)}",
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
# Auto-detect dataset
|
|
253
|
+
ds = _find_waveform_dataset(f)
|
|
254
|
+
if ds is None:
|
|
255
|
+
available = list_datasets(path)
|
|
256
|
+
raise FormatError(
|
|
257
|
+
"No waveform data found in HDF5 file",
|
|
258
|
+
file_path=str(path),
|
|
259
|
+
expected=f"Dataset named: {', '.join(DATASET_NAMES)}",
|
|
260
|
+
got=f"Datasets: {', '.join(available)}",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Extract data
|
|
264
|
+
if not isinstance(ds, h5py.Dataset):
|
|
265
|
+
raise FormatError(
|
|
266
|
+
"Selected path is not a dataset",
|
|
267
|
+
file_path=str(path),
|
|
268
|
+
got=type(ds).__name__,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
data = np.asarray(ds, dtype=np.float64)
|
|
272
|
+
if data.ndim > 1:
|
|
273
|
+
data = data.ravel()
|
|
274
|
+
|
|
275
|
+
# Extract metadata from attributes
|
|
276
|
+
detected_sample_rate = sample_rate
|
|
277
|
+
if detected_sample_rate is None:
|
|
278
|
+
detected_sample_rate = _find_sample_rate(f, ds)
|
|
279
|
+
|
|
280
|
+
if detected_sample_rate is None:
|
|
281
|
+
detected_sample_rate = 1e6 # Default
|
|
282
|
+
|
|
283
|
+
# Get other metadata
|
|
284
|
+
vertical_scale = _get_attr(ds, ["vertical_scale", "v_scale", "scale"])
|
|
285
|
+
vertical_offset = _get_attr(ds, ["vertical_offset", "v_offset", "offset"])
|
|
286
|
+
channel_name = _get_attr(ds, ["channel_name", "name", "channel"])
|
|
287
|
+
|
|
288
|
+
if channel_name is None:
|
|
289
|
+
channel_name = ds.name.split("/")[-1] if ds.name else "CH1"
|
|
290
|
+
|
|
291
|
+
metadata = TraceMetadata(
|
|
292
|
+
sample_rate=float(detected_sample_rate),
|
|
293
|
+
vertical_scale=float(vertical_scale) if vertical_scale else None,
|
|
294
|
+
vertical_offset=float(vertical_offset) if vertical_offset else None,
|
|
295
|
+
source_file=str(path),
|
|
296
|
+
channel_name=str(channel_name),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Return memory-mapped trace if requested
|
|
300
|
+
if mmap:
|
|
301
|
+
return HDF5MmapTrace(
|
|
302
|
+
file_path=path,
|
|
303
|
+
dataset_path=ds.name,
|
|
304
|
+
metadata=metadata,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return WaveformTrace(data=data, metadata=metadata)
|
|
308
|
+
|
|
309
|
+
except OSError as e:
|
|
310
|
+
raise LoaderError(
|
|
311
|
+
"Failed to read HDF5 file",
|
|
312
|
+
file_path=str(path),
|
|
313
|
+
details=str(e),
|
|
314
|
+
) from e
|
|
315
|
+
except Exception as e:
|
|
316
|
+
if isinstance(e, LoaderError | FormatError):
|
|
317
|
+
raise
|
|
318
|
+
raise LoaderError(
|
|
319
|
+
"Failed to load HDF5 file",
|
|
320
|
+
file_path=str(path),
|
|
321
|
+
details=str(e),
|
|
322
|
+
) from e
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _find_waveform_dataset(f: h5py.File) -> h5py.Dataset | None:
|
|
326
|
+
"""Find a waveform dataset in the HDF5 file."""
|
|
327
|
+
result: h5py.Dataset | None = None
|
|
328
|
+
|
|
329
|
+
def visitor(name: str, obj: Any) -> None:
|
|
330
|
+
nonlocal result
|
|
331
|
+
if result is not None:
|
|
332
|
+
return
|
|
333
|
+
if isinstance(obj, h5py.Dataset):
|
|
334
|
+
name_lower = name.lower().split("/")[-1]
|
|
335
|
+
# Check for common names
|
|
336
|
+
for ds_name in DATASET_NAMES:
|
|
337
|
+
if ds_name in name_lower:
|
|
338
|
+
result = obj
|
|
339
|
+
return
|
|
340
|
+
# Check if it's a 1D numeric array
|
|
341
|
+
if obj.ndim == 1 and obj.size > 10 and np.issubdtype(obj.dtype, np.number):
|
|
342
|
+
if result is None:
|
|
343
|
+
result = obj
|
|
344
|
+
|
|
345
|
+
f.visititems(visitor)
|
|
346
|
+
return result
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _find_dataset_by_name(f: h5py.File, name: str) -> h5py.Dataset | None:
|
|
350
|
+
"""Find a dataset by name (case-insensitive partial match)."""
|
|
351
|
+
name_lower = name.lower()
|
|
352
|
+
result: h5py.Dataset | None = None
|
|
353
|
+
|
|
354
|
+
def visitor(path: str, obj: Any) -> None:
|
|
355
|
+
nonlocal result
|
|
356
|
+
if result is not None:
|
|
357
|
+
return
|
|
358
|
+
if isinstance(obj, h5py.Dataset):
|
|
359
|
+
path_lower = path.lower()
|
|
360
|
+
if name_lower in path_lower:
|
|
361
|
+
result = obj
|
|
362
|
+
|
|
363
|
+
f.visititems(visitor)
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _find_sample_rate(f: h5py.File, ds: h5py.Dataset) -> float | None:
|
|
368
|
+
"""Find sample rate from HDF5 attributes."""
|
|
369
|
+
# Check dataset attributes first
|
|
370
|
+
for attr_name in SAMPLE_RATE_ATTRS:
|
|
371
|
+
if attr_name in ds.attrs:
|
|
372
|
+
value = ds.attrs[attr_name]
|
|
373
|
+
if attr_name in ("sample_interval", "dt") and value > 0:
|
|
374
|
+
return 1.0 / float(value)
|
|
375
|
+
return float(value)
|
|
376
|
+
|
|
377
|
+
# Check parent group attributes
|
|
378
|
+
if ds.parent is not None:
|
|
379
|
+
for attr_name in SAMPLE_RATE_ATTRS:
|
|
380
|
+
if attr_name in ds.parent.attrs:
|
|
381
|
+
value = ds.parent.attrs[attr_name]
|
|
382
|
+
if attr_name in ("sample_interval", "dt") and value > 0:
|
|
383
|
+
return 1.0 / float(value)
|
|
384
|
+
return float(value)
|
|
385
|
+
|
|
386
|
+
# Check root attributes
|
|
387
|
+
for attr_name in SAMPLE_RATE_ATTRS:
|
|
388
|
+
if attr_name in f.attrs:
|
|
389
|
+
value = f.attrs[attr_name]
|
|
390
|
+
if attr_name in ("sample_interval", "dt") and value > 0:
|
|
391
|
+
return 1.0 / float(value)
|
|
392
|
+
return float(value)
|
|
393
|
+
|
|
394
|
+
# Check for metadata group
|
|
395
|
+
if "metadata" in f:
|
|
396
|
+
meta = f["metadata"]
|
|
397
|
+
if isinstance(meta, h5py.Group | h5py.Dataset):
|
|
398
|
+
for attr_name in SAMPLE_RATE_ATTRS:
|
|
399
|
+
if attr_name in meta.attrs:
|
|
400
|
+
value = meta.attrs[attr_name]
|
|
401
|
+
if attr_name in ("sample_interval", "dt") and value > 0:
|
|
402
|
+
return 1.0 / float(value)
|
|
403
|
+
return float(value)
|
|
404
|
+
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _get_attr(obj: h5py.Dataset | h5py.Group, names: list[str]) -> Any | None:
|
|
409
|
+
"""Get attribute value by trying multiple names."""
|
|
410
|
+
for name in names:
|
|
411
|
+
if name in obj.attrs:
|
|
412
|
+
value = obj.attrs[name]
|
|
413
|
+
if isinstance(value, bytes):
|
|
414
|
+
return value.decode("utf-8")
|
|
415
|
+
return value
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def list_datasets(path: str | PathLike[str]) -> list[str]:
|
|
420
|
+
"""List all datasets in an HDF5 file.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
path: Path to the HDF5 file.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
List of dataset paths.
|
|
427
|
+
|
|
428
|
+
Raises:
|
|
429
|
+
LoaderError: If h5py is not available or file not found.
|
|
430
|
+
|
|
431
|
+
Example:
|
|
432
|
+
>>> datasets = list_datasets("data.h5")
|
|
433
|
+
>>> print(datasets)
|
|
434
|
+
['/measurements/ch1', '/measurements/ch2', '/time']
|
|
435
|
+
"""
|
|
436
|
+
if not H5PY_AVAILABLE:
|
|
437
|
+
raise LoaderError(
|
|
438
|
+
"HDF5 support not available",
|
|
439
|
+
details="h5py package is required",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
path = Path(path)
|
|
443
|
+
if not path.exists():
|
|
444
|
+
raise LoaderError("File not found", file_path=str(path))
|
|
445
|
+
|
|
446
|
+
datasets: list[str] = []
|
|
447
|
+
|
|
448
|
+
def visitor(name: str, obj: Any) -> None:
|
|
449
|
+
if isinstance(obj, h5py.Dataset):
|
|
450
|
+
datasets.append("/" + name)
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
with h5py.File(path, "r") as f:
|
|
454
|
+
f.visititems(visitor)
|
|
455
|
+
except Exception as e:
|
|
456
|
+
raise LoaderError(
|
|
457
|
+
"Failed to read HDF5 file",
|
|
458
|
+
file_path=str(path),
|
|
459
|
+
details=str(e),
|
|
460
|
+
) from e
|
|
461
|
+
|
|
462
|
+
return datasets
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def get_attributes(
|
|
466
|
+
path: str | PathLike[str],
|
|
467
|
+
dataset: str | None = None,
|
|
468
|
+
) -> dict[str, Any]:
|
|
469
|
+
"""Get attributes from an HDF5 file or dataset.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
path: Path to the HDF5 file.
|
|
473
|
+
dataset: Dataset path. If None, returns root attributes.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
Dictionary of attributes.
|
|
477
|
+
|
|
478
|
+
Raises:
|
|
479
|
+
LoaderError: If h5py is not available or file not found.
|
|
480
|
+
"""
|
|
481
|
+
if not H5PY_AVAILABLE:
|
|
482
|
+
raise LoaderError("HDF5 support not available")
|
|
483
|
+
|
|
484
|
+
path = Path(path)
|
|
485
|
+
if not path.exists():
|
|
486
|
+
raise LoaderError("File not found", file_path=str(path))
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
with h5py.File(path, "r") as f:
|
|
490
|
+
obj = f[dataset] if dataset is not None else f
|
|
491
|
+
|
|
492
|
+
attrs = {}
|
|
493
|
+
for key, value in obj.attrs.items():
|
|
494
|
+
if isinstance(value, bytes):
|
|
495
|
+
value = value.decode("utf-8")
|
|
496
|
+
elif isinstance(value, np.ndarray):
|
|
497
|
+
value = value.tolist()
|
|
498
|
+
attrs[key] = value
|
|
499
|
+
|
|
500
|
+
return attrs
|
|
501
|
+
|
|
502
|
+
except Exception as e:
|
|
503
|
+
raise LoaderError(
|
|
504
|
+
"Failed to read HDF5 attributes",
|
|
505
|
+
file_path=str(path),
|
|
506
|
+
details=str(e),
|
|
507
|
+
) from e
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
__all__ = ["get_attributes", "list_datasets", "load_hdf5"]
|