oscura 0.5.1__py3-none-any.whl → 0.7.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/edges.py +325 -65
- 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/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 +17 -102
- 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/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- 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 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- 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/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 +183 -67
- 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/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- 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/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/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 +1 -1
- 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.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- 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/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 -291
- 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/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/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /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/{schemas → core/schemas}/bus_configuration.json +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.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,7 +5,7 @@ This module provides impedance extraction from Time Domain Reflectometry
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.component import extract_impedance
|
|
8
|
+
>>> from oscura.utils.component import extract_impedance
|
|
9
9
|
>>> z0, z_profile = extract_impedance(tdr_trace)
|
|
10
10
|
|
|
11
11
|
References:
|
|
@@ -124,10 +124,36 @@ def extract_impedance(
|
|
|
124
124
|
References:
|
|
125
125
|
IPC-TM-650 2.5.5.7
|
|
126
126
|
"""
|
|
127
|
-
data = trace
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
data, sample_rate = _prepare_tdr_data(trace)
|
|
128
|
+
velocity_val = _compute_velocity(velocity, velocity_factor)
|
|
129
|
+
time_axis, distance_axis = _create_axes(data, sample_rate, velocity_val)
|
|
130
|
+
start_idx, end_idx = _compute_analysis_window(len(data), sample_rate, start_time, end_time)
|
|
131
|
+
impedance = _compute_impedance_profile(data, z0_source)
|
|
132
|
+
z0, stats = _extract_impedance_statistics(impedance, start_idx, end_idx, distance_axis)
|
|
133
|
+
profile = ImpedanceProfile(
|
|
134
|
+
distance=distance_axis,
|
|
135
|
+
time=time_axis,
|
|
136
|
+
impedance=impedance,
|
|
137
|
+
z0_source=z0_source,
|
|
138
|
+
velocity=velocity_val,
|
|
139
|
+
statistics=stats,
|
|
140
|
+
)
|
|
141
|
+
return z0, profile
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _prepare_tdr_data(trace: WaveformTrace) -> tuple[NDArray[np.float64], float]:
|
|
145
|
+
"""Prepare TDR data for analysis.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
trace: TDR reflection waveform.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Tuple of (data array, sample rate).
|
|
130
152
|
|
|
153
|
+
Raises:
|
|
154
|
+
InsufficientDataError: If trace has fewer than 10 samples.
|
|
155
|
+
"""
|
|
156
|
+
data = trace.data.astype(np.float64)
|
|
131
157
|
if len(data) < 10:
|
|
132
158
|
raise InsufficientDataError(
|
|
133
159
|
"TDR analysis requires at least 10 samples",
|
|
@@ -135,72 +161,110 @@ def extract_impedance(
|
|
|
135
161
|
available=len(data),
|
|
136
162
|
analysis_type="tdr_impedance",
|
|
137
163
|
)
|
|
164
|
+
return data, trace.metadata.sample_rate
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _compute_velocity(velocity: float | None, velocity_factor: float) -> float:
|
|
168
|
+
"""Compute propagation velocity.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
velocity: Explicit velocity or None.
|
|
172
|
+
velocity_factor: Fraction of speed of light.
|
|
138
173
|
|
|
139
|
-
|
|
174
|
+
Returns:
|
|
175
|
+
Propagation velocity in m/s.
|
|
176
|
+
"""
|
|
177
|
+
if velocity is not None:
|
|
178
|
+
return velocity
|
|
140
179
|
c = 299792458.0 # Speed of light in m/s
|
|
141
|
-
|
|
142
|
-
|
|
180
|
+
return c * velocity_factor
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _create_axes(
|
|
184
|
+
data: NDArray[np.float64], sample_rate: float, velocity: float
|
|
185
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
186
|
+
"""Create time and distance axes for TDR.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
data: TDR data array.
|
|
190
|
+
sample_rate: Sample rate in Hz.
|
|
191
|
+
velocity: Propagation velocity in m/s.
|
|
143
192
|
|
|
144
|
-
|
|
193
|
+
Returns:
|
|
194
|
+
Tuple of (time_axis, distance_axis).
|
|
195
|
+
"""
|
|
196
|
+
dt = 1.0 / sample_rate
|
|
145
197
|
time_axis = np.arange(len(data)) * dt
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
# Find the incident step level in TDR data
|
|
161
|
-
# For TDR with a matched load (Z = Z0), the steady-state voltage is V_source/2
|
|
162
|
-
incident_level = _find_incident_level(data)
|
|
198
|
+
distance_axis = velocity * time_axis / 2.0 # Round trip
|
|
199
|
+
return time_axis, distance_axis
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _compute_analysis_window(
|
|
203
|
+
data_len: int, sample_rate: float, start_time: float | None, end_time: float | None
|
|
204
|
+
) -> tuple[int, int]:
|
|
205
|
+
"""Compute analysis window indices.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
data_len: Length of data array.
|
|
209
|
+
sample_rate: Sample rate in Hz.
|
|
210
|
+
start_time: Start time in seconds.
|
|
211
|
+
end_time: End time in seconds.
|
|
163
212
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
213
|
+
Returns:
|
|
214
|
+
Tuple of (start_idx, end_idx).
|
|
215
|
+
"""
|
|
216
|
+
start_idx = 0 if start_time is None else int(start_time * sample_rate)
|
|
217
|
+
end_idx = data_len if end_time is None else int(end_time * sample_rate)
|
|
218
|
+
start_idx = max(0, min(start_idx, data_len - 1))
|
|
219
|
+
end_idx = max(start_idx + 1, min(end_idx, data_len))
|
|
220
|
+
return start_idx, end_idx
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _compute_impedance_profile(data: NDArray[np.float64], z0_source: float) -> NDArray[np.float64]:
|
|
224
|
+
"""Compute impedance profile from TDR data.
|
|
168
225
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
# Fallback: assume data is already normalized
|
|
173
|
-
rho = data - 1.0
|
|
226
|
+
Args:
|
|
227
|
+
data: TDR voltage data.
|
|
228
|
+
z0_source: Source impedance.
|
|
174
229
|
|
|
175
|
-
|
|
176
|
-
|
|
230
|
+
Returns:
|
|
231
|
+
Impedance profile array.
|
|
232
|
+
"""
|
|
233
|
+
incident_level = _find_incident_level(data)
|
|
234
|
+
rho = (data / incident_level) - 1.0 if incident_level > 0 else data - 1.0
|
|
177
235
|
with np.errstate(divide="ignore", invalid="ignore"):
|
|
178
236
|
impedance = z0_source * (1 + rho) / (1 - rho)
|
|
179
|
-
|
|
180
|
-
impedance = np.clip(impedance, 1.0, 10000.0)
|
|
237
|
+
return np.clip(impedance, 1.0, 10000.0)
|
|
181
238
|
|
|
182
|
-
# Extract characteristic impedance from stable region
|
|
183
|
-
stable_region = impedance[start_idx:end_idx]
|
|
184
|
-
z0 = float(np.median(stable_region))
|
|
185
239
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
statistics={
|
|
194
|
-
"z0_measured": z0,
|
|
195
|
-
"z0_std": float(np.std(stable_region)),
|
|
196
|
-
"z0_min": float(np.min(stable_region)),
|
|
197
|
-
"z0_max": float(np.max(stable_region)),
|
|
198
|
-
"analysis_start_m": float(distance_axis[start_idx]),
|
|
199
|
-
"analysis_end_m": float(distance_axis[end_idx - 1]),
|
|
200
|
-
},
|
|
201
|
-
)
|
|
240
|
+
def _extract_impedance_statistics(
|
|
241
|
+
impedance: NDArray[np.float64],
|
|
242
|
+
start_idx: int,
|
|
243
|
+
end_idx: int,
|
|
244
|
+
distance_axis: NDArray[np.float64],
|
|
245
|
+
) -> tuple[float, dict[str, float]]:
|
|
246
|
+
"""Extract impedance statistics from stable region.
|
|
202
247
|
|
|
203
|
-
|
|
248
|
+
Args:
|
|
249
|
+
impedance: Impedance profile.
|
|
250
|
+
start_idx: Start index of analysis window.
|
|
251
|
+
end_idx: End index of analysis window.
|
|
252
|
+
distance_axis: Distance array.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Tuple of (characteristic impedance, statistics dict).
|
|
256
|
+
"""
|
|
257
|
+
stable_region = impedance[start_idx:end_idx]
|
|
258
|
+
z0 = float(np.median(stable_region))
|
|
259
|
+
stats = {
|
|
260
|
+
"z0_measured": z0,
|
|
261
|
+
"z0_std": float(np.std(stable_region)),
|
|
262
|
+
"z0_min": float(np.min(stable_region)),
|
|
263
|
+
"z0_max": float(np.max(stable_region)),
|
|
264
|
+
"analysis_start_m": float(distance_axis[start_idx]),
|
|
265
|
+
"analysis_end_m": float(distance_axis[end_idx - 1]),
|
|
266
|
+
}
|
|
267
|
+
return z0, stats
|
|
204
268
|
|
|
205
269
|
|
|
206
270
|
def impedance_profile(
|
|
@@ -5,7 +5,7 @@ waveform data, including parasitic extraction.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.component import measure_capacitance, measure_inductance
|
|
8
|
+
>>> from oscura.utils.component import measure_capacitance, measure_inductance
|
|
9
9
|
>>> C = measure_capacitance(voltage_trace, current_trace)
|
|
10
10
|
>>> L = measure_inductance(voltage_trace, current_trace)
|
|
11
11
|
|
|
@@ -137,95 +137,95 @@ def measure_capacitance(
|
|
|
137
137
|
)
|
|
138
138
|
|
|
139
139
|
if method == "charge" and current_trace is not None:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
min_len = min(len(voltage), len(current))
|
|
143
|
-
voltage = voltage[:min_len]
|
|
144
|
-
current = current[:min_len]
|
|
145
|
-
|
|
146
|
-
# Integrate current to get charge
|
|
147
|
-
charge = np.cumsum(current) * dt
|
|
148
|
-
delta_v = np.max(voltage) - np.min(voltage)
|
|
149
|
-
|
|
150
|
-
if delta_v > 1e-10:
|
|
151
|
-
delta_q = np.max(charge) - np.min(charge)
|
|
152
|
-
capacitance = delta_q / delta_v
|
|
153
|
-
else:
|
|
154
|
-
raise AnalysisError("Voltage change too small for capacitance measurement")
|
|
155
|
-
|
|
156
|
-
# Estimate ESR from phase relationship
|
|
157
|
-
esr = _estimate_esr(voltage, current, sample_rate)
|
|
158
|
-
|
|
159
|
-
return CapacitanceMeasurement(
|
|
160
|
-
capacitance=float(abs(capacitance)),
|
|
161
|
-
esr=esr,
|
|
162
|
-
method="charge_integration",
|
|
163
|
-
confidence=0.9,
|
|
164
|
-
statistics={
|
|
165
|
-
"delta_v": delta_v,
|
|
166
|
-
"delta_q": delta_q,
|
|
167
|
-
"num_samples": min_len,
|
|
168
|
-
},
|
|
140
|
+
return _measure_capacitance_charge(
|
|
141
|
+
voltage, current_trace.data.astype(np.float64), dt, sample_rate
|
|
169
142
|
)
|
|
170
|
-
|
|
171
143
|
elif method == "slope" and current_trace is not None:
|
|
172
|
-
|
|
173
|
-
current = current_trace.data.astype(np.float64)
|
|
174
|
-
min_len = min(len(voltage), len(current))
|
|
175
|
-
voltage = voltage[:min_len]
|
|
176
|
-
current = current[:min_len]
|
|
177
|
-
|
|
178
|
-
# Calculate dV/dt
|
|
179
|
-
dv_dt = np.diff(voltage) / dt
|
|
180
|
-
|
|
181
|
-
# Find region where dV/dt is significant
|
|
182
|
-
significant_mask = np.abs(dv_dt) > np.max(np.abs(dv_dt)) * 0.1
|
|
183
|
-
if np.sum(significant_mask) < 5:
|
|
184
|
-
raise AnalysisError("Insufficient voltage slope for capacitance measurement")
|
|
185
|
-
|
|
186
|
-
# Use corresponding current values
|
|
187
|
-
current_for_slope = current[:-1][significant_mask]
|
|
188
|
-
dv_dt_significant = dv_dt[significant_mask]
|
|
189
|
-
|
|
190
|
-
# C = I / (dV/dt)
|
|
191
|
-
capacitance_values = current_for_slope / dv_dt_significant
|
|
192
|
-
capacitance = float(np.median(np.abs(capacitance_values)))
|
|
193
|
-
|
|
194
|
-
return CapacitanceMeasurement(
|
|
195
|
-
capacitance=capacitance,
|
|
196
|
-
method="slope",
|
|
197
|
-
confidence=0.85,
|
|
198
|
-
statistics={
|
|
199
|
-
"num_valid_points": int(np.sum(significant_mask)),
|
|
200
|
-
"capacitance_std": float(np.std(np.abs(capacitance_values))),
|
|
201
|
-
},
|
|
202
|
-
)
|
|
203
|
-
|
|
144
|
+
return _measure_capacitance_slope(voltage, current_trace.data.astype(np.float64), dt)
|
|
204
145
|
elif method == "frequency":
|
|
205
|
-
|
|
206
|
-
if resistance is None:
|
|
207
|
-
raise AnalysisError("Resistance value required for frequency method")
|
|
208
|
-
|
|
209
|
-
# Find time constant from step response
|
|
210
|
-
tau = _extract_time_constant(voltage, sample_rate)
|
|
211
|
-
|
|
212
|
-
# C = tau / R
|
|
213
|
-
capacitance = tau / resistance
|
|
214
|
-
|
|
215
|
-
return CapacitanceMeasurement(
|
|
216
|
-
capacitance=float(capacitance),
|
|
217
|
-
method="time_constant",
|
|
218
|
-
confidence=0.8,
|
|
219
|
-
statistics={
|
|
220
|
-
"time_constant": tau,
|
|
221
|
-
"resistance": resistance,
|
|
222
|
-
},
|
|
223
|
-
)
|
|
224
|
-
|
|
146
|
+
return _measure_capacitance_frequency(voltage, sample_rate, resistance)
|
|
225
147
|
else:
|
|
226
148
|
raise AnalysisError(f"Method '{method}' requires current_trace or resistance parameter")
|
|
227
149
|
|
|
228
150
|
|
|
151
|
+
def _measure_capacitance_charge(
|
|
152
|
+
voltage: NDArray[np.float64],
|
|
153
|
+
current: NDArray[np.float64],
|
|
154
|
+
dt: float,
|
|
155
|
+
sample_rate: float,
|
|
156
|
+
) -> CapacitanceMeasurement:
|
|
157
|
+
"""Measure capacitance using charge integration method."""
|
|
158
|
+
min_len = min(len(voltage), len(current))
|
|
159
|
+
voltage, current = voltage[:min_len], current[:min_len]
|
|
160
|
+
|
|
161
|
+
charge = np.cumsum(current) * dt
|
|
162
|
+
delta_v = np.max(voltage) - np.min(voltage)
|
|
163
|
+
|
|
164
|
+
if delta_v <= 1e-10:
|
|
165
|
+
raise AnalysisError("Voltage change too small for capacitance measurement")
|
|
166
|
+
|
|
167
|
+
delta_q = np.max(charge) - np.min(charge)
|
|
168
|
+
capacitance = delta_q / delta_v
|
|
169
|
+
esr = _estimate_esr(voltage, current, sample_rate)
|
|
170
|
+
|
|
171
|
+
return CapacitanceMeasurement(
|
|
172
|
+
capacitance=float(abs(capacitance)),
|
|
173
|
+
esr=esr,
|
|
174
|
+
method="charge_integration",
|
|
175
|
+
confidence=0.9,
|
|
176
|
+
statistics={"delta_v": delta_v, "delta_q": delta_q, "num_samples": min_len},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _measure_capacitance_slope(
|
|
181
|
+
voltage: NDArray[np.float64],
|
|
182
|
+
current: NDArray[np.float64],
|
|
183
|
+
dt: float,
|
|
184
|
+
) -> CapacitanceMeasurement:
|
|
185
|
+
"""Measure capacitance using slope method."""
|
|
186
|
+
min_len = min(len(voltage), len(current))
|
|
187
|
+
voltage, current = voltage[:min_len], current[:min_len]
|
|
188
|
+
|
|
189
|
+
dv_dt = np.diff(voltage) / dt
|
|
190
|
+
significant_mask = np.abs(dv_dt) > np.max(np.abs(dv_dt)) * 0.1
|
|
191
|
+
|
|
192
|
+
if np.sum(significant_mask) < 5:
|
|
193
|
+
raise AnalysisError("Insufficient voltage slope for capacitance measurement")
|
|
194
|
+
|
|
195
|
+
capacitance_values = current[:-1][significant_mask] / dv_dt[significant_mask]
|
|
196
|
+
capacitance = float(np.median(np.abs(capacitance_values)))
|
|
197
|
+
|
|
198
|
+
return CapacitanceMeasurement(
|
|
199
|
+
capacitance=capacitance,
|
|
200
|
+
method="slope",
|
|
201
|
+
confidence=0.85,
|
|
202
|
+
statistics={
|
|
203
|
+
"num_valid_points": int(np.sum(significant_mask)),
|
|
204
|
+
"capacitance_std": float(np.std(np.abs(capacitance_values))),
|
|
205
|
+
},
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _measure_capacitance_frequency(
|
|
210
|
+
voltage: NDArray[np.float64],
|
|
211
|
+
sample_rate: float,
|
|
212
|
+
resistance: float | None,
|
|
213
|
+
) -> CapacitanceMeasurement:
|
|
214
|
+
"""Measure capacitance using time constant method."""
|
|
215
|
+
if resistance is None:
|
|
216
|
+
raise AnalysisError("Resistance value required for frequency method")
|
|
217
|
+
|
|
218
|
+
tau = _extract_time_constant(voltage, sample_rate)
|
|
219
|
+
capacitance = tau / resistance
|
|
220
|
+
|
|
221
|
+
return CapacitanceMeasurement(
|
|
222
|
+
capacitance=float(capacitance),
|
|
223
|
+
method="time_constant",
|
|
224
|
+
confidence=0.8,
|
|
225
|
+
statistics={"time_constant": tau, "resistance": resistance},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
229
|
def measure_inductance(
|
|
230
230
|
voltage_trace: WaveformTrace,
|
|
231
231
|
current_trace: WaveformTrace | None = None,
|
|
@@ -274,95 +274,92 @@ def measure_inductance(
|
|
|
274
274
|
)
|
|
275
275
|
|
|
276
276
|
if method == "flux" and current_trace is not None:
|
|
277
|
-
|
|
278
|
-
current = current_trace.data.astype(np.float64)
|
|
279
|
-
min_len = min(len(voltage), len(current))
|
|
280
|
-
voltage = voltage[:min_len]
|
|
281
|
-
current = current[:min_len]
|
|
282
|
-
|
|
283
|
-
# Integrate voltage to get flux linkage
|
|
284
|
-
flux = np.cumsum(voltage) * dt
|
|
285
|
-
delta_i = np.max(current) - np.min(current)
|
|
286
|
-
|
|
287
|
-
if delta_i > 1e-10:
|
|
288
|
-
delta_flux = np.max(flux) - np.min(flux)
|
|
289
|
-
inductance = delta_flux / delta_i
|
|
290
|
-
else:
|
|
291
|
-
raise AnalysisError("Current change too small for inductance measurement")
|
|
292
|
-
|
|
293
|
-
# Estimate DCR from steady-state
|
|
294
|
-
dcr = _estimate_dcr(voltage, current)
|
|
295
|
-
|
|
296
|
-
return InductanceMeasurement(
|
|
297
|
-
inductance=float(abs(inductance)),
|
|
298
|
-
dcr=dcr,
|
|
299
|
-
method="flux_integration",
|
|
300
|
-
confidence=0.9,
|
|
301
|
-
statistics={
|
|
302
|
-
"delta_i": delta_i,
|
|
303
|
-
"delta_flux": delta_flux,
|
|
304
|
-
"num_samples": min_len,
|
|
305
|
-
},
|
|
306
|
-
)
|
|
307
|
-
|
|
277
|
+
return _measure_inductance_flux(voltage, current_trace.data.astype(np.float64), dt)
|
|
308
278
|
elif method == "slope" and current_trace is not None:
|
|
309
|
-
|
|
310
|
-
current = current_trace.data.astype(np.float64)
|
|
311
|
-
min_len = min(len(voltage), len(current))
|
|
312
|
-
voltage = voltage[:min_len]
|
|
313
|
-
current = current[:min_len]
|
|
314
|
-
|
|
315
|
-
# Calculate dI/dt
|
|
316
|
-
di_dt = np.diff(current) / dt
|
|
317
|
-
|
|
318
|
-
# Find region where dI/dt is significant
|
|
319
|
-
significant_mask = np.abs(di_dt) > np.max(np.abs(di_dt)) * 0.1
|
|
320
|
-
if np.sum(significant_mask) < 5:
|
|
321
|
-
raise AnalysisError("Insufficient current slope for inductance measurement")
|
|
322
|
-
|
|
323
|
-
# Use corresponding voltage values
|
|
324
|
-
voltage_for_slope = voltage[:-1][significant_mask]
|
|
325
|
-
di_dt_significant = di_dt[significant_mask]
|
|
326
|
-
|
|
327
|
-
# L = V / (dI/dt)
|
|
328
|
-
inductance_values = voltage_for_slope / di_dt_significant
|
|
329
|
-
inductance = float(np.median(np.abs(inductance_values)))
|
|
330
|
-
|
|
331
|
-
return InductanceMeasurement(
|
|
332
|
-
inductance=inductance,
|
|
333
|
-
method="slope",
|
|
334
|
-
confidence=0.85,
|
|
335
|
-
statistics={
|
|
336
|
-
"num_valid_points": int(np.sum(significant_mask)),
|
|
337
|
-
"inductance_std": float(np.std(np.abs(inductance_values))),
|
|
338
|
-
},
|
|
339
|
-
)
|
|
340
|
-
|
|
279
|
+
return _measure_inductance_slope(voltage, current_trace.data.astype(np.float64), dt)
|
|
341
280
|
elif method == "frequency":
|
|
342
|
-
|
|
343
|
-
if resistance is None:
|
|
344
|
-
raise AnalysisError("Resistance value required for frequency method")
|
|
345
|
-
|
|
346
|
-
# Find time constant from step response
|
|
347
|
-
tau = _extract_time_constant(voltage, sample_rate)
|
|
348
|
-
|
|
349
|
-
# L = tau * R
|
|
350
|
-
inductance = tau * resistance
|
|
351
|
-
|
|
352
|
-
return InductanceMeasurement(
|
|
353
|
-
inductance=float(inductance),
|
|
354
|
-
method="time_constant",
|
|
355
|
-
confidence=0.8,
|
|
356
|
-
statistics={
|
|
357
|
-
"time_constant": tau,
|
|
358
|
-
"resistance": resistance,
|
|
359
|
-
},
|
|
360
|
-
)
|
|
361
|
-
|
|
281
|
+
return _measure_inductance_frequency(voltage, sample_rate, resistance)
|
|
362
282
|
else:
|
|
363
283
|
raise AnalysisError(f"Method '{method}' requires current_trace or resistance parameter")
|
|
364
284
|
|
|
365
285
|
|
|
286
|
+
def _measure_inductance_flux(
|
|
287
|
+
voltage: NDArray[np.float64],
|
|
288
|
+
current: NDArray[np.float64],
|
|
289
|
+
dt: float,
|
|
290
|
+
) -> InductanceMeasurement:
|
|
291
|
+
"""Measure inductance using flux integration method."""
|
|
292
|
+
min_len = min(len(voltage), len(current))
|
|
293
|
+
voltage, current = voltage[:min_len], current[:min_len]
|
|
294
|
+
|
|
295
|
+
flux = np.cumsum(voltage) * dt
|
|
296
|
+
delta_i = np.max(current) - np.min(current)
|
|
297
|
+
|
|
298
|
+
if delta_i <= 1e-10:
|
|
299
|
+
raise AnalysisError("Current change too small for inductance measurement")
|
|
300
|
+
|
|
301
|
+
delta_flux = np.max(flux) - np.min(flux)
|
|
302
|
+
inductance = delta_flux / delta_i
|
|
303
|
+
dcr = _estimate_dcr(voltage, current)
|
|
304
|
+
|
|
305
|
+
return InductanceMeasurement(
|
|
306
|
+
inductance=float(abs(inductance)),
|
|
307
|
+
dcr=dcr,
|
|
308
|
+
method="flux_integration",
|
|
309
|
+
confidence=0.9,
|
|
310
|
+
statistics={"delta_i": delta_i, "delta_flux": delta_flux, "num_samples": min_len},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _measure_inductance_slope(
|
|
315
|
+
voltage: NDArray[np.float64],
|
|
316
|
+
current: NDArray[np.float64],
|
|
317
|
+
dt: float,
|
|
318
|
+
) -> InductanceMeasurement:
|
|
319
|
+
"""Measure inductance using slope method."""
|
|
320
|
+
min_len = min(len(voltage), len(current))
|
|
321
|
+
voltage, current = voltage[:min_len], current[:min_len]
|
|
322
|
+
|
|
323
|
+
di_dt = np.diff(current) / dt
|
|
324
|
+
significant_mask = np.abs(di_dt) > np.max(np.abs(di_dt)) * 0.1
|
|
325
|
+
|
|
326
|
+
if np.sum(significant_mask) < 5:
|
|
327
|
+
raise AnalysisError("Insufficient current slope for inductance measurement")
|
|
328
|
+
|
|
329
|
+
inductance_values = voltage[:-1][significant_mask] / di_dt[significant_mask]
|
|
330
|
+
inductance = float(np.median(np.abs(inductance_values)))
|
|
331
|
+
|
|
332
|
+
return InductanceMeasurement(
|
|
333
|
+
inductance=inductance,
|
|
334
|
+
method="slope",
|
|
335
|
+
confidence=0.85,
|
|
336
|
+
statistics={
|
|
337
|
+
"num_valid_points": int(np.sum(significant_mask)),
|
|
338
|
+
"inductance_std": float(np.std(np.abs(inductance_values))),
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _measure_inductance_frequency(
|
|
344
|
+
voltage: NDArray[np.float64],
|
|
345
|
+
sample_rate: float,
|
|
346
|
+
resistance: float | None,
|
|
347
|
+
) -> InductanceMeasurement:
|
|
348
|
+
"""Measure inductance using time constant method."""
|
|
349
|
+
if resistance is None:
|
|
350
|
+
raise AnalysisError("Resistance value required for frequency method")
|
|
351
|
+
|
|
352
|
+
tau = _extract_time_constant(voltage, sample_rate)
|
|
353
|
+
inductance = tau * resistance
|
|
354
|
+
|
|
355
|
+
return InductanceMeasurement(
|
|
356
|
+
inductance=float(inductance),
|
|
357
|
+
method="time_constant",
|
|
358
|
+
confidence=0.8,
|
|
359
|
+
statistics={"time_constant": tau, "resistance": resistance},
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
366
363
|
def extract_parasitics(
|
|
367
364
|
voltage_trace: WaveformTrace,
|
|
368
365
|
current_trace: WaveformTrace,
|
|
@@ -530,7 +527,7 @@ def _fit_series_rlc(
|
|
|
530
527
|
# Simple optimization
|
|
531
528
|
from scipy.optimize import minimize
|
|
532
529
|
|
|
533
|
-
def objective(params: NDArray[np.float64]) -> np.floating[Any]:
|
|
530
|
+
def objective(params: NDArray[np.float64]) -> np.floating[Any]:
|
|
534
531
|
R, L, C = params
|
|
535
532
|
Z_model = model(omega, R, L, C)
|
|
536
533
|
return float(np.sum(np.abs(Z - Z_model) ** 2)) # type: ignore[return-value]
|
|
@@ -563,7 +560,7 @@ def _fit_parallel_rlc(
|
|
|
563
560
|
try:
|
|
564
561
|
from scipy.optimize import minimize
|
|
565
562
|
|
|
566
|
-
def objective(params: NDArray[np.float64]) -> np.floating[Any]:
|
|
563
|
+
def objective(params: NDArray[np.float64]) -> np.floating[Any]:
|
|
567
564
|
R, L, C = params
|
|
568
565
|
Y_model = 1 / R + 1j * omega * C + 1 / (1j * omega * L + 1e-20)
|
|
569
566
|
Z_model = 1 / (Y_model + 1e-20)
|
|
@@ -5,7 +5,7 @@ characteristic impedance, propagation delay, and velocity factor.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.component import transmission_line_analysis
|
|
8
|
+
>>> from oscura.utils.component import transmission_line_analysis
|
|
9
9
|
>>> result = transmission_line_analysis(tdr_trace)
|
|
10
10
|
|
|
11
11
|
References:
|
|
@@ -79,7 +79,7 @@ def transmission_line_analysis(
|
|
|
79
79
|
>>> result = transmission_line_analysis(tdr_trace, line_length=0.1)
|
|
80
80
|
>>> print(f"Z0 = {result.z0:.1f} ohms, delay = {result.propagation_delay*1e9:.2f} ns")
|
|
81
81
|
"""
|
|
82
|
-
from oscura.component.impedance import extract_impedance
|
|
82
|
+
from oscura.utils.component.impedance import extract_impedance
|
|
83
83
|
|
|
84
84
|
# Speed of light
|
|
85
85
|
c = 299792458.0
|
|
@@ -162,7 +162,7 @@ def characteristic_impedance(
|
|
|
162
162
|
>>> z0 = characteristic_impedance(tdr_trace)
|
|
163
163
|
>>> print(f"Z0 = {z0:.1f} ohms")
|
|
164
164
|
"""
|
|
165
|
-
from oscura.component.impedance import extract_impedance
|
|
165
|
+
from oscura.utils.component.impedance import extract_impedance
|
|
166
166
|
|
|
167
167
|
z0, _ = extract_impedance(
|
|
168
168
|
trace,
|
|
@@ -6,20 +6,20 @@ Bessel, Elliptic), and convenience filters (moving average, median).
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Example:
|
|
9
|
-
>>> from oscura.filtering import LowPassFilter, design_filter
|
|
9
|
+
>>> from oscura.utils.filtering import LowPassFilter, design_filter
|
|
10
10
|
>>> lpf = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
|
|
11
11
|
>>> filtered_trace = lpf.apply(trace)
|
|
12
12
|
>>> w, h = lpf.get_frequency_response()
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
# Import filters module as namespace for DSL compatibility
|
|
16
|
-
from oscura.filtering import filters
|
|
17
|
-
from oscura.filtering.base import (
|
|
16
|
+
from oscura.utils.filtering import filters
|
|
17
|
+
from oscura.utils.filtering.base import (
|
|
18
18
|
Filter,
|
|
19
19
|
FIRFilter,
|
|
20
20
|
IIRFilter,
|
|
21
21
|
)
|
|
22
|
-
from oscura.filtering.convenience import (
|
|
22
|
+
from oscura.utils.filtering.convenience import (
|
|
23
23
|
band_pass,
|
|
24
24
|
band_stop,
|
|
25
25
|
high_pass,
|
|
@@ -30,7 +30,7 @@ from oscura.filtering.convenience import (
|
|
|
30
30
|
notch_filter,
|
|
31
31
|
savgol_filter,
|
|
32
32
|
)
|
|
33
|
-
from oscura.filtering.design import (
|
|
33
|
+
from oscura.utils.filtering.design import (
|
|
34
34
|
BandPassFilter,
|
|
35
35
|
BandStopFilter,
|
|
36
36
|
BesselFilter,
|
|
@@ -43,7 +43,7 @@ from oscura.filtering.design import (
|
|
|
43
43
|
design_filter,
|
|
44
44
|
design_filter_spec,
|
|
45
45
|
)
|
|
46
|
-
from oscura.filtering.introspection import (
|
|
46
|
+
from oscura.utils.filtering.introspection import (
|
|
47
47
|
FilterIntrospection,
|
|
48
48
|
plot_bode,
|
|
49
49
|
plot_impulse,
|
|
@@ -453,7 +453,7 @@ class FIRFilter(Filter):
|
|
|
453
453
|
# Check symmetry
|
|
454
454
|
symmetric = np.allclose(self._coeffs, self._coeffs[::-1])
|
|
455
455
|
antisymmetric = np.allclose(self._coeffs, -self._coeffs[::-1])
|
|
456
|
-
return symmetric or antisymmetric
|
|
456
|
+
return symmetric or antisymmetric
|
|
457
457
|
|
|
458
458
|
def apply(
|
|
459
459
|
self,
|
|
@@ -5,7 +5,7 @@ moving average, median filter, Savitzky-Golay smoothing, and matched
|
|
|
5
5
|
filtering.
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.filtering.convenience import low_pass, moving_average
|
|
8
|
+
>>> from oscura.utils.filtering.convenience import low_pass, moving_average
|
|
9
9
|
>>> filtered = low_pass(trace, cutoff=1e6)
|
|
10
10
|
>>> smoothed = moving_average(trace, window_size=11)
|
|
11
11
|
"""
|
|
@@ -19,7 +19,7 @@ from scipy import ndimage, signal
|
|
|
19
19
|
|
|
20
20
|
from oscura.core.exceptions import AnalysisError
|
|
21
21
|
from oscura.core.types import WaveformTrace
|
|
22
|
-
from oscura.filtering.design import (
|
|
22
|
+
from oscura.utils.filtering.design import (
|
|
23
23
|
BandPassFilter,
|
|
24
24
|
BandStopFilter,
|
|
25
25
|
HighPassFilter,
|