oscura 0.5.1__py3-none-any.whl → 0.7.0__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 +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +183 -67
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +1 -1
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -291
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
oscura/exporters/csv.py
DELETED
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
"""CSV export functionality.
|
|
2
|
-
|
|
3
|
-
This module provides trace and measurement export to CSV format.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Example:
|
|
7
|
-
>>> from oscura.exporters.csv import export_csv
|
|
8
|
-
>>> export_csv(trace, "output.csv")
|
|
9
|
-
>>> export_csv(measurements, "results.csv")
|
|
10
|
-
|
|
11
|
-
References:
|
|
12
|
-
RFC 4180 (CSV format)
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import csv
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import TYPE_CHECKING, Any
|
|
20
|
-
|
|
21
|
-
import numpy as np
|
|
22
|
-
|
|
23
|
-
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
24
|
-
|
|
25
|
-
if TYPE_CHECKING:
|
|
26
|
-
from numpy.typing import NDArray
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def export_csv(
|
|
30
|
-
data: WaveformTrace | DigitalTrace | dict[str, Any] | NDArray[Any],
|
|
31
|
-
path: str | Path,
|
|
32
|
-
*,
|
|
33
|
-
include_time: bool = True,
|
|
34
|
-
time_unit: str = "s",
|
|
35
|
-
precision: int = 9,
|
|
36
|
-
delimiter: str = ",",
|
|
37
|
-
header: bool = True,
|
|
38
|
-
) -> None:
|
|
39
|
-
"""Export data to CSV format.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
data: Data to export. Can be:
|
|
43
|
-
- WaveformTrace or DigitalTrace (with metadata as comments)
|
|
44
|
-
- Dictionary of measurements
|
|
45
|
-
- NumPy array
|
|
46
|
-
path: Output file path.
|
|
47
|
-
include_time: Include time column for traces.
|
|
48
|
-
time_unit: Time unit ("s", "ms", "us", "ns").
|
|
49
|
-
precision: Decimal precision for floating point values.
|
|
50
|
-
delimiter: Column delimiter.
|
|
51
|
-
header: Include header row and metadata comments.
|
|
52
|
-
|
|
53
|
-
Raises:
|
|
54
|
-
TypeError: If data type is not supported.
|
|
55
|
-
|
|
56
|
-
Example:
|
|
57
|
-
>>> export_csv(trace, "waveform.csv")
|
|
58
|
-
>>> export_csv(trace, "data.csv", precision=6, delimiter="\t")
|
|
59
|
-
>>> export_csv(measurements, "results.csv")
|
|
60
|
-
|
|
61
|
-
Note:
|
|
62
|
-
When exporting traces, metadata is included as comment lines
|
|
63
|
-
starting with '#' when header=True.
|
|
64
|
-
|
|
65
|
-
References:
|
|
66
|
-
EXP-001
|
|
67
|
-
"""
|
|
68
|
-
path = Path(path)
|
|
69
|
-
|
|
70
|
-
if isinstance(data, WaveformTrace | DigitalTrace):
|
|
71
|
-
_export_trace(data, path, include_time, time_unit, precision, delimiter, header)
|
|
72
|
-
elif isinstance(data, dict):
|
|
73
|
-
_export_dict(data, path, precision, delimiter, header)
|
|
74
|
-
elif isinstance(data, np.ndarray):
|
|
75
|
-
_export_array(data, path, precision, delimiter, header)
|
|
76
|
-
else:
|
|
77
|
-
raise TypeError(f"Unsupported data type: {type(data)}")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _export_trace(
|
|
81
|
-
trace: WaveformTrace | DigitalTrace,
|
|
82
|
-
path: Path,
|
|
83
|
-
include_time: bool,
|
|
84
|
-
time_unit: str,
|
|
85
|
-
precision: int,
|
|
86
|
-
delimiter: str,
|
|
87
|
-
header: bool,
|
|
88
|
-
) -> None:
|
|
89
|
-
"""Export trace to CSV.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
trace: Trace to export.
|
|
93
|
-
path: Output file path.
|
|
94
|
-
include_time: Include time column.
|
|
95
|
-
time_unit: Time unit for column.
|
|
96
|
-
precision: Decimal precision.
|
|
97
|
-
delimiter: Column delimiter.
|
|
98
|
-
header: Include header row.
|
|
99
|
-
"""
|
|
100
|
-
time_multipliers = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}
|
|
101
|
-
multiplier = time_multipliers.get(time_unit, 1.0)
|
|
102
|
-
|
|
103
|
-
with open(path, "w", newline="") as f:
|
|
104
|
-
writer = csv.writer(f, delimiter=delimiter)
|
|
105
|
-
|
|
106
|
-
# Write metadata as comments if header is enabled
|
|
107
|
-
if header:
|
|
108
|
-
# Metadata comments
|
|
109
|
-
meta = trace.metadata
|
|
110
|
-
f.write("# Oscura CSV Export\n")
|
|
111
|
-
f.write(f"# Sample Rate: {meta.sample_rate} Hz\n")
|
|
112
|
-
f.write(f"# Time Base: {meta.time_base} s\n")
|
|
113
|
-
f.write(f"# Samples: {len(trace.data)}\n")
|
|
114
|
-
f.write(f"# Duration: {trace.duration} s\n")
|
|
115
|
-
|
|
116
|
-
if meta.vertical_scale is not None:
|
|
117
|
-
f.write(f"# Vertical Scale: {meta.vertical_scale} V/div\n")
|
|
118
|
-
if meta.vertical_offset is not None:
|
|
119
|
-
f.write(f"# Vertical Offset: {meta.vertical_offset} V\n")
|
|
120
|
-
if meta.acquisition_time is not None:
|
|
121
|
-
f.write(f"# Acquisition Time: {meta.acquisition_time.isoformat()}\n")
|
|
122
|
-
if meta.source_file is not None:
|
|
123
|
-
f.write(f"# Source File: {meta.source_file}\n")
|
|
124
|
-
if meta.channel_name is not None:
|
|
125
|
-
f.write(f"# Channel: {meta.channel_name}\n")
|
|
126
|
-
|
|
127
|
-
f.write("#\n")
|
|
128
|
-
|
|
129
|
-
# Column headers
|
|
130
|
-
if include_time:
|
|
131
|
-
if isinstance(trace, WaveformTrace):
|
|
132
|
-
writer.writerow([f"Time ({time_unit})", "Voltage"])
|
|
133
|
-
else:
|
|
134
|
-
writer.writerow([f"Time ({time_unit})", "Digital"])
|
|
135
|
-
elif isinstance(trace, WaveformTrace):
|
|
136
|
-
writer.writerow(["Voltage"])
|
|
137
|
-
else:
|
|
138
|
-
writer.writerow(["Digital"])
|
|
139
|
-
|
|
140
|
-
# Data
|
|
141
|
-
n_samples = len(trace.data)
|
|
142
|
-
time_base = trace.metadata.time_base
|
|
143
|
-
|
|
144
|
-
for i in range(n_samples):
|
|
145
|
-
if include_time:
|
|
146
|
-
time_val = i * time_base * multiplier
|
|
147
|
-
if isinstance(trace, WaveformTrace):
|
|
148
|
-
writer.writerow([f"{time_val:.{precision}g}", f"{trace.data[i]:.{precision}g}"])
|
|
149
|
-
else:
|
|
150
|
-
writer.writerow([f"{time_val:.{precision}g}", int(trace.data[i])])
|
|
151
|
-
elif isinstance(trace, WaveformTrace):
|
|
152
|
-
writer.writerow([f"{trace.data[i]:.{precision}g}"])
|
|
153
|
-
else:
|
|
154
|
-
writer.writerow([int(trace.data[i])])
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def _export_dict(
|
|
158
|
-
data: dict[str, Any],
|
|
159
|
-
path: Path,
|
|
160
|
-
precision: int,
|
|
161
|
-
delimiter: str,
|
|
162
|
-
header: bool,
|
|
163
|
-
) -> None:
|
|
164
|
-
"""Export dictionary to CSV.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
data: Dictionary to export.
|
|
168
|
-
path: Output file path.
|
|
169
|
-
precision: Decimal precision.
|
|
170
|
-
delimiter: Column delimiter.
|
|
171
|
-
header: Include header row.
|
|
172
|
-
"""
|
|
173
|
-
with open(path, "w", newline="") as f:
|
|
174
|
-
writer = csv.writer(f, delimiter=delimiter)
|
|
175
|
-
|
|
176
|
-
if header:
|
|
177
|
-
writer.writerow(["Parameter", "Value", "Unit"])
|
|
178
|
-
|
|
179
|
-
for key, value in data.items():
|
|
180
|
-
if isinstance(value, dict):
|
|
181
|
-
# Nested dict with value/unit
|
|
182
|
-
val = value.get("value", value)
|
|
183
|
-
unit = value.get("unit", "")
|
|
184
|
-
if isinstance(val, float):
|
|
185
|
-
writer.writerow([key, f"{val:.{precision}g}", unit])
|
|
186
|
-
else:
|
|
187
|
-
writer.writerow([key, val, unit])
|
|
188
|
-
elif isinstance(value, float):
|
|
189
|
-
writer.writerow([key, f"{value:.{precision}g}", ""])
|
|
190
|
-
else:
|
|
191
|
-
writer.writerow([key, value, ""])
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def _export_array(
|
|
195
|
-
data: NDArray[Any],
|
|
196
|
-
path: Path,
|
|
197
|
-
precision: int,
|
|
198
|
-
delimiter: str,
|
|
199
|
-
header: bool,
|
|
200
|
-
) -> None:
|
|
201
|
-
"""Export numpy array to CSV.
|
|
202
|
-
|
|
203
|
-
Args:
|
|
204
|
-
data: NumPy array to export.
|
|
205
|
-
path: Output file path.
|
|
206
|
-
precision: Decimal precision.
|
|
207
|
-
delimiter: Column delimiter.
|
|
208
|
-
header: Include header row.
|
|
209
|
-
"""
|
|
210
|
-
# Handle different array dimensions
|
|
211
|
-
if data.ndim == 1:
|
|
212
|
-
data = data.reshape(-1, 1)
|
|
213
|
-
|
|
214
|
-
with open(path, "w", newline="") as f:
|
|
215
|
-
writer = csv.writer(f, delimiter=delimiter)
|
|
216
|
-
|
|
217
|
-
if header:
|
|
218
|
-
cols = [f"Column_{i}" for i in range(data.shape[1])]
|
|
219
|
-
writer.writerow(cols)
|
|
220
|
-
|
|
221
|
-
for row in data:
|
|
222
|
-
formatted = []
|
|
223
|
-
for val in row:
|
|
224
|
-
if isinstance(val, float | np.floating):
|
|
225
|
-
formatted.append(f"{val:.{precision}g}")
|
|
226
|
-
else:
|
|
227
|
-
formatted.append(str(val)) # type: ignore[unreachable]
|
|
228
|
-
writer.writerow(formatted)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def export_multi_trace_csv(
|
|
232
|
-
traces: list[WaveformTrace | DigitalTrace],
|
|
233
|
-
path: str | Path,
|
|
234
|
-
*,
|
|
235
|
-
names: list[str] | None = None,
|
|
236
|
-
include_time: bool = True,
|
|
237
|
-
time_unit: str = "s",
|
|
238
|
-
precision: int = 9,
|
|
239
|
-
) -> None:
|
|
240
|
-
"""Export multiple traces to single CSV file.
|
|
241
|
-
|
|
242
|
-
Args:
|
|
243
|
-
traces: List of traces to export.
|
|
244
|
-
path: Output file path.
|
|
245
|
-
names: Column names for each trace.
|
|
246
|
-
include_time: Include time column.
|
|
247
|
-
time_unit: Time unit.
|
|
248
|
-
precision: Decimal precision.
|
|
249
|
-
|
|
250
|
-
Example:
|
|
251
|
-
>>> export_multi_trace_csv([ch1, ch2, ch3], "channels.csv",
|
|
252
|
-
... names=["CH1", "CH2", "CH3"])
|
|
253
|
-
"""
|
|
254
|
-
if len(traces) == 0:
|
|
255
|
-
return
|
|
256
|
-
|
|
257
|
-
path = Path(path)
|
|
258
|
-
|
|
259
|
-
if names is None:
|
|
260
|
-
names = [f"Trace_{i}" for i in range(len(traces))]
|
|
261
|
-
|
|
262
|
-
# Use first trace for timing
|
|
263
|
-
ref_trace = traces[0]
|
|
264
|
-
n_samples = len(ref_trace.data)
|
|
265
|
-
time_base = ref_trace.metadata.time_base
|
|
266
|
-
|
|
267
|
-
time_multipliers = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}
|
|
268
|
-
multiplier = time_multipliers.get(time_unit, 1.0)
|
|
269
|
-
|
|
270
|
-
with open(path, "w", newline="") as f:
|
|
271
|
-
writer = csv.writer(f)
|
|
272
|
-
|
|
273
|
-
# Header
|
|
274
|
-
header_row = []
|
|
275
|
-
if include_time:
|
|
276
|
-
header_row.append(f"Time ({time_unit})")
|
|
277
|
-
header_row.extend(names)
|
|
278
|
-
writer.writerow(header_row)
|
|
279
|
-
|
|
280
|
-
# Data
|
|
281
|
-
for i in range(n_samples):
|
|
282
|
-
row = []
|
|
283
|
-
|
|
284
|
-
if include_time:
|
|
285
|
-
time_val = i * time_base * multiplier
|
|
286
|
-
row.append(f"{time_val:.{precision}g}")
|
|
287
|
-
|
|
288
|
-
for trace in traces:
|
|
289
|
-
if i < len(trace.data):
|
|
290
|
-
if isinstance(trace, WaveformTrace):
|
|
291
|
-
row.append(f"{trace.data[i]:.{precision}g}")
|
|
292
|
-
else:
|
|
293
|
-
row.append(str(int(trace.data[i])))
|
|
294
|
-
else:
|
|
295
|
-
row.append("")
|
|
296
|
-
|
|
297
|
-
writer.writerow(row)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
__all__ = [
|
|
301
|
-
"export_csv",
|
|
302
|
-
"export_multi_trace_csv",
|
|
303
|
-
]
|
oscura/exporters/exporters.py
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"""Exporters namespace module.
|
|
2
|
-
|
|
3
|
-
This module provides a namespace for export functions to support:
|
|
4
|
-
from oscura.exporters import exporters
|
|
5
|
-
exporters.csv(trace, "output.csv")
|
|
6
|
-
|
|
7
|
-
Re-exports main export functions with short names.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from oscura.exporters.csv import (
|
|
11
|
-
export_csv as csv,
|
|
12
|
-
)
|
|
13
|
-
from oscura.exporters.hdf5 import (
|
|
14
|
-
export_hdf5 as hdf5,
|
|
15
|
-
)
|
|
16
|
-
from oscura.exporters.html_export import (
|
|
17
|
-
export_html as html,
|
|
18
|
-
)
|
|
19
|
-
from oscura.exporters.json_export import (
|
|
20
|
-
export_json as json,
|
|
21
|
-
)
|
|
22
|
-
from oscura.exporters.markdown_export import (
|
|
23
|
-
export_markdown as markdown,
|
|
24
|
-
)
|
|
25
|
-
from oscura.exporters.matlab_export import (
|
|
26
|
-
export_mat as mat,
|
|
27
|
-
)
|
|
28
|
-
from oscura.exporters.npz_export import (
|
|
29
|
-
export_npz as npz,
|
|
30
|
-
)
|
|
31
|
-
from oscura.exporters.spice_export import (
|
|
32
|
-
export_pwl as pwl,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
__all__ = [
|
|
36
|
-
"csv",
|
|
37
|
-
"hdf5",
|
|
38
|
-
"html",
|
|
39
|
-
"json",
|
|
40
|
-
"markdown",
|
|
41
|
-
"mat",
|
|
42
|
-
"npz",
|
|
43
|
-
"pwl",
|
|
44
|
-
]
|
oscura/exporters/hdf5.py
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
"""HDF5 export functionality.
|
|
2
|
-
|
|
3
|
-
This module provides trace export to HDF5 format with metadata attributes.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Example:
|
|
7
|
-
>>> from oscura.exporters.hdf5 import export_hdf5
|
|
8
|
-
>>> export_hdf5(trace, "output.h5")
|
|
9
|
-
|
|
10
|
-
References:
|
|
11
|
-
HDF5 specification (https://www.hdfgroup.org/)
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from datetime import datetime
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any
|
|
17
|
-
|
|
18
|
-
import numpy as np
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
import h5py
|
|
22
|
-
|
|
23
|
-
HAS_H5PY = True
|
|
24
|
-
except ImportError:
|
|
25
|
-
HAS_H5PY = False
|
|
26
|
-
|
|
27
|
-
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def export_hdf5(
|
|
31
|
-
data: WaveformTrace | DigitalTrace | dict[str, WaveformTrace | DigitalTrace],
|
|
32
|
-
path: str | Path,
|
|
33
|
-
*,
|
|
34
|
-
compression: str | None = "gzip",
|
|
35
|
-
compression_opts: int = 4,
|
|
36
|
-
include_metadata: bool = True,
|
|
37
|
-
) -> None:
|
|
38
|
-
"""Export data to HDF5 format.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
data: Data to export. Can be:
|
|
42
|
-
- Single WaveformTrace or DigitalTrace
|
|
43
|
-
- Dictionary mapping names to traces
|
|
44
|
-
path: Output file path.
|
|
45
|
-
compression: Compression algorithm ("gzip", "lzf", None).
|
|
46
|
-
compression_opts: Compression level (1-9 for gzip).
|
|
47
|
-
include_metadata: Include trace metadata as attributes.
|
|
48
|
-
|
|
49
|
-
Raises:
|
|
50
|
-
ImportError: If h5py is not installed.
|
|
51
|
-
|
|
52
|
-
Example:
|
|
53
|
-
>>> export_hdf5(trace, "waveform.h5")
|
|
54
|
-
>>> export_hdf5({"ch1": ch1, "ch2": ch2}, "channels.h5")
|
|
55
|
-
"""
|
|
56
|
-
if not HAS_H5PY:
|
|
57
|
-
raise ImportError("h5py is required for HDF5 export. Install with: pip install h5py")
|
|
58
|
-
|
|
59
|
-
path = Path(path)
|
|
60
|
-
|
|
61
|
-
if isinstance(data, WaveformTrace | DigitalTrace):
|
|
62
|
-
data = {"trace": data}
|
|
63
|
-
|
|
64
|
-
with h5py.File(path, "w") as f:
|
|
65
|
-
# Add file-level metadata
|
|
66
|
-
f.attrs["created"] = datetime.now().isoformat()
|
|
67
|
-
f.attrs["oscura_version"] = "1.0"
|
|
68
|
-
f.attrs["format"] = "oscura_hdf5"
|
|
69
|
-
|
|
70
|
-
for name, trace in data.items():
|
|
71
|
-
_write_trace_dataset(
|
|
72
|
-
f,
|
|
73
|
-
name,
|
|
74
|
-
trace,
|
|
75
|
-
compression,
|
|
76
|
-
compression_opts,
|
|
77
|
-
include_metadata,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _write_trace_dataset(
|
|
82
|
-
f: "h5py.File",
|
|
83
|
-
name: str,
|
|
84
|
-
trace: WaveformTrace | DigitalTrace,
|
|
85
|
-
compression: str | None,
|
|
86
|
-
compression_opts: int,
|
|
87
|
-
include_metadata: bool,
|
|
88
|
-
) -> None:
|
|
89
|
-
"""Write trace to HDF5 dataset.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
f: HDF5 file object.
|
|
93
|
-
name: Dataset name.
|
|
94
|
-
trace: Trace to write.
|
|
95
|
-
compression: Compression algorithm.
|
|
96
|
-
compression_opts: Compression level.
|
|
97
|
-
include_metadata: Include metadata as attributes.
|
|
98
|
-
"""
|
|
99
|
-
# Create dataset
|
|
100
|
-
dtype = np.float64 if isinstance(trace, WaveformTrace) else np.bool_
|
|
101
|
-
|
|
102
|
-
kwargs = {}
|
|
103
|
-
if compression:
|
|
104
|
-
kwargs["compression"] = compression
|
|
105
|
-
if compression == "gzip":
|
|
106
|
-
kwargs["compression_opts"] = compression_opts # type: ignore[assignment]
|
|
107
|
-
|
|
108
|
-
ds = f.create_dataset(name, data=trace.data.astype(dtype), **kwargs)
|
|
109
|
-
|
|
110
|
-
# Add metadata attributes
|
|
111
|
-
if include_metadata:
|
|
112
|
-
meta = trace.metadata
|
|
113
|
-
|
|
114
|
-
ds.attrs["sample_rate"] = meta.sample_rate
|
|
115
|
-
ds.attrs["time_base"] = meta.time_base
|
|
116
|
-
|
|
117
|
-
if meta.vertical_scale is not None:
|
|
118
|
-
ds.attrs["vertical_scale"] = meta.vertical_scale
|
|
119
|
-
|
|
120
|
-
if meta.vertical_offset is not None:
|
|
121
|
-
ds.attrs["vertical_offset"] = meta.vertical_offset
|
|
122
|
-
|
|
123
|
-
if meta.acquisition_time is not None:
|
|
124
|
-
ds.attrs["acquisition_time"] = meta.acquisition_time.isoformat()
|
|
125
|
-
|
|
126
|
-
if meta.source_file is not None:
|
|
127
|
-
ds.attrs["source_file"] = str(meta.source_file)
|
|
128
|
-
|
|
129
|
-
if meta.channel_name is not None:
|
|
130
|
-
ds.attrs["channel_name"] = meta.channel_name
|
|
131
|
-
|
|
132
|
-
if meta.trigger_info:
|
|
133
|
-
for key, value in meta.trigger_info.items():
|
|
134
|
-
ds.attrs[f"trigger_{key}"] = value
|
|
135
|
-
|
|
136
|
-
# Type indicator
|
|
137
|
-
ds.attrs["trace_type"] = "waveform" if isinstance(trace, WaveformTrace) else "digital"
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def export_measurement_results(
|
|
141
|
-
results: dict[str, Any],
|
|
142
|
-
path: str | Path,
|
|
143
|
-
*,
|
|
144
|
-
group_name: str = "measurements",
|
|
145
|
-
) -> None:
|
|
146
|
-
"""Export measurement results to HDF5.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
results: Dictionary of measurement results.
|
|
150
|
-
path: Output file path.
|
|
151
|
-
group_name: HDF5 group name for results.
|
|
152
|
-
|
|
153
|
-
Raises:
|
|
154
|
-
ImportError: If h5py is not installed.
|
|
155
|
-
|
|
156
|
-
Example:
|
|
157
|
-
>>> results = measure(trace)
|
|
158
|
-
>>> export_measurement_results(results, "measurements.h5")
|
|
159
|
-
"""
|
|
160
|
-
if not HAS_H5PY:
|
|
161
|
-
raise ImportError("h5py is required for HDF5 export")
|
|
162
|
-
|
|
163
|
-
path = Path(path)
|
|
164
|
-
|
|
165
|
-
with h5py.File(path, "a") as f:
|
|
166
|
-
grp = f.require_group(group_name)
|
|
167
|
-
|
|
168
|
-
for name, value in results.items():
|
|
169
|
-
if isinstance(value, dict):
|
|
170
|
-
# Nested dict (value/unit pairs)
|
|
171
|
-
sub_grp = grp.require_group(name)
|
|
172
|
-
for k, v in value.items():
|
|
173
|
-
if isinstance(v, np.ndarray):
|
|
174
|
-
sub_grp.create_dataset(k, data=v)
|
|
175
|
-
else:
|
|
176
|
-
sub_grp.attrs[k] = v
|
|
177
|
-
elif isinstance(value, np.ndarray):
|
|
178
|
-
grp.create_dataset(name, data=value)
|
|
179
|
-
else:
|
|
180
|
-
grp.attrs[name] = value
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def append_trace(
|
|
184
|
-
path: str | Path,
|
|
185
|
-
name: str,
|
|
186
|
-
trace: WaveformTrace | DigitalTrace,
|
|
187
|
-
*,
|
|
188
|
-
compression: str | None = "gzip",
|
|
189
|
-
) -> None:
|
|
190
|
-
"""Append trace to existing HDF5 file.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
path: HDF5 file path.
|
|
194
|
-
name: Dataset name for new trace.
|
|
195
|
-
trace: Trace to append.
|
|
196
|
-
compression: Compression algorithm.
|
|
197
|
-
|
|
198
|
-
Raises:
|
|
199
|
-
ImportError: If h5py is not installed.
|
|
200
|
-
|
|
201
|
-
Example:
|
|
202
|
-
>>> append_trace("data.h5", "ch3", channel3_trace)
|
|
203
|
-
"""
|
|
204
|
-
if not HAS_H5PY:
|
|
205
|
-
raise ImportError("h5py is required for HDF5 export")
|
|
206
|
-
|
|
207
|
-
path = Path(path)
|
|
208
|
-
|
|
209
|
-
with h5py.File(path, "a") as f:
|
|
210
|
-
_write_trace_dataset(f, name, trace, compression, 4, True)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
__all__ = [
|
|
214
|
-
"append_trace",
|
|
215
|
-
"export_hdf5",
|
|
216
|
-
"export_measurement_results",
|
|
217
|
-
]
|