oscura 0.5.0__py3-none-any.whl → 0.6.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/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- 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/__init__.py +1 -22
- 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 +2763 -0
- 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/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- 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 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- 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/README.md +7 -7
- 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 +171 -63
- 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/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- 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/reporting/templates/index.md +13 -13
- 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/autodetect.py +1 -5
- 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 +11 -3
- 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.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- 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/export/wavedrom.py +0 -430
- 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 -338
- 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/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- 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/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /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/{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.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,7 +5,7 @@ results first with options to drill down into details on demand.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.ui import ProgressiveDisplay
|
|
8
|
+
>>> from oscura.jupyter.ui import ProgressiveDisplay
|
|
9
9
|
>>> display = ProgressiveDisplay()
|
|
10
10
|
>>> output = display.render(result)
|
|
11
11
|
>>> print(output.summary()) # Level 1: Summary
|
|
@@ -215,7 +215,27 @@ class ProgressiveDisplay:
|
|
|
215
215
|
>>> output = display.render(characterization_result)
|
|
216
216
|
>>> print(output.summary())
|
|
217
217
|
"""
|
|
218
|
-
|
|
218
|
+
level1_content = self._build_level1_summary(result)
|
|
219
|
+
level2_sections = self._build_level2_sections(result)
|
|
220
|
+
level3_data = self._build_level3_expert_data(result)
|
|
221
|
+
current_level = self._determine_current_level()
|
|
222
|
+
|
|
223
|
+
return ProgressiveOutput(
|
|
224
|
+
level1_content=level1_content,
|
|
225
|
+
level2_sections=level2_sections,
|
|
226
|
+
level3_data=level3_data,
|
|
227
|
+
current_level=current_level,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def _build_level1_summary(self, result: Any) -> str:
|
|
231
|
+
"""Build Level 1 summary content.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
result: Analysis result.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Summary string with key items.
|
|
238
|
+
"""
|
|
219
239
|
level1_items = []
|
|
220
240
|
|
|
221
241
|
if hasattr(result, "signal_type"):
|
|
@@ -231,85 +251,136 @@ class ProgressiveDisplay:
|
|
|
231
251
|
if hasattr(result, "status"):
|
|
232
252
|
level1_items.append(f"Status: {result.status}")
|
|
233
253
|
|
|
234
|
-
# Limit to max_summary_items
|
|
235
254
|
level1_items = level1_items[: self.max_summary_items]
|
|
236
255
|
|
|
237
|
-
level1_content = "\n".join(level1_items)
|
|
238
|
-
|
|
239
256
|
if not level1_items:
|
|
240
|
-
|
|
257
|
+
return "Analysis complete. Expand for details."
|
|
258
|
+
|
|
259
|
+
return "\n".join(level1_items)
|
|
260
|
+
|
|
261
|
+
def _build_level2_sections(self, result: Any) -> list[Section]:
|
|
262
|
+
"""Build Level 2 detailed sections.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
result: Analysis result.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
List of Section objects.
|
|
269
|
+
"""
|
|
270
|
+
sections = []
|
|
271
|
+
|
|
272
|
+
params_section = self._build_parameters_section(result)
|
|
273
|
+
if params_section:
|
|
274
|
+
sections.append(params_section)
|
|
241
275
|
|
|
242
|
-
|
|
243
|
-
|
|
276
|
+
quality_section = self._build_quality_metrics_section(result)
|
|
277
|
+
if quality_section:
|
|
278
|
+
sections.append(quality_section)
|
|
244
279
|
|
|
245
|
-
|
|
246
|
-
if
|
|
247
|
-
|
|
248
|
-
|
|
280
|
+
findings_section = self._build_findings_section(result)
|
|
281
|
+
if findings_section:
|
|
282
|
+
sections.append(findings_section)
|
|
283
|
+
|
|
284
|
+
return sections
|
|
285
|
+
|
|
286
|
+
def _build_parameters_section(self, result: Any) -> Section | None:
|
|
287
|
+
"""Build parameters section.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
result: Analysis result.
|
|
249
291
|
|
|
250
|
-
|
|
251
|
-
|
|
292
|
+
Returns:
|
|
293
|
+
Section or None if no parameters.
|
|
294
|
+
"""
|
|
295
|
+
if not (hasattr(result, "parameters") and result.parameters):
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
params = result.parameters
|
|
299
|
+
summary = f"{len(params)} parameters detected"
|
|
300
|
+
|
|
301
|
+
content = "Parameters:\n"
|
|
302
|
+
for key, value in params.items():
|
|
303
|
+
content += f" {key}: {value}\n"
|
|
304
|
+
|
|
305
|
+
return Section(
|
|
306
|
+
title="Parameters",
|
|
307
|
+
summary=summary,
|
|
308
|
+
content=content,
|
|
309
|
+
is_collapsed=self.enable_collapsible_sections,
|
|
310
|
+
detail_level=2,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
def _build_quality_metrics_section(self, result: Any) -> Section | None:
|
|
314
|
+
"""Build quality metrics section.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
result: Analysis result.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Section or None if no quality data.
|
|
321
|
+
"""
|
|
322
|
+
if not (hasattr(result, "quality") or hasattr(result, "metrics")):
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
metrics = getattr(result, "metrics", {})
|
|
326
|
+
summary = "Quality assessment available"
|
|
327
|
+
content = "Quality Metrics:\n"
|
|
328
|
+
|
|
329
|
+
if hasattr(result, "quality"):
|
|
330
|
+
content += f" Overall: {result.quality}\n"
|
|
331
|
+
|
|
332
|
+
if metrics:
|
|
333
|
+
for key, value in metrics.items():
|
|
252
334
|
content += f" {key}: {value}\n"
|
|
253
335
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
level2_sections.append(
|
|
303
|
-
Section(
|
|
304
|
-
title="Findings",
|
|
305
|
-
summary=summary,
|
|
306
|
-
content=content,
|
|
307
|
-
is_collapsed=self.enable_collapsible_sections,
|
|
308
|
-
detail_level=2,
|
|
309
|
-
)
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
# Level 3: Build expert data
|
|
336
|
+
return Section(
|
|
337
|
+
title="Quality Metrics",
|
|
338
|
+
summary=summary,
|
|
339
|
+
content=content,
|
|
340
|
+
is_collapsed=self.enable_collapsible_sections,
|
|
341
|
+
detail_level=2,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def _build_findings_section(self, result: Any) -> Section | None:
|
|
345
|
+
"""Build findings section.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
result: Analysis result.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Section or None if no findings.
|
|
352
|
+
"""
|
|
353
|
+
if not (hasattr(result, "findings") and result.findings):
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
findings = result.findings
|
|
357
|
+
summary = f"{len(findings)} findings"
|
|
358
|
+
content = "Findings:\n"
|
|
359
|
+
|
|
360
|
+
for i, finding in enumerate(findings, 1):
|
|
361
|
+
if hasattr(finding, "title") and hasattr(finding, "description"):
|
|
362
|
+
content += f"\n{i}. {finding.title}\n"
|
|
363
|
+
content += f" {finding.description}\n"
|
|
364
|
+
else:
|
|
365
|
+
content += f"\n{i}. {finding}\n"
|
|
366
|
+
|
|
367
|
+
return Section(
|
|
368
|
+
title="Findings",
|
|
369
|
+
summary=summary,
|
|
370
|
+
content=content,
|
|
371
|
+
is_collapsed=self.enable_collapsible_sections,
|
|
372
|
+
detail_level=2,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def _build_level3_expert_data(self, result: Any) -> dict[str, Any]:
|
|
376
|
+
"""Build Level 3 expert data.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
result: Analysis result.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Dict of expert-level data.
|
|
383
|
+
"""
|
|
313
384
|
level3_data = {}
|
|
314
385
|
|
|
315
386
|
if hasattr(result, "raw_data"):
|
|
@@ -321,16 +392,16 @@ class ProgressiveDisplay:
|
|
|
321
392
|
if hasattr(result, "debug_trace"):
|
|
322
393
|
level3_data["debug_trace"] = result.debug_trace
|
|
323
394
|
|
|
324
|
-
|
|
325
|
-
level_map = {"summary": 1, "intermediate": 2, "expert": 3}
|
|
326
|
-
current_level = level_map.get(self.default_level, 1)
|
|
395
|
+
return level3_data
|
|
327
396
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
397
|
+
def _determine_current_level(self) -> int:
|
|
398
|
+
"""Determine current detail level from default.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Current level (1-3).
|
|
402
|
+
"""
|
|
403
|
+
level_map = {"summary": 1, "intermediate": 2, "expert": 3}
|
|
404
|
+
return level_map.get(self.default_level, 1)
|
|
334
405
|
|
|
335
406
|
|
|
336
407
|
__all__ = [
|
oscura/loaders/__init__.py
CHANGED
|
@@ -95,6 +95,7 @@ from oscura.loaders import (
|
|
|
95
95
|
csv,
|
|
96
96
|
hdf5,
|
|
97
97
|
)
|
|
98
|
+
from oscura.loaders.binary import load_binary
|
|
98
99
|
|
|
99
100
|
# Import configurable binary loading functionality
|
|
100
101
|
from oscura.loaders.configurable import (
|
|
@@ -122,6 +123,44 @@ from oscura.loaders.preprocessing import (
|
|
|
122
123
|
get_idle_statistics,
|
|
123
124
|
trim_idle,
|
|
124
125
|
)
|
|
126
|
+
|
|
127
|
+
# LAZY IMPORT: load_touchstone is loaded on first use to avoid 14.5s import penalty
|
|
128
|
+
# The touchstone loader imports signal_integrity which pulls in the entire analyzer chain
|
|
129
|
+
# See .claude/PERFORMANCE_PROFILE_2026-01-25.md for performance analysis
|
|
130
|
+
_load_touchstone_impl: Any = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def load_touchstone(path: str | Path) -> Any:
|
|
134
|
+
"""Load S-parameter data from Touchstone file (lazy import).
|
|
135
|
+
|
|
136
|
+
This function uses lazy imports to avoid a 14.5 second import penalty
|
|
137
|
+
from the touchstone -> signal_integrity -> analyzer dependency chain.
|
|
138
|
+
|
|
139
|
+
Supports .s1p through .s8p formats and both Touchstone 1.0
|
|
140
|
+
and 2.0 file formats.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
path: Path to Touchstone file.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
SParameterData with loaded S-parameters.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
LoaderError: If file cannot be read.
|
|
150
|
+
FormatError: If file format is invalid.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> s_params = load_touchstone("cable.s2p")
|
|
154
|
+
>>> print(f"Loaded {s_params.n_ports}-port, {len(s_params.frequencies)} points")
|
|
155
|
+
"""
|
|
156
|
+
global _load_touchstone_impl
|
|
157
|
+
if _load_touchstone_impl is None:
|
|
158
|
+
from oscura.loaders.touchstone import load_touchstone as _impl
|
|
159
|
+
|
|
160
|
+
_load_touchstone_impl = _impl
|
|
161
|
+
return _load_touchstone_impl(path)
|
|
162
|
+
|
|
163
|
+
|
|
125
164
|
from oscura.loaders.validation import (
|
|
126
165
|
PacketValidator,
|
|
127
166
|
SequenceGap,
|
|
@@ -302,7 +341,7 @@ def load_all_channels(
|
|
|
302
341
|
path: str | PathLike[str],
|
|
303
342
|
*,
|
|
304
343
|
format: str | None = None,
|
|
305
|
-
) -> dict[str, WaveformTrace | DigitalTrace]:
|
|
344
|
+
) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
|
|
306
345
|
"""Load all channels from a multi-channel waveform file.
|
|
307
346
|
|
|
308
347
|
Reads the file once and extracts all available channels (both analog
|
|
@@ -360,12 +399,12 @@ def load_all_channels(
|
|
|
360
399
|
# For other formats, try loading as single channel
|
|
361
400
|
trace = load(path, format=format)
|
|
362
401
|
channel_name = getattr(trace.metadata, "channel_name", None) or "ch1"
|
|
363
|
-
return {channel_name: trace}
|
|
402
|
+
return {channel_name: trace}
|
|
364
403
|
|
|
365
404
|
|
|
366
405
|
def _load_all_channels_tektronix(
|
|
367
406
|
path: Path,
|
|
368
|
-
) -> dict[str, WaveformTrace | DigitalTrace]:
|
|
407
|
+
) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
|
|
369
408
|
"""Load all channels from a Tektronix WFM file.
|
|
370
409
|
|
|
371
410
|
Args:
|
|
@@ -377,16 +416,52 @@ def _load_all_channels_tektronix(
|
|
|
377
416
|
Raises:
|
|
378
417
|
LoaderError: If the file cannot be read or parsed.
|
|
379
418
|
"""
|
|
419
|
+
wfm = _read_tektronix_file(path)
|
|
420
|
+
channels: dict[str, WaveformTrace | DigitalTrace | IQTrace] = {}
|
|
421
|
+
|
|
422
|
+
# Extract analog waveforms
|
|
423
|
+
_extract_analog_waveforms(wfm, path, channels)
|
|
424
|
+
|
|
425
|
+
# Extract digital waveforms
|
|
426
|
+
_extract_digital_waveforms(wfm, path, channels)
|
|
427
|
+
|
|
428
|
+
# Handle direct waveform formats (single file = single channel)
|
|
429
|
+
if not channels:
|
|
430
|
+
_extract_direct_waveform(wfm, path, channels)
|
|
431
|
+
|
|
432
|
+
if not channels:
|
|
433
|
+
raise LoaderError(
|
|
434
|
+
"No channels found in file",
|
|
435
|
+
file_path=str(path),
|
|
436
|
+
fix_hint="File may be empty or use an unsupported format variant.",
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
return channels
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _read_tektronix_file(path: Path) -> Any:
|
|
443
|
+
"""Read Tektronix WFM file, falling back to single channel if tm_data_types unavailable.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
path: Path to file.
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
WFM file object.
|
|
450
|
+
|
|
451
|
+
Raises:
|
|
452
|
+
LoaderError: If file cannot be read.
|
|
453
|
+
"""
|
|
380
454
|
try:
|
|
381
|
-
import tm_data_types # type: ignore[import-
|
|
455
|
+
import tm_data_types # type: ignore[import-untyped]
|
|
382
456
|
except ImportError:
|
|
383
457
|
# Fall back to single channel loading
|
|
384
458
|
trace = load(path, format="tektronix")
|
|
385
459
|
channel_name = getattr(trace.metadata, "channel_name", None) or "ch1"
|
|
386
|
-
|
|
460
|
+
# Return a dict-like object to maintain compatibility
|
|
461
|
+
return {"__fallback__": {channel_name: trace}}
|
|
387
462
|
|
|
388
463
|
try:
|
|
389
|
-
|
|
464
|
+
return tm_data_types.read_file(str(path))
|
|
390
465
|
except Exception as e:
|
|
391
466
|
raise LoaderError(
|
|
392
467
|
"Failed to read Tektronix WFM file",
|
|
@@ -394,72 +469,103 @@ def _load_all_channels_tektronix(
|
|
|
394
469
|
details=str(e),
|
|
395
470
|
) from e
|
|
396
471
|
|
|
397
|
-
channels: dict[str, WaveformTrace | DigitalTrace] = {}
|
|
398
472
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
from oscura.loaders.tektronix import _build_waveform_trace
|
|
404
|
-
|
|
405
|
-
for i, awfm in enumerate(wfm.analog_waveforms):
|
|
406
|
-
try:
|
|
407
|
-
data = np.array(awfm.y_data, dtype=np.float64)
|
|
408
|
-
x_increment = getattr(awfm, "x_increment", 1e-6)
|
|
409
|
-
sample_rate = 1.0 / x_increment if x_increment > 0 else 1e6
|
|
410
|
-
vertical_scale = getattr(awfm, "y_scale", None)
|
|
411
|
-
vertical_offset = getattr(awfm, "y_offset", None)
|
|
412
|
-
channel_name = getattr(awfm, "name", f"CH{i + 1}")
|
|
413
|
-
|
|
414
|
-
trace = _build_waveform_trace(
|
|
415
|
-
data=data,
|
|
416
|
-
sample_rate=sample_rate,
|
|
417
|
-
vertical_scale=vertical_scale,
|
|
418
|
-
vertical_offset=vertical_offset,
|
|
419
|
-
channel_name=channel_name,
|
|
420
|
-
path=path,
|
|
421
|
-
wfm=awfm,
|
|
422
|
-
)
|
|
423
|
-
channels[f"ch{i + 1}"] = trace
|
|
424
|
-
except Exception as e:
|
|
425
|
-
logger.warning("Failed to extract analog channel %d: %s", i + 1, e)
|
|
473
|
+
def _extract_analog_waveforms(
|
|
474
|
+
wfm: Any, path: Path, channels: dict[str, WaveformTrace | DigitalTrace | IQTrace]
|
|
475
|
+
) -> None:
|
|
476
|
+
"""Extract analog waveforms from WFM file.
|
|
426
477
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
478
|
+
Args:
|
|
479
|
+
wfm: WFM file object.
|
|
480
|
+
path: Path to file.
|
|
481
|
+
channels: Dictionary to populate with channels.
|
|
482
|
+
"""
|
|
483
|
+
if not hasattr(wfm, "analog_waveforms") or not wfm.analog_waveforms:
|
|
484
|
+
return
|
|
485
|
+
|
|
486
|
+
import numpy as np
|
|
487
|
+
|
|
488
|
+
from oscura.loaders.tektronix import _build_waveform_trace
|
|
489
|
+
|
|
490
|
+
for i, awfm in enumerate(wfm.analog_waveforms):
|
|
491
|
+
try:
|
|
492
|
+
data = np.array(awfm.y_data, dtype=np.float64)
|
|
493
|
+
x_increment = getattr(awfm, "x_increment", 1e-6)
|
|
494
|
+
sample_rate = 1.0 / x_increment if x_increment > 0 else 1e6
|
|
495
|
+
vertical_scale = getattr(awfm, "y_scale", None)
|
|
496
|
+
vertical_offset = getattr(awfm, "y_offset", None)
|
|
497
|
+
channel_name = getattr(awfm, "name", f"CH{i + 1}")
|
|
498
|
+
|
|
499
|
+
trace = _build_waveform_trace(
|
|
500
|
+
data=data,
|
|
501
|
+
sample_rate=sample_rate,
|
|
502
|
+
vertical_scale=vertical_scale,
|
|
503
|
+
vertical_offset=vertical_offset,
|
|
504
|
+
channel_name=channel_name,
|
|
505
|
+
path=path,
|
|
506
|
+
wfm=awfm,
|
|
507
|
+
)
|
|
508
|
+
channels[f"ch{i + 1}"] = trace
|
|
509
|
+
except Exception as e:
|
|
510
|
+
logger.warning("Failed to extract analog channel %d: %s", i + 1, e)
|
|
430
511
|
|
|
431
|
-
for i, dwfm in enumerate(wfm.digital_waveforms):
|
|
432
|
-
try:
|
|
433
|
-
trace = _load_digital_waveform(dwfm, path, i)
|
|
434
|
-
channels[f"d{i + 1}"] = trace
|
|
435
|
-
except Exception as e:
|
|
436
|
-
logger.warning("Failed to extract digital channel %d: %s", i + 1, e)
|
|
437
512
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
513
|
+
def _extract_digital_waveforms(
|
|
514
|
+
wfm: Any, path: Path, channels: dict[str, WaveformTrace | DigitalTrace | IQTrace]
|
|
515
|
+
) -> None:
|
|
516
|
+
"""Extract digital waveforms from WFM file.
|
|
441
517
|
|
|
442
|
-
|
|
443
|
-
|
|
518
|
+
Args:
|
|
519
|
+
wfm: WFM file object.
|
|
520
|
+
path: Path to file.
|
|
521
|
+
channels: Dictionary to populate with channels.
|
|
522
|
+
"""
|
|
523
|
+
if not hasattr(wfm, "digital_waveforms") or not wfm.digital_waveforms:
|
|
524
|
+
return
|
|
444
525
|
|
|
445
|
-
|
|
446
|
-
channel_name = trace.metadata.channel_name or "d1"
|
|
447
|
-
channels[channel_name.lower()] = trace
|
|
526
|
+
from oscura.loaders.tektronix import _load_digital_waveform
|
|
448
527
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
trace =
|
|
452
|
-
|
|
453
|
-
|
|
528
|
+
for i, dwfm in enumerate(wfm.digital_waveforms):
|
|
529
|
+
try:
|
|
530
|
+
trace = _load_digital_waveform(dwfm, path, i)
|
|
531
|
+
channels[f"d{i + 1}"] = trace
|
|
532
|
+
except Exception as e:
|
|
533
|
+
logger.warning("Failed to extract digital channel %d: %s", i + 1, e)
|
|
454
534
|
|
|
455
|
-
if not channels:
|
|
456
|
-
raise LoaderError(
|
|
457
|
-
"No channels found in file",
|
|
458
|
-
file_path=str(path),
|
|
459
|
-
fix_hint="File may be empty or use an unsupported format variant.",
|
|
460
|
-
)
|
|
461
535
|
|
|
462
|
-
|
|
536
|
+
def _extract_direct_waveform(
|
|
537
|
+
wfm: Any, path: Path, channels: dict[str, WaveformTrace | DigitalTrace | IQTrace]
|
|
538
|
+
) -> None:
|
|
539
|
+
"""Extract single channel from direct waveform format.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
wfm: WFM file object.
|
|
543
|
+
path: Path to file.
|
|
544
|
+
channels: Dictionary to populate with channels.
|
|
545
|
+
"""
|
|
546
|
+
wfm_type = type(wfm).__name__
|
|
547
|
+
|
|
548
|
+
if wfm_type == "DigitalWaveform" or hasattr(wfm, "y_axis_byte_values"):
|
|
549
|
+
from oscura.loaders.tektronix import _load_digital_waveform
|
|
550
|
+
|
|
551
|
+
trace = _load_digital_waveform(wfm, path, 0)
|
|
552
|
+
channel_name = trace.metadata.channel_name or "d1"
|
|
553
|
+
channels[channel_name.lower()] = trace
|
|
554
|
+
|
|
555
|
+
elif wfm_type == "IQWaveform" or (
|
|
556
|
+
hasattr(wfm, "i_axis_values") and hasattr(wfm, "q_axis_values")
|
|
557
|
+
):
|
|
558
|
+
# IQWaveform format (RF/SDR data)
|
|
559
|
+
loaded_trace = load(path, format="tektronix")
|
|
560
|
+
channel_name = loaded_trace.metadata.channel_name or "iq1"
|
|
561
|
+
channels[channel_name.lower()] = loaded_trace
|
|
562
|
+
|
|
563
|
+
elif hasattr(wfm, "y_axis_values") or hasattr(wfm, "y_data"):
|
|
564
|
+
# Direct analog waveform
|
|
565
|
+
loaded_trace = load(path, format="tektronix")
|
|
566
|
+
# Add both analog and digital traces to channels
|
|
567
|
+
channel_name = loaded_trace.metadata.channel_name or "ch1"
|
|
568
|
+
channels[channel_name.lower()] = loaded_trace
|
|
463
569
|
|
|
464
570
|
|
|
465
571
|
def get_supported_formats() -> list[str]:
|
|
@@ -535,9 +641,11 @@ __all__ = [
|
|
|
535
641
|
"hdf5",
|
|
536
642
|
"load",
|
|
537
643
|
"load_all_channels",
|
|
644
|
+
"load_binary",
|
|
538
645
|
"load_binary_packets",
|
|
539
646
|
"load_lazy",
|
|
540
647
|
"load_packets_streaming",
|
|
648
|
+
"load_touchstone",
|
|
541
649
|
"load_trace_lazy",
|
|
542
650
|
"trim_idle",
|
|
543
651
|
]
|