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
|
@@ -10,18 +10,18 @@ legacy hardware, and corrupted or noisy data.
|
|
|
10
10
|
- UNKNOWN-005: Reverse Engineering Workflow
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from oscura.exploratory.error_recovery import (
|
|
13
|
+
from oscura.jupyter.exploratory.error_recovery import (
|
|
14
14
|
ErrorContext,
|
|
15
15
|
partial_decode,
|
|
16
16
|
recover_corrupted_data,
|
|
17
17
|
retry_with_adjustment,
|
|
18
18
|
)
|
|
19
|
-
from oscura.exploratory.fuzzy import (
|
|
19
|
+
from oscura.jupyter.exploratory.fuzzy import (
|
|
20
20
|
fuzzy_pattern_match,
|
|
21
21
|
fuzzy_protocol_detect,
|
|
22
22
|
fuzzy_timing_match,
|
|
23
23
|
)
|
|
24
|
-
from oscura.exploratory.fuzzy_advanced import (
|
|
24
|
+
from oscura.jupyter.exploratory.fuzzy_advanced import (
|
|
25
25
|
AlignmentResult,
|
|
26
26
|
PositionAnalysis,
|
|
27
27
|
VariantCharacterization,
|
|
@@ -30,33 +30,33 @@ from oscura.exploratory.fuzzy_advanced import (
|
|
|
30
30
|
characterize_variants,
|
|
31
31
|
compute_conservation_scores,
|
|
32
32
|
)
|
|
33
|
-
from oscura.exploratory.legacy import (
|
|
33
|
+
from oscura.jupyter.exploratory.legacy import (
|
|
34
34
|
assess_signal_quality,
|
|
35
35
|
characterize_test_points,
|
|
36
36
|
cross_correlate_multi_reference,
|
|
37
37
|
detect_logic_families_multi_channel,
|
|
38
38
|
)
|
|
39
|
-
from oscura.exploratory.parse import (
|
|
39
|
+
from oscura.jupyter.exploratory.parse import (
|
|
40
40
|
DecodedFrame,
|
|
41
41
|
ErrorTolerance,
|
|
42
42
|
TimestampCorrection,
|
|
43
43
|
correct_timestamp_jitter,
|
|
44
44
|
decode_with_error_tolerance,
|
|
45
45
|
)
|
|
46
|
-
from oscura.exploratory.recovery import (
|
|
46
|
+
from oscura.jupyter.exploratory.recovery import (
|
|
47
47
|
ErrorAnalysis,
|
|
48
48
|
ErrorPattern,
|
|
49
49
|
analyze_bit_errors,
|
|
50
50
|
generate_error_visualization_data,
|
|
51
51
|
)
|
|
52
|
-
from oscura.exploratory.sync import (
|
|
52
|
+
from oscura.jupyter.exploratory.sync import (
|
|
53
53
|
PacketParseResult,
|
|
54
54
|
RecoveryStrategy,
|
|
55
55
|
SyncMatch,
|
|
56
56
|
fuzzy_sync_search,
|
|
57
57
|
parse_variable_length_packets,
|
|
58
58
|
)
|
|
59
|
-
from oscura.exploratory.unknown import (
|
|
59
|
+
from oscura.jupyter.exploratory.unknown import (
|
|
60
60
|
analyze_pattern_frequency,
|
|
61
61
|
characterize_unknown_signal,
|
|
62
62
|
detect_binary_fields,
|
|
@@ -5,7 +5,7 @@ noisy, or incomplete signal data.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.exploratory.error_recovery import recover_corrupted_data
|
|
8
|
+
>>> from oscura.jupyter.exploratory.error_recovery import recover_corrupted_data
|
|
9
9
|
>>> recovered, stats = recover_corrupted_data(trace)
|
|
10
10
|
>>> print(f"Recovered {stats.recovered_samples} samples")
|
|
11
11
|
"""
|
|
@@ -83,7 +83,64 @@ def recover_corrupted_data(
|
|
|
83
83
|
data = trace.data.copy()
|
|
84
84
|
n = len(data)
|
|
85
85
|
|
|
86
|
-
# Detect corrupted samples
|
|
86
|
+
# Detect corrupted samples
|
|
87
|
+
corrupted_mask, corrupted_indices = _detect_corrupted_samples(data, corruption_threshold)
|
|
88
|
+
n_corrupted = len(corrupted_indices)
|
|
89
|
+
|
|
90
|
+
if n_corrupted == 0:
|
|
91
|
+
return trace, RecoveryStats(
|
|
92
|
+
total_samples=n,
|
|
93
|
+
corrupted_samples=0,
|
|
94
|
+
recovered_samples=0,
|
|
95
|
+
unrecoverable_samples=0,
|
|
96
|
+
recovery_method="none",
|
|
97
|
+
confidence=1.0,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Group corrupted samples into contiguous regions (gaps)
|
|
101
|
+
gaps = _find_corruption_gaps(corrupted_indices)
|
|
102
|
+
|
|
103
|
+
# Attempt recovery for each gap
|
|
104
|
+
recovered, unrecoverable = _recover_gaps(
|
|
105
|
+
data, gaps, corrupted_mask, n, recovery_method, max_gap_samples
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Create recovered trace
|
|
109
|
+
recovered_trace = WaveformTrace(
|
|
110
|
+
data=data,
|
|
111
|
+
metadata=trace.metadata,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Calculate confidence
|
|
115
|
+
recovery_ratio = recovered / max(n_corrupted, 1)
|
|
116
|
+
gap_sizes = [end - start + 1 for start, end in gaps]
|
|
117
|
+
avg_gap_size = np.mean(gap_sizes) if gap_sizes else 0
|
|
118
|
+
confidence = recovery_ratio * (1 - avg_gap_size / max_gap_samples)
|
|
119
|
+
|
|
120
|
+
confidence_clamped: float = float(max(0.0, min(1.0, confidence)))
|
|
121
|
+
return recovered_trace, RecoveryStats(
|
|
122
|
+
total_samples=n,
|
|
123
|
+
corrupted_samples=n_corrupted,
|
|
124
|
+
recovered_samples=recovered,
|
|
125
|
+
unrecoverable_samples=unrecoverable,
|
|
126
|
+
recovery_method=recovery_method,
|
|
127
|
+
confidence=confidence_clamped,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _detect_corrupted_samples(
|
|
132
|
+
data: NDArray[np.float64],
|
|
133
|
+
corruption_threshold: float,
|
|
134
|
+
) -> tuple[NDArray[np.bool_], NDArray[np.int_]]:
|
|
135
|
+
"""Detect corrupted samples using statistical outlier detection.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
data: Waveform data array.
|
|
139
|
+
corruption_threshold: Threshold in standard deviations.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Tuple of (corrupted_mask, corrupted_indices).
|
|
143
|
+
"""
|
|
87
144
|
# Filter out nan/inf for initial statistics calculation
|
|
88
145
|
valid_mask = np.isfinite(data)
|
|
89
146
|
valid_data = data[valid_mask] if np.any(valid_mask) else data
|
|
@@ -94,46 +151,70 @@ def recover_corrupted_data(
|
|
|
94
151
|
if mad < 1e-10:
|
|
95
152
|
mad = np.std(valid_data) if len(valid_data) > 0 else 1.0
|
|
96
153
|
|
|
97
|
-
# Z-score based on MAD
|
|
154
|
+
# Z-score based on MAD (Median Absolute Deviation)
|
|
98
155
|
z_scores = np.abs(data - median) / (1.4826 * mad + 1e-10)
|
|
99
156
|
|
|
100
|
-
# Find corrupted samples
|
|
157
|
+
# Find corrupted samples (statistical outliers or invalid values)
|
|
101
158
|
corrupted_mask = z_scores > corruption_threshold
|
|
102
|
-
|
|
103
|
-
# Also detect NaN and Inf
|
|
104
159
|
corrupted_mask |= np.isnan(data)
|
|
105
160
|
corrupted_mask |= np.isinf(data)
|
|
106
161
|
|
|
107
162
|
corrupted_indices = np.where(corrupted_mask)[0]
|
|
108
|
-
n_corrupted = len(corrupted_indices)
|
|
109
163
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
164
|
+
return corrupted_mask, corrupted_indices
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _find_corruption_gaps(
|
|
168
|
+
corrupted_indices: NDArray[np.int_],
|
|
169
|
+
) -> list[tuple[int, int]]:
|
|
170
|
+
"""Group corrupted indices into contiguous regions (gaps).
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
corrupted_indices: Indices of corrupted samples.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of (start, end) tuples for each gap.
|
|
177
|
+
"""
|
|
178
|
+
if len(corrupted_indices) == 0:
|
|
179
|
+
return []
|
|
119
180
|
|
|
120
|
-
# Group corrupted samples into contiguous regions
|
|
121
181
|
gaps = []
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
gap_end = corrupted_indices[0]
|
|
182
|
+
gap_start = corrupted_indices[0]
|
|
183
|
+
gap_end = corrupted_indices[0]
|
|
125
184
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
185
|
+
for idx in corrupted_indices[1:]:
|
|
186
|
+
if idx == gap_end + 1:
|
|
187
|
+
gap_end = idx
|
|
188
|
+
else:
|
|
189
|
+
gaps.append((gap_start, gap_end))
|
|
190
|
+
gap_start = idx
|
|
191
|
+
gap_end = idx
|
|
133
192
|
|
|
134
|
-
|
|
193
|
+
gaps.append((gap_start, gap_end))
|
|
194
|
+
return gaps
|
|
135
195
|
|
|
136
|
-
|
|
196
|
+
|
|
197
|
+
def _recover_gaps(
|
|
198
|
+
data: NDArray[np.float64],
|
|
199
|
+
gaps: list[tuple[int, int]],
|
|
200
|
+
corrupted_mask: NDArray[np.bool_],
|
|
201
|
+
n: int,
|
|
202
|
+
recovery_method: str,
|
|
203
|
+
max_gap_samples: int,
|
|
204
|
+
) -> tuple[int, int]:
|
|
205
|
+
"""Attempt recovery for each corruption gap.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
data: Waveform data array (modified in place).
|
|
209
|
+
gaps: List of (start, end) tuples for gaps.
|
|
210
|
+
corrupted_mask: Boolean mask of corrupted samples.
|
|
211
|
+
n: Total number of samples.
|
|
212
|
+
recovery_method: Recovery method ('interpolate', 'median', 'zero').
|
|
213
|
+
max_gap_samples: Maximum recoverable gap size.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Tuple of (recovered_count, unrecoverable_count).
|
|
217
|
+
"""
|
|
137
218
|
recovered = 0
|
|
138
219
|
unrecoverable = 0
|
|
139
220
|
|
|
@@ -145,68 +226,93 @@ def recover_corrupted_data(
|
|
|
145
226
|
continue
|
|
146
227
|
|
|
147
228
|
if recovery_method == "interpolate":
|
|
148
|
-
|
|
149
|
-
left_idx = max(0, start - 1)
|
|
150
|
-
right_idx = min(n - 1, end + 1)
|
|
151
|
-
|
|
152
|
-
if left_idx < start and right_idx > end:
|
|
153
|
-
# Can interpolate
|
|
154
|
-
left_val = data[left_idx]
|
|
155
|
-
right_val = data[right_idx]
|
|
156
|
-
for i, idx in enumerate(range(start, end + 1)):
|
|
157
|
-
t = (i + 1) / (gap_length + 1)
|
|
158
|
-
data[idx] = left_val * (1 - t) + right_val * t
|
|
159
|
-
recovered += gap_length
|
|
160
|
-
else:
|
|
161
|
-
# Edge case - use nearest valid value
|
|
162
|
-
if left_idx >= start:
|
|
163
|
-
data[start : end + 1] = data[right_idx]
|
|
164
|
-
else:
|
|
165
|
-
data[start : end + 1] = data[left_idx]
|
|
166
|
-
recovered += gap_length
|
|
167
|
-
|
|
229
|
+
recovered += _recover_gap_interpolate(data, start, end, gap_length, n)
|
|
168
230
|
elif recovery_method == "median":
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
window_end = min(n, end + 50)
|
|
172
|
-
window_data = data[window_start:window_end]
|
|
173
|
-
valid_data = window_data[~corrupted_mask[window_start:window_end]]
|
|
174
|
-
|
|
175
|
-
if len(valid_data) > 0:
|
|
176
|
-
fill_value = np.median(valid_data)
|
|
177
|
-
data[start : end + 1] = fill_value
|
|
231
|
+
result = _recover_gap_median(data, corrupted_mask, start, end, gap_length, n)
|
|
232
|
+
if result:
|
|
178
233
|
recovered += gap_length
|
|
179
234
|
else:
|
|
180
235
|
unrecoverable += gap_length
|
|
181
|
-
|
|
182
236
|
elif recovery_method == "zero":
|
|
183
|
-
# Replace with zero
|
|
184
237
|
data[start : end + 1] = 0
|
|
185
238
|
recovered += gap_length
|
|
186
|
-
|
|
187
239
|
else:
|
|
188
240
|
unrecoverable += gap_length
|
|
189
241
|
|
|
190
|
-
|
|
191
|
-
recovered_trace = WaveformTrace(
|
|
192
|
-
data=data,
|
|
193
|
-
metadata=trace.metadata,
|
|
194
|
-
)
|
|
242
|
+
return recovered, unrecoverable
|
|
195
243
|
|
|
196
|
-
# Calculate confidence
|
|
197
|
-
recovery_ratio = recovered / max(n_corrupted, 1)
|
|
198
|
-
gap_sizes = [end - start + 1 for start, end in gaps]
|
|
199
|
-
avg_gap_size = np.mean(gap_sizes) if gap_sizes else 0
|
|
200
|
-
confidence = recovery_ratio * (1 - avg_gap_size / max_gap_samples)
|
|
201
244
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
def _recover_gap_interpolate(
|
|
246
|
+
data: NDArray[np.float64],
|
|
247
|
+
start: int,
|
|
248
|
+
end: int,
|
|
249
|
+
gap_length: int,
|
|
250
|
+
n: int,
|
|
251
|
+
) -> int:
|
|
252
|
+
"""Recover gap using linear interpolation.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
data: Waveform data array (modified in place).
|
|
256
|
+
start: Gap start index.
|
|
257
|
+
end: Gap end index.
|
|
258
|
+
gap_length: Gap length.
|
|
259
|
+
n: Total number of samples.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Number of samples recovered.
|
|
263
|
+
"""
|
|
264
|
+
left_idx = max(0, start - 1)
|
|
265
|
+
right_idx = min(n - 1, end + 1)
|
|
266
|
+
|
|
267
|
+
if left_idx < start and right_idx > end:
|
|
268
|
+
# Can interpolate between valid samples
|
|
269
|
+
left_val = data[left_idx]
|
|
270
|
+
right_val = data[right_idx]
|
|
271
|
+
for i, idx in enumerate(range(start, end + 1)):
|
|
272
|
+
t = (i + 1) / (gap_length + 1)
|
|
273
|
+
data[idx] = left_val * (1 - t) + right_val * t
|
|
274
|
+
else:
|
|
275
|
+
# Edge case - use nearest valid value
|
|
276
|
+
if left_idx >= start:
|
|
277
|
+
data[start : end + 1] = data[right_idx]
|
|
278
|
+
else:
|
|
279
|
+
data[start : end + 1] = data[left_idx]
|
|
280
|
+
|
|
281
|
+
return gap_length
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _recover_gap_median(
|
|
285
|
+
data: NDArray[np.float64],
|
|
286
|
+
corrupted_mask: NDArray[np.bool_],
|
|
287
|
+
start: int,
|
|
288
|
+
end: int,
|
|
289
|
+
gap_length: int,
|
|
290
|
+
n: int,
|
|
291
|
+
) -> bool:
|
|
292
|
+
"""Recover gap using local median value.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
data: Waveform data array (modified in place).
|
|
296
|
+
corrupted_mask: Boolean mask of corrupted samples.
|
|
297
|
+
start: Gap start index.
|
|
298
|
+
end: Gap end index.
|
|
299
|
+
gap_length: Gap length.
|
|
300
|
+
n: Total number of samples.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
True if recovery succeeded, False otherwise.
|
|
304
|
+
"""
|
|
305
|
+
window_start = max(0, start - 50)
|
|
306
|
+
window_end = min(n, end + 50)
|
|
307
|
+
window_data = data[window_start:window_end]
|
|
308
|
+
valid_data = window_data[~corrupted_mask[window_start:window_end]]
|
|
309
|
+
|
|
310
|
+
if len(valid_data) > 0:
|
|
311
|
+
fill_value = np.median(valid_data)
|
|
312
|
+
data[start : end + 1] = fill_value
|
|
313
|
+
return True
|
|
314
|
+
else:
|
|
315
|
+
return False
|
|
210
316
|
|
|
211
317
|
|
|
212
318
|
@dataclass
|
|
@@ -358,73 +464,20 @@ def partial_decode(
|
|
|
358
464
|
References:
|
|
359
465
|
ERROR-003: Partial Decode Support
|
|
360
466
|
"""
|
|
361
|
-
|
|
362
|
-
n = len(data)
|
|
363
|
-
|
|
364
|
-
complete_packets: list[dict[str, Any]] = []
|
|
365
|
-
partial_packets: list[dict[str, Any]] = []
|
|
366
|
-
error_regions: list[dict[str, Any]] = []
|
|
367
|
-
|
|
368
|
-
total_samples = 0
|
|
369
|
-
decoded_samples = 0
|
|
370
|
-
|
|
371
|
-
# Try to decode entire trace first
|
|
372
|
-
try:
|
|
373
|
-
full_result = decode_func(trace)
|
|
374
|
-
if full_result:
|
|
375
|
-
complete_packets.extend(full_result)
|
|
376
|
-
decoded_samples = n
|
|
377
|
-
total_samples = n
|
|
378
|
-
except Exception as e:
|
|
379
|
-
logger.info("Full decode failed, falling back to segment decode: %s", e)
|
|
380
|
-
# Fall back to segment-by-segment decode
|
|
381
|
-
for start in range(0, n, segment_size):
|
|
382
|
-
end = min(start + segment_size, n)
|
|
383
|
-
segment_data = data[start:end]
|
|
384
|
-
|
|
385
|
-
# Create segment trace
|
|
386
|
-
segment_trace = WaveformTrace(
|
|
387
|
-
data=segment_data,
|
|
388
|
-
metadata=trace.metadata,
|
|
389
|
-
)
|
|
467
|
+
n = len(trace.data)
|
|
390
468
|
|
|
391
|
-
|
|
469
|
+
# Try full decode first
|
|
470
|
+
full_decode_result = _try_full_decode(trace, decode_func, n)
|
|
471
|
+
if full_decode_result is not None:
|
|
472
|
+
return full_decode_result
|
|
392
473
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
# Adjust timestamps
|
|
398
|
-
for packet in segment_result:
|
|
399
|
-
if "timestamp" in packet:
|
|
400
|
-
packet["timestamp"] += start / trace.metadata.sample_rate
|
|
401
|
-
if "sample" in packet:
|
|
402
|
-
packet["sample"] += start
|
|
403
|
-
|
|
404
|
-
# Check if segment is valid
|
|
405
|
-
valid_ratio = len(segment_result) / max(len(segment_data) / 100, 1)
|
|
406
|
-
|
|
407
|
-
if valid_ratio >= min_valid_ratio:
|
|
408
|
-
complete_packets.extend(segment_result)
|
|
409
|
-
decoded_samples += len(segment_data)
|
|
410
|
-
else:
|
|
411
|
-
partial_packets.extend(segment_result)
|
|
412
|
-
decoded_samples += len(segment_data) // 2
|
|
413
|
-
|
|
414
|
-
except Exception as e:
|
|
415
|
-
logger.debug("Segment decode failed at sample %d: %s", start, e)
|
|
416
|
-
error_regions.append(
|
|
417
|
-
{
|
|
418
|
-
"start_sample": start,
|
|
419
|
-
"end_sample": end,
|
|
420
|
-
"error": str(e),
|
|
421
|
-
}
|
|
422
|
-
)
|
|
474
|
+
# Fall back to segment decode
|
|
475
|
+
complete_packets, partial_packets, error_regions, decoded_samples, total_samples = (
|
|
476
|
+
_segment_decode(trace, decode_func, segment_size, min_valid_ratio, n)
|
|
477
|
+
)
|
|
423
478
|
|
|
424
479
|
# Calculate statistics
|
|
425
480
|
decode_rate = decoded_samples / max(total_samples, 1)
|
|
426
|
-
|
|
427
|
-
# Calculate confidence
|
|
428
481
|
error_ratio = len(error_regions) / max((n // segment_size), 1)
|
|
429
482
|
confidence = decode_rate * (1 - error_ratio)
|
|
430
483
|
|
|
@@ -437,6 +490,110 @@ def partial_decode(
|
|
|
437
490
|
)
|
|
438
491
|
|
|
439
492
|
|
|
493
|
+
def _try_full_decode(
|
|
494
|
+
trace: WaveformTrace,
|
|
495
|
+
decode_func: Callable[[WaveformTrace], list[dict[str, Any]]],
|
|
496
|
+
n: int,
|
|
497
|
+
) -> PartialDecodeResult | None:
|
|
498
|
+
"""Try to decode entire trace at once.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
trace: Trace to decode.
|
|
502
|
+
decode_func: Protocol decode function.
|
|
503
|
+
n: Number of samples.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
PartialDecodeResult if successful, None if failed.
|
|
507
|
+
"""
|
|
508
|
+
try:
|
|
509
|
+
full_result = decode_func(trace)
|
|
510
|
+
if full_result:
|
|
511
|
+
return PartialDecodeResult(
|
|
512
|
+
complete_packets=full_result,
|
|
513
|
+
partial_packets=[],
|
|
514
|
+
error_regions=[],
|
|
515
|
+
decode_rate=1.0,
|
|
516
|
+
confidence=1.0,
|
|
517
|
+
)
|
|
518
|
+
except Exception as e:
|
|
519
|
+
logger.info("Full decode failed, falling back to segment decode: %s", e)
|
|
520
|
+
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def _segment_decode(
|
|
525
|
+
trace: WaveformTrace,
|
|
526
|
+
decode_func: Callable[[WaveformTrace], list[dict[str, Any]]],
|
|
527
|
+
segment_size: int,
|
|
528
|
+
min_valid_ratio: float,
|
|
529
|
+
n: int,
|
|
530
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], int, int]:
|
|
531
|
+
"""Decode trace segment by segment.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
trace: Trace to decode.
|
|
535
|
+
decode_func: Protocol decode function.
|
|
536
|
+
segment_size: Size of segments to try independently.
|
|
537
|
+
min_valid_ratio: Minimum valid ratio to accept segment.
|
|
538
|
+
n: Number of samples.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
Tuple of (complete_packets, partial_packets, error_regions, decoded_samples, total_samples).
|
|
542
|
+
"""
|
|
543
|
+
complete_packets: list[dict[str, Any]] = []
|
|
544
|
+
partial_packets: list[dict[str, Any]] = []
|
|
545
|
+
error_regions: list[dict[str, Any]] = []
|
|
546
|
+
total_samples = 0
|
|
547
|
+
decoded_samples = 0
|
|
548
|
+
|
|
549
|
+
for start in range(0, n, segment_size):
|
|
550
|
+
end = min(start + segment_size, n)
|
|
551
|
+
segment_data = trace.data[start:end]
|
|
552
|
+
|
|
553
|
+
segment_trace = WaveformTrace(data=segment_data, metadata=trace.metadata)
|
|
554
|
+
total_samples += len(segment_data)
|
|
555
|
+
|
|
556
|
+
try:
|
|
557
|
+
segment_result = decode_func(segment_trace)
|
|
558
|
+
|
|
559
|
+
if segment_result:
|
|
560
|
+
_adjust_packet_timestamps(segment_result, start, trace.metadata.sample_rate)
|
|
561
|
+
|
|
562
|
+
# Classify segment as complete or partial
|
|
563
|
+
valid_ratio = len(segment_result) / max(len(segment_data) / 100, 1)
|
|
564
|
+
if valid_ratio >= min_valid_ratio:
|
|
565
|
+
complete_packets.extend(segment_result)
|
|
566
|
+
decoded_samples += len(segment_data)
|
|
567
|
+
else:
|
|
568
|
+
partial_packets.extend(segment_result)
|
|
569
|
+
decoded_samples += len(segment_data) // 2
|
|
570
|
+
|
|
571
|
+
except Exception as e:
|
|
572
|
+
logger.debug("Segment decode failed at sample %d: %s", start, e)
|
|
573
|
+
error_regions.append({"start_sample": start, "end_sample": end, "error": str(e)})
|
|
574
|
+
|
|
575
|
+
return complete_packets, partial_packets, error_regions, decoded_samples, total_samples
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _adjust_packet_timestamps(
|
|
579
|
+
packets: list[dict[str, Any]],
|
|
580
|
+
start_sample: int,
|
|
581
|
+
sample_rate: float,
|
|
582
|
+
) -> None:
|
|
583
|
+
"""Adjust packet timestamps to account for segment offset.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
packets: List of decoded packets to adjust.
|
|
587
|
+
start_sample: Start sample of this segment.
|
|
588
|
+
sample_rate: Sample rate in Hz.
|
|
589
|
+
"""
|
|
590
|
+
for packet in packets:
|
|
591
|
+
if "timestamp" in packet:
|
|
592
|
+
packet["timestamp"] += start_sample / sample_rate
|
|
593
|
+
if "sample" in packet:
|
|
594
|
+
packet["sample"] += start_sample
|
|
595
|
+
|
|
596
|
+
|
|
440
597
|
@dataclass
|
|
441
598
|
class ErrorContext:
|
|
442
599
|
"""Preserved error context for debugging.
|