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
|
@@ -52,17 +52,9 @@ from oscura.analyzers.digital.edges import (
|
|
|
52
52
|
from oscura.analyzers.digital.extraction import (
|
|
53
53
|
LOGIC_FAMILIES,
|
|
54
54
|
detect_edges,
|
|
55
|
-
detect_logic_family,
|
|
56
|
-
detect_open_collector,
|
|
57
55
|
get_logic_threshold,
|
|
58
56
|
to_digital,
|
|
59
57
|
)
|
|
60
|
-
from oscura.analyzers.digital.ic_database import (
|
|
61
|
-
IC_DATABASE,
|
|
62
|
-
ICTiming,
|
|
63
|
-
identify_ic,
|
|
64
|
-
validate_ic_timing,
|
|
65
|
-
)
|
|
66
58
|
from oscura.analyzers.digital.quality import (
|
|
67
59
|
Glitch,
|
|
68
60
|
NoiseMarginResult,
|
|
@@ -105,37 +97,13 @@ from oscura.analyzers.digital.timing import (
|
|
|
105
97
|
skew,
|
|
106
98
|
slew_rate,
|
|
107
99
|
)
|
|
108
|
-
from oscura.analyzers.digital.timing_paths import (
|
|
109
|
-
ICStage,
|
|
110
|
-
SetupHoldAnalysis,
|
|
111
|
-
TimingPathResult,
|
|
112
|
-
analyze_setup_hold,
|
|
113
|
-
analyze_timing_path,
|
|
114
|
-
calculate_timing_budget,
|
|
115
|
-
find_critical_paths,
|
|
116
|
-
)
|
|
117
|
-
from oscura.analyzers.digital.vintage import (
|
|
118
|
-
REPLACEMENT_DATABASE,
|
|
119
|
-
analyze_vintage_logic,
|
|
120
|
-
)
|
|
121
|
-
from oscura.analyzers.digital.vintage_result import (
|
|
122
|
-
BOMEntry,
|
|
123
|
-
ICIdentificationResult,
|
|
124
|
-
ModernReplacementIC,
|
|
125
|
-
VintageLogicAnalysisResult,
|
|
126
|
-
)
|
|
127
100
|
|
|
128
101
|
__all__ = [
|
|
129
|
-
# IC Database
|
|
130
|
-
"IC_DATABASE",
|
|
131
102
|
# Extraction
|
|
132
103
|
"LOGIC_FAMILIES",
|
|
133
|
-
"REPLACEMENT_DATABASE",
|
|
134
104
|
"AdaptiveThresholdResult",
|
|
135
105
|
# Adaptive Thresholds (RE-THR-001)
|
|
136
106
|
"AdaptiveThresholder",
|
|
137
|
-
# Vintage Logic Analysis
|
|
138
|
-
"BOMEntry",
|
|
139
107
|
# Clock Recovery (DSP-002)
|
|
140
108
|
"BaudRateResult",
|
|
141
109
|
# Bus Decoding (DSP-003)
|
|
@@ -157,10 +125,6 @@ __all__ = [
|
|
|
157
125
|
"EdgeTimingViolation",
|
|
158
126
|
# Quality
|
|
159
127
|
"Glitch",
|
|
160
|
-
"ICIdentificationResult",
|
|
161
|
-
"ICStage",
|
|
162
|
-
"ICTiming",
|
|
163
|
-
"ModernReplacementIC",
|
|
164
128
|
# Multi-Level Logic (RE-THR-002)
|
|
165
129
|
"MultiLevelDetector",
|
|
166
130
|
"MultiLevelResult",
|
|
@@ -168,25 +132,18 @@ __all__ = [
|
|
|
168
132
|
# Signal Quality (DSP-005)
|
|
169
133
|
"NoiseMargins",
|
|
170
134
|
"ParallelBusConfig",
|
|
171
|
-
"SetupHoldAnalysis",
|
|
172
135
|
"SignalIntegrityReport",
|
|
173
136
|
"SignalQualityAnalyzer",
|
|
174
137
|
"SimpleQualityMetrics",
|
|
175
138
|
"ThresholdConfig",
|
|
176
139
|
"TimingConstraint",
|
|
177
|
-
"TimingPathResult",
|
|
178
140
|
"TimingViolation",
|
|
179
141
|
"TransitionMetrics",
|
|
180
|
-
"VintageLogicAnalysisResult",
|
|
181
142
|
"Violation",
|
|
182
143
|
"align_by_trigger",
|
|
183
|
-
"analyze_setup_hold",
|
|
184
144
|
"analyze_signal_integrity",
|
|
185
|
-
"analyze_timing_path",
|
|
186
|
-
"analyze_vintage_logic",
|
|
187
145
|
"apply_adaptive_threshold",
|
|
188
146
|
"calculate_threshold_snr",
|
|
189
|
-
"calculate_timing_budget",
|
|
190
147
|
"check_timing_constraints",
|
|
191
148
|
"classify_edge_quality",
|
|
192
149
|
"correlate_channels",
|
|
@@ -196,14 +153,10 @@ __all__ = [
|
|
|
196
153
|
"detect_edges",
|
|
197
154
|
"detect_edges_advanced",
|
|
198
155
|
"detect_glitches",
|
|
199
|
-
"detect_logic_family",
|
|
200
156
|
"detect_multi_level",
|
|
201
|
-
"detect_open_collector",
|
|
202
157
|
"detect_violations",
|
|
203
|
-
"find_critical_paths",
|
|
204
158
|
"get_logic_threshold",
|
|
205
159
|
"hold_time",
|
|
206
|
-
"identify_ic",
|
|
207
160
|
"interpolate_edge_time",
|
|
208
161
|
"measure_clock_jitter",
|
|
209
162
|
"measure_edge_timing",
|
|
@@ -221,5 +174,4 @@ __all__ = [
|
|
|
221
174
|
"skew",
|
|
222
175
|
"slew_rate",
|
|
223
176
|
"to_digital",
|
|
224
|
-
"validate_ic_timing",
|
|
225
177
|
]
|
|
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Literal
|
|
|
24
24
|
import numpy as np
|
|
25
25
|
|
|
26
26
|
from oscura.core.memoize import memoize_analysis
|
|
27
|
+
from oscura.core.numba_backend import njit
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
29
30
|
from numpy.typing import NDArray
|
|
@@ -118,11 +119,19 @@ def detect_edges(
|
|
|
118
119
|
threshold: float | Literal["auto"] = "auto",
|
|
119
120
|
hysteresis: float = 0.0,
|
|
120
121
|
sample_rate: float = 1.0,
|
|
122
|
+
use_numba: bool = True,
|
|
121
123
|
) -> list[Edge]:
|
|
122
124
|
"""Detect signal edges with configurable threshold.
|
|
123
125
|
|
|
124
126
|
Detects rising and/or falling edges in a digital or analog signal with
|
|
125
|
-
optional hysteresis for noise immunity.
|
|
127
|
+
optional hysteresis for noise immunity. Uses Numba JIT compilation for
|
|
128
|
+
15-30x speedup on large signals (>1000 samples).
|
|
129
|
+
|
|
130
|
+
Performance characteristics:
|
|
131
|
+
- Small signals (<1000 samples): Pure Python (no overhead)
|
|
132
|
+
- Large signals (>=1000 samples): Numba JIT (15-30x faster)
|
|
133
|
+
- First Numba call: ~100-200ms compilation overhead (cached)
|
|
134
|
+
- Subsequent calls: <1ms for 100k samples
|
|
126
135
|
|
|
127
136
|
Args:
|
|
128
137
|
trace: Input signal trace (analog or digital).
|
|
@@ -130,6 +139,7 @@ def detect_edges(
|
|
|
130
139
|
threshold: Detection threshold. 'auto' computes from signal midpoint.
|
|
131
140
|
hysteresis: Hysteresis amount for noise immunity (signal units).
|
|
132
141
|
sample_rate: Sample rate in Hz for time calculation.
|
|
142
|
+
use_numba: Use Numba JIT acceleration for large signals (default: True).
|
|
133
143
|
|
|
134
144
|
Returns:
|
|
135
145
|
List of Edge objects with detected edges.
|
|
@@ -139,93 +149,343 @@ def detect_edges(
|
|
|
139
149
|
>>> edges = detect_edges(signal, edge_type='rising')
|
|
140
150
|
>>> len(edges)
|
|
141
151
|
1
|
|
152
|
+
|
|
153
|
+
Note:
|
|
154
|
+
Numba JIT compilation provides significant speedup for repeated edge
|
|
155
|
+
detection on large signals. The compilation overhead (~100-200ms) is
|
|
156
|
+
amortized across subsequent calls due to caching.
|
|
142
157
|
"""
|
|
143
158
|
if len(trace) < 2:
|
|
144
159
|
return []
|
|
145
160
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
thresh_val: float
|
|
150
|
-
if threshold == "auto":
|
|
151
|
-
thresh_val = float((np.max(trace) + np.min(trace)) / 2.0)
|
|
161
|
+
# Handle WaveformTrace objects by extracting data
|
|
162
|
+
if hasattr(trace, "data"):
|
|
163
|
+
trace_data: NDArray[np.float64] = np.asarray(trace.data, dtype=np.float64)
|
|
152
164
|
else:
|
|
153
|
-
|
|
165
|
+
trace_data = np.asarray(trace, dtype=np.float64)
|
|
166
|
+
thresh_val = _compute_threshold(trace_data, threshold)
|
|
167
|
+
thresh_high, thresh_low = _apply_hysteresis(thresh_val, hysteresis)
|
|
168
|
+
time_base = 1.0 / sample_rate
|
|
154
169
|
|
|
155
|
-
#
|
|
156
|
-
if
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
else:
|
|
160
|
-
thresh_high = thresh_val
|
|
161
|
-
thresh_low = thresh_val
|
|
170
|
+
# Use Numba for large signals to achieve 15-30x speedup
|
|
171
|
+
if use_numba and len(trace_data) >= 1000:
|
|
172
|
+
detect_rising = edge_type in ["rising", "both"]
|
|
173
|
+
detect_falling = edge_type in ["falling", "both"]
|
|
162
174
|
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
edge_indices, edge_types = _find_edges_numba(
|
|
176
|
+
trace_data, thresh_high, thresh_low, detect_rising, detect_falling
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Build Edge objects from Numba results
|
|
180
|
+
edges: list[Edge] = []
|
|
181
|
+
for idx, is_rising in zip(edge_indices, edge_types, strict=True):
|
|
182
|
+
i = int(idx)
|
|
183
|
+
prev_val = trace_data[i - 1] if i > 0 else trace_data[i]
|
|
184
|
+
curr_val = trace_data[i]
|
|
185
|
+
edge_type_str: Literal["rising", "falling"] = "rising" if is_rising else "falling"
|
|
186
|
+
|
|
187
|
+
edge = _create_edge(
|
|
188
|
+
trace_data, i, edge_type_str, prev_val, curr_val, time_base, sample_rate
|
|
189
|
+
)
|
|
190
|
+
edges.append(edge)
|
|
165
191
|
|
|
166
|
-
|
|
167
|
-
state = trace[0] > thresh_val # Initial state
|
|
192
|
+
return edges
|
|
168
193
|
|
|
169
|
-
for
|
|
170
|
-
|
|
171
|
-
|
|
194
|
+
# Fallback to pure Python for small signals or when Numba disabled
|
|
195
|
+
edges = []
|
|
196
|
+
state = trace_data[0] > thresh_val
|
|
197
|
+
|
|
198
|
+
for i in range(1, len(trace_data)):
|
|
199
|
+
prev_val, curr_val = trace_data[i - 1], trace_data[i]
|
|
172
200
|
|
|
173
|
-
# Detect transitions with hysteresis
|
|
174
201
|
if not state and curr_val > thresh_high:
|
|
175
|
-
# Rising edge
|
|
176
202
|
if edge_type in ["rising", "both"]:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
time = (i - 1 + interp_time) * time_base
|
|
180
|
-
|
|
181
|
-
# Calculate edge properties
|
|
182
|
-
amplitude = curr_val - prev_val
|
|
183
|
-
slew_rate = amplitude * sample_rate
|
|
184
|
-
|
|
185
|
-
# Classify quality (simple heuristic)
|
|
186
|
-
quality = classify_edge_quality(trace, i, sample_rate)
|
|
187
|
-
|
|
188
|
-
edges.append(
|
|
189
|
-
Edge(
|
|
190
|
-
sample_index=i,
|
|
191
|
-
time=time,
|
|
192
|
-
edge_type="rising",
|
|
193
|
-
amplitude=abs(amplitude),
|
|
194
|
-
slew_rate=slew_rate,
|
|
195
|
-
quality=quality,
|
|
196
|
-
)
|
|
203
|
+
edge = _create_edge(
|
|
204
|
+
trace_data, i, "rising", prev_val, curr_val, time_base, sample_rate
|
|
197
205
|
)
|
|
206
|
+
edges.append(edge)
|
|
198
207
|
state = True
|
|
199
208
|
|
|
200
209
|
elif state and curr_val < thresh_low:
|
|
201
|
-
# Falling edge
|
|
202
210
|
if edge_type in ["falling", "both"]:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
time = (i - 1 + interp_time) * time_base
|
|
206
|
-
|
|
207
|
-
# Calculate edge properties
|
|
208
|
-
amplitude = prev_val - curr_val
|
|
209
|
-
slew_rate = -amplitude * sample_rate
|
|
210
|
-
|
|
211
|
-
# Classify quality (simple heuristic)
|
|
212
|
-
quality = classify_edge_quality(trace, i, sample_rate)
|
|
213
|
-
|
|
214
|
-
edges.append(
|
|
215
|
-
Edge(
|
|
216
|
-
sample_index=i,
|
|
217
|
-
time=time,
|
|
218
|
-
edge_type="falling",
|
|
219
|
-
amplitude=abs(amplitude),
|
|
220
|
-
slew_rate=slew_rate,
|
|
221
|
-
quality=quality,
|
|
222
|
-
)
|
|
211
|
+
edge = _create_edge(
|
|
212
|
+
trace_data, i, "falling", prev_val, curr_val, time_base, sample_rate
|
|
223
213
|
)
|
|
214
|
+
edges.append(edge)
|
|
224
215
|
state = False
|
|
225
216
|
|
|
226
217
|
return edges
|
|
227
218
|
|
|
228
219
|
|
|
220
|
+
def _compute_threshold(trace: NDArray[np.float64], threshold: float | Literal["auto"]) -> float:
|
|
221
|
+
"""Compute detection threshold.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
trace: Signal trace.
|
|
225
|
+
threshold: Threshold value or "auto".
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Threshold value.
|
|
229
|
+
"""
|
|
230
|
+
if threshold == "auto":
|
|
231
|
+
return float((np.max(trace) + np.min(trace)) / 2.0)
|
|
232
|
+
else:
|
|
233
|
+
return threshold
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _apply_hysteresis(thresh_val: float, hysteresis: float) -> tuple[float, float]:
|
|
237
|
+
"""Apply hysteresis to threshold.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
thresh_val: Base threshold.
|
|
241
|
+
hysteresis: Hysteresis amount.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Tuple of (thresh_high, thresh_low).
|
|
245
|
+
"""
|
|
246
|
+
if hysteresis > 0:
|
|
247
|
+
return thresh_val + hysteresis / 2.0, thresh_val - hysteresis / 2.0
|
|
248
|
+
else:
|
|
249
|
+
return thresh_val, thresh_val
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@njit(cache=True) # type: ignore[untyped-decorator]
|
|
253
|
+
def _find_edges_numba(
|
|
254
|
+
data: NDArray[np.float64],
|
|
255
|
+
thresh_high: float,
|
|
256
|
+
thresh_low: float,
|
|
257
|
+
detect_rising: bool,
|
|
258
|
+
detect_falling: bool,
|
|
259
|
+
) -> tuple[NDArray[np.int64], NDArray[np.bool_]]:
|
|
260
|
+
"""Find edges with hysteresis using Numba JIT compilation.
|
|
261
|
+
|
|
262
|
+
This function provides 15-30x speedup vs pure Python loops for edge detection
|
|
263
|
+
on large signals. The first call includes ~100-200ms compilation overhead,
|
|
264
|
+
but subsequent calls with cached compilation are extremely fast.
|
|
265
|
+
|
|
266
|
+
Performance characteristics:
|
|
267
|
+
- First call: ~100-200ms compilation + execution
|
|
268
|
+
- Subsequent calls: <1ms for 100k samples (15-30x faster than Python)
|
|
269
|
+
- Memory efficient: O(n) space for edge storage
|
|
270
|
+
- Parallel execution: Uses single thread (hysteresis requires sequential state)
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
data: Input signal trace (must be contiguous float64 array).
|
|
274
|
+
thresh_high: Upper threshold for hysteresis (rising edge detection).
|
|
275
|
+
thresh_low: Lower threshold for hysteresis (falling edge detection).
|
|
276
|
+
detect_rising: Whether to detect rising edges.
|
|
277
|
+
detect_falling: Whether to detect falling edges.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Tuple of (edge_indices, edge_types) where:
|
|
281
|
+
- edge_indices: Array of sample indices where edges occur
|
|
282
|
+
- edge_types: Boolean array (True=rising, False=falling)
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
>>> signal = np.array([0, 0, 1, 1, 0, 0], dtype=np.float64)
|
|
286
|
+
>>> indices, types = _find_edges_numba(signal, 0.5, 0.5, True, True)
|
|
287
|
+
>>> indices # [2, 4]
|
|
288
|
+
>>> types # [True, False]
|
|
289
|
+
"""
|
|
290
|
+
n = len(data)
|
|
291
|
+
if n < 2:
|
|
292
|
+
return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.bool_)
|
|
293
|
+
|
|
294
|
+
# Pre-allocate arrays for worst case (all samples are edges)
|
|
295
|
+
edge_indices = np.empty(n, dtype=np.int64)
|
|
296
|
+
edge_types = np.empty(n, dtype=np.bool_)
|
|
297
|
+
edge_count = 0
|
|
298
|
+
|
|
299
|
+
# Initial state based on first sample
|
|
300
|
+
state = data[0] > (thresh_high + thresh_low) / 2.0
|
|
301
|
+
|
|
302
|
+
for i in range(1, n):
|
|
303
|
+
curr_val = data[i]
|
|
304
|
+
|
|
305
|
+
if not state and curr_val > thresh_high:
|
|
306
|
+
# Rising edge detected
|
|
307
|
+
if detect_rising:
|
|
308
|
+
edge_indices[edge_count] = i
|
|
309
|
+
edge_types[edge_count] = True # Rising
|
|
310
|
+
edge_count += 1
|
|
311
|
+
state = True
|
|
312
|
+
|
|
313
|
+
elif state and curr_val < thresh_low:
|
|
314
|
+
# Falling edge detected
|
|
315
|
+
if detect_falling:
|
|
316
|
+
edge_indices[edge_count] = i
|
|
317
|
+
edge_types[edge_count] = False # Falling
|
|
318
|
+
edge_count += 1
|
|
319
|
+
state = False
|
|
320
|
+
|
|
321
|
+
# Return trimmed arrays
|
|
322
|
+
return edge_indices[:edge_count], edge_types[:edge_count]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@njit(cache=True) # type: ignore[untyped-decorator]
|
|
326
|
+
def _measure_pulse_widths_numba(
|
|
327
|
+
edge_indices: NDArray[np.int64],
|
|
328
|
+
edge_types: NDArray[np.bool_],
|
|
329
|
+
time_base: float,
|
|
330
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
331
|
+
"""Measure pulse widths between edges using Numba JIT.
|
|
332
|
+
|
|
333
|
+
Computes high pulse widths (rising to falling) and low pulse widths
|
|
334
|
+
(falling to rising) from detected edges. Provides 10-20x speedup vs
|
|
335
|
+
Python loops for large edge lists.
|
|
336
|
+
|
|
337
|
+
Performance characteristics:
|
|
338
|
+
- First call: ~50-100ms compilation overhead
|
|
339
|
+
- Subsequent calls: <0.5ms for 10k edges
|
|
340
|
+
- Memory: O(n) space for pulse arrays
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
edge_indices: Array of edge sample indices.
|
|
344
|
+
edge_types: Boolean array (True=rising, False=falling).
|
|
345
|
+
time_base: Time per sample (1 / sample_rate).
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
Tuple of (high_widths, low_widths) where:
|
|
349
|
+
- high_widths: Array of high pulse widths in seconds
|
|
350
|
+
- low_widths: Array of low pulse widths in seconds
|
|
351
|
+
|
|
352
|
+
Example:
|
|
353
|
+
>>> indices = np.array([100, 200, 300, 400], dtype=np.int64)
|
|
354
|
+
>>> types = np.array([True, False, True, False])
|
|
355
|
+
>>> high, low = _measure_pulse_widths_numba(indices, types, 1e-6)
|
|
356
|
+
>>> high # [100e-6, 100e-6] (200-100, 400-300)
|
|
357
|
+
>>> low # [100e-6] (300-200)
|
|
358
|
+
"""
|
|
359
|
+
n_edges = len(edge_indices)
|
|
360
|
+
if n_edges < 2:
|
|
361
|
+
return np.empty(0, dtype=np.float64), np.empty(0, dtype=np.float64)
|
|
362
|
+
|
|
363
|
+
# Pre-allocate arrays
|
|
364
|
+
high_widths = np.empty(n_edges, dtype=np.float64)
|
|
365
|
+
low_widths = np.empty(n_edges, dtype=np.float64)
|
|
366
|
+
high_count = 0
|
|
367
|
+
low_count = 0
|
|
368
|
+
|
|
369
|
+
for i in range(n_edges - 1):
|
|
370
|
+
if edge_types[i]: # Rising edge
|
|
371
|
+
# Look for next falling edge
|
|
372
|
+
if not edge_types[i + 1]:
|
|
373
|
+
width = (edge_indices[i + 1] - edge_indices[i]) * time_base
|
|
374
|
+
high_widths[high_count] = width
|
|
375
|
+
high_count += 1
|
|
376
|
+
else: # Falling edge
|
|
377
|
+
# Look for next rising edge
|
|
378
|
+
if edge_types[i + 1]:
|
|
379
|
+
width = (edge_indices[i + 1] - edge_indices[i]) * time_base
|
|
380
|
+
low_widths[low_count] = width
|
|
381
|
+
low_count += 1
|
|
382
|
+
|
|
383
|
+
return high_widths[:high_count], low_widths[:low_count]
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@njit(cache=True) # type: ignore[untyped-decorator]
|
|
387
|
+
def _compute_slew_rates_numba(
|
|
388
|
+
data: NDArray[np.float64],
|
|
389
|
+
edge_indices: NDArray[np.int64],
|
|
390
|
+
edge_types: NDArray[np.bool_],
|
|
391
|
+
sample_rate: float,
|
|
392
|
+
) -> NDArray[np.float64]:
|
|
393
|
+
"""Compute slew rates at edges using Numba JIT.
|
|
394
|
+
|
|
395
|
+
Calculates the rate of voltage change (dV/dt) at each edge by examining
|
|
396
|
+
the samples before and after the edge. Provides 15-25x speedup vs Python.
|
|
397
|
+
|
|
398
|
+
Performance characteristics:
|
|
399
|
+
- First call: ~50-100ms compilation overhead
|
|
400
|
+
- Subsequent calls: <0.3ms for 10k edges
|
|
401
|
+
- Memory: O(n) space for slew rate array
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
data: Input signal trace.
|
|
405
|
+
edge_indices: Array of edge sample indices.
|
|
406
|
+
edge_types: Boolean array (True=rising, False=falling).
|
|
407
|
+
sample_rate: Sample rate in Hz.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Array of slew rates in signal units per second (V/s).
|
|
411
|
+
Positive for rising edges, negative for falling edges.
|
|
412
|
+
|
|
413
|
+
Example:
|
|
414
|
+
>>> signal = np.array([0, 0, 1, 1, 0, 0], dtype=np.float64)
|
|
415
|
+
>>> indices = np.array([2, 4], dtype=np.int64)
|
|
416
|
+
>>> types = np.array([True, False])
|
|
417
|
+
>>> slew = _compute_slew_rates_numba(signal, indices, types, 1e6)
|
|
418
|
+
>>> slew # [1e6, -1e6] (1V in 1us)
|
|
419
|
+
"""
|
|
420
|
+
n_edges = len(edge_indices)
|
|
421
|
+
slew_rates = np.empty(n_edges, dtype=np.float64)
|
|
422
|
+
|
|
423
|
+
for i in range(n_edges):
|
|
424
|
+
idx = edge_indices[i]
|
|
425
|
+
|
|
426
|
+
if idx < 1 or idx >= len(data):
|
|
427
|
+
slew_rates[i] = 0.0
|
|
428
|
+
continue
|
|
429
|
+
|
|
430
|
+
# Compute amplitude change
|
|
431
|
+
prev_val = data[idx - 1]
|
|
432
|
+
curr_val = data[idx]
|
|
433
|
+
amplitude = abs(curr_val - prev_val)
|
|
434
|
+
|
|
435
|
+
# Compute slew rate
|
|
436
|
+
if edge_types[i]: # Rising
|
|
437
|
+
slew_rates[i] = amplitude * sample_rate
|
|
438
|
+
else: # Falling
|
|
439
|
+
slew_rates[i] = -amplitude * sample_rate
|
|
440
|
+
|
|
441
|
+
return slew_rates
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _create_edge(
|
|
445
|
+
trace: NDArray[np.float64],
|
|
446
|
+
index: int,
|
|
447
|
+
edge_type: Literal["rising", "falling"],
|
|
448
|
+
prev_val: float,
|
|
449
|
+
curr_val: float,
|
|
450
|
+
time_base: float,
|
|
451
|
+
sample_rate: float,
|
|
452
|
+
) -> Edge:
|
|
453
|
+
"""Create edge object from detected transition.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
trace: Signal trace.
|
|
457
|
+
index: Sample index.
|
|
458
|
+
edge_type: Edge type.
|
|
459
|
+
prev_val: Previous value.
|
|
460
|
+
curr_val: Current value.
|
|
461
|
+
time_base: Time per sample.
|
|
462
|
+
sample_rate: Sample rate.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Edge object.
|
|
466
|
+
"""
|
|
467
|
+
interp_time = interpolate_edge_time(trace, index - 1, method="linear")
|
|
468
|
+
time = (index - 1 + interp_time) * time_base
|
|
469
|
+
|
|
470
|
+
if edge_type == "rising":
|
|
471
|
+
amplitude = curr_val - prev_val
|
|
472
|
+
slew_rate = amplitude * sample_rate
|
|
473
|
+
else: # falling
|
|
474
|
+
amplitude = prev_val - curr_val
|
|
475
|
+
slew_rate = -amplitude * sample_rate
|
|
476
|
+
|
|
477
|
+
quality = classify_edge_quality(trace, index, sample_rate)
|
|
478
|
+
|
|
479
|
+
return Edge(
|
|
480
|
+
sample_index=index,
|
|
481
|
+
time=time,
|
|
482
|
+
edge_type=edge_type,
|
|
483
|
+
amplitude=abs(amplitude),
|
|
484
|
+
slew_rate=slew_rate,
|
|
485
|
+
quality=quality,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
|
|
229
489
|
def interpolate_edge_time(
|
|
230
490
|
trace: NDArray[np.float64], sample_index: int, method: Literal["linear", "quadratic"] = "linear"
|
|
231
491
|
) -> float:
|