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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Oscura Validate Command - Protocol Specification Validation.
|
|
2
|
+
|
|
3
|
+
Provides CLI for validating protocol specifications and message structures.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
$ oscura validate protocol_spec.yaml
|
|
8
|
+
$ oscura validate --spec uart --test-data capture.wfm
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("oscura.cli.validate")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command()
|
|
23
|
+
@click.argument("spec", type=click.Path(exists=True))
|
|
24
|
+
@click.option(
|
|
25
|
+
"--test-data",
|
|
26
|
+
type=click.Path(exists=True),
|
|
27
|
+
default=None,
|
|
28
|
+
help="Test data file for validation.",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--output",
|
|
32
|
+
type=click.Choice(["json", "table"], case_sensitive=False),
|
|
33
|
+
default="table",
|
|
34
|
+
help="Output format.",
|
|
35
|
+
)
|
|
36
|
+
@click.pass_context
|
|
37
|
+
def validate(
|
|
38
|
+
ctx: click.Context,
|
|
39
|
+
spec: str,
|
|
40
|
+
test_data: str | None,
|
|
41
|
+
output: str,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Validate protocol specification.
|
|
44
|
+
|
|
45
|
+
Validates protocol specification files (YAML/JSON) and optionally
|
|
46
|
+
tests them against real data.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
ctx: Click context object.
|
|
50
|
+
spec: Path to specification file.
|
|
51
|
+
test_data: Optional test data file.
|
|
52
|
+
output: Output format.
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
|
|
56
|
+
\b
|
|
57
|
+
# Validate spec only
|
|
58
|
+
$ oscura validate protocol_spec.yaml
|
|
59
|
+
|
|
60
|
+
\b
|
|
61
|
+
# Validate against test data
|
|
62
|
+
$ oscura validate uart_spec.yaml --test-data capture.wfm
|
|
63
|
+
"""
|
|
64
|
+
verbose = ctx.obj.get("verbose", 0)
|
|
65
|
+
|
|
66
|
+
if verbose:
|
|
67
|
+
logger.info(f"Validating specification: {spec}")
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
import yaml
|
|
71
|
+
|
|
72
|
+
# Load specification
|
|
73
|
+
spec_path = Path(spec)
|
|
74
|
+
with open(spec_path) as f:
|
|
75
|
+
if spec_path.suffix in [".yaml", ".yml"]:
|
|
76
|
+
spec_data = yaml.safe_load(f)
|
|
77
|
+
else:
|
|
78
|
+
import json
|
|
79
|
+
|
|
80
|
+
spec_data = json.load(f)
|
|
81
|
+
|
|
82
|
+
# Validate spec structure
|
|
83
|
+
validation_results: dict[str, Any] = {
|
|
84
|
+
"spec_file": str(spec_path.name),
|
|
85
|
+
"valid": True,
|
|
86
|
+
"errors": [],
|
|
87
|
+
"warnings": [],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_validate_spec_structure(spec_data, validation_results)
|
|
91
|
+
|
|
92
|
+
# Validate against test data if provided
|
|
93
|
+
if test_data:
|
|
94
|
+
from oscura.loaders import load
|
|
95
|
+
|
|
96
|
+
trace = load(test_data)
|
|
97
|
+
_validate_against_data(spec_data, trace, validation_results)
|
|
98
|
+
|
|
99
|
+
# Output results
|
|
100
|
+
if output == "json":
|
|
101
|
+
import json
|
|
102
|
+
|
|
103
|
+
click.echo(json.dumps(validation_results, indent=2))
|
|
104
|
+
else:
|
|
105
|
+
_print_validation_results(validation_results)
|
|
106
|
+
|
|
107
|
+
# Exit with error if validation failed
|
|
108
|
+
if not validation_results["valid"]:
|
|
109
|
+
ctx.exit(1)
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Validation failed: {e}")
|
|
113
|
+
if verbose > 1:
|
|
114
|
+
raise
|
|
115
|
+
click.echo(f"Error: {e}", err=True)
|
|
116
|
+
ctx.exit(1)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _validate_spec_structure(spec: dict[str, Any], results: dict[str, Any]) -> None:
|
|
120
|
+
"""Validate specification structure.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
spec: Specification dictionary.
|
|
124
|
+
results: Results dictionary to update.
|
|
125
|
+
"""
|
|
126
|
+
required_fields = ["name", "version"]
|
|
127
|
+
|
|
128
|
+
for field in required_fields:
|
|
129
|
+
if field not in spec:
|
|
130
|
+
results["errors"].append(f"Missing required field: {field}")
|
|
131
|
+
results["valid"] = False
|
|
132
|
+
|
|
133
|
+
# Check optional but recommended fields
|
|
134
|
+
recommended_fields = ["description", "fields", "constraints"]
|
|
135
|
+
for field in recommended_fields:
|
|
136
|
+
if field not in spec:
|
|
137
|
+
results["warnings"].append(f"Missing recommended field: {field}")
|
|
138
|
+
|
|
139
|
+
# Validate fields if present
|
|
140
|
+
if "fields" in spec and isinstance(spec["fields"], list):
|
|
141
|
+
for i, field in enumerate(spec["fields"]):
|
|
142
|
+
if not isinstance(field, dict):
|
|
143
|
+
results["errors"].append(f"Field {i} is not a dictionary")
|
|
144
|
+
results["valid"] = False
|
|
145
|
+
elif "name" not in field:
|
|
146
|
+
results["errors"].append(f"Field {i} missing 'name' attribute")
|
|
147
|
+
results["valid"] = False
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _validate_against_data(spec: dict[str, Any], trace: Any, results: dict[str, Any]) -> None:
|
|
151
|
+
"""Validate specification against test data.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
spec: Specification dictionary.
|
|
155
|
+
trace: Test data trace.
|
|
156
|
+
results: Results dictionary to update.
|
|
157
|
+
"""
|
|
158
|
+
# Check sample rate requirements
|
|
159
|
+
if "sample_rate_min" in spec:
|
|
160
|
+
min_rate = spec["sample_rate_min"]
|
|
161
|
+
if trace.metadata.sample_rate < min_rate:
|
|
162
|
+
results["warnings"].append(
|
|
163
|
+
f"Sample rate {trace.metadata.sample_rate} below minimum {min_rate}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Check data length
|
|
167
|
+
if "min_samples" in spec:
|
|
168
|
+
min_samples = spec["min_samples"]
|
|
169
|
+
if len(trace.data) < min_samples:
|
|
170
|
+
results["errors"].append(
|
|
171
|
+
f"Data has {len(trace.data)} samples, need at least {min_samples}"
|
|
172
|
+
)
|
|
173
|
+
results["valid"] = False
|
|
174
|
+
|
|
175
|
+
results["test_data_samples"] = len(trace.data)
|
|
176
|
+
results["test_data_sample_rate"] = trace.metadata.sample_rate
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _print_validation_results(results: dict[str, Any]) -> None:
|
|
180
|
+
"""Print validation results.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
results: Validation results dictionary.
|
|
184
|
+
"""
|
|
185
|
+
click.echo("\n=== Validation Results ===\n")
|
|
186
|
+
click.echo(f"Specification: {results['spec_file']}")
|
|
187
|
+
click.echo(f"Status: {'PASS' if results['valid'] else 'FAIL'}\n")
|
|
188
|
+
|
|
189
|
+
if results["errors"]:
|
|
190
|
+
click.echo("Errors:")
|
|
191
|
+
for err in results["errors"]:
|
|
192
|
+
click.echo(f" - {err}")
|
|
193
|
+
click.echo()
|
|
194
|
+
|
|
195
|
+
if results["warnings"]:
|
|
196
|
+
click.echo("Warnings:")
|
|
197
|
+
for warn in results["warnings"]:
|
|
198
|
+
click.echo(f" - {warn}")
|
|
199
|
+
click.echo()
|
|
200
|
+
|
|
201
|
+
if "test_data_samples" in results:
|
|
202
|
+
click.echo("Test Data:")
|
|
203
|
+
click.echo(f" Samples: {results['test_data_samples']}")
|
|
204
|
+
click.echo(f" Sample Rate: {results['test_data_sample_rate']} Hz")
|
oscura/cli/visualize.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Oscura Visualize Command - Interactive Waveform Viewer.
|
|
2
|
+
|
|
3
|
+
Provides CLI for launching interactive waveform visualization.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
$ oscura visualize signal.wfm
|
|
8
|
+
$ oscura visualize capture.wfm --protocol uart
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from matplotlib.axes import Axes
|
|
21
|
+
|
|
22
|
+
from oscura.core.types import WaveformTrace
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("oscura.cli.visualize")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command()
|
|
28
|
+
@click.argument("file", type=click.Path(exists=True))
|
|
29
|
+
@click.option(
|
|
30
|
+
"--protocol",
|
|
31
|
+
type=str,
|
|
32
|
+
default=None,
|
|
33
|
+
help="Protocol for overlay annotations.",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--save",
|
|
37
|
+
type=click.Path(),
|
|
38
|
+
default=None,
|
|
39
|
+
help="Save plot to file instead of showing.",
|
|
40
|
+
)
|
|
41
|
+
@click.pass_context
|
|
42
|
+
def visualize(
|
|
43
|
+
ctx: click.Context,
|
|
44
|
+
file: str,
|
|
45
|
+
protocol: str | None,
|
|
46
|
+
save: str | None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Launch interactive waveform viewer.
|
|
49
|
+
|
|
50
|
+
Opens an interactive plot window for waveform analysis with zoom,
|
|
51
|
+
pan, and measurement tools.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
ctx: Click context object.
|
|
55
|
+
file: Path to waveform file.
|
|
56
|
+
protocol: Optional protocol for annotations.
|
|
57
|
+
save: Save plot to file instead of displaying.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
|
|
61
|
+
\b
|
|
62
|
+
# Interactive viewer
|
|
63
|
+
$ oscura visualize signal.wfm
|
|
64
|
+
|
|
65
|
+
\b
|
|
66
|
+
# With protocol overlay
|
|
67
|
+
$ oscura visualize capture.wfm --protocol uart
|
|
68
|
+
|
|
69
|
+
\b
|
|
70
|
+
# Save to file
|
|
71
|
+
$ oscura visualize data.wfm --save plot.png
|
|
72
|
+
"""
|
|
73
|
+
verbose = ctx.obj.get("verbose", 0)
|
|
74
|
+
|
|
75
|
+
if verbose:
|
|
76
|
+
logger.info(f"Visualizing: {file}")
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
import matplotlib.pyplot as plt
|
|
80
|
+
import numpy as np
|
|
81
|
+
|
|
82
|
+
from oscura.core.types import IQTrace, WaveformTrace
|
|
83
|
+
from oscura.loaders import load
|
|
84
|
+
|
|
85
|
+
# Load file
|
|
86
|
+
trace = load(file)
|
|
87
|
+
|
|
88
|
+
# Create plot
|
|
89
|
+
fig, ax = plt.subplots(figsize=(12, 6))
|
|
90
|
+
|
|
91
|
+
# Plot waveform (handle IQTrace separately)
|
|
92
|
+
if isinstance(trace, IQTrace):
|
|
93
|
+
# Plot I/Q components
|
|
94
|
+
time_axis = np.arange(len(trace.i_data)) / trace.metadata.sample_rate
|
|
95
|
+
ax.plot(time_axis * 1e3, trace.i_data, linewidth=0.5, label="I")
|
|
96
|
+
ax.plot(time_axis * 1e3, trace.q_data, linewidth=0.5, label="Q")
|
|
97
|
+
ax.legend()
|
|
98
|
+
else:
|
|
99
|
+
# Plot regular waveform or digital trace
|
|
100
|
+
time_axis = np.arange(len(trace.data)) / trace.metadata.sample_rate
|
|
101
|
+
ax.plot(time_axis * 1e3, trace.data, linewidth=0.5)
|
|
102
|
+
|
|
103
|
+
ax.set_xlabel("Time (ms)")
|
|
104
|
+
ax.set_ylabel("Voltage (V)")
|
|
105
|
+
ax.set_title(f"Waveform: {Path(file).name}")
|
|
106
|
+
ax.grid(True, alpha=0.3)
|
|
107
|
+
|
|
108
|
+
# Add protocol overlay if requested (only for non-IQ traces)
|
|
109
|
+
if protocol and isinstance(trace, WaveformTrace):
|
|
110
|
+
_add_protocol_overlay(ax, trace, protocol)
|
|
111
|
+
|
|
112
|
+
# Save or show
|
|
113
|
+
if save:
|
|
114
|
+
plt.savefig(save, dpi=300, bbox_inches="tight")
|
|
115
|
+
click.echo(f"Saved to: {save}")
|
|
116
|
+
else:
|
|
117
|
+
plt.tight_layout()
|
|
118
|
+
plt.show()
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"Visualization failed: {e}")
|
|
122
|
+
if verbose > 1:
|
|
123
|
+
raise
|
|
124
|
+
click.echo(f"Error: {e}", err=True)
|
|
125
|
+
ctx.exit(1)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _add_protocol_overlay(ax: Axes, trace: WaveformTrace, protocol: str) -> None:
|
|
129
|
+
"""Add protocol annotations to plot.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
ax: Matplotlib axis.
|
|
133
|
+
trace: Waveform trace.
|
|
134
|
+
protocol: Protocol name.
|
|
135
|
+
"""
|
|
136
|
+
import numpy as np
|
|
137
|
+
|
|
138
|
+
# Convert to digital
|
|
139
|
+
threshold = (np.max(trace.data) + np.min(trace.data)) / 2
|
|
140
|
+
digital = trace.data > threshold
|
|
141
|
+
|
|
142
|
+
# Find edges
|
|
143
|
+
edges = np.where(np.diff(digital.astype(int)) != 0)[0]
|
|
144
|
+
|
|
145
|
+
# Mark edges
|
|
146
|
+
time_axis = np.arange(len(trace.data)) / trace.metadata.sample_rate
|
|
147
|
+
for edge in edges[:100]: # Limit to first 100 edges
|
|
148
|
+
ax.axvline(time_axis[edge] * 1e3, color="red", alpha=0.3, linewidth=0.5)
|
|
149
|
+
|
|
150
|
+
ax.text(
|
|
151
|
+
0.98,
|
|
152
|
+
0.98,
|
|
153
|
+
f"Protocol: {protocol.upper()}",
|
|
154
|
+
transform=ax.transAxes,
|
|
155
|
+
ha="right",
|
|
156
|
+
va="top",
|
|
157
|
+
bbox={"boxstyle": "round", "facecolor": "wheat", "alpha": 0.5},
|
|
158
|
+
)
|
oscura/convenience.py
CHANGED
|
@@ -195,109 +195,142 @@ def auto_decode(
|
|
|
195
195
|
References:
|
|
196
196
|
sigrok Protocol Decoder API
|
|
197
197
|
"""
|
|
198
|
+
|
|
199
|
+
# Prepare digital trace
|
|
200
|
+
digital_trace = _prepare_digital_trace(trace)
|
|
201
|
+
|
|
202
|
+
# Detect or use specified protocol
|
|
203
|
+
protocol, config, confidence = _detect_or_select_protocol(
|
|
204
|
+
trace, protocol, min_confidence, digital_trace
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Decode frames
|
|
208
|
+
frames, errors = _decode_protocol_frames(protocol, config, digital_trace)
|
|
209
|
+
|
|
210
|
+
# Calculate statistics
|
|
211
|
+
error_frames = sum(1 for f in frames if hasattr(f, "errors") and f.errors)
|
|
212
|
+
statistics = {
|
|
213
|
+
"total_frames": len(frames),
|
|
214
|
+
"error_frames": error_frames,
|
|
215
|
+
"error_rate": error_frames / len(frames) if frames else 0,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return DecodeResult(
|
|
219
|
+
protocol=protocol,
|
|
220
|
+
frames=frames,
|
|
221
|
+
confidence=confidence,
|
|
222
|
+
baud_rate=config.get("baud_rate"),
|
|
223
|
+
config=config,
|
|
224
|
+
errors=errors,
|
|
225
|
+
statistics=statistics,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _prepare_digital_trace(trace: WaveformTrace | DigitalTrace) -> DigitalTrace:
|
|
230
|
+
"""Convert to digital trace if needed."""
|
|
198
231
|
from oscura.core.types import WaveformTrace
|
|
199
|
-
from oscura.inference.protocol import detect_protocol
|
|
200
232
|
|
|
201
|
-
# Convert to digital if needed
|
|
202
233
|
if isinstance(trace, WaveformTrace):
|
|
203
234
|
from oscura.analyzers.digital.extraction import to_digital
|
|
204
235
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
236
|
+
return to_digital(trace, threshold="auto")
|
|
237
|
+
return trace
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _detect_or_select_protocol(
|
|
241
|
+
trace: WaveformTrace | DigitalTrace,
|
|
242
|
+
protocol: str | None,
|
|
243
|
+
min_confidence: float,
|
|
244
|
+
digital_trace: DigitalTrace,
|
|
245
|
+
) -> tuple[str, dict[str, Any], float]:
|
|
246
|
+
"""Detect protocol or use specified one."""
|
|
247
|
+
from oscura.core.types import WaveformTrace
|
|
208
248
|
|
|
209
|
-
# Auto-detect protocol if not specified
|
|
210
249
|
if protocol is None or protocol.lower() == "auto":
|
|
211
|
-
# detect_protocol only accepts WaveformTrace, use original trace if it's waveform
|
|
212
250
|
if isinstance(trace, WaveformTrace):
|
|
251
|
+
from oscura.inference.protocol import detect_protocol
|
|
252
|
+
|
|
213
253
|
detection = detect_protocol(
|
|
214
254
|
trace, min_confidence=min_confidence, return_candidates=True
|
|
215
255
|
)
|
|
216
256
|
else:
|
|
217
|
-
# For DigitalTrace, we can't auto-detect protocol, use default UART
|
|
218
257
|
detection = {"protocol": "UART", "config": {}, "confidence": 0.5}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
258
|
+
return (
|
|
259
|
+
detection.get("protocol", "unknown"),
|
|
260
|
+
detection.get("config", {}),
|
|
261
|
+
detection.get("confidence", 0.0),
|
|
262
|
+
)
|
|
222
263
|
else:
|
|
223
|
-
protocol
|
|
224
|
-
|
|
225
|
-
config = _get_default_protocol_config(protocol)
|
|
264
|
+
return protocol.upper(), _get_default_protocol_config(protocol.upper()), 1.0
|
|
265
|
+
|
|
226
266
|
|
|
227
|
-
|
|
267
|
+
def _decode_protocol_frames(
|
|
268
|
+
protocol: str, config: dict[str, Any], digital_trace: DigitalTrace
|
|
269
|
+
) -> tuple[list[Any], list[str]]:
|
|
270
|
+
"""Decode frames based on detected protocol."""
|
|
228
271
|
frames: list[Any] = []
|
|
229
272
|
errors: list[str] = []
|
|
230
273
|
|
|
231
274
|
try:
|
|
232
275
|
if protocol == "UART":
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
baud_rate = config.get("baud_rate", 115200)
|
|
236
|
-
decoder = UARTDecoder(
|
|
237
|
-
baudrate=baud_rate,
|
|
238
|
-
data_bits=config.get("data_bits", 8),
|
|
239
|
-
parity=config.get("parity", "none"),
|
|
240
|
-
stop_bits=config.get("stop_bits", 1),
|
|
241
|
-
)
|
|
242
|
-
frames = list(decoder.decode(digital_trace))
|
|
243
|
-
|
|
276
|
+
frames = _decode_uart(digital_trace, config)
|
|
244
277
|
elif protocol == "SPI":
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
spi_decoder = SPIDecoder(
|
|
248
|
-
cpol=config.get("clock_polarity", 0),
|
|
249
|
-
cpha=config.get("clock_phase", 0),
|
|
250
|
-
)
|
|
251
|
-
# Single channel decode - pass trace with channel mapping
|
|
252
|
-
frames = list(
|
|
253
|
-
spi_decoder.decode(digital_trace, clk=digital_trace.data, mosi=digital_trace.data)
|
|
254
|
-
)
|
|
255
|
-
|
|
278
|
+
frames = _decode_spi(digital_trace, config)
|
|
256
279
|
elif protocol == "I2C":
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
i2c_decoder = I2CDecoder()
|
|
260
|
-
# Single channel - use SDA and create synthetic SCL
|
|
261
|
-
sda = digital_trace.data
|
|
262
|
-
edges = np.where(np.diff(sda.astype(int)) != 0)[0]
|
|
263
|
-
scl = np.ones_like(sda, dtype=bool)
|
|
264
|
-
for i, edge in enumerate(edges):
|
|
265
|
-
if i % 2 == 0 and edge + 10 < len(scl):
|
|
266
|
-
scl[edge : edge + 10] = False
|
|
267
|
-
frames = list(i2c_decoder.decode(digital_trace, scl=scl, sda=sda))
|
|
268
|
-
|
|
280
|
+
frames = _decode_i2c(digital_trace, config)
|
|
269
281
|
elif protocol == "CAN":
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
can_decoder = CANDecoder(
|
|
273
|
-
bitrate=config.get("baud_rate", 500000),
|
|
274
|
-
sample_point=config.get("sample_point", 0.75),
|
|
275
|
-
)
|
|
276
|
-
frames = list(can_decoder.decode(digital_trace))
|
|
277
|
-
|
|
282
|
+
frames = _decode_can(digital_trace, config)
|
|
278
283
|
else:
|
|
279
284
|
errors.append(f"Unsupported protocol: {protocol}")
|
|
280
|
-
|
|
281
285
|
except Exception as e:
|
|
282
286
|
errors.append(f"Decoding error: {e!s}")
|
|
283
287
|
|
|
284
|
-
|
|
285
|
-
error_frames = sum(1 for f in frames if hasattr(f, "errors") and f.errors)
|
|
286
|
-
statistics = {
|
|
287
|
-
"total_frames": len(frames),
|
|
288
|
-
"error_frames": error_frames,
|
|
289
|
-
"error_rate": error_frames / len(frames) if frames else 0,
|
|
290
|
-
}
|
|
288
|
+
return frames, errors
|
|
291
289
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
290
|
+
|
|
291
|
+
def _decode_uart(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
|
|
292
|
+
"""Decode UART frames."""
|
|
293
|
+
from oscura.analyzers.protocols.uart import UARTDecoder
|
|
294
|
+
|
|
295
|
+
decoder = UARTDecoder(
|
|
296
|
+
baudrate=config.get("baud_rate", 115200),
|
|
297
|
+
data_bits=config.get("data_bits", 8),
|
|
298
|
+
parity=config.get("parity", "none"),
|
|
299
|
+
stop_bits=config.get("stop_bits", 1),
|
|
300
|
+
)
|
|
301
|
+
return list(decoder.decode(digital_trace))
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _decode_spi(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
|
|
305
|
+
"""Decode SPI frames."""
|
|
306
|
+
from oscura.analyzers.protocols.spi import SPIDecoder
|
|
307
|
+
|
|
308
|
+
decoder = SPIDecoder(cpol=config.get("clock_polarity", 0), cpha=config.get("clock_phase", 0))
|
|
309
|
+
return list(decoder.decode(digital_trace, clk=digital_trace.data, mosi=digital_trace.data))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _decode_i2c(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
|
|
313
|
+
"""Decode I2C frames."""
|
|
314
|
+
from oscura.analyzers.protocols.i2c import I2CDecoder
|
|
315
|
+
|
|
316
|
+
decoder = I2CDecoder()
|
|
317
|
+
sda = digital_trace.data
|
|
318
|
+
edges = np.where(np.diff(sda.astype(int)) != 0)[0]
|
|
319
|
+
scl = np.ones_like(sda, dtype=bool)
|
|
320
|
+
for i, edge in enumerate(edges):
|
|
321
|
+
if i % 2 == 0 and edge + 10 < len(scl):
|
|
322
|
+
scl[edge : edge + 10] = False
|
|
323
|
+
return list(decoder.decode(digital_trace, scl=scl, sda=sda))
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _decode_can(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
|
|
327
|
+
"""Decode CAN frames."""
|
|
328
|
+
from oscura.analyzers.protocols.can import CANDecoder
|
|
329
|
+
|
|
330
|
+
decoder = CANDecoder(
|
|
331
|
+
bitrate=config.get("baud_rate", 500000), sample_point=config.get("sample_point", 0.75)
|
|
300
332
|
)
|
|
333
|
+
return list(decoder.decode(digital_trace))
|
|
301
334
|
|
|
302
335
|
|
|
303
336
|
def smart_filter(
|
|
@@ -330,7 +363,7 @@ def smart_filter(
|
|
|
330
363
|
>>> # Or auto-detect
|
|
331
364
|
>>> clean = osc.smart_filter(noisy, target="auto")
|
|
332
365
|
"""
|
|
333
|
-
from oscura.filtering.convenience import (
|
|
366
|
+
from oscura.utils.filtering.convenience import (
|
|
334
367
|
high_pass,
|
|
335
368
|
low_pass,
|
|
336
369
|
median_filter,
|
|
@@ -405,11 +438,24 @@ def _detect_noise_type(
|
|
|
405
438
|
# Get noise floor estimate
|
|
406
439
|
noise_floor = np.median(mag_db[10 : len(mag_db) // 4])
|
|
407
440
|
|
|
408
|
-
# Check for power line hum
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
441
|
+
# Check for power line hum - must be clear peak at exact frequency
|
|
442
|
+
# Verify it's a local maximum to avoid false positives from spectral leakage
|
|
443
|
+
if idx_60 < len(mag_db) - 5 and idx_60 > 5 and idx_60 < len(freq):
|
|
444
|
+
# Must be within 3 Hz of actual 60 Hz (tighter tolerance)
|
|
445
|
+
if abs(freq[idx_60] - 60) < 3:
|
|
446
|
+
# Check if it's a local maximum (peak, not leakage)
|
|
447
|
+
local_max = mag_db[idx_60 - 2 : idx_60 + 3].max()
|
|
448
|
+
is_peak = mag_db[idx_60] >= local_max - 0.1 # Allow tiny numerical error
|
|
449
|
+
if is_peak and mag_db[idx_60] > noise_floor + 20:
|
|
450
|
+
return "60hz_hum"
|
|
451
|
+
if idx_50 < len(mag_db) - 5 and idx_50 > 5 and idx_50 < len(freq):
|
|
452
|
+
# Must be within 3 Hz of actual 50 Hz (tighter tolerance)
|
|
453
|
+
if abs(freq[idx_50] - 50) < 3:
|
|
454
|
+
# Check if it's a local maximum (peak, not leakage)
|
|
455
|
+
local_max = mag_db[idx_50 - 2 : idx_50 + 3].max()
|
|
456
|
+
is_peak = mag_db[idx_50] >= local_max - 0.1 # Allow tiny numerical error
|
|
457
|
+
if is_peak and mag_db[idx_50] > noise_floor + 20:
|
|
458
|
+
return "50hz_hum"
|
|
413
459
|
|
|
414
460
|
# Check frequency distribution
|
|
415
461
|
low_power = np.mean(mag_db[1 : len(mag_db) // 10])
|
oscura/core/__init__.py
CHANGED
|
@@ -39,8 +39,9 @@ from oscura.core.cross_domain import (
|
|
|
39
39
|
correlate_results,
|
|
40
40
|
)
|
|
41
41
|
from oscura.core.debug import (
|
|
42
|
+
DebugContext,
|
|
42
43
|
DebugLevel,
|
|
43
|
-
debug_context,
|
|
44
|
+
debug_context, # Backward compatibility alias
|
|
44
45
|
disable_debug,
|
|
45
46
|
enable_debug,
|
|
46
47
|
get_debug_level,
|
|
@@ -180,6 +181,7 @@ __all__ = [
|
|
|
180
181
|
"CorrelationResult",
|
|
181
182
|
"CrossDomainCorrelator",
|
|
182
183
|
"CrossDomainInsight",
|
|
184
|
+
"DebugContext",
|
|
183
185
|
"DebugLevel",
|
|
184
186
|
"DigitalTrace",
|
|
185
187
|
# Edge cases (EDGE-001, EDGE-002, EDGE-003)
|
|
@@ -251,7 +253,7 @@ __all__ = [
|
|
|
251
253
|
"create_progress_tracker",
|
|
252
254
|
"create_provenance",
|
|
253
255
|
"create_simple_progress",
|
|
254
|
-
"debug_context",
|
|
256
|
+
"debug_context", # Backward compatibility
|
|
255
257
|
"disable_debug",
|
|
256
258
|
# Debug (LOG-007)
|
|
257
259
|
"enable_debug",
|
oscura/core/backend_selector.py
CHANGED
|
@@ -43,7 +43,7 @@ except (ImportError, AttributeError):
|
|
|
43
43
|
HAS_GPU = False
|
|
44
44
|
|
|
45
45
|
try:
|
|
46
|
-
import numba
|
|
46
|
+
import numba
|
|
47
47
|
|
|
48
48
|
HAS_NUMBA = True
|
|
49
49
|
del numba
|
|
@@ -51,10 +51,10 @@ except ImportError:
|
|
|
51
51
|
HAS_NUMBA = False
|
|
52
52
|
|
|
53
53
|
try:
|
|
54
|
-
import dask.array # type: ignore[import-not-found
|
|
54
|
+
import dask.array # type: ignore[import-not-found] # Optional dependency
|
|
55
55
|
|
|
56
56
|
HAS_DASK = True
|
|
57
|
-
del dask
|
|
57
|
+
del dask
|
|
58
58
|
except ImportError:
|
|
59
59
|
HAS_DASK = False
|
|
60
60
|
|