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
|
@@ -5,7 +5,7 @@ including eye diagram masks and custom polygon masks.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.comparison import mask_test, eye_mask
|
|
8
|
+
>>> from oscura.utils.comparison import mask_test, eye_mask
|
|
9
9
|
>>> mask = eye_mask(0.5, 0.4, 0.3)
|
|
10
10
|
>>> result = mask_test(trace, mask)
|
|
11
11
|
|
|
@@ -274,20 +274,77 @@ def mask_test(
|
|
|
274
274
|
>>> result = mask_test(eye_trace, mask)
|
|
275
275
|
>>> print(f"Violations: {result.num_violations}")
|
|
276
276
|
"""
|
|
277
|
-
#
|
|
277
|
+
# Prepare test data
|
|
278
|
+
x_arr, y_arr = _prepare_mask_test_data(trace, x_data, normalize)
|
|
279
|
+
|
|
280
|
+
# Find all violations
|
|
281
|
+
violations, violations_by_region = _find_mask_violations(x_arr, y_arr, mask)
|
|
282
|
+
|
|
283
|
+
# Calculate statistics
|
|
284
|
+
unique_violations = list(set(violations))
|
|
285
|
+
num_violations = len(unique_violations)
|
|
286
|
+
violation_rate = num_violations / len(y_arr) if len(y_arr) > 0 else 0.0
|
|
287
|
+
|
|
288
|
+
# Calculate margin to mask boundary
|
|
289
|
+
margin = _calculate_mask_margin(x_arr, y_arr, mask, num_violations)
|
|
290
|
+
|
|
291
|
+
return MaskTestResult(
|
|
292
|
+
passed=num_violations == 0,
|
|
293
|
+
num_violations=num_violations,
|
|
294
|
+
violation_rate=violation_rate,
|
|
295
|
+
violation_points=unique_violations,
|
|
296
|
+
violations_by_region=violations_by_region,
|
|
297
|
+
margin=margin,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _prepare_mask_test_data(
|
|
302
|
+
trace: WaveformTrace,
|
|
303
|
+
x_data: NDArray[np.floating[Any]] | None,
|
|
304
|
+
normalize: bool,
|
|
305
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
306
|
+
"""Prepare X and Y data arrays for mask testing.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
trace: Waveform trace.
|
|
310
|
+
x_data: Optional X coordinates (defaults to sample indices).
|
|
311
|
+
normalize: Whether to normalize Y data to [-1, 1].
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Tuple of (x_array, y_array) as float64.
|
|
315
|
+
"""
|
|
278
316
|
y_data = trace.data.astype(np.float64)
|
|
279
317
|
|
|
280
|
-
#
|
|
318
|
+
# Generate or use provided X data
|
|
281
319
|
if x_data is None:
|
|
282
|
-
|
|
320
|
+
x_arr = np.arange(len(y_data), dtype=np.float64)
|
|
321
|
+
else:
|
|
322
|
+
x_arr = x_data
|
|
283
323
|
|
|
284
|
-
# Normalize if requested
|
|
324
|
+
# Normalize Y data if requested
|
|
285
325
|
if normalize:
|
|
286
326
|
y_min, y_max = np.min(y_data), np.max(y_data)
|
|
287
327
|
if y_max - y_min > 0:
|
|
288
328
|
y_data = 2 * (y_data - y_min) / (y_max - y_min) - 1
|
|
289
329
|
|
|
290
|
-
|
|
330
|
+
return (x_arr, y_data)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _find_mask_violations(
|
|
334
|
+
x_data: NDArray[np.float64],
|
|
335
|
+
y_data: NDArray[np.float64],
|
|
336
|
+
mask: Mask,
|
|
337
|
+
) -> tuple[list[tuple[float, float]], dict[str, int]]:
|
|
338
|
+
"""Find all mask violations in waveform data.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
x_data: X coordinates.
|
|
342
|
+
y_data: Y coordinates.
|
|
343
|
+
mask: Mask with regions to test.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Tuple of (violation_points, violations_by_region_name).
|
|
347
|
+
"""
|
|
291
348
|
violations: list[tuple[float, float]] = []
|
|
292
349
|
violations_by_region: dict[str, int] = {}
|
|
293
350
|
|
|
@@ -296,48 +353,97 @@ def mask_test(
|
|
|
296
353
|
violations_by_region[region_name] = 0
|
|
297
354
|
|
|
298
355
|
if region.region_type == "violation":
|
|
299
|
-
#
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
violations_by_region[region_name] += 1
|
|
304
|
-
|
|
356
|
+
# Points inside violation region are violations
|
|
357
|
+
region_violations = _check_violation_region(x_data, y_data, region)
|
|
358
|
+
violations.extend(region_violations)
|
|
359
|
+
violations_by_region[region_name] = len(region_violations)
|
|
305
360
|
elif region.region_type == "boundary":
|
|
306
|
-
#
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
violations_by_region[region_name] += 1
|
|
361
|
+
# Points outside boundary region are violations
|
|
362
|
+
region_violations = _check_boundary_region(x_data, y_data, region)
|
|
363
|
+
violations.extend(region_violations)
|
|
364
|
+
violations_by_region[region_name] = len(region_violations)
|
|
311
365
|
|
|
312
|
-
|
|
313
|
-
unique_violations = list(set(violations))
|
|
314
|
-
num_violations = len(unique_violations)
|
|
315
|
-
violation_rate = num_violations / len(y_data) if len(y_data) > 0 else 0.0
|
|
316
|
-
|
|
317
|
-
# Estimate margin (simplified - distance to nearest mask edge)
|
|
318
|
-
margin = None
|
|
319
|
-
if num_violations == 0 and mask.regions:
|
|
320
|
-
# Find minimum distance to any violation region
|
|
321
|
-
min_dist = float("inf")
|
|
322
|
-
for region in mask.regions:
|
|
323
|
-
if region.region_type == "violation":
|
|
324
|
-
for x, y in zip(x_data, y_data, strict=False):
|
|
325
|
-
for i in range(len(region.vertices)):
|
|
326
|
-
x1, y1 = region.vertices[i]
|
|
327
|
-
x2, y2 = region.vertices[(i + 1) % len(region.vertices)]
|
|
328
|
-
# Distance to line segment
|
|
329
|
-
dist = _point_to_segment_distance(x, y, x1, y1, x2, y2)
|
|
330
|
-
min_dist = min(min_dist, dist)
|
|
331
|
-
margin = min_dist if min_dist != float("inf") else None
|
|
366
|
+
return (violations, violations_by_region)
|
|
332
367
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
368
|
+
|
|
369
|
+
def _check_violation_region(
|
|
370
|
+
x_data: NDArray[np.float64],
|
|
371
|
+
y_data: NDArray[np.float64],
|
|
372
|
+
region: MaskRegion,
|
|
373
|
+
) -> list[tuple[float, float]]:
|
|
374
|
+
"""Check for points inside violation region.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
x_data: X coordinates.
|
|
378
|
+
y_data: Y coordinates.
|
|
379
|
+
region: Violation region.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
List of (x, y) points violating this region.
|
|
383
|
+
"""
|
|
384
|
+
violations = []
|
|
385
|
+
for x, y in zip(x_data, y_data, strict=False):
|
|
386
|
+
if region.contains_point(float(x), float(y)):
|
|
387
|
+
violations.append((float(x), float(y)))
|
|
388
|
+
return violations
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _check_boundary_region(
|
|
392
|
+
x_data: NDArray[np.float64],
|
|
393
|
+
y_data: NDArray[np.float64],
|
|
394
|
+
region: MaskRegion,
|
|
395
|
+
) -> list[tuple[float, float]]:
|
|
396
|
+
"""Check for points outside boundary region.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
x_data: X coordinates.
|
|
400
|
+
y_data: Y coordinates.
|
|
401
|
+
region: Boundary region.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
List of (x, y) points violating this region.
|
|
405
|
+
"""
|
|
406
|
+
violations = []
|
|
407
|
+
for x, y in zip(x_data, y_data, strict=False):
|
|
408
|
+
if not region.contains_point(float(x), float(y)):
|
|
409
|
+
violations.append((float(x), float(y)))
|
|
410
|
+
return violations
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _calculate_mask_margin(
|
|
414
|
+
x_data: NDArray[np.float64],
|
|
415
|
+
y_data: NDArray[np.float64],
|
|
416
|
+
mask: Mask,
|
|
417
|
+
num_violations: int,
|
|
418
|
+
) -> float | None:
|
|
419
|
+
"""Calculate margin to nearest mask edge.
|
|
420
|
+
|
|
421
|
+
Only calculated when there are no violations.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
x_data: X coordinates.
|
|
425
|
+
y_data: Y coordinates.
|
|
426
|
+
mask: Mask with regions.
|
|
427
|
+
num_violations: Number of violations found.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Minimum distance to mask boundary, or None if violations exist or no regions.
|
|
431
|
+
"""
|
|
432
|
+
if num_violations > 0 or not mask.regions:
|
|
433
|
+
return None
|
|
434
|
+
|
|
435
|
+
# Find minimum distance to any violation region edge
|
|
436
|
+
min_dist = float("inf")
|
|
437
|
+
for region in mask.regions:
|
|
438
|
+
if region.region_type == "violation":
|
|
439
|
+
for x, y in zip(x_data, y_data, strict=False):
|
|
440
|
+
for i in range(len(region.vertices)):
|
|
441
|
+
x1, y1 = region.vertices[i]
|
|
442
|
+
x2, y2 = region.vertices[(i + 1) % len(region.vertices)]
|
|
443
|
+
dist = _point_to_segment_distance(x, y, x1, y1, x2, y2)
|
|
444
|
+
min_dist = min(min_dist, dist)
|
|
445
|
+
|
|
446
|
+
return min_dist if min_dist != float("inf") else None
|
|
341
447
|
|
|
342
448
|
|
|
343
449
|
def _point_to_segment_distance(
|
|
@@ -6,7 +6,7 @@ discovery.comparison module to maintain API compatibility.
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Example:
|
|
9
|
-
>>> from oscura.comparison import compare_traces
|
|
9
|
+
>>> from oscura.utils.comparison import compare_traces
|
|
10
10
|
>>> diff = compare_traces(trace1, trace2)
|
|
11
11
|
>>> for d in diff.differences:
|
|
12
12
|
... print(f"{d.category}: {d.description}")
|
|
@@ -5,7 +5,7 @@ overlay plots, difference plots, and comparison heat maps.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.comparison.visualization import (
|
|
8
|
+
>>> from oscura.utils.comparison.visualization import (
|
|
9
9
|
... plot_overlay,
|
|
10
10
|
... plot_difference,
|
|
11
11
|
... plot_comparison_heatmap
|
|
@@ -24,12 +24,14 @@ from typing import TYPE_CHECKING, Any
|
|
|
24
24
|
import matplotlib.pyplot as plt
|
|
25
25
|
import numpy as np
|
|
26
26
|
from matplotlib.gridspec import GridSpec
|
|
27
|
+
from numpy.typing import NDArray
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
30
|
+
from matplotlib.axes import Axes
|
|
29
31
|
from matplotlib.figure import Figure
|
|
30
32
|
|
|
31
|
-
from oscura.comparison.compare import ComparisonResult
|
|
32
33
|
from oscura.core.types import WaveformTrace
|
|
34
|
+
from oscura.utils.comparison.compare import ComparisonResult
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def plot_overlay(
|
|
@@ -61,7 +63,7 @@ def plot_overlay(
|
|
|
61
63
|
Matplotlib Figure object
|
|
62
64
|
|
|
63
65
|
Example:
|
|
64
|
-
>>> from oscura.comparison.visualization import plot_overlay
|
|
66
|
+
>>> from oscura.utils.comparison.visualization import plot_overlay
|
|
65
67
|
>>> fig = plot_overlay(measured, reference,
|
|
66
68
|
... labels=("Measured", "Reference"))
|
|
67
69
|
>>> plt.show()
|
|
@@ -160,7 +162,7 @@ def plot_difference(
|
|
|
160
162
|
Matplotlib Figure object
|
|
161
163
|
|
|
162
164
|
Example:
|
|
163
|
-
>>> from oscura.comparison.visualization import plot_difference
|
|
165
|
+
>>> from oscura.utils.comparison.visualization import plot_difference
|
|
164
166
|
>>> fig = plot_difference(measured, reference, normalize=True)
|
|
165
167
|
>>> plt.show()
|
|
166
168
|
|
|
@@ -259,89 +261,111 @@ def plot_comparison_heatmap(
|
|
|
259
261
|
Matplotlib Figure object
|
|
260
262
|
|
|
261
263
|
Example:
|
|
262
|
-
>>> from oscura.comparison.visualization import plot_comparison_heatmap
|
|
264
|
+
>>> from oscura.utils.comparison.visualization import plot_comparison_heatmap
|
|
263
265
|
>>> fig = plot_comparison_heatmap(trace1, trace2, window_size=50)
|
|
264
266
|
>>> plt.show()
|
|
265
267
|
|
|
266
268
|
References:
|
|
267
269
|
CMP-003: Difference heat map showing where changes occur
|
|
268
270
|
"""
|
|
271
|
+
fig, (ax_heat, ax_trace) = _create_heatmap_axes(figsize)
|
|
272
|
+
|
|
273
|
+
# Align data and compute difference
|
|
274
|
+
data1, data2, diff, min_len = _prepare_diff_data(trace1, trace2)
|
|
275
|
+
|
|
276
|
+
# Create and plot heatmap
|
|
277
|
+
n_windows, window_size = _compute_window_params(min_len, window_size)
|
|
278
|
+
heatmap_data = _build_heatmap(data1, data2, diff, n_windows, window_size, min_len)
|
|
279
|
+
_plot_heatmap(ax_heat, heatmap_data, title, **kwargs)
|
|
280
|
+
|
|
281
|
+
# Plot difference trace
|
|
282
|
+
time, xlabel = _compute_time_axis(trace1, min_len)
|
|
283
|
+
_plot_diff_trace(ax_trace, time, diff, xlabel)
|
|
284
|
+
|
|
285
|
+
plt.tight_layout()
|
|
286
|
+
return fig
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _create_heatmap_axes(figsize: tuple[float, float]) -> tuple[Figure, tuple[Axes, Axes]]:
|
|
290
|
+
"""Create figure with heatmap and trace axes."""
|
|
269
291
|
fig = plt.figure(figsize=figsize)
|
|
270
292
|
gs = GridSpec(2, 1, height_ratios=[3, 1], hspace=0.3)
|
|
271
293
|
ax_heat = fig.add_subplot(gs[0])
|
|
272
294
|
ax_trace = fig.add_subplot(gs[1], sharex=ax_heat)
|
|
295
|
+
return fig, (ax_heat, ax_trace)
|
|
273
296
|
|
|
274
|
-
|
|
297
|
+
|
|
298
|
+
def _prepare_diff_data(
|
|
299
|
+
trace1: WaveformTrace, trace2: WaveformTrace
|
|
300
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64], int]:
|
|
301
|
+
"""Prepare aligned data and compute difference."""
|
|
275
302
|
data1 = trace1.data.astype(np.float64)
|
|
276
303
|
data2 = trace2.data.astype(np.float64)
|
|
277
|
-
|
|
278
|
-
# Align lengths
|
|
279
304
|
min_len = min(len(data1), len(data2))
|
|
280
|
-
data1 = data1[:min_len]
|
|
281
|
-
data2 = data2[:min_len]
|
|
282
|
-
|
|
283
|
-
# Compute difference
|
|
305
|
+
data1, data2 = data1[:min_len], data2[:min_len]
|
|
284
306
|
diff = np.abs(data1 - data2)
|
|
307
|
+
return data1, data2, diff, min_len
|
|
308
|
+
|
|
285
309
|
|
|
286
|
-
|
|
310
|
+
def _compute_window_params(min_len: int, window_size: int) -> tuple[int, int]:
|
|
311
|
+
"""Compute window parameters for heatmap."""
|
|
287
312
|
n_windows = min_len // window_size
|
|
288
313
|
if n_windows == 0:
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
314
|
+
return 1, min_len
|
|
315
|
+
return n_windows, window_size
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _build_heatmap(
|
|
319
|
+
data1: NDArray[np.float64],
|
|
320
|
+
data2: NDArray[np.float64],
|
|
321
|
+
diff: NDArray[np.float64],
|
|
322
|
+
n_windows: int,
|
|
323
|
+
window_size: int,
|
|
324
|
+
min_len: int,
|
|
325
|
+
) -> NDArray[np.float64]:
|
|
326
|
+
"""Build heatmap data from windowed differences."""
|
|
292
327
|
heatmap_data = np.zeros((10, n_windows))
|
|
293
328
|
for i in range(n_windows):
|
|
294
329
|
start = i * window_size
|
|
295
330
|
end = min(start + window_size, min_len)
|
|
296
|
-
window_diff = diff[start:end]
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
y_min = min(np.min(window_data1), np.min(window_data2))
|
|
302
|
-
y_max = max(np.max(window_data1), np.max(window_data2))
|
|
303
|
-
|
|
331
|
+
window_data1, window_diff = data1[start:end], diff[start:end]
|
|
332
|
+
y_min, y_max = (
|
|
333
|
+
min(np.min(data1[start:end]), np.min(data2[start:end])),
|
|
334
|
+
max(np.max(data1[start:end]), np.max(data2[start:end])),
|
|
335
|
+
)
|
|
304
336
|
if y_max - y_min > 0:
|
|
305
337
|
bins = np.linspace(y_min, y_max, 11)
|
|
306
|
-
for sample_idx in
|
|
307
|
-
|
|
308
|
-
bin_idx = np.digitize(y_val, bins) - 1
|
|
309
|
-
bin_idx = max(0, min(9, bin_idx))
|
|
338
|
+
for sample_idx, y_val in enumerate(window_data1):
|
|
339
|
+
bin_idx = max(0, min(9, np.digitize(y_val, bins) - 1))
|
|
310
340
|
heatmap_data[bin_idx, i] += window_diff[sample_idx]
|
|
341
|
+
return heatmap_data / window_size
|
|
342
|
+
|
|
311
343
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
im = ax_heat.imshow(
|
|
317
|
-
heatmap_data,
|
|
318
|
-
aspect="auto",
|
|
319
|
-
cmap="hot",
|
|
320
|
-
origin="lower",
|
|
321
|
-
interpolation="nearest",
|
|
322
|
-
**kwargs,
|
|
344
|
+
def _plot_heatmap(ax: Axes, heatmap_data: NDArray[np.float64], title: str, **kwargs: Any) -> None:
|
|
345
|
+
"""Plot heatmap on axes."""
|
|
346
|
+
im = ax.imshow(
|
|
347
|
+
heatmap_data, aspect="auto", cmap="hot", origin="lower", interpolation="nearest", **kwargs
|
|
323
348
|
)
|
|
324
|
-
plt.colorbar(im, ax=
|
|
349
|
+
plt.colorbar(im, ax=ax, label="Average Difference")
|
|
350
|
+
ax.set_ylabel("Amplitude Bin")
|
|
351
|
+
ax.set_title(title)
|
|
325
352
|
|
|
326
|
-
ax_heat.set_ylabel("Amplitude Bin")
|
|
327
|
-
ax_heat.set_title(title)
|
|
328
353
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
else:
|
|
335
|
-
time = np.arange(min_len, dtype=np.float64)
|
|
336
|
-
xlabel = "Sample"
|
|
354
|
+
def _compute_time_axis(trace: WaveformTrace, min_len: int) -> tuple[NDArray[np.float64], str]:
|
|
355
|
+
"""Compute time axis and label."""
|
|
356
|
+
if hasattr(trace, "metadata") and trace.metadata.sample_rate is not None:
|
|
357
|
+
return np.arange(min_len) / trace.metadata.sample_rate, "Time (s)"
|
|
358
|
+
return np.arange(min_len, dtype=np.float64), "Sample"
|
|
337
359
|
|
|
338
|
-
ax_trace.plot(time, diff, linewidth=0.5)
|
|
339
|
-
ax_trace.set_xlabel(xlabel)
|
|
340
|
-
ax_trace.set_ylabel("Difference")
|
|
341
|
-
ax_trace.grid(True, alpha=0.3)
|
|
342
360
|
|
|
343
|
-
|
|
344
|
-
|
|
361
|
+
def _plot_diff_trace(
|
|
362
|
+
ax: Axes, time: NDArray[np.float64], diff: NDArray[np.float64], xlabel: str
|
|
363
|
+
) -> None:
|
|
364
|
+
"""Plot difference trace on axes."""
|
|
365
|
+
ax.plot(time, diff, linewidth=0.5)
|
|
366
|
+
ax.set_xlabel(xlabel)
|
|
367
|
+
ax.set_ylabel("Difference")
|
|
368
|
+
ax.grid(True, alpha=0.3)
|
|
345
369
|
|
|
346
370
|
|
|
347
371
|
def plot_comparison_summary(
|
|
@@ -363,8 +387,8 @@ def plot_comparison_summary(
|
|
|
363
387
|
Matplotlib Figure object
|
|
364
388
|
|
|
365
389
|
Example:
|
|
366
|
-
>>> from oscura.comparison import compare_traces
|
|
367
|
-
>>> from oscura.comparison.visualization import plot_comparison_summary
|
|
390
|
+
>>> from oscura.utils.comparison import compare_traces
|
|
391
|
+
>>> from oscura.utils.comparison.visualization import plot_comparison_summary
|
|
368
392
|
>>> result = compare_traces(trace1, trace2)
|
|
369
393
|
>>> fig = plot_comparison_summary(result)
|
|
370
394
|
>>> plt.show()
|
|
@@ -375,7 +399,28 @@ def plot_comparison_summary(
|
|
|
375
399
|
fig = plt.figure(figsize=figsize)
|
|
376
400
|
gs = GridSpec(3, 2, hspace=0.4, wspace=0.3)
|
|
377
401
|
|
|
378
|
-
#
|
|
402
|
+
# Create statistics table
|
|
403
|
+
_plot_statistics_table(fig, gs, result, title)
|
|
404
|
+
|
|
405
|
+
# Plot difference trace
|
|
406
|
+
if result.difference_trace is not None:
|
|
407
|
+
_plot_difference_trace(fig, gs, result.difference_trace)
|
|
408
|
+
_plot_difference_histogram(fig, gs, result.difference_trace)
|
|
409
|
+
|
|
410
|
+
# Plot violation locations
|
|
411
|
+
_plot_violations(fig, gs, result)
|
|
412
|
+
|
|
413
|
+
plt.tight_layout()
|
|
414
|
+
return fig
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _plot_statistics_table(
|
|
418
|
+
fig: Figure,
|
|
419
|
+
gs: GridSpec,
|
|
420
|
+
result: ComparisonResult,
|
|
421
|
+
title: str,
|
|
422
|
+
) -> None:
|
|
423
|
+
"""Plot statistics table at top of summary."""
|
|
379
424
|
ax_stats = fig.add_subplot(gs[0, :])
|
|
380
425
|
ax_stats.axis("off")
|
|
381
426
|
|
|
@@ -415,34 +460,50 @@ def plot_comparison_summary(
|
|
|
415
460
|
|
|
416
461
|
ax_stats.set_title(title, fontsize=14, fontweight="bold", pad=20)
|
|
417
462
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
463
|
+
|
|
464
|
+
def _plot_difference_trace(
|
|
465
|
+
fig: Figure,
|
|
466
|
+
gs: GridSpec,
|
|
467
|
+
difference_trace: Any,
|
|
468
|
+
) -> None:
|
|
469
|
+
"""Plot difference trace in middle row."""
|
|
470
|
+
ax_overlay = fig.add_subplot(gs[1, :])
|
|
471
|
+
n_samples = len(difference_trace.data)
|
|
472
|
+
time = np.arange(n_samples)
|
|
473
|
+
ax_overlay.plot(time, difference_trace.data, label="Difference")
|
|
474
|
+
ax_overlay.axhline(y=0, color="k", linestyle="--", alpha=0.5)
|
|
475
|
+
ax_overlay.set_xlabel("Sample")
|
|
476
|
+
ax_overlay.set_ylabel("Difference")
|
|
477
|
+
ax_overlay.set_title("Difference Trace")
|
|
478
|
+
ax_overlay.legend()
|
|
479
|
+
ax_overlay.grid(True, alpha=0.3)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _plot_difference_histogram(
|
|
483
|
+
fig: Figure,
|
|
484
|
+
gs: GridSpec,
|
|
485
|
+
difference_trace: Any,
|
|
486
|
+
) -> None:
|
|
487
|
+
"""Plot histogram of differences."""
|
|
488
|
+
ax_hist = fig.add_subplot(gs[2, 0])
|
|
489
|
+
diff_data = difference_trace.data
|
|
490
|
+
ax_hist.hist(diff_data, bins=50, edgecolor="black", alpha=0.7)
|
|
491
|
+
ax_hist.axvline(x=0, color="r", linestyle="--", linewidth=2, label="Zero difference")
|
|
492
|
+
ax_hist.set_xlabel("Difference")
|
|
493
|
+
ax_hist.set_ylabel("Count")
|
|
494
|
+
ax_hist.set_title("Difference Distribution")
|
|
495
|
+
ax_hist.legend()
|
|
496
|
+
ax_hist.grid(True, alpha=0.3)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _plot_violations(
|
|
500
|
+
fig: Figure,
|
|
501
|
+
gs: GridSpec,
|
|
502
|
+
result: ComparisonResult,
|
|
503
|
+
) -> None:
|
|
504
|
+
"""Plot violation locations."""
|
|
445
505
|
ax_viol = fig.add_subplot(gs[2, 1])
|
|
506
|
+
|
|
446
507
|
if result.violations is not None and len(result.violations) > 0:
|
|
447
508
|
ax_viol.scatter(
|
|
448
509
|
result.violations,
|
|
@@ -469,9 +530,6 @@ def plot_comparison_summary(
|
|
|
469
530
|
)
|
|
470
531
|
ax_viol.axis("off")
|
|
471
532
|
|
|
472
|
-
plt.tight_layout()
|
|
473
|
-
return fig
|
|
474
|
-
|
|
475
533
|
|
|
476
534
|
__all__ = [
|
|
477
535
|
"plot_comparison_heatmap",
|
|
@@ -4,17 +4,17 @@ This module provides TDR-based impedance extraction, capacitance/inductance
|
|
|
4
4
|
measurement, parasitic extraction, and transmission line analysis.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from oscura.component.impedance import (
|
|
7
|
+
from oscura.utils.component.impedance import (
|
|
8
8
|
discontinuity_analysis,
|
|
9
9
|
extract_impedance,
|
|
10
10
|
impedance_profile,
|
|
11
11
|
)
|
|
12
|
-
from oscura.component.reactive import (
|
|
12
|
+
from oscura.utils.component.reactive import (
|
|
13
13
|
extract_parasitics,
|
|
14
14
|
measure_capacitance,
|
|
15
15
|
measure_inductance,
|
|
16
16
|
)
|
|
17
|
-
from oscura.component.transmission_line import (
|
|
17
|
+
from oscura.utils.component.transmission_line import (
|
|
18
18
|
characteristic_impedance,
|
|
19
19
|
propagation_delay,
|
|
20
20
|
transmission_line_analysis,
|