oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/workflows/compliance.py
CHANGED
|
@@ -21,6 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
from typing import TYPE_CHECKING, Any, Literal
|
|
22
22
|
|
|
23
23
|
import numpy as np
|
|
24
|
+
from numpy.typing import NDArray
|
|
24
25
|
|
|
25
26
|
from oscura.core.exceptions import AnalysisError
|
|
26
27
|
|
|
@@ -69,19 +70,6 @@ def emc_compliance_test(
|
|
|
69
70
|
- limit_freq: Frequency array for limit mask
|
|
70
71
|
- limit_mag: Magnitude array for limit mask
|
|
71
72
|
|
|
72
|
-
Returns:
|
|
73
|
-
Dictionary containing:
|
|
74
|
-
- status: 'PASS' or 'FAIL'
|
|
75
|
-
- standard: Standard tested against
|
|
76
|
-
- violations: List of frequency violations
|
|
77
|
-
- margin_to_limit: Minimum margin in dB (negative if failing)
|
|
78
|
-
- worst_frequency: Frequency with worst margin
|
|
79
|
-
- worst_margin: Worst margin value in dB
|
|
80
|
-
- spectrum_freq: Frequency array for spectrum
|
|
81
|
-
- spectrum_mag: Magnitude array for spectrum (dBµV or dBm)
|
|
82
|
-
- limit_freq: Frequency array for limit mask
|
|
83
|
-
- limit_mag: Magnitude array for limit mask
|
|
84
|
-
|
|
85
73
|
Example:
|
|
86
74
|
>>> trace = osc.load('radiated_emissions.wfm')
|
|
87
75
|
>>> result = osc.emc_compliance_test(trace, standard='FCC_Part15_ClassB')
|
|
@@ -95,65 +83,33 @@ def emc_compliance_test(
|
|
|
95
83
|
CISPR 22/32 (Information Technology Equipment EMC)
|
|
96
84
|
MIL-STD-461G (Military EMC Requirements)
|
|
97
85
|
"""
|
|
98
|
-
# Import spectral analysis
|
|
99
86
|
from oscura.analyzers.waveform.spectral import fft
|
|
100
87
|
|
|
101
|
-
# Calculate spectrum
|
|
88
|
+
# Calculate spectrum and convert to dBµV
|
|
102
89
|
freq, mag_db = fft(trace) # type: ignore[misc]
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Convert to dBµV (typical EMC unit)
|
|
106
|
-
# mag_db is in dBV, convert to dBµV: dBµV = dBV + 120
|
|
107
|
-
# (since 1µV = 1e-6 V, and 20*log10(1e6) = 120 dB)
|
|
108
|
-
spectrum_dbuv = mag_db + 120
|
|
90
|
+
spectrum_dbuv = mag_db + 120 # Convert dBV to dBµV
|
|
109
91
|
|
|
110
|
-
# Load limit
|
|
92
|
+
# Load limit and apply frequency range
|
|
111
93
|
limit_freq, limit_mag = _load_emc_mask(standard)
|
|
94
|
+
freq, spectrum_dbuv = _apply_frequency_range(freq, spectrum_dbuv, frequency_range)
|
|
112
95
|
|
|
113
|
-
#
|
|
114
|
-
if frequency_range is not None:
|
|
115
|
-
f_min, f_max = frequency_range
|
|
116
|
-
mask = (freq >= f_min) & (freq <= f_max)
|
|
117
|
-
freq = freq[mask]
|
|
118
|
-
spectrum_dbuv = spectrum_dbuv[mask]
|
|
119
|
-
|
|
120
|
-
# Interpolate limit to spectrum frequencies
|
|
96
|
+
# Check compliance
|
|
121
97
|
limit_interp = np.interp(freq, limit_freq, limit_mag)
|
|
122
|
-
|
|
123
|
-
# Find violations (spectrum exceeds limit)
|
|
124
98
|
margin = limit_interp - spectrum_dbuv
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
# Build violations list
|
|
128
|
-
violations = []
|
|
129
|
-
if np.any(violations_mask):
|
|
130
|
-
violation_indices = np.where(violations_mask)[0]
|
|
131
|
-
for idx in violation_indices:
|
|
132
|
-
violations.append(
|
|
133
|
-
{
|
|
134
|
-
"frequency": freq[idx],
|
|
135
|
-
"measured_dbuv": spectrum_dbuv[idx],
|
|
136
|
-
"limit_dbuv": limit_interp[idx],
|
|
137
|
-
"excess_db": -margin[idx], # Positive value for excess
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
# Overall status
|
|
99
|
+
violations = _build_violations_list(freq, spectrum_dbuv, limit_interp, margin)
|
|
142
100
|
status = "FAIL" if violations else "PASS"
|
|
143
101
|
|
|
144
102
|
# Margin analysis
|
|
145
103
|
margin_to_limit = np.min(margin)
|
|
146
104
|
worst_idx = np.argmin(margin)
|
|
147
|
-
worst_frequency = freq[worst_idx]
|
|
148
|
-
worst_margin = margin[worst_idx]
|
|
149
105
|
|
|
150
106
|
result = {
|
|
151
107
|
"status": status,
|
|
152
108
|
"standard": standard,
|
|
153
109
|
"violations": violations,
|
|
154
110
|
"margin_to_limit": margin_to_limit,
|
|
155
|
-
"worst_frequency":
|
|
156
|
-
"worst_margin":
|
|
111
|
+
"worst_frequency": freq[worst_idx],
|
|
112
|
+
"worst_margin": margin[worst_idx],
|
|
157
113
|
"spectrum_freq": freq,
|
|
158
114
|
"spectrum_mag": spectrum_dbuv,
|
|
159
115
|
"limit_freq": limit_freq,
|
|
@@ -161,13 +117,47 @@ def emc_compliance_test(
|
|
|
161
117
|
"detector": detector,
|
|
162
118
|
}
|
|
163
119
|
|
|
164
|
-
# Generate report if requested
|
|
165
120
|
if report is not None:
|
|
166
121
|
_generate_compliance_report(result, report)
|
|
167
122
|
|
|
168
123
|
return result
|
|
169
124
|
|
|
170
125
|
|
|
126
|
+
def _apply_frequency_range(
|
|
127
|
+
freq: NDArray[Any],
|
|
128
|
+
spectrum: NDArray[Any],
|
|
129
|
+
frequency_range: tuple[float, float] | None,
|
|
130
|
+
) -> tuple[NDArray[Any], NDArray[Any]]:
|
|
131
|
+
"""Apply frequency range filter if specified."""
|
|
132
|
+
if frequency_range is not None:
|
|
133
|
+
f_min, f_max = frequency_range
|
|
134
|
+
mask = (freq >= f_min) & (freq <= f_max)
|
|
135
|
+
return freq[mask], spectrum[mask]
|
|
136
|
+
return freq, spectrum
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _build_violations_list(
|
|
140
|
+
freq: NDArray[Any],
|
|
141
|
+
spectrum_dbuv: NDArray[Any],
|
|
142
|
+
limit_interp: NDArray[Any],
|
|
143
|
+
margin: NDArray[Any],
|
|
144
|
+
) -> list[dict[str, float]]:
|
|
145
|
+
"""Build list of compliance violations."""
|
|
146
|
+
violations = []
|
|
147
|
+
violations_mask = margin < 0
|
|
148
|
+
if np.any(violations_mask):
|
|
149
|
+
for idx in np.where(violations_mask)[0]:
|
|
150
|
+
violations.append(
|
|
151
|
+
{
|
|
152
|
+
"frequency": freq[idx],
|
|
153
|
+
"measured_dbuv": spectrum_dbuv[idx],
|
|
154
|
+
"limit_dbuv": limit_interp[idx],
|
|
155
|
+
"excess_db": -margin[idx],
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
return violations
|
|
159
|
+
|
|
160
|
+
|
|
171
161
|
def _load_emc_mask(
|
|
172
162
|
standard: str,
|
|
173
163
|
) -> tuple[np.ndarray[Any, np.dtype[np.float64]], np.ndarray[Any, np.dtype[np.float64]]]:
|
oscura/workflows/digital.py
CHANGED
|
@@ -85,17 +85,67 @@ def characterize_buffer(
|
|
|
85
85
|
IEEE 181-2011 Section 5.2 (Edge timing)
|
|
86
86
|
JEDEC Standard No. 65B (Logic family specifications)
|
|
87
87
|
"""
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
# Detect or use specified logic family
|
|
89
|
+
logic_family, confidence, voh, vol = _determine_logic_family(trace, logic_family)
|
|
90
|
+
|
|
91
|
+
# Measure timing parameters
|
|
92
|
+
t_rise, t_fall = _measure_timing_params(trace)
|
|
93
|
+
|
|
94
|
+
# Measure voltage overshoots
|
|
95
|
+
v_overshoot, v_undershoot, overshoot_pct, undershoot_pct = _measure_overshoots(trace, voh, vol)
|
|
96
|
+
|
|
97
|
+
# Calculate noise margins
|
|
98
|
+
logic_specs = _get_logic_specs(logic_family)
|
|
99
|
+
noise_margin_high = voh - logic_specs["vih"]
|
|
100
|
+
noise_margin_low = logic_specs["vil"] - vol
|
|
101
|
+
|
|
102
|
+
# Measure propagation delay if reference provided
|
|
103
|
+
propagation_delay = _measure_propagation_delay(reference_trace, trace)
|
|
104
|
+
|
|
105
|
+
# Determine pass/fail status
|
|
106
|
+
status = _evaluate_pass_fail(t_rise, t_fall, overshoot_pct, thresholds, logic_specs)
|
|
107
|
+
|
|
108
|
+
# Build result dictionary
|
|
109
|
+
result = _build_result_dict(
|
|
110
|
+
logic_family,
|
|
111
|
+
confidence,
|
|
112
|
+
t_rise,
|
|
113
|
+
t_fall,
|
|
114
|
+
propagation_delay,
|
|
115
|
+
v_overshoot,
|
|
116
|
+
overshoot_pct,
|
|
117
|
+
v_undershoot,
|
|
118
|
+
undershoot_pct,
|
|
119
|
+
noise_margin_high,
|
|
120
|
+
noise_margin_low,
|
|
121
|
+
voh,
|
|
122
|
+
vol,
|
|
123
|
+
status,
|
|
124
|
+
reference_trace,
|
|
94
125
|
)
|
|
95
|
-
from oscura.inference.logic import detect_logic_family
|
|
96
126
|
|
|
97
|
-
#
|
|
127
|
+
# Generate report if requested
|
|
128
|
+
if report is not None:
|
|
129
|
+
_generate_buffer_report(result, report)
|
|
130
|
+
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _determine_logic_family(
|
|
135
|
+
trace: WaveformTrace, logic_family: str | None
|
|
136
|
+
) -> tuple[str, float, float, float]:
|
|
137
|
+
"""Determine logic family and voltage levels.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
trace: Input waveform trace.
|
|
141
|
+
logic_family: Optional logic family override.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Tuple of (logic_family, confidence, voh, vol).
|
|
145
|
+
"""
|
|
98
146
|
if logic_family is None:
|
|
147
|
+
from oscura.inference.logic import detect_logic_family
|
|
148
|
+
|
|
99
149
|
detection = detect_logic_family(trace)
|
|
100
150
|
logic_family = detection["primary"]["name"]
|
|
101
151
|
confidence = detection["primary"]["confidence"]
|
|
@@ -103,67 +153,167 @@ def characterize_buffer(
|
|
|
103
153
|
vol = detection["primary"]["vol"]
|
|
104
154
|
else:
|
|
105
155
|
confidence = 1.0
|
|
106
|
-
# Measure VOH/VOL from trace
|
|
107
156
|
voh = np.percentile(trace.data, 95)
|
|
108
157
|
vol = np.percentile(trace.data, 5)
|
|
109
158
|
|
|
110
|
-
|
|
159
|
+
return logic_family, confidence, voh, vol
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _measure_timing_params(trace: WaveformTrace) -> tuple[float, float]:
|
|
163
|
+
"""Measure rise and fall times.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
trace: Input waveform trace.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Tuple of (rise_time, fall_time).
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
AnalysisError: If measurement fails.
|
|
173
|
+
"""
|
|
174
|
+
from oscura.analyzers.waveform.measurements import fall_time, rise_time
|
|
175
|
+
|
|
111
176
|
try:
|
|
112
|
-
|
|
113
|
-
|
|
177
|
+
t_rise_raw = rise_time(trace)
|
|
178
|
+
t_fall_raw = fall_time(trace)
|
|
179
|
+
t_rise: float = float(t_rise_raw)
|
|
180
|
+
t_fall: float = float(t_fall_raw)
|
|
114
181
|
except Exception as e:
|
|
115
182
|
raise AnalysisError(f"Failed to measure rise/fall time: {e}") from e
|
|
116
183
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
184
|
+
return t_rise, t_fall
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _measure_overshoots(
|
|
188
|
+
trace: WaveformTrace, voh: float, vol: float
|
|
189
|
+
) -> tuple[float, float, float, float]:
|
|
190
|
+
"""Measure voltage overshoots and undershoots.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
trace: Input waveform trace.
|
|
194
|
+
voh: High voltage level.
|
|
195
|
+
vol: Low voltage level.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Tuple of (v_overshoot, v_undershoot, overshoot_pct, undershoot_pct).
|
|
199
|
+
"""
|
|
200
|
+
from oscura.analyzers.waveform.measurements import overshoot, undershoot
|
|
201
|
+
|
|
202
|
+
v_overshoot_raw = overshoot(trace)
|
|
203
|
+
v_undershoot_raw = undershoot(trace)
|
|
204
|
+
v_overshoot: float = float(v_overshoot_raw)
|
|
205
|
+
v_undershoot: float = float(v_undershoot_raw)
|
|
120
206
|
|
|
121
|
-
# Calculate percentages
|
|
122
207
|
swing = voh - vol
|
|
123
208
|
if swing > 0:
|
|
124
|
-
overshoot_pct = (v_overshoot / swing) * 100.0
|
|
125
|
-
undershoot_pct = (v_undershoot / swing) * 100.0
|
|
209
|
+
overshoot_pct: float = (v_overshoot / swing) * 100.0
|
|
210
|
+
undershoot_pct: float = (v_undershoot / swing) * 100.0
|
|
126
211
|
else:
|
|
127
212
|
overshoot_pct = 0.0
|
|
128
213
|
undershoot_pct = 0.0
|
|
129
214
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
215
|
+
return v_overshoot, v_undershoot, overshoot_pct, undershoot_pct
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _measure_propagation_delay(
|
|
219
|
+
reference_trace: WaveformTrace | None, trace: WaveformTrace
|
|
220
|
+
) -> float | None:
|
|
221
|
+
"""Measure propagation delay if reference trace provided.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
reference_trace: Optional reference trace.
|
|
225
|
+
trace: Output trace.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Propagation delay in seconds, or None if not measurable.
|
|
229
|
+
"""
|
|
230
|
+
if reference_trace is None:
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
from oscura.analyzers.digital.timing import propagation_delay as prop_delay
|
|
235
|
+
|
|
236
|
+
delay_raw = prop_delay(reference_trace, trace)
|
|
237
|
+
delay: float = float(delay_raw)
|
|
238
|
+
return delay
|
|
239
|
+
except Exception:
|
|
240
|
+
return None
|
|
241
|
+
|
|
135
242
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
243
|
+
def _evaluate_pass_fail(
|
|
244
|
+
t_rise: float,
|
|
245
|
+
t_fall: float,
|
|
246
|
+
overshoot_pct: float,
|
|
247
|
+
thresholds: dict[str, float] | None,
|
|
248
|
+
logic_specs: dict[str, float],
|
|
249
|
+
) -> str:
|
|
250
|
+
"""Evaluate pass/fail status based on thresholds.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
t_rise: Rise time measurement.
|
|
254
|
+
t_fall: Fall time measurement.
|
|
255
|
+
overshoot_pct: Overshoot percentage.
|
|
256
|
+
thresholds: Optional custom thresholds.
|
|
257
|
+
logic_specs: Logic family specifications.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
"PASS" or "FAIL" status string.
|
|
261
|
+
"""
|
|
152
262
|
if thresholds is not None:
|
|
153
263
|
if "rise_time" in thresholds and t_rise > thresholds["rise_time"]:
|
|
154
|
-
|
|
264
|
+
return "FAIL"
|
|
155
265
|
if "fall_time" in thresholds and t_fall > thresholds["fall_time"]:
|
|
156
|
-
|
|
266
|
+
return "FAIL"
|
|
157
267
|
if "overshoot_percent" in thresholds and overshoot_pct > thresholds["overshoot_percent"]:
|
|
158
|
-
|
|
268
|
+
return "FAIL"
|
|
159
269
|
else:
|
|
160
|
-
# Use logic family defaults
|
|
161
270
|
if t_rise > logic_specs.get("max_rise_time", float("inf")):
|
|
162
|
-
|
|
271
|
+
return "FAIL"
|
|
163
272
|
if t_fall > logic_specs.get("max_fall_time", float("inf")):
|
|
164
|
-
|
|
273
|
+
return "FAIL"
|
|
274
|
+
|
|
275
|
+
return "PASS"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _build_result_dict(
|
|
279
|
+
logic_family: str,
|
|
280
|
+
confidence: float,
|
|
281
|
+
t_rise: float,
|
|
282
|
+
t_fall: float,
|
|
283
|
+
propagation_delay: float | None,
|
|
284
|
+
v_overshoot: float,
|
|
285
|
+
overshoot_pct: float,
|
|
286
|
+
v_undershoot: float,
|
|
287
|
+
undershoot_pct: float,
|
|
288
|
+
noise_margin_high: float,
|
|
289
|
+
noise_margin_low: float,
|
|
290
|
+
voh: float,
|
|
291
|
+
vol: float,
|
|
292
|
+
status: str,
|
|
293
|
+
reference_trace: WaveformTrace | None,
|
|
294
|
+
) -> dict[str, Any]:
|
|
295
|
+
"""Build result dictionary.
|
|
165
296
|
|
|
166
|
-
|
|
297
|
+
Args:
|
|
298
|
+
logic_family: Logic family name.
|
|
299
|
+
confidence: Detection confidence.
|
|
300
|
+
t_rise: Rise time.
|
|
301
|
+
t_fall: Fall time.
|
|
302
|
+
propagation_delay: Propagation delay measurement.
|
|
303
|
+
v_overshoot: Overshoot voltage.
|
|
304
|
+
overshoot_pct: Overshoot percentage.
|
|
305
|
+
v_undershoot: Undershoot voltage.
|
|
306
|
+
undershoot_pct: Undershoot percentage.
|
|
307
|
+
noise_margin_high: High noise margin.
|
|
308
|
+
noise_margin_low: Low noise margin.
|
|
309
|
+
voh: High output voltage.
|
|
310
|
+
vol: Low output voltage.
|
|
311
|
+
status: Pass/fail status.
|
|
312
|
+
reference_trace: Optional reference trace.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Result dictionary with all measurements.
|
|
316
|
+
"""
|
|
167
317
|
result = {
|
|
168
318
|
"logic_family": logic_family,
|
|
169
319
|
"confidence": confidence,
|
|
@@ -185,13 +335,9 @@ def characterize_buffer(
|
|
|
185
335
|
if reference_trace is not None and propagation_delay is not None:
|
|
186
336
|
result["reference_comparison"] = {
|
|
187
337
|
"propagation_delay": propagation_delay,
|
|
188
|
-
"timing_drift":
|
|
338
|
+
"timing_drift": None,
|
|
189
339
|
}
|
|
190
340
|
|
|
191
|
-
# Generate report if requested
|
|
192
|
-
if report is not None:
|
|
193
|
-
_generate_buffer_report(result, report)
|
|
194
|
-
|
|
195
341
|
return result
|
|
196
342
|
|
|
197
343
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Legacy workflow patterns for backward compatibility.
|
|
2
|
+
|
|
3
|
+
This module provides legacy workflow execution patterns that have been
|
|
4
|
+
superseded by modern composition patterns. Kept for backward compatibility
|
|
5
|
+
with existing code and tests.
|
|
6
|
+
|
|
7
|
+
For new code, use the modern workflow patterns in the parent module.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from oscura.workflows.legacy.dag import TaskNode, WorkflowDAG
|
|
11
|
+
|
|
12
|
+
__all__ = ["TaskNode", "WorkflowDAG"]
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides DAG-based workflow execution with automatic dependency
|
|
4
4
|
resolution and parallel execution of independent tasks.
|
|
5
|
+
|
|
6
|
+
NOTE: This is legacy API for backward compatibility.
|
|
7
|
+
For new code, use the modern workflow composition patterns.
|
|
5
8
|
"""
|
|
6
9
|
|
|
7
10
|
from __future__ import annotations
|
|
@@ -51,7 +54,7 @@ class WorkflowDAG:
|
|
|
51
54
|
with automatic parallelization of independent tasks.
|
|
52
55
|
|
|
53
56
|
Example:
|
|
54
|
-
>>> from oscura.
|
|
57
|
+
>>> from oscura.workflows.legacy.dag import WorkflowDAG
|
|
55
58
|
>>> dag = WorkflowDAG()
|
|
56
59
|
>>> dag.add_task('load', load_trace, depends_on=[])
|
|
57
60
|
>>> dag.add_task('fft', compute_fft, depends_on=['load'])
|
oscura/workflows/multi_trace.py
CHANGED
|
@@ -107,7 +107,7 @@ class MultiTraceWorkflow:
|
|
|
107
107
|
if not self.pattern:
|
|
108
108
|
return
|
|
109
109
|
|
|
110
|
-
paths = glob_func(self.pattern)
|
|
110
|
+
paths = glob_func(self.pattern)
|
|
111
111
|
if not paths:
|
|
112
112
|
raise OscuraError(f"No files match pattern: {self.pattern}")
|
|
113
113
|
|
|
@@ -131,20 +131,20 @@ class MultiTraceWorkflow:
|
|
|
131
131
|
|
|
132
132
|
try:
|
|
133
133
|
if ext == ".csv":
|
|
134
|
-
from oscura.loaders.csv import (
|
|
135
|
-
load_csv,
|
|
134
|
+
from oscura.loaders.csv import (
|
|
135
|
+
load_csv,
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
return load_csv(str(path))
|
|
139
139
|
elif ext == ".bin":
|
|
140
|
-
from oscura.loaders.binary import (
|
|
141
|
-
load_binary,
|
|
140
|
+
from oscura.loaders.binary import (
|
|
141
|
+
load_binary,
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
return load_binary(str(path))
|
|
145
145
|
elif ext in (".h5", ".hdf5"):
|
|
146
|
-
from oscura.loaders.hdf5 import (
|
|
147
|
-
load_hdf5,
|
|
146
|
+
from oscura.loaders.hdf5 import (
|
|
147
|
+
load_hdf5,
|
|
148
148
|
)
|
|
149
149
|
|
|
150
150
|
return load_hdf5(str(path))
|
|
@@ -152,7 +152,7 @@ class MultiTraceWorkflow:
|
|
|
152
152
|
raise OscuraError(f"Unsupported format: {ext}")
|
|
153
153
|
|
|
154
154
|
except ImportError as e:
|
|
155
|
-
raise OscuraError(f"Loader not available for {ext}: {e}")
|
|
155
|
+
raise OscuraError(f"Loader not available for {ext}: {e}")
|
|
156
156
|
|
|
157
157
|
def _iter_traces(self, lazy: bool = False) -> Iterator[tuple[str, Any]]:
|
|
158
158
|
"""Iterate over traces.
|
|
@@ -495,7 +495,7 @@ def load_all(pattern: str, lazy: bool = True) -> list[Any]:
|
|
|
495
495
|
Raises:
|
|
496
496
|
OscuraError: If no traces found
|
|
497
497
|
"""
|
|
498
|
-
paths = glob_func(pattern)
|
|
498
|
+
paths = glob_func(pattern)
|
|
499
499
|
if not paths:
|
|
500
500
|
raise OscuraError(f"No files match pattern: {pattern}")
|
|
501
501
|
|
oscura/workflows/power.py
CHANGED
|
@@ -38,12 +38,7 @@ def power_analysis(
|
|
|
38
38
|
) -> dict[str, Any]:
|
|
39
39
|
"""Comprehensive power consumption analysis.
|
|
40
40
|
|
|
41
|
-
Analyzes power consumption from voltage and current measurements
|
|
42
|
-
- Instantaneous power calculation
|
|
43
|
-
- Average, RMS, and peak power
|
|
44
|
-
- Energy consumption
|
|
45
|
-
- Efficiency (if input power provided)
|
|
46
|
-
- Power profile generation
|
|
41
|
+
Analyzes power consumption from voltage and current measurements.
|
|
47
42
|
|
|
48
43
|
Args:
|
|
49
44
|
voltage: Output voltage trace.
|
|
@@ -53,56 +48,47 @@ def power_analysis(
|
|
|
53
48
|
report: Optional path to save HTML report.
|
|
54
49
|
|
|
55
50
|
Returns:
|
|
56
|
-
Dictionary
|
|
57
|
-
|
|
58
|
-
- average_power: Mean power in watts
|
|
59
|
-
- output_power_avg: Average output power (same as average_power)
|
|
60
|
-
- output_power_rms: RMS output power in watts
|
|
61
|
-
- peak_power: Maximum power in watts
|
|
62
|
-
- min_power: Minimum power in watts
|
|
63
|
-
- energy: Total energy in joules
|
|
64
|
-
- duration: Measurement duration in seconds
|
|
65
|
-
- efficiency: Efficiency percentage (if input provided)
|
|
66
|
-
- power_loss: Power loss in watts (if input provided)
|
|
67
|
-
- input_power_avg: Average input power (if input provided)
|
|
51
|
+
Dictionary with power_trace, average_power, output_power_avg, output_power_rms,
|
|
52
|
+
peak_power, min_power, energy, duration, and optionally efficiency, power_loss, input_power_avg.
|
|
68
53
|
|
|
69
54
|
Raises:
|
|
70
|
-
AnalysisError: If traces have incompatible sample rates
|
|
55
|
+
AnalysisError: If traces have incompatible sample rates.
|
|
71
56
|
|
|
72
57
|
Example:
|
|
73
|
-
>>>
|
|
74
|
-
>>> current = osc.load('iout.wfm')
|
|
75
|
-
>>> result = osc.power_analysis(voltage, current)
|
|
58
|
+
>>> result = osc.power_analysis(v_trace, i_trace)
|
|
76
59
|
>>> print(f"Average: {result['average_power']*1e3:.2f} mW")
|
|
77
|
-
>>> print(f"Peak: {result['peak_power']*1e3:.2f} mW")
|
|
78
|
-
>>> print(f"Energy: {result['energy']*1e6:.2f} µJ")
|
|
79
60
|
|
|
80
61
|
References:
|
|
81
|
-
IEC 61000-4-7
|
|
82
|
-
IEEE 1459-2010: Definitions for measurement of electric power
|
|
62
|
+
IEC 61000-4-7, IEEE 1459-2010
|
|
83
63
|
"""
|
|
84
|
-
|
|
85
|
-
from oscura.analyzers.power.basic import (
|
|
86
|
-
instantaneous_power,
|
|
87
|
-
power_statistics,
|
|
88
|
-
)
|
|
64
|
+
from oscura.analyzers.power.basic import instantaneous_power, power_statistics
|
|
89
65
|
|
|
90
|
-
|
|
66
|
+
_validate_sample_rates(voltage, current)
|
|
67
|
+
power_trace = instantaneous_power(voltage, current)
|
|
68
|
+
stats = power_statistics(power_trace)
|
|
69
|
+
|
|
70
|
+
result = _build_power_result(power_trace, stats)
|
|
71
|
+
|
|
72
|
+
if input_voltage is not None and input_current is not None:
|
|
73
|
+
result.update(_calculate_efficiency(input_voltage, input_current, stats["average"]))
|
|
74
|
+
|
|
75
|
+
if report is not None:
|
|
76
|
+
_generate_power_report(result, report)
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _validate_sample_rates(voltage: WaveformTrace, current: WaveformTrace) -> None:
|
|
82
|
+
"""Validate that traces have matching sample rates."""
|
|
91
83
|
if voltage.metadata.sample_rate != current.metadata.sample_rate:
|
|
92
|
-
# Would need interpolation in real implementation
|
|
93
84
|
raise AnalysisError(
|
|
94
|
-
"
|
|
95
|
-
f"Got {voltage.metadata.sample_rate} and {current.metadata.sample_rate}"
|
|
85
|
+
f"Sample rate mismatch: {voltage.metadata.sample_rate} vs {current.metadata.sample_rate}"
|
|
96
86
|
)
|
|
97
87
|
|
|
98
|
-
# Calculate instantaneous power
|
|
99
|
-
power_trace = instantaneous_power(voltage, current)
|
|
100
|
-
|
|
101
|
-
# Calculate power statistics
|
|
102
|
-
stats = power_statistics(power_trace)
|
|
103
88
|
|
|
104
|
-
|
|
105
|
-
result
|
|
89
|
+
def _build_power_result(power_trace: WaveformTrace, stats: dict[str, Any]) -> dict[str, Any]:
|
|
90
|
+
"""Build power analysis result dictionary."""
|
|
91
|
+
return {
|
|
106
92
|
"power_trace": power_trace,
|
|
107
93
|
"average_power": stats["average"],
|
|
108
94
|
"output_power_avg": stats["average"],
|
|
@@ -113,30 +99,24 @@ def power_analysis(
|
|
|
113
99
|
"duration": stats["duration"],
|
|
114
100
|
}
|
|
115
101
|
|
|
116
|
-
# Calculate efficiency if input provided
|
|
117
|
-
if input_voltage is not None and input_current is not None:
|
|
118
|
-
input_power_trace = instantaneous_power(input_voltage, input_current)
|
|
119
|
-
input_stats = power_statistics(input_power_trace)
|
|
120
102
|
|
|
121
|
-
|
|
122
|
-
|
|
103
|
+
def _calculate_efficiency(
|
|
104
|
+
input_voltage: WaveformTrace, input_current: WaveformTrace, output_power_avg: float
|
|
105
|
+
) -> dict[str, float]:
|
|
106
|
+
"""Calculate power efficiency metrics."""
|
|
107
|
+
from oscura.analyzers.power.basic import instantaneous_power, power_statistics
|
|
123
108
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
else:
|
|
128
|
-
efficiency = 0.0
|
|
129
|
-
power_loss = 0.0
|
|
109
|
+
input_power_trace = instantaneous_power(input_voltage, input_current)
|
|
110
|
+
input_stats = power_statistics(input_power_trace)
|
|
111
|
+
input_power_avg = input_stats["average"]
|
|
130
112
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
113
|
+
if input_power_avg > 0:
|
|
114
|
+
efficiency = (output_power_avg / input_power_avg) * 100.0
|
|
115
|
+
power_loss = input_power_avg - output_power_avg
|
|
116
|
+
else:
|
|
117
|
+
efficiency = power_loss = 0.0
|
|
134
118
|
|
|
135
|
-
|
|
136
|
-
if report is not None:
|
|
137
|
-
_generate_power_report(result, report)
|
|
138
|
-
|
|
139
|
-
return result
|
|
119
|
+
return {"efficiency": efficiency, "power_loss": power_loss, "input_power_avg": input_power_avg}
|
|
140
120
|
|
|
141
121
|
|
|
142
122
|
def _generate_power_report(result: dict[str, Any], output_path: str) -> None:
|