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
|
@@ -79,13 +79,13 @@ def correct_timestamp_jitter(
|
|
|
79
79
|
) -> TimestampCorrection:
|
|
80
80
|
"""Correct timestamp jitter using filtering or PLL model.
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Compensates for clock jitter in logic analyzer
|
|
83
83
|
captures (e.g., USB transmission jitter) while preserving phase.
|
|
84
84
|
|
|
85
85
|
Correction constraints (DAQ-003):
|
|
86
|
-
- Max correction per sample: ±max_correction_factor
|
|
87
|
-
- Filter cutoff: expected_rate / 10 (removes
|
|
88
|
-
- Target reduction:
|
|
86
|
+
- Max correction per sample: ±max_correction_factor x expected_period
|
|
87
|
+
- Filter cutoff: expected_rate / 10 (removes 10x jitter frequency)
|
|
88
|
+
- Target reduction: >=5x for typical USB jitter
|
|
89
89
|
|
|
90
90
|
Args:
|
|
91
91
|
timestamps: Original jittery timestamps in seconds
|
|
@@ -113,109 +113,149 @@ def correct_timestamp_jitter(
|
|
|
113
113
|
References:
|
|
114
114
|
DAQ-003: Timestamp Jitter Compensation and Clock Correction
|
|
115
115
|
"""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if expected_rate <= 0:
|
|
120
|
-
raise ValueError("expected_rate must be positive")
|
|
121
|
-
|
|
122
|
-
if max_correction_factor <= 0:
|
|
123
|
-
raise ValueError("max_correction_factor must be positive")
|
|
116
|
+
# Validate inputs
|
|
117
|
+
_validate_jitter_correction_inputs(timestamps, expected_rate, max_correction_factor)
|
|
124
118
|
|
|
119
|
+
# Handle edge cases
|
|
125
120
|
if len(timestamps) < 3:
|
|
126
|
-
|
|
127
|
-
return TimestampCorrection(
|
|
128
|
-
corrected_timestamps=timestamps.copy(),
|
|
129
|
-
original_jitter_rms=0.0,
|
|
130
|
-
corrected_jitter_rms=0.0,
|
|
131
|
-
reduction_ratio=1.0,
|
|
132
|
-
samples_corrected=0,
|
|
133
|
-
max_correction=0.0,
|
|
134
|
-
)
|
|
121
|
+
return _create_no_correction_result(timestamps)
|
|
135
122
|
|
|
136
123
|
expected_period = 1.0 / expected_rate
|
|
137
124
|
max_correction = max_correction_factor * expected_period
|
|
138
125
|
|
|
139
126
|
# Calculate original jitter
|
|
140
|
-
|
|
141
|
-
original_jitter = diffs - expected_period
|
|
142
|
-
original_jitter_rms = float(np.sqrt(np.mean(original_jitter**2)))
|
|
127
|
+
original_jitter_rms = _calculate_original_jitter_rms(timestamps, expected_period)
|
|
143
128
|
|
|
144
|
-
#
|
|
145
|
-
# This avoids correcting floating-point rounding errors in perfect timestamps
|
|
129
|
+
# Skip correction if jitter is negligible
|
|
146
130
|
if original_jitter_rms < 1e-9:
|
|
147
|
-
return
|
|
148
|
-
corrected_timestamps=timestamps.copy(),
|
|
149
|
-
original_jitter_rms=original_jitter_rms,
|
|
150
|
-
corrected_jitter_rms=original_jitter_rms,
|
|
151
|
-
reduction_ratio=1.0,
|
|
152
|
-
samples_corrected=0,
|
|
153
|
-
max_correction=0.0,
|
|
154
|
-
)
|
|
131
|
+
return _create_negligible_jitter_result(timestamps, original_jitter_rms)
|
|
155
132
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
nyquist = 0.5 * expected_rate
|
|
133
|
+
# Apply correction method
|
|
134
|
+
corrected = _apply_jitter_correction(
|
|
135
|
+
timestamps, expected_rate, expected_period, max_correction, method
|
|
136
|
+
)
|
|
161
137
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
138
|
+
# Build and return result
|
|
139
|
+
return _build_jitter_correction_result(
|
|
140
|
+
timestamps, corrected, expected_period, original_jitter_rms, max_correction
|
|
141
|
+
)
|
|
165
142
|
|
|
166
|
-
# Design 2nd order Butterworth
|
|
167
|
-
sos = signal.butter(2, cutoff_freq / nyquist, btype="low", output="sos")
|
|
168
143
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
144
|
+
def _validate_jitter_correction_inputs(
|
|
145
|
+
timestamps: NDArray[np.float64], expected_rate: float, max_correction_factor: float
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Validate jitter correction input parameters."""
|
|
148
|
+
if len(timestamps) == 0:
|
|
149
|
+
raise ValueError("Timestamps array cannot be empty")
|
|
150
|
+
if expected_rate <= 0:
|
|
151
|
+
raise ValueError("expected_rate must be positive")
|
|
152
|
+
if max_correction_factor <= 0:
|
|
153
|
+
raise ValueError("max_correction_factor must be positive")
|
|
173
154
|
|
|
174
|
-
# Apply filter
|
|
175
|
-
filtered = signal.sosfiltfilt(sos, t_detrended)
|
|
176
|
-
corrected = filtered + t_mean
|
|
177
155
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
156
|
+
def _create_no_correction_result(timestamps: NDArray[np.float64]) -> TimestampCorrection:
|
|
157
|
+
"""Create result for insufficient data case."""
|
|
158
|
+
return TimestampCorrection(
|
|
159
|
+
corrected_timestamps=timestamps.copy(),
|
|
160
|
+
original_jitter_rms=0.0,
|
|
161
|
+
corrected_jitter_rms=0.0,
|
|
162
|
+
reduction_ratio=1.0,
|
|
163
|
+
samples_corrected=0,
|
|
164
|
+
max_correction=0.0,
|
|
165
|
+
)
|
|
183
166
|
|
|
184
|
-
# PLL state
|
|
185
|
-
phase = 0.0
|
|
186
|
-
phase_increment = 2 * np.pi * expected_rate
|
|
187
167
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
168
|
+
def _calculate_original_jitter_rms(
|
|
169
|
+
timestamps: NDArray[np.float64], expected_period: float
|
|
170
|
+
) -> float:
|
|
171
|
+
"""Calculate RMS jitter from timestamps."""
|
|
172
|
+
diffs = np.diff(timestamps)
|
|
173
|
+
original_jitter = diffs - expected_period
|
|
174
|
+
return float(np.sqrt(np.mean(original_jitter**2)))
|
|
191
175
|
|
|
192
|
-
# Measure phase error
|
|
193
|
-
actual = timestamps[i]
|
|
194
|
-
error = actual - predicted
|
|
195
176
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
177
|
+
def _create_negligible_jitter_result(
|
|
178
|
+
timestamps: NDArray[np.float64], original_jitter_rms: float
|
|
179
|
+
) -> TimestampCorrection:
|
|
180
|
+
"""Create result for negligible jitter case."""
|
|
181
|
+
return TimestampCorrection(
|
|
182
|
+
corrected_timestamps=timestamps.copy(),
|
|
183
|
+
original_jitter_rms=original_jitter_rms,
|
|
184
|
+
corrected_jitter_rms=original_jitter_rms,
|
|
185
|
+
reduction_ratio=1.0,
|
|
186
|
+
samples_corrected=0,
|
|
187
|
+
max_correction=0.0,
|
|
188
|
+
)
|
|
199
189
|
|
|
200
|
-
|
|
201
|
-
|
|
190
|
+
|
|
191
|
+
def _apply_jitter_correction(
|
|
192
|
+
timestamps: NDArray[np.float64],
|
|
193
|
+
expected_rate: float,
|
|
194
|
+
expected_period: float,
|
|
195
|
+
max_correction: float,
|
|
196
|
+
method: Literal["lowpass", "pll"],
|
|
197
|
+
) -> NDArray[np.float64]:
|
|
198
|
+
"""Apply jitter correction using selected method."""
|
|
199
|
+
if method == "lowpass":
|
|
200
|
+
corrected = _apply_lowpass_correction(timestamps, expected_rate)
|
|
201
|
+
else: # pll
|
|
202
|
+
corrected = _apply_pll_correction(timestamps, expected_period, max_correction)
|
|
202
203
|
|
|
203
204
|
# Limit corrections to max_correction
|
|
204
205
|
corrections = corrected - timestamps
|
|
205
206
|
exceeded = np.abs(corrections) > max_correction
|
|
206
207
|
corrections[exceeded] = np.sign(corrections[exceeded]) * max_correction
|
|
207
|
-
|
|
208
|
+
return timestamps + corrections
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _apply_lowpass_correction(
|
|
212
|
+
timestamps: NDArray[np.float64], expected_rate: float
|
|
213
|
+
) -> NDArray[np.float64]:
|
|
214
|
+
"""Apply low-pass filter correction."""
|
|
215
|
+
cutoff_freq = expected_rate / 10.0
|
|
216
|
+
nyquist = 0.5 * expected_rate
|
|
217
|
+
if cutoff_freq >= nyquist:
|
|
218
|
+
cutoff_freq = nyquist * 0.8
|
|
219
|
+
|
|
220
|
+
sos = signal.butter(2, cutoff_freq / nyquist, btype="low", output="sos")
|
|
221
|
+
t_mean: np.floating[Any] = np.mean(timestamps)
|
|
222
|
+
t_detrended = timestamps - t_mean
|
|
223
|
+
filtered = signal.sosfiltfilt(sos, t_detrended)
|
|
224
|
+
result: NDArray[np.float64] = np.asarray(filtered + t_mean, dtype=np.float64)
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _apply_pll_correction(
|
|
229
|
+
timestamps: NDArray[np.float64], expected_period: float, max_correction: float
|
|
230
|
+
) -> NDArray[np.float64]:
|
|
231
|
+
"""Apply phase-locked loop correction."""
|
|
232
|
+
corrected = np.zeros_like(timestamps)
|
|
233
|
+
corrected[0] = timestamps[0]
|
|
234
|
+
|
|
235
|
+
for i in range(1, len(timestamps)):
|
|
236
|
+
predicted = corrected[i - 1] + expected_period
|
|
237
|
+
error = timestamps[i] - predicted
|
|
238
|
+
correction = np.clip(error * 0.5, -max_correction, max_correction)
|
|
239
|
+
corrected[i] = predicted + correction
|
|
240
|
+
|
|
241
|
+
return corrected
|
|
208
242
|
|
|
209
|
-
|
|
243
|
+
|
|
244
|
+
def _build_jitter_correction_result(
|
|
245
|
+
timestamps: NDArray[np.float64],
|
|
246
|
+
corrected: NDArray[np.float64],
|
|
247
|
+
expected_period: float,
|
|
248
|
+
original_jitter_rms: float,
|
|
249
|
+
max_correction: float,
|
|
250
|
+
) -> TimestampCorrection:
|
|
251
|
+
"""Build final jitter correction result."""
|
|
210
252
|
corrected_diffs = np.diff(corrected)
|
|
211
253
|
corrected_jitter = corrected_diffs - expected_period
|
|
212
254
|
corrected_jitter_rms = float(np.sqrt(np.mean(corrected_jitter**2)))
|
|
213
255
|
|
|
214
|
-
|
|
256
|
+
corrections = corrected - timestamps
|
|
215
257
|
samples_corrected = int(np.sum(np.abs(corrections) > 1e-12))
|
|
216
258
|
max_correction_applied = float(np.max(np.abs(corrections)))
|
|
217
|
-
|
|
218
|
-
# original_jitter_rms is always > 0 here (early return handles negligible jitter)
|
|
219
259
|
reduction_ratio = original_jitter_rms / max(corrected_jitter_rms, 1e-15)
|
|
220
260
|
|
|
221
261
|
return TimestampCorrection(
|
|
@@ -228,6 +268,52 @@ def correct_timestamp_jitter(
|
|
|
228
268
|
)
|
|
229
269
|
|
|
230
270
|
|
|
271
|
+
def _decode_uart_tolerant(
|
|
272
|
+
data: NDArray[np.uint8],
|
|
273
|
+
tolerance: ErrorTolerance,
|
|
274
|
+
baud: float,
|
|
275
|
+
) -> list[DecodedFrame]:
|
|
276
|
+
"""Decode UART frames with error tolerance (simplified implementation)."""
|
|
277
|
+
frames: list[DecodedFrame] = []
|
|
278
|
+
pos = 0
|
|
279
|
+
|
|
280
|
+
while pos < len(data):
|
|
281
|
+
try:
|
|
282
|
+
if pos + 1 >= len(data):
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
frame_data = bytes([data[pos]])
|
|
286
|
+
timestamp = float(pos) / baud
|
|
287
|
+
|
|
288
|
+
# Validate frame (simplified - real decoder checks start/stop bits)
|
|
289
|
+
is_valid = data[pos] != 0xFF # Example error condition
|
|
290
|
+
error_type = "framing" if not is_valid else None
|
|
291
|
+
|
|
292
|
+
frames.append(
|
|
293
|
+
DecodedFrame(
|
|
294
|
+
data=frame_data,
|
|
295
|
+
timestamp=timestamp,
|
|
296
|
+
valid=is_valid,
|
|
297
|
+
error_type=error_type,
|
|
298
|
+
position=pos,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if not is_valid and tolerance == ErrorTolerance.STRICT:
|
|
303
|
+
break
|
|
304
|
+
elif not is_valid and tolerance == ErrorTolerance.TOLERANT:
|
|
305
|
+
pos += 1 # Skip error frame, resync
|
|
306
|
+
else:
|
|
307
|
+
pos += 1 # Permissive: record error, continue
|
|
308
|
+
|
|
309
|
+
except Exception:
|
|
310
|
+
if tolerance == ErrorTolerance.STRICT:
|
|
311
|
+
raise
|
|
312
|
+
pos += 1
|
|
313
|
+
|
|
314
|
+
return frames
|
|
315
|
+
|
|
316
|
+
|
|
231
317
|
def decode_with_error_tolerance(
|
|
232
318
|
data: NDArray[np.uint8],
|
|
233
319
|
protocol: Literal["uart", "spi", "i2c", "can"],
|
|
@@ -279,82 +365,18 @@ def decode_with_error_tolerance(
|
|
|
279
365
|
if protocol not in ("uart", "spi", "i2c", "can"):
|
|
280
366
|
raise ValueError(f"Unsupported protocol: {protocol}")
|
|
281
367
|
|
|
282
|
-
frames: list[DecodedFrame] = []
|
|
283
|
-
pos = 0
|
|
284
|
-
|
|
285
368
|
# Protocol-specific decode logic
|
|
286
369
|
# This is a simplified implementation showing the error handling pattern
|
|
287
370
|
# Full protocol decoders are in oscura.analyzers.protocols
|
|
288
371
|
|
|
289
372
|
if protocol == "uart":
|
|
290
|
-
# UART parameters
|
|
291
373
|
if "baud" not in protocol_params:
|
|
292
374
|
raise ValueError("UART requires 'baud' parameter")
|
|
375
|
+
return _decode_uart_tolerant(data, tolerance, protocol_params["baud"])
|
|
293
376
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# This is simplified - real UART decoder would analyze bit timing
|
|
299
|
-
|
|
300
|
-
# Check for valid frame (simplified)
|
|
301
|
-
if pos + 1 >= len(data):
|
|
302
|
-
break
|
|
303
|
-
|
|
304
|
-
frame_data = bytes([data[pos]])
|
|
305
|
-
timestamp = float(pos) / protocol_params["baud"]
|
|
306
|
-
|
|
307
|
-
# Validate frame (simplified - would check start/stop bits)
|
|
308
|
-
is_valid = True
|
|
309
|
-
error_type = None
|
|
310
|
-
|
|
311
|
-
# Example: detect framing error (no proper stop bit)
|
|
312
|
-
if data[pos] == 0xFF: # Example error condition
|
|
313
|
-
is_valid = False
|
|
314
|
-
error_type = "framing"
|
|
315
|
-
|
|
316
|
-
frames.append(
|
|
317
|
-
DecodedFrame(
|
|
318
|
-
data=frame_data,
|
|
319
|
-
timestamp=timestamp,
|
|
320
|
-
valid=is_valid,
|
|
321
|
-
error_type=error_type,
|
|
322
|
-
position=pos,
|
|
323
|
-
)
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
if not is_valid and tolerance == ErrorTolerance.STRICT:
|
|
327
|
-
# Strict mode: abort on error
|
|
328
|
-
break
|
|
329
|
-
elif not is_valid and tolerance == ErrorTolerance.TOLERANT:
|
|
330
|
-
# Tolerant: skip error frame, resync
|
|
331
|
-
# Search for next valid start bit
|
|
332
|
-
pos += 1
|
|
333
|
-
# In real implementation, would search for start bit pattern
|
|
334
|
-
else:
|
|
335
|
-
# Permissive: record error, continue
|
|
336
|
-
pos += 1
|
|
337
|
-
|
|
338
|
-
except Exception:
|
|
339
|
-
if tolerance == ErrorTolerance.STRICT:
|
|
340
|
-
raise
|
|
341
|
-
else:
|
|
342
|
-
# Log error and continue
|
|
343
|
-
pos += 1
|
|
344
|
-
|
|
345
|
-
elif protocol == "spi":
|
|
346
|
-
# SPI: Re-align on CS edge
|
|
347
|
-
# Simplified placeholder
|
|
348
|
-
pass
|
|
349
|
-
|
|
350
|
-
elif protocol == "i2c":
|
|
351
|
-
# I2C: Search for START condition
|
|
352
|
-
# Simplified placeholder
|
|
353
|
-
pass
|
|
354
|
-
|
|
355
|
-
elif protocol == "can":
|
|
356
|
-
# CAN: Wait for SOF after error
|
|
357
|
-
# Simplified placeholder
|
|
358
|
-
pass
|
|
377
|
+
elif protocol in ("spi", "i2c", "can"):
|
|
378
|
+
# SPI/I2C/CAN: Simplified placeholders
|
|
379
|
+
# Real implementations would have protocol-specific resync logic
|
|
380
|
+
return []
|
|
359
381
|
|
|
360
|
-
return
|
|
382
|
+
return []
|
|
@@ -113,102 +113,23 @@ def analyze_bit_errors(
|
|
|
113
113
|
References:
|
|
114
114
|
DAQ-005: Bit Error Pattern Analysis and Capture Diagnostics
|
|
115
115
|
"""
|
|
116
|
-
|
|
117
|
-
raise ValueError("Received and expected arrays must have same length")
|
|
118
|
-
|
|
119
|
-
if len(received) == 0:
|
|
120
|
-
raise ValueError("Arrays cannot be empty")
|
|
116
|
+
_validate_inputs(received, expected)
|
|
121
117
|
|
|
122
|
-
# Find bit errors (XOR)
|
|
123
118
|
errors = received != expected
|
|
124
119
|
error_positions = np.where(errors)[0]
|
|
125
120
|
error_count = len(error_positions)
|
|
126
121
|
total_bits = len(received)
|
|
127
|
-
|
|
128
|
-
# Calculate BER
|
|
129
122
|
bit_error_rate = error_count / total_bits if total_bits > 0 else 0.0
|
|
130
123
|
|
|
131
124
|
if error_count == 0:
|
|
132
|
-
|
|
133
|
-
return ErrorAnalysis(
|
|
134
|
-
bit_error_rate=0.0,
|
|
135
|
-
error_count=0,
|
|
136
|
-
total_bits=total_bits,
|
|
137
|
-
pattern_type=ErrorPattern.RANDOM,
|
|
138
|
-
mean_error_gap=float(total_bits),
|
|
139
|
-
error_positions=error_positions,
|
|
140
|
-
diagnosis="No errors detected - good capture quality",
|
|
141
|
-
severity="low",
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
# Calculate error gaps
|
|
145
|
-
if error_count > 1:
|
|
146
|
-
error_gaps = np.diff(error_positions)
|
|
147
|
-
mean_gap = float(np.mean(error_gaps))
|
|
148
|
-
else:
|
|
149
|
-
mean_gap = float(total_bits)
|
|
150
|
-
|
|
151
|
-
# Classify error pattern
|
|
152
|
-
pattern_type = ErrorPattern.UNKNOWN
|
|
153
|
-
diagnosis = ""
|
|
125
|
+
return _create_no_errors_analysis(total_bits, error_positions)
|
|
154
126
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# Need at least 10 errors for reliable periodicity detection
|
|
162
|
-
elif error_count >= 10:
|
|
163
|
-
# Create binary error signal
|
|
164
|
-
error_signal = errors.astype(float)
|
|
165
|
-
|
|
166
|
-
# Compute FFT to detect periodicity
|
|
167
|
-
fft = np.fft.rfft(error_signal)
|
|
168
|
-
fft_mag = np.abs(fft[1:]) # Skip DC component
|
|
169
|
-
|
|
170
|
-
if len(fft_mag) > 0:
|
|
171
|
-
# Check if there's a strong peak relative to mean (not just max)
|
|
172
|
-
mean_mag = np.mean(fft_mag)
|
|
173
|
-
max_mag = np.max(fft_mag)
|
|
174
|
-
peak_ratio = max_mag / (mean_mag + 1e-12)
|
|
175
|
-
|
|
176
|
-
# Require strong peak (>10x mean) and exceeds threshold
|
|
177
|
-
if peak_ratio > 10 and (max_mag / (np.max(fft_mag) + 1e-12)) > periodicity_threshold:
|
|
178
|
-
pattern_type = ErrorPattern.PERIODIC
|
|
179
|
-
diagnosis = "Periodic errors detected - likely clock jitter or interference"
|
|
180
|
-
|
|
181
|
-
# If not burst or periodic, classify as random
|
|
182
|
-
if pattern_type == ErrorPattern.UNKNOWN:
|
|
183
|
-
# Check if errors are uniformly distributed
|
|
184
|
-
if error_count > 2:
|
|
185
|
-
# Use coefficient of variation of gaps
|
|
186
|
-
if error_count > 1:
|
|
187
|
-
gap_std = float(np.std(error_gaps))
|
|
188
|
-
gap_cv = gap_std / (mean_gap + 1e-12)
|
|
189
|
-
|
|
190
|
-
if gap_cv < 1.0: # Relatively uniform spacing
|
|
191
|
-
pattern_type = ErrorPattern.RANDOM
|
|
192
|
-
diagnosis = "Random errors detected - likely EMI or noise"
|
|
193
|
-
else:
|
|
194
|
-
diagnosis = "Mixed error pattern - multiple causes possible"
|
|
195
|
-
else:
|
|
196
|
-
pattern_type = ErrorPattern.RANDOM
|
|
197
|
-
diagnosis = "Single error - insufficient data for classification"
|
|
198
|
-
else:
|
|
199
|
-
pattern_type = ErrorPattern.RANDOM
|
|
200
|
-
diagnosis = "Few errors - likely random EMI or noise"
|
|
201
|
-
|
|
202
|
-
# Determine severity based on BER
|
|
203
|
-
if bit_error_rate > 0.01:
|
|
204
|
-
severity = "severe"
|
|
205
|
-
diagnosis += ". SEVERE: Check connections and hardware"
|
|
206
|
-
elif bit_error_rate > 0.001:
|
|
207
|
-
severity = "moderate"
|
|
208
|
-
diagnosis += ". MODERATE: Consider reducing sample rate"
|
|
209
|
-
else:
|
|
210
|
-
severity = "low"
|
|
211
|
-
diagnosis += ". Acceptable error rate"
|
|
127
|
+
mean_gap = _calculate_mean_gap(error_count, error_positions, total_bits)
|
|
128
|
+
pattern_type, diagnosis = _classify_error_pattern(
|
|
129
|
+
errors, error_count, error_positions, mean_gap, burst_threshold, periodicity_threshold
|
|
130
|
+
)
|
|
131
|
+
severity = _determine_severity(bit_error_rate)
|
|
132
|
+
diagnosis = _append_severity_message(diagnosis, severity)
|
|
212
133
|
|
|
213
134
|
return ErrorAnalysis(
|
|
214
135
|
bit_error_rate=bit_error_rate,
|
|
@@ -222,6 +143,117 @@ def analyze_bit_errors(
|
|
|
222
143
|
)
|
|
223
144
|
|
|
224
145
|
|
|
146
|
+
def _validate_inputs(received: NDArray[np.uint8], expected: NDArray[np.uint8]) -> None:
|
|
147
|
+
"""Validate input arrays for error analysis."""
|
|
148
|
+
if len(received) != len(expected):
|
|
149
|
+
raise ValueError("Received and expected arrays must have same length")
|
|
150
|
+
if len(received) == 0:
|
|
151
|
+
raise ValueError("Arrays cannot be empty")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _create_no_errors_analysis(
|
|
155
|
+
total_bits: int, error_positions: NDArray[np.int64]
|
|
156
|
+
) -> ErrorAnalysis:
|
|
157
|
+
"""Create ErrorAnalysis for zero errors case."""
|
|
158
|
+
return ErrorAnalysis(
|
|
159
|
+
bit_error_rate=0.0,
|
|
160
|
+
error_count=0,
|
|
161
|
+
total_bits=total_bits,
|
|
162
|
+
pattern_type=ErrorPattern.RANDOM,
|
|
163
|
+
mean_error_gap=float(total_bits),
|
|
164
|
+
error_positions=error_positions,
|
|
165
|
+
diagnosis="No errors detected - good capture quality",
|
|
166
|
+
severity="low",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _calculate_mean_gap(
|
|
171
|
+
error_count: int, error_positions: NDArray[np.int64], total_bits: int
|
|
172
|
+
) -> float:
|
|
173
|
+
"""Calculate mean gap between errors."""
|
|
174
|
+
if error_count > 1:
|
|
175
|
+
error_gaps = np.diff(error_positions)
|
|
176
|
+
return float(np.mean(error_gaps))
|
|
177
|
+
return float(total_bits)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _classify_error_pattern(
|
|
181
|
+
errors: NDArray[np.bool_],
|
|
182
|
+
error_count: int,
|
|
183
|
+
error_positions: NDArray[np.int64],
|
|
184
|
+
mean_gap: float,
|
|
185
|
+
burst_threshold: int,
|
|
186
|
+
periodicity_threshold: float,
|
|
187
|
+
) -> tuple[ErrorPattern, str]:
|
|
188
|
+
"""Classify error pattern type and generate diagnosis."""
|
|
189
|
+
if error_count > 1 and mean_gap < burst_threshold:
|
|
190
|
+
return ErrorPattern.BURST, "Burst errors detected - likely USB transmission issue"
|
|
191
|
+
|
|
192
|
+
if error_count >= 10:
|
|
193
|
+
pattern, diagnosis = _check_periodic_pattern(errors, periodicity_threshold)
|
|
194
|
+
if pattern == ErrorPattern.PERIODIC:
|
|
195
|
+
return pattern, diagnosis
|
|
196
|
+
|
|
197
|
+
return _classify_random_pattern(error_count, error_positions, mean_gap)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _check_periodic_pattern(
|
|
201
|
+
errors: NDArray[np.bool_], periodicity_threshold: float
|
|
202
|
+
) -> tuple[ErrorPattern, str]:
|
|
203
|
+
"""Check if errors show periodic pattern via FFT."""
|
|
204
|
+
error_signal = errors.astype(float)
|
|
205
|
+
fft = np.fft.rfft(error_signal)
|
|
206
|
+
fft_mag = np.abs(fft[1:])
|
|
207
|
+
|
|
208
|
+
if len(fft_mag) > 0:
|
|
209
|
+
mean_mag = np.mean(fft_mag)
|
|
210
|
+
max_mag = np.max(fft_mag)
|
|
211
|
+
peak_ratio = max_mag / (mean_mag + 1e-12)
|
|
212
|
+
|
|
213
|
+
if peak_ratio > 10 and (max_mag / (np.max(fft_mag) + 1e-12)) > periodicity_threshold:
|
|
214
|
+
return (
|
|
215
|
+
ErrorPattern.PERIODIC,
|
|
216
|
+
"Periodic errors detected - likely clock jitter or interference",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return ErrorPattern.UNKNOWN, ""
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _classify_random_pattern(
|
|
223
|
+
error_count: int, error_positions: NDArray[np.int64], mean_gap: float
|
|
224
|
+
) -> tuple[ErrorPattern, str]:
|
|
225
|
+
"""Classify as random pattern with appropriate diagnosis."""
|
|
226
|
+
if error_count <= 2:
|
|
227
|
+
return ErrorPattern.RANDOM, "Few errors - likely random EMI or noise"
|
|
228
|
+
|
|
229
|
+
error_gaps = np.diff(error_positions)
|
|
230
|
+
gap_std = float(np.std(error_gaps))
|
|
231
|
+
gap_cv = gap_std / (mean_gap + 1e-12)
|
|
232
|
+
|
|
233
|
+
if gap_cv < 1.0:
|
|
234
|
+
return ErrorPattern.RANDOM, "Random errors detected - likely EMI or noise"
|
|
235
|
+
|
|
236
|
+
return ErrorPattern.UNKNOWN, "Mixed error pattern - multiple causes possible"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _determine_severity(bit_error_rate: float) -> str:
|
|
240
|
+
"""Determine severity level based on BER."""
|
|
241
|
+
if bit_error_rate > 0.01:
|
|
242
|
+
return "severe"
|
|
243
|
+
if bit_error_rate > 0.001:
|
|
244
|
+
return "moderate"
|
|
245
|
+
return "low"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _append_severity_message(diagnosis: str, severity: str) -> str:
|
|
249
|
+
"""Append severity-specific message to diagnosis."""
|
|
250
|
+
if severity == "severe":
|
|
251
|
+
return diagnosis + ". SEVERE: Check connections and hardware"
|
|
252
|
+
if severity == "moderate":
|
|
253
|
+
return diagnosis + ". MODERATE: Consider reducing sample rate"
|
|
254
|
+
return diagnosis + ". Acceptable error rate"
|
|
255
|
+
|
|
256
|
+
|
|
225
257
|
def generate_error_visualization_data(
|
|
226
258
|
analysis: ErrorAnalysis,
|
|
227
259
|
*,
|