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
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
"""MATLAB export functionality.
|
|
2
|
-
|
|
3
|
-
This module provides trace export to MATLAB .mat format with metadata.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Example:
|
|
7
|
-
>>> from oscura.exporters.matlab_export import export_mat
|
|
8
|
-
>>> export_mat(trace, "waveform.mat")
|
|
9
|
-
>>> export_mat({"ch1": ch1, "ch2": ch2}, "channels.mat")
|
|
10
|
-
|
|
11
|
-
References:
|
|
12
|
-
MATLAB MAT-File Format (https://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf)
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
from datetime import datetime
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import Any
|
|
20
|
-
|
|
21
|
-
import numpy as np
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
import scipy.io as sio
|
|
25
|
-
|
|
26
|
-
HAS_SCIPY = True
|
|
27
|
-
except ImportError:
|
|
28
|
-
HAS_SCIPY = False
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
import h5py
|
|
32
|
-
|
|
33
|
-
HAS_H5PY = True
|
|
34
|
-
except ImportError:
|
|
35
|
-
HAS_H5PY = False
|
|
36
|
-
|
|
37
|
-
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def export_mat(
|
|
41
|
-
data: WaveformTrace | DigitalTrace | dict[str, Any],
|
|
42
|
-
path: str | Path,
|
|
43
|
-
*,
|
|
44
|
-
version: str = "5",
|
|
45
|
-
compression: bool = True,
|
|
46
|
-
include_metadata: bool = True,
|
|
47
|
-
) -> None:
|
|
48
|
-
"""Export data to MATLAB .mat format.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
data: Data to export. Can be:
|
|
52
|
-
- Single WaveformTrace or DigitalTrace
|
|
53
|
-
- Dictionary mapping names to traces or data
|
|
54
|
-
path: Output file path.
|
|
55
|
-
version: MAT-file version ("5", "7.3"). Version 5 is more compatible.
|
|
56
|
-
Version 7.3 requires h5py and uses HDF5 backend for large files.
|
|
57
|
-
compression: Enable compression.
|
|
58
|
-
include_metadata: Include trace metadata in output.
|
|
59
|
-
|
|
60
|
-
Raises:
|
|
61
|
-
ImportError: If scipy is not installed, or h5py for version 7.3.
|
|
62
|
-
|
|
63
|
-
Raises:
|
|
64
|
-
TypeError: If data type is not supported.
|
|
65
|
-
|
|
66
|
-
Example:
|
|
67
|
-
>>> export_mat(trace, "waveform.mat")
|
|
68
|
-
>>> export_mat({"ch1": ch1, "ch2": ch2}, "channels.mat")
|
|
69
|
-
>>> export_mat(measurements, "results.mat", version="5")
|
|
70
|
-
|
|
71
|
-
Note:
|
|
72
|
-
Version 5 is the default and most compatible format, readable by
|
|
73
|
-
scipy.io.loadmat and MATLAB.
|
|
74
|
-
|
|
75
|
-
Version 7.3 uses HDF5 backend and supports:
|
|
76
|
-
- Files > 2 GB
|
|
77
|
-
- Compression
|
|
78
|
-
- But requires h5py and cannot be read by scipy.io.loadmat
|
|
79
|
-
|
|
80
|
-
References:
|
|
81
|
-
EXP-008
|
|
82
|
-
"""
|
|
83
|
-
if not HAS_SCIPY:
|
|
84
|
-
raise ImportError("scipy is required for MATLAB export. Install with: pip install scipy")
|
|
85
|
-
|
|
86
|
-
path = Path(path)
|
|
87
|
-
|
|
88
|
-
# Prepare data dictionary for MATLAB
|
|
89
|
-
mat_dict: dict[str, Any] = {}
|
|
90
|
-
|
|
91
|
-
if isinstance(data, WaveformTrace | DigitalTrace):
|
|
92
|
-
# Single trace - use standard variable names
|
|
93
|
-
_add_trace_to_dict(mat_dict, "trace", data, include_metadata)
|
|
94
|
-
elif isinstance(data, dict):
|
|
95
|
-
for name, value in data.items():
|
|
96
|
-
if isinstance(value, WaveformTrace | DigitalTrace):
|
|
97
|
-
_add_trace_to_dict(mat_dict, name, value, include_metadata)
|
|
98
|
-
else:
|
|
99
|
-
# Convert numpy arrays and other types
|
|
100
|
-
mat_dict[_sanitize_varname(name)] = _convert_value(value)
|
|
101
|
-
else:
|
|
102
|
-
raise TypeError(f"Unsupported data type: {type(data)}")
|
|
103
|
-
|
|
104
|
-
# Add export metadata
|
|
105
|
-
# Note: MATLAB field names cannot start with underscore
|
|
106
|
-
if include_metadata:
|
|
107
|
-
mat_dict["oscura_export"] = {
|
|
108
|
-
"version": "1.0",
|
|
109
|
-
"exported_at": datetime.now().isoformat(),
|
|
110
|
-
"format": "oscura_matlab",
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
# Save to .mat file
|
|
114
|
-
if version == "7.3":
|
|
115
|
-
# Use HDF5 backend (requires h5py)
|
|
116
|
-
if not HAS_H5PY:
|
|
117
|
-
raise ImportError(
|
|
118
|
-
"h5py is required for MATLAB v7.3 export. "
|
|
119
|
-
"Install with: pip install h5py, or use version='5'"
|
|
120
|
-
)
|
|
121
|
-
_save_hdf5_mat(path, mat_dict, compression)
|
|
122
|
-
else:
|
|
123
|
-
# Version 5 format (default, most compatible)
|
|
124
|
-
sio.savemat(
|
|
125
|
-
str(path),
|
|
126
|
-
mat_dict,
|
|
127
|
-
do_compression=compression,
|
|
128
|
-
oned_as="column",
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _save_hdf5_mat(path: Path, mat_dict: dict[str, Any], compression: bool) -> None:
|
|
133
|
-
"""Save MATLAB 7.3 format file using h5py (HDF5 backend).
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
path: Output file path.
|
|
137
|
-
mat_dict: Dictionary of MATLAB variables.
|
|
138
|
-
compression: Enable compression.
|
|
139
|
-
"""
|
|
140
|
-
compression_opts = "gzip" if compression else None
|
|
141
|
-
|
|
142
|
-
with h5py.File(path, "w") as f:
|
|
143
|
-
# Set MATLAB 7.3 header attributes
|
|
144
|
-
f.attrs["MATLAB_class"] = np.bytes_("struct")
|
|
145
|
-
|
|
146
|
-
for key, value in mat_dict.items():
|
|
147
|
-
_write_hdf5_value(f, key, value, compression_opts)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _write_hdf5_value(
|
|
151
|
-
parent: h5py.File | h5py.Group, key: str, value: Any, compression: str | None
|
|
152
|
-
) -> None:
|
|
153
|
-
"""Write a value to HDF5 file in MATLAB 7.3 compatible format.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
parent: HDF5 file or group object.
|
|
157
|
-
key: Variable name.
|
|
158
|
-
value: Value to write.
|
|
159
|
-
compression: Compression algorithm.
|
|
160
|
-
"""
|
|
161
|
-
if isinstance(value, np.ndarray):
|
|
162
|
-
# Create dataset for arrays
|
|
163
|
-
if compression and value.size > 100:
|
|
164
|
-
parent.create_dataset(key, data=value, compression=compression)
|
|
165
|
-
else:
|
|
166
|
-
parent.create_dataset(key, data=value)
|
|
167
|
-
# Set MATLAB class attribute
|
|
168
|
-
parent[key].attrs["MATLAB_class"] = np.bytes_("double")
|
|
169
|
-
elif isinstance(value, dict):
|
|
170
|
-
# Create group for structs/dicts
|
|
171
|
-
grp = parent.create_group(key)
|
|
172
|
-
grp.attrs["MATLAB_class"] = np.bytes_("struct")
|
|
173
|
-
for k, v in value.items():
|
|
174
|
-
_write_hdf5_value(grp, k, v, compression)
|
|
175
|
-
elif isinstance(value, str):
|
|
176
|
-
# String as uint16 array (MATLAB format)
|
|
177
|
-
dt = h5py.string_dtype(encoding="utf-8")
|
|
178
|
-
parent.create_dataset(key, data=value, dtype=dt)
|
|
179
|
-
parent[key].attrs["MATLAB_class"] = np.bytes_("char")
|
|
180
|
-
elif isinstance(value, int | float):
|
|
181
|
-
# Scalar as 1x1 array
|
|
182
|
-
parent.create_dataset(key, data=np.array([[value]]))
|
|
183
|
-
parent[key].attrs["MATLAB_class"] = np.bytes_("double")
|
|
184
|
-
elif isinstance(value, bool):
|
|
185
|
-
parent.create_dataset(key, data=np.array([[value]], dtype=np.uint8))
|
|
186
|
-
parent[key].attrs["MATLAB_class"] = np.bytes_("logical")
|
|
187
|
-
elif isinstance(value, list):
|
|
188
|
-
# Convert list to array
|
|
189
|
-
arr = np.array(value)
|
|
190
|
-
if compression and arr.size > 100:
|
|
191
|
-
parent.create_dataset(key, data=arr, compression=compression)
|
|
192
|
-
else:
|
|
193
|
-
parent.create_dataset(key, data=arr)
|
|
194
|
-
parent[key].attrs["MATLAB_class"] = np.bytes_("double")
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def _add_trace_to_dict(
|
|
198
|
-
mat_dict: dict[str, Any],
|
|
199
|
-
name: str,
|
|
200
|
-
trace: WaveformTrace | DigitalTrace,
|
|
201
|
-
include_metadata: bool,
|
|
202
|
-
) -> None:
|
|
203
|
-
"""Add trace to MATLAB dictionary with metadata.
|
|
204
|
-
|
|
205
|
-
Args:
|
|
206
|
-
mat_dict: MATLAB variable dictionary.
|
|
207
|
-
name: Variable name for trace.
|
|
208
|
-
trace: Trace to add.
|
|
209
|
-
include_metadata: Include metadata fields.
|
|
210
|
-
"""
|
|
211
|
-
name = _sanitize_varname(name)
|
|
212
|
-
|
|
213
|
-
# Add waveform data
|
|
214
|
-
if isinstance(trace, WaveformTrace):
|
|
215
|
-
mat_dict[f"{name}_data"] = trace.data
|
|
216
|
-
mat_dict[f"{name}_time"] = trace.time_vector
|
|
217
|
-
else: # DigitalTrace
|
|
218
|
-
mat_dict[f"{name}_data"] = trace.data.astype(np.uint8)
|
|
219
|
-
mat_dict[f"{name}_time"] = trace.time_vector
|
|
220
|
-
|
|
221
|
-
# Add metadata as struct
|
|
222
|
-
if include_metadata:
|
|
223
|
-
meta = trace.metadata
|
|
224
|
-
metadata_struct: dict[str, Any] = {
|
|
225
|
-
"sample_rate": meta.sample_rate,
|
|
226
|
-
"time_base": meta.time_base,
|
|
227
|
-
"num_samples": len(trace.data),
|
|
228
|
-
"duration": trace.duration,
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if meta.vertical_scale is not None:
|
|
232
|
-
metadata_struct["vertical_scale"] = meta.vertical_scale
|
|
233
|
-
if meta.vertical_offset is not None:
|
|
234
|
-
metadata_struct["vertical_offset"] = meta.vertical_offset
|
|
235
|
-
if meta.acquisition_time is not None:
|
|
236
|
-
metadata_struct["acquisition_time"] = meta.acquisition_time.isoformat()
|
|
237
|
-
if meta.source_file is not None:
|
|
238
|
-
metadata_struct["source_file"] = str(meta.source_file)
|
|
239
|
-
if meta.channel_name is not None:
|
|
240
|
-
metadata_struct["channel_name"] = meta.channel_name
|
|
241
|
-
if meta.trigger_info:
|
|
242
|
-
metadata_struct["trigger_info"] = meta.trigger_info
|
|
243
|
-
|
|
244
|
-
metadata_struct["trace_type"] = (
|
|
245
|
-
"waveform" if isinstance(trace, WaveformTrace) else "digital"
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
mat_dict[f"{name}_metadata"] = metadata_struct
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def _sanitize_varname(name: str) -> str:
|
|
252
|
-
"""Sanitize variable name for MATLAB compatibility.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
name: Variable name to sanitize.
|
|
256
|
-
|
|
257
|
-
Returns:
|
|
258
|
-
Sanitized variable name compatible with MATLAB.
|
|
259
|
-
|
|
260
|
-
Note:
|
|
261
|
-
MATLAB variable names must:
|
|
262
|
-
- Start with a letter
|
|
263
|
-
- Contain only letters, digits, and underscores
|
|
264
|
-
- Be <= 63 characters
|
|
265
|
-
"""
|
|
266
|
-
import re
|
|
267
|
-
|
|
268
|
-
# Replace invalid characters with underscores
|
|
269
|
-
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
|
270
|
-
|
|
271
|
-
# Ensure starts with letter
|
|
272
|
-
if name and not name[0].isalpha():
|
|
273
|
-
name = "var_" + name
|
|
274
|
-
|
|
275
|
-
# Truncate to 63 characters
|
|
276
|
-
if len(name) > 63:
|
|
277
|
-
name = name[:63]
|
|
278
|
-
|
|
279
|
-
return name if name else "var"
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def _convert_value(value: Any) -> Any:
|
|
283
|
-
"""Convert Python value to MATLAB-compatible format.
|
|
284
|
-
|
|
285
|
-
Args:
|
|
286
|
-
value: Python value to convert.
|
|
287
|
-
|
|
288
|
-
Returns:
|
|
289
|
-
MATLAB-compatible representation of the value.
|
|
290
|
-
"""
|
|
291
|
-
if isinstance(value, np.ndarray):
|
|
292
|
-
return value
|
|
293
|
-
if isinstance(value, list):
|
|
294
|
-
return np.array(value)
|
|
295
|
-
if isinstance(value, dict):
|
|
296
|
-
# Convert nested dict
|
|
297
|
-
return {_sanitize_varname(k): _convert_value(v) for k, v in value.items()}
|
|
298
|
-
if isinstance(value, str | int | float | bool):
|
|
299
|
-
return value
|
|
300
|
-
if isinstance(value, complex):
|
|
301
|
-
return value
|
|
302
|
-
if isinstance(value, datetime):
|
|
303
|
-
return value.isoformat()
|
|
304
|
-
# For other types, try converting to string
|
|
305
|
-
return str(value)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def export_multi_trace_mat(
|
|
309
|
-
traces: list[WaveformTrace | DigitalTrace],
|
|
310
|
-
path: str | Path,
|
|
311
|
-
*,
|
|
312
|
-
names: list[str] | None = None,
|
|
313
|
-
version: str = "5",
|
|
314
|
-
include_metadata: bool = True,
|
|
315
|
-
) -> None:
|
|
316
|
-
"""Export multiple traces to single MATLAB file.
|
|
317
|
-
|
|
318
|
-
Args:
|
|
319
|
-
traces: List of traces to export.
|
|
320
|
-
path: Output file path.
|
|
321
|
-
names: Variable names for each trace. If not provided, uses trace_1, trace_2, etc.
|
|
322
|
-
version: MAT-file version ("5", "7.3").
|
|
323
|
-
include_metadata: Include trace metadata.
|
|
324
|
-
|
|
325
|
-
Raises:
|
|
326
|
-
ValueError: If number of names does not match number of traces.
|
|
327
|
-
|
|
328
|
-
Example:
|
|
329
|
-
>>> export_multi_trace_mat(
|
|
330
|
-
... [ch1, ch2, ch3],
|
|
331
|
-
... "channels.mat",
|
|
332
|
-
... names=["ch1", "ch2", "ch3"]
|
|
333
|
-
... )
|
|
334
|
-
|
|
335
|
-
References:
|
|
336
|
-
EXP-008
|
|
337
|
-
"""
|
|
338
|
-
if names is None:
|
|
339
|
-
names = [f"trace_{i + 1}" for i in range(len(traces))]
|
|
340
|
-
|
|
341
|
-
if len(names) != len(traces):
|
|
342
|
-
raise ValueError("Number of names must match number of traces")
|
|
343
|
-
|
|
344
|
-
# Create dictionary mapping names to traces
|
|
345
|
-
trace_dict = dict(zip(names, traces, strict=True))
|
|
346
|
-
|
|
347
|
-
# Export using main function
|
|
348
|
-
export_mat(trace_dict, path, version=version, include_metadata=include_metadata)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
__all__ = [
|
|
352
|
-
"export_mat",
|
|
353
|
-
"export_multi_trace_mat",
|
|
354
|
-
]
|
oscura/exporters/npz_export.py
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
"""NumPy NPZ export functionality for Oscura.
|
|
2
|
-
|
|
3
|
-
This module provides export to NumPy's compressed archive format for
|
|
4
|
-
efficient storage and fast loading of trace data.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Example:
|
|
8
|
-
>>> from oscura.exporters.npz_export import export_npz
|
|
9
|
-
>>> export_npz(trace, "waveform.npz")
|
|
10
|
-
>>> # Load later with numpy
|
|
11
|
-
>>> import numpy as np
|
|
12
|
-
>>> data = np.load("waveform.npz")
|
|
13
|
-
>>> signal = data['signal']
|
|
14
|
-
>>> sample_rate = float(data['sample_rate'])
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
import contextlib
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from typing import TYPE_CHECKING, Any
|
|
22
|
-
|
|
23
|
-
import numpy as np
|
|
24
|
-
|
|
25
|
-
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
26
|
-
|
|
27
|
-
if TYPE_CHECKING:
|
|
28
|
-
from numpy.typing import NDArray
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def export_npz(
|
|
32
|
-
data: WaveformTrace | DigitalTrace | dict[str, Any] | NDArray[Any],
|
|
33
|
-
path: str | Path,
|
|
34
|
-
*,
|
|
35
|
-
compressed: bool = True,
|
|
36
|
-
include_metadata: bool = True,
|
|
37
|
-
include_time: bool = False,
|
|
38
|
-
) -> None:
|
|
39
|
-
"""Export data to NumPy NPZ archive format.
|
|
40
|
-
|
|
41
|
-
Creates a NumPy .npz file containing the trace data and optional metadata.
|
|
42
|
-
Files can be loaded with `numpy.load()` for fast array access.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
data: Data to export. Can be:
|
|
46
|
-
- WaveformTrace or DigitalTrace
|
|
47
|
-
- Dictionary of arrays
|
|
48
|
-
- NumPy array
|
|
49
|
-
path: Output file path (should end with .npz).
|
|
50
|
-
compressed: Use compression (default True). Results in smaller files
|
|
51
|
-
but slightly slower save/load.
|
|
52
|
-
include_metadata: Include metadata in the archive.
|
|
53
|
-
include_time: Include precomputed time array (increases file size).
|
|
54
|
-
|
|
55
|
-
Raises:
|
|
56
|
-
TypeError: If data type is not supported.
|
|
57
|
-
|
|
58
|
-
Example:
|
|
59
|
-
>>> export_npz(trace, "waveform.npz")
|
|
60
|
-
>>> # Load later
|
|
61
|
-
>>> data = np.load("waveform.npz")
|
|
62
|
-
>>> signal = data['signal']
|
|
63
|
-
>>> sample_rate = float(data['sample_rate'])
|
|
64
|
-
>>> time = np.arange(len(signal)) / sample_rate
|
|
65
|
-
|
|
66
|
-
References:
|
|
67
|
-
EXP-004
|
|
68
|
-
"""
|
|
69
|
-
path = Path(path)
|
|
70
|
-
|
|
71
|
-
# Ensure .npz extension
|
|
72
|
-
if path.suffix != ".npz":
|
|
73
|
-
path = path.with_suffix(".npz")
|
|
74
|
-
|
|
75
|
-
if isinstance(data, WaveformTrace | DigitalTrace):
|
|
76
|
-
_export_trace(data, path, compressed, include_metadata, include_time)
|
|
77
|
-
elif isinstance(data, dict):
|
|
78
|
-
_export_dict(data, path, compressed)
|
|
79
|
-
elif isinstance(data, np.ndarray):
|
|
80
|
-
_export_array(data, path, compressed)
|
|
81
|
-
else:
|
|
82
|
-
raise TypeError(f"Unsupported data type: {type(data)}")
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _export_trace(
|
|
86
|
-
trace: WaveformTrace | DigitalTrace,
|
|
87
|
-
path: Path,
|
|
88
|
-
compressed: bool,
|
|
89
|
-
include_metadata: bool,
|
|
90
|
-
include_time: bool,
|
|
91
|
-
) -> None:
|
|
92
|
-
"""Export trace to NPZ.
|
|
93
|
-
|
|
94
|
-
Args:
|
|
95
|
-
trace: Trace to export.
|
|
96
|
-
path: Output file path.
|
|
97
|
-
compressed: Use compression.
|
|
98
|
-
include_metadata: Include metadata arrays.
|
|
99
|
-
include_time: Include time array.
|
|
100
|
-
"""
|
|
101
|
-
arrays: dict[str, Any] = {}
|
|
102
|
-
|
|
103
|
-
# Main signal data
|
|
104
|
-
arrays["signal"] = trace.data
|
|
105
|
-
|
|
106
|
-
# Time array (optional - can be reconstructed from sample_rate)
|
|
107
|
-
if include_time:
|
|
108
|
-
arrays["time"] = trace.time_vector
|
|
109
|
-
|
|
110
|
-
# Metadata as scalars
|
|
111
|
-
if include_metadata:
|
|
112
|
-
meta = trace.metadata
|
|
113
|
-
arrays["sample_rate"] = np.array(meta.sample_rate)
|
|
114
|
-
arrays["samples"] = np.array(len(trace.data))
|
|
115
|
-
|
|
116
|
-
if hasattr(meta, "channel"):
|
|
117
|
-
arrays["channel"] = np.array(str(meta.channel or ""), dtype="U64")
|
|
118
|
-
|
|
119
|
-
if hasattr(meta, "source_file") and meta.source_file:
|
|
120
|
-
arrays["source_file"] = np.array(str(meta.source_file), dtype="U256")
|
|
121
|
-
|
|
122
|
-
if hasattr(meta, "capture_time") and meta.capture_time:
|
|
123
|
-
arrays["capture_time"] = np.array(meta.capture_time.isoformat(), dtype="U64")
|
|
124
|
-
|
|
125
|
-
if hasattr(meta, "units") and meta.units:
|
|
126
|
-
arrays["units"] = np.array(str(meta.units), dtype="U16")
|
|
127
|
-
|
|
128
|
-
# Add trace type marker
|
|
129
|
-
if isinstance(trace, DigitalTrace):
|
|
130
|
-
arrays["trace_type"] = np.array("digital", dtype="U16")
|
|
131
|
-
else:
|
|
132
|
-
arrays["trace_type"] = np.array("waveform", dtype="U16")
|
|
133
|
-
|
|
134
|
-
# Save
|
|
135
|
-
if compressed:
|
|
136
|
-
np.savez_compressed(path, **arrays)
|
|
137
|
-
else:
|
|
138
|
-
np.savez(path, **arrays)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _export_dict(
|
|
142
|
-
data: dict[str, Any],
|
|
143
|
-
path: Path,
|
|
144
|
-
compressed: bool,
|
|
145
|
-
) -> None:
|
|
146
|
-
"""Export dictionary of arrays to NPZ.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
data: Dictionary to export.
|
|
150
|
-
path: Output file path.
|
|
151
|
-
compressed: Use compression.
|
|
152
|
-
"""
|
|
153
|
-
# Convert values to arrays
|
|
154
|
-
arrays = {}
|
|
155
|
-
for key, value in data.items():
|
|
156
|
-
if isinstance(value, np.ndarray):
|
|
157
|
-
arrays[key] = value
|
|
158
|
-
elif isinstance(value, list | tuple | int | float):
|
|
159
|
-
arrays[key] = np.array(value)
|
|
160
|
-
elif isinstance(value, str):
|
|
161
|
-
arrays[key] = np.array(value, dtype="U256")
|
|
162
|
-
else:
|
|
163
|
-
# Try to convert, skip on failure
|
|
164
|
-
with contextlib.suppress(TypeError, ValueError):
|
|
165
|
-
arrays[key] = np.array(value)
|
|
166
|
-
|
|
167
|
-
if compressed:
|
|
168
|
-
np.savez_compressed(path, **arrays)
|
|
169
|
-
else:
|
|
170
|
-
np.savez(path, **arrays)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _export_array(
|
|
174
|
-
data: NDArray[Any],
|
|
175
|
-
path: Path,
|
|
176
|
-
compressed: bool,
|
|
177
|
-
) -> None:
|
|
178
|
-
"""Export single array to NPZ.
|
|
179
|
-
|
|
180
|
-
Args:
|
|
181
|
-
data: Array to export.
|
|
182
|
-
path: Output file path.
|
|
183
|
-
compressed: Use compression.
|
|
184
|
-
"""
|
|
185
|
-
if compressed:
|
|
186
|
-
np.savez_compressed(path, data=data)
|
|
187
|
-
else:
|
|
188
|
-
np.savez(path, data=data)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def load_npz(path: str | Path) -> dict[str, NDArray[Any]]:
|
|
192
|
-
"""Load NPZ file and return dictionary of arrays.
|
|
193
|
-
|
|
194
|
-
Convenience wrapper around numpy.load() that returns a regular dict.
|
|
195
|
-
|
|
196
|
-
Args:
|
|
197
|
-
path: Path to NPZ file.
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
Dictionary mapping array names to numpy arrays.
|
|
201
|
-
|
|
202
|
-
Example:
|
|
203
|
-
>>> data = load_npz("waveform.npz")
|
|
204
|
-
>>> signal = data['signal']
|
|
205
|
-
>>> sample_rate = float(data['sample_rate'])
|
|
206
|
-
|
|
207
|
-
References:
|
|
208
|
-
EXP-004
|
|
209
|
-
"""
|
|
210
|
-
path = Path(path)
|
|
211
|
-
|
|
212
|
-
with np.load(path) as npz:
|
|
213
|
-
return {key: npz[key] for key in npz.files}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
__all__ = [
|
|
217
|
-
"export_npz",
|
|
218
|
-
"load_npz",
|
|
219
|
-
]
|