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
oscura/discovery/auto_decoder.py
CHANGED
|
@@ -180,33 +180,41 @@ def decode_protocol(
|
|
|
180
180
|
)
|
|
181
181
|
|
|
182
182
|
|
|
183
|
-
def
|
|
183
|
+
def _extract_uart_data_and_sample_rate(
|
|
184
184
|
trace: WaveformTrace | DigitalTrace,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return_errors: bool,
|
|
188
|
-
) -> DecodeResult:
|
|
189
|
-
"""Auto-decode UART with parameter detection.
|
|
185
|
+
) -> tuple[NDArray[np.floating[Any]], float]:
|
|
186
|
+
"""Extract data array and sample rate from trace.
|
|
190
187
|
|
|
191
188
|
Args:
|
|
192
|
-
trace: Input trace
|
|
193
|
-
params_hint: Optional parameter hints.
|
|
194
|
-
confidence_threshold: Minimum confidence threshold.
|
|
195
|
-
return_errors: Whether to include errors.
|
|
189
|
+
trace: Input trace
|
|
196
190
|
|
|
197
191
|
Returns:
|
|
198
|
-
|
|
192
|
+
Tuple of (data array, sample rate)
|
|
199
193
|
"""
|
|
200
|
-
from oscura.analyzers.protocols.uart import UARTDecoder
|
|
201
|
-
|
|
202
|
-
# Get data array
|
|
203
194
|
if isinstance(trace, WaveformTrace):
|
|
204
195
|
data = trace.data
|
|
205
196
|
sample_rate = trace.metadata.sample_rate
|
|
206
197
|
else:
|
|
207
198
|
data = trace.data.astype(np.float64)
|
|
208
199
|
sample_rate = trace.metadata.sample_rate
|
|
200
|
+
return data, sample_rate
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _determine_uart_parameters(
|
|
204
|
+
params_hint: dict[str, Any] | None,
|
|
205
|
+
data: NDArray[np.floating[Any]],
|
|
206
|
+
sample_rate: float,
|
|
207
|
+
) -> tuple[int, int, str, int]:
|
|
208
|
+
"""Determine UART parameters from hints or auto-detection.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
params_hint: Optional parameter hints
|
|
212
|
+
data: Signal data
|
|
213
|
+
sample_rate: Sampling rate
|
|
209
214
|
|
|
215
|
+
Returns:
|
|
216
|
+
Tuple of (baud_rate, data_bits, parity, stop_bits)
|
|
217
|
+
"""
|
|
210
218
|
# Auto-detect baud rate if not provided
|
|
211
219
|
if params_hint and "baud_rate" in params_hint:
|
|
212
220
|
baud_rate = params_hint["baud_rate"]
|
|
@@ -218,27 +226,41 @@ def _decode_uart_auto(
|
|
|
218
226
|
parity = params_hint.get("parity", "none") if params_hint else "none"
|
|
219
227
|
stop_bits = params_hint.get("stop_bits", 1) if params_hint else 1
|
|
220
228
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
return baud_rate, data_bits, parity, stop_bits
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _decode_uart_packets(
|
|
233
|
+
decoder: Any,
|
|
234
|
+
trace: WaveformTrace | DigitalTrace,
|
|
235
|
+
baud_rate: int,
|
|
236
|
+
) -> list[Any]:
|
|
237
|
+
"""Decode UART packets from trace.
|
|
228
238
|
|
|
229
|
-
|
|
239
|
+
Args:
|
|
240
|
+
decoder: UART decoder instance
|
|
241
|
+
trace: Input trace
|
|
242
|
+
baud_rate: Baud rate for error reporting
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
List of decoded packets (empty on failure)
|
|
246
|
+
"""
|
|
230
247
|
try:
|
|
231
|
-
|
|
248
|
+
return list(decoder.decode(trace))
|
|
232
249
|
except Exception:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
return []
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _convert_uart_packets_to_bytes(
|
|
254
|
+
packets: list[Any],
|
|
255
|
+
) -> tuple[list[DecodedByte], int]:
|
|
256
|
+
"""Convert UART packets to DecodedByte format.
|
|
240
257
|
|
|
241
|
-
|
|
258
|
+
Args:
|
|
259
|
+
packets: List of decoded packets
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Tuple of (decoded bytes list, error count)
|
|
263
|
+
"""
|
|
242
264
|
decoded_bytes: list[DecodedByte] = []
|
|
243
265
|
error_count = 0
|
|
244
266
|
|
|
@@ -263,12 +285,72 @@ def _decode_uart_auto(
|
|
|
263
285
|
)
|
|
264
286
|
)
|
|
265
287
|
|
|
266
|
-
|
|
288
|
+
return decoded_bytes, error_count
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _calculate_uart_confidence(decoded_bytes: list[DecodedByte]) -> float:
|
|
292
|
+
"""Calculate overall confidence from decoded bytes.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
decoded_bytes: List of decoded bytes
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Overall confidence score
|
|
299
|
+
"""
|
|
267
300
|
if decoded_bytes:
|
|
268
301
|
avg_confidence = np.mean([b.confidence for b in decoded_bytes])
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
302
|
+
return float(round(float(avg_confidence), 2))
|
|
303
|
+
return 0.0
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _decode_uart_auto(
|
|
307
|
+
trace: WaveformTrace | DigitalTrace,
|
|
308
|
+
params_hint: dict[str, Any] | None,
|
|
309
|
+
confidence_threshold: float,
|
|
310
|
+
return_errors: bool,
|
|
311
|
+
) -> DecodeResult:
|
|
312
|
+
"""Auto-decode UART with parameter detection.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
trace: Input trace.
|
|
316
|
+
params_hint: Optional parameter hints.
|
|
317
|
+
confidence_threshold: Minimum confidence threshold.
|
|
318
|
+
return_errors: Whether to include errors.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
DecodeResult for UART.
|
|
322
|
+
"""
|
|
323
|
+
from oscura.analyzers.protocols.uart import UARTDecoder
|
|
324
|
+
|
|
325
|
+
# Extract data and determine parameters
|
|
326
|
+
data, sample_rate = _extract_uart_data_and_sample_rate(trace)
|
|
327
|
+
baud_rate, data_bits, parity, stop_bits = _determine_uart_parameters(
|
|
328
|
+
params_hint, data, sample_rate
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Create decoder with detected parameters
|
|
332
|
+
decoder = UARTDecoder(
|
|
333
|
+
baudrate=baud_rate,
|
|
334
|
+
data_bits=data_bits,
|
|
335
|
+
parity=parity, # type: ignore[arg-type]
|
|
336
|
+
stop_bits=stop_bits,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Decode packets
|
|
340
|
+
packets = _decode_uart_packets(decoder, trace, baud_rate)
|
|
341
|
+
|
|
342
|
+
# Handle decode failure
|
|
343
|
+
if not packets:
|
|
344
|
+
return DecodeResult(
|
|
345
|
+
protocol="UART",
|
|
346
|
+
overall_confidence=0.3,
|
|
347
|
+
detected_params={"baud_rate": baud_rate},
|
|
348
|
+
data=[],
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Convert packets to bytes and calculate confidence
|
|
352
|
+
decoded_bytes, error_count = _convert_uart_packets_to_bytes(packets)
|
|
353
|
+
overall_confidence = _calculate_uart_confidence(decoded_bytes)
|
|
272
354
|
|
|
273
355
|
return DecodeResult(
|
|
274
356
|
protocol="UART",
|
oscura/discovery/comparison.py
CHANGED
|
@@ -336,6 +336,187 @@ def _detect_pattern_differences(
|
|
|
336
336
|
return differences
|
|
337
337
|
|
|
338
338
|
|
|
339
|
+
def _compute_correlation(data1: NDArray[np.float64], data2: NDArray[np.float64]) -> float:
|
|
340
|
+
"""Compute normalized correlation between two signals.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
data1: First signal array.
|
|
344
|
+
data2: Second signal array.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Correlation coefficient in range [-1, 1].
|
|
348
|
+
"""
|
|
349
|
+
if len(data1) <= 1:
|
|
350
|
+
return 1.0 if len(data1) == 0 or data1[0] == data2[0] else 0.0
|
|
351
|
+
|
|
352
|
+
d1_norm = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
|
|
353
|
+
d2_norm = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
|
|
354
|
+
return float(np.corrcoef(d1_norm, d2_norm)[0, 1])
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _try_alignment_method(
|
|
358
|
+
trace1: WaveformTrace, trace2: WaveformTrace, method: str
|
|
359
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], int, float]:
|
|
360
|
+
"""Try single alignment method and return correlation.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
trace1: First waveform trace.
|
|
364
|
+
trace2: Second waveform trace.
|
|
365
|
+
method: Alignment method ("time", "trigger", or "pattern").
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Tuple of (aligned_data1, aligned_data2, offset, correlation).
|
|
369
|
+
"""
|
|
370
|
+
if method == "time":
|
|
371
|
+
d1, d2, offset = _align_time_based(trace1, trace2)
|
|
372
|
+
elif method == "trigger":
|
|
373
|
+
d1, d2, offset = _align_trigger_based(trace1, trace2)
|
|
374
|
+
else: # pattern
|
|
375
|
+
d1, d2, offset = _align_pattern_based(trace1, trace2)
|
|
376
|
+
|
|
377
|
+
corr = _compute_correlation(d1, d2)
|
|
378
|
+
return d1, d2, offset, corr
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _auto_align_traces(
|
|
382
|
+
trace1: WaveformTrace, trace2: WaveformTrace
|
|
383
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], int, str]:
|
|
384
|
+
"""Automatically choose best alignment method.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
trace1: First waveform trace.
|
|
388
|
+
trace2: Second waveform trace.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Tuple of (aligned_data1, aligned_data2, offset, method_name).
|
|
392
|
+
"""
|
|
393
|
+
methods = ["time", "trigger", "pattern"]
|
|
394
|
+
best_corr = -1.0
|
|
395
|
+
best_result = None
|
|
396
|
+
best_method = "time"
|
|
397
|
+
|
|
398
|
+
for method in methods:
|
|
399
|
+
d1, d2, offset, corr = _try_alignment_method(trace1, trace2, method)
|
|
400
|
+
|
|
401
|
+
if corr > best_corr:
|
|
402
|
+
best_corr = corr
|
|
403
|
+
best_method = method
|
|
404
|
+
best_result = (d1, d2, offset)
|
|
405
|
+
|
|
406
|
+
data1, data2, offset = best_result # type: ignore[misc]
|
|
407
|
+
return data1, data2, offset, f"{best_method}-based"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _align_traces_by_method(
|
|
411
|
+
trace1: WaveformTrace, trace2: WaveformTrace, alignment: str
|
|
412
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], int, str]:
|
|
413
|
+
"""Align traces using specified method.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
trace1: First waveform trace.
|
|
417
|
+
trace2: Second waveform trace.
|
|
418
|
+
alignment: Alignment method name.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Tuple of (aligned_data1, aligned_data2, offset, method_description).
|
|
422
|
+
"""
|
|
423
|
+
if alignment == "auto":
|
|
424
|
+
return _auto_align_traces(trace1, trace2)
|
|
425
|
+
|
|
426
|
+
if alignment == "time":
|
|
427
|
+
data1, data2, offset = _align_time_based(trace1, trace2)
|
|
428
|
+
elif alignment == "trigger":
|
|
429
|
+
data1, data2, offset = _align_trigger_based(trace1, trace2)
|
|
430
|
+
else: # pattern
|
|
431
|
+
data1, data2, offset = _align_pattern_based(trace1, trace2)
|
|
432
|
+
|
|
433
|
+
return data1, data2, offset, f"{alignment}-based"
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _collect_differences(
|
|
437
|
+
data1: NDArray[np.float64],
|
|
438
|
+
data2: NDArray[np.float64],
|
|
439
|
+
sample_rate: float,
|
|
440
|
+
difference_types: list[str],
|
|
441
|
+
) -> list[Difference]:
|
|
442
|
+
"""Detect all requested difference types.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
data1: First aligned signal.
|
|
446
|
+
data2: Second aligned signal.
|
|
447
|
+
sample_rate: Sample rate in Hz.
|
|
448
|
+
difference_types: Types to detect.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
List of detected differences sorted by impact.
|
|
452
|
+
"""
|
|
453
|
+
all_differences = []
|
|
454
|
+
|
|
455
|
+
if "timing" in difference_types:
|
|
456
|
+
all_differences.extend(_detect_timing_differences(data1, data2, sample_rate))
|
|
457
|
+
|
|
458
|
+
if "amplitude" in difference_types:
|
|
459
|
+
all_differences.extend(_detect_amplitude_differences(data1, data2, sample_rate))
|
|
460
|
+
|
|
461
|
+
if "pattern" in difference_types:
|
|
462
|
+
all_differences.extend(_detect_pattern_differences(data1, data2, sample_rate))
|
|
463
|
+
|
|
464
|
+
# Sort by impact score (descending)
|
|
465
|
+
all_differences.sort(key=lambda d: d.impact_score, reverse=True)
|
|
466
|
+
return all_differences
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _filter_by_severity(
|
|
470
|
+
differences: list[Difference], severity_threshold: str | None
|
|
471
|
+
) -> list[Difference]:
|
|
472
|
+
"""Filter differences by severity threshold.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
differences: List of detected differences.
|
|
476
|
+
severity_threshold: Minimum severity level.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
Filtered list of differences.
|
|
480
|
+
"""
|
|
481
|
+
if not severity_threshold:
|
|
482
|
+
return differences
|
|
483
|
+
|
|
484
|
+
severity_order = {"INFO": 0, "WARNING": 1, "CRITICAL": 2}
|
|
485
|
+
threshold_level = severity_order.get(severity_threshold, 0)
|
|
486
|
+
|
|
487
|
+
return [d for d in differences if severity_order.get(d.severity, 0) >= threshold_level]
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _build_summary(similarity_score: float, differences: list[Difference]) -> str:
|
|
491
|
+
"""Build human-readable summary of comparison.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
similarity_score: Overall similarity score [0, 1].
|
|
495
|
+
differences: List of detected differences.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
Plain-language summary string.
|
|
499
|
+
"""
|
|
500
|
+
if similarity_score > 0.95:
|
|
501
|
+
summary = "Signals are very similar"
|
|
502
|
+
elif similarity_score > 0.85:
|
|
503
|
+
summary = "Signals are similar with minor differences"
|
|
504
|
+
elif similarity_score > 0.70:
|
|
505
|
+
summary = "Signals show moderate differences"
|
|
506
|
+
else:
|
|
507
|
+
summary = "Signals are significantly different"
|
|
508
|
+
|
|
509
|
+
critical_count = sum(1 for d in differences if d.severity == "CRITICAL")
|
|
510
|
+
warning_count = sum(1 for d in differences if d.severity == "WARNING")
|
|
511
|
+
|
|
512
|
+
if critical_count > 0:
|
|
513
|
+
summary += f" ({critical_count} critical issue(s))"
|
|
514
|
+
elif warning_count > 0:
|
|
515
|
+
summary += f" ({warning_count} warning(s))"
|
|
516
|
+
|
|
517
|
+
return summary
|
|
518
|
+
|
|
519
|
+
|
|
339
520
|
def compare_traces(
|
|
340
521
|
trace1: WaveformTrace,
|
|
341
522
|
trace2: WaveformTrace,
|
|
@@ -378,103 +559,27 @@ def compare_traces(
|
|
|
378
559
|
"transitions",
|
|
379
560
|
]
|
|
380
561
|
|
|
381
|
-
#
|
|
382
|
-
|
|
383
|
-
# Try all methods and pick best correlation
|
|
384
|
-
methods = ["time", "trigger", "pattern"]
|
|
385
|
-
best_corr = -1
|
|
386
|
-
best_method = "time"
|
|
387
|
-
best_aligned = None
|
|
388
|
-
|
|
389
|
-
for method in methods:
|
|
390
|
-
if method == "time":
|
|
391
|
-
d1, d2, offset = _align_time_based(trace1, trace2)
|
|
392
|
-
elif method == "trigger":
|
|
393
|
-
d1, d2, offset = _align_trigger_based(trace1, trace2)
|
|
394
|
-
else: # pattern
|
|
395
|
-
d1, d2, offset = _align_pattern_based(trace1, trace2)
|
|
396
|
-
|
|
397
|
-
# Compute correlation
|
|
398
|
-
if len(d1) > 1:
|
|
399
|
-
d1_norm = (d1 - np.mean(d1)) / (np.std(d1) + 1e-10)
|
|
400
|
-
d2_norm = (d2 - np.mean(d2)) / (np.std(d2) + 1e-10)
|
|
401
|
-
corr = np.corrcoef(d1_norm, d2_norm)[0, 1]
|
|
402
|
-
|
|
403
|
-
if corr > best_corr:
|
|
404
|
-
best_corr = corr
|
|
405
|
-
best_method = method
|
|
406
|
-
best_aligned = (d1, d2, offset)
|
|
407
|
-
|
|
408
|
-
data1, data2, offset = best_aligned # type: ignore[misc]
|
|
409
|
-
alignment_method = f"{best_method}-based"
|
|
410
|
-
else:
|
|
411
|
-
# Use specified method
|
|
412
|
-
if alignment == "time":
|
|
413
|
-
data1, data2, offset = _align_time_based(trace1, trace2)
|
|
414
|
-
elif alignment == "trigger":
|
|
415
|
-
data1, data2, offset = _align_trigger_based(trace1, trace2)
|
|
416
|
-
else: # pattern
|
|
417
|
-
data1, data2, offset = _align_pattern_based(trace1, trace2)
|
|
418
|
-
|
|
419
|
-
alignment_method = f"{alignment}-based"
|
|
562
|
+
# Align traces
|
|
563
|
+
data1, data2, offset, alignment_method = _align_traces_by_method(trace1, trace2, alignment)
|
|
420
564
|
|
|
421
565
|
sample_rate = trace1.metadata.sample_rate
|
|
422
566
|
|
|
423
567
|
# Detect differences
|
|
424
|
-
all_differences =
|
|
425
|
-
|
|
426
|
-
if "timing" in difference_types:
|
|
427
|
-
all_differences.extend(_detect_timing_differences(data1, data2, sample_rate))
|
|
568
|
+
all_differences = _collect_differences(data1, data2, sample_rate, difference_types)
|
|
428
569
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if "pattern" in difference_types:
|
|
433
|
-
all_differences.extend(_detect_pattern_differences(data1, data2, sample_rate))
|
|
570
|
+
# Filter by severity
|
|
571
|
+
all_differences = _filter_by_severity(all_differences, severity_threshold)
|
|
434
572
|
|
|
435
|
-
#
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
# Filter by severity threshold
|
|
439
|
-
if severity_threshold:
|
|
440
|
-
severity_order = {"INFO": 0, "WARNING": 1, "CRITICAL": 2}
|
|
441
|
-
threshold_level = severity_order.get(severity_threshold, 0)
|
|
442
|
-
|
|
443
|
-
filtered = [
|
|
444
|
-
d for d in all_differences if severity_order.get(d.severity, 0) >= threshold_level
|
|
445
|
-
]
|
|
446
|
-
all_differences = filtered
|
|
447
|
-
|
|
448
|
-
# Compute similarity score
|
|
449
|
-
if len(data1) > 1:
|
|
450
|
-
data1_norm = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
|
|
451
|
-
data2_norm = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
|
|
452
|
-
correlation = np.corrcoef(data1_norm, data2_norm)[0, 1]
|
|
453
|
-
similarity_score = float((correlation + 1) / 2) # Map [-1,1] to [0,1]
|
|
454
|
-
else:
|
|
455
|
-
similarity_score = 1.0 if len(data1) == 0 or data1[0] == data2[0] else 0.0
|
|
573
|
+
# Compute similarity
|
|
574
|
+
correlation = _compute_correlation(data1, data2)
|
|
575
|
+
similarity_score = (correlation + 1) / 2 # Map [-1,1] to [0,1]
|
|
456
576
|
|
|
457
577
|
# Build summary
|
|
458
|
-
|
|
459
|
-
summary = "Signals are very similar"
|
|
460
|
-
elif similarity_score > 0.85:
|
|
461
|
-
summary = "Signals are similar with minor differences"
|
|
462
|
-
elif similarity_score > 0.70:
|
|
463
|
-
summary = "Signals show moderate differences"
|
|
464
|
-
else:
|
|
465
|
-
summary = "Signals are significantly different"
|
|
466
|
-
|
|
467
|
-
critical_count = sum(1 for d in all_differences if d.severity == "CRITICAL")
|
|
468
|
-
warning_count = sum(1 for d in all_differences if d.severity == "WARNING")
|
|
469
|
-
|
|
470
|
-
if critical_count > 0:
|
|
471
|
-
summary += f" ({critical_count} critical issue(s))"
|
|
472
|
-
elif warning_count > 0:
|
|
473
|
-
summary += f" ({warning_count} warning(s))"
|
|
578
|
+
summary = _build_summary(similarity_score, all_differences)
|
|
474
579
|
|
|
475
580
|
# Statistics
|
|
476
581
|
stats = {
|
|
477
|
-
"correlation": float(correlation)
|
|
582
|
+
"correlation": float(correlation),
|
|
478
583
|
"rms_error": float(np.sqrt(np.mean((data1 - data2) ** 2))),
|
|
479
584
|
"max_deviation": float(np.max(np.abs(data1 - data2))),
|
|
480
585
|
"max_deviation_time": float(np.argmax(np.abs(data1 - data2)) / sample_rate),
|