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
oscura/loaders/tss.py
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""Tektronix Session File (.tss) Loader.
|
|
2
|
+
|
|
3
|
+
This module provides loading functionality for Tektronix session files (.tss),
|
|
4
|
+
which are ZIP archives containing multiple waveform captures, instrument
|
|
5
|
+
configuration, measurements, and annotations.
|
|
6
|
+
|
|
7
|
+
A .tss session file typically contains:
|
|
8
|
+
- Multiple .wfm waveform files (one per channel/capture)
|
|
9
|
+
- session.json: Instrument configuration and setup
|
|
10
|
+
- measurements.json: Stored measurement results (optional)
|
|
11
|
+
- annotations.json: User annotations and markers (optional)
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> import oscura as osc
|
|
15
|
+
>>> trace = osc.load("oscilloscope_session.tss")
|
|
16
|
+
>>> print(f"Channel: {trace.metadata.channel_name}")
|
|
17
|
+
>>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
|
|
18
|
+
|
|
19
|
+
>>> # Load specific channel
|
|
20
|
+
>>> trace = osc.load("session.tss", channel="CH2")
|
|
21
|
+
|
|
22
|
+
>>> # Load all channels
|
|
23
|
+
>>> channels = osc.load_all_channels("session.tss")
|
|
24
|
+
>>> for name, trace in channels.items():
|
|
25
|
+
... print(f"{name}: {len(trace.data)} samples")
|
|
26
|
+
|
|
27
|
+
References:
|
|
28
|
+
Tektronix Programming Manual for Session Files
|
|
29
|
+
TekScope PC Analysis Software Documentation
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import json
|
|
35
|
+
import tempfile
|
|
36
|
+
import zipfile
|
|
37
|
+
from os import PathLike
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
from oscura.core.exceptions import FormatError, LoaderError
|
|
42
|
+
from oscura.core.types import DigitalTrace, IQTrace, WaveformTrace
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_tss(
|
|
46
|
+
path: str | PathLike[str],
|
|
47
|
+
*,
|
|
48
|
+
channel: str | int | None = None,
|
|
49
|
+
) -> WaveformTrace | DigitalTrace | IQTrace:
|
|
50
|
+
"""Load a Tektronix session file (.tss).
|
|
51
|
+
|
|
52
|
+
Tektronix session files are ZIP archives containing multiple waveform
|
|
53
|
+
captures along with instrument configuration and analysis results.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
path: Path to the Tektronix .tss session file.
|
|
57
|
+
channel: Optional channel name or index to load. If None,
|
|
58
|
+
loads the first waveform found (alphabetically).
|
|
59
|
+
String names are case-insensitive (e.g., "ch1", "CH1").
|
|
60
|
+
Integer index is 0-based.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
WaveformTrace, DigitalTrace, or IQTrace containing the channel data.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
LoaderError: If the file cannot be loaded or doesn't exist.
|
|
67
|
+
FormatError: If the file is not a valid Tektronix session.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> # Load first channel (default)
|
|
71
|
+
>>> trace = load_tss("session.tss")
|
|
72
|
+
|
|
73
|
+
>>> # Load specific channel by name
|
|
74
|
+
>>> trace = load_tss("session.tss", channel="CH2")
|
|
75
|
+
|
|
76
|
+
>>> # Load by index
|
|
77
|
+
>>> trace = load_tss("session.tss", channel=1) # Second channel
|
|
78
|
+
"""
|
|
79
|
+
path = Path(path)
|
|
80
|
+
|
|
81
|
+
# Validate file
|
|
82
|
+
_validate_tss_file(path)
|
|
83
|
+
|
|
84
|
+
# Load all waveforms and select requested channel
|
|
85
|
+
waveforms = _load_all_waveforms(path)
|
|
86
|
+
|
|
87
|
+
if not waveforms:
|
|
88
|
+
raise FormatError(
|
|
89
|
+
"No waveforms found in session file",
|
|
90
|
+
file_path=str(path),
|
|
91
|
+
expected="At least one .wfm file in the archive",
|
|
92
|
+
got="Empty session or no waveform files",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Select channel
|
|
96
|
+
trace, channel_name = _select_channel(waveforms, channel, path)
|
|
97
|
+
|
|
98
|
+
# Enrich metadata with session information
|
|
99
|
+
try:
|
|
100
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
101
|
+
session_metadata = _parse_session_metadata(zf, path)
|
|
102
|
+
trace = _enrich_metadata_from_session(trace, session_metadata, str(path))
|
|
103
|
+
except Exception:
|
|
104
|
+
# Session metadata is optional, continue with waveform metadata
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
return trace
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def load_all_channels_tss(
|
|
111
|
+
path: Path,
|
|
112
|
+
) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
|
|
113
|
+
"""Load all channels from a Tektronix session file.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
path: Path to the .tss session file.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dictionary mapping channel names to traces.
|
|
120
|
+
Channel names are derived from .wfm filenames (e.g., "ch1", "ch2").
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
LoaderError: If the file cannot be loaded.
|
|
124
|
+
FormatError: If no waveforms found in session.
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
>>> channels = load_all_channels_tss(Path("session.tss"))
|
|
128
|
+
>>> for name, trace in channels.items():
|
|
129
|
+
... print(f"{name}: {trace.metadata.sample_rate} Hz")
|
|
130
|
+
"""
|
|
131
|
+
# Validate file
|
|
132
|
+
_validate_tss_file(path)
|
|
133
|
+
|
|
134
|
+
# Load all waveforms
|
|
135
|
+
waveforms = _load_all_waveforms(path)
|
|
136
|
+
|
|
137
|
+
if not waveforms:
|
|
138
|
+
raise FormatError(
|
|
139
|
+
"No waveforms found in session file",
|
|
140
|
+
file_path=str(path),
|
|
141
|
+
expected="At least one .wfm file",
|
|
142
|
+
got="Empty archive",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Enrich all traces with session metadata
|
|
146
|
+
try:
|
|
147
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
148
|
+
session_metadata = _parse_session_metadata(zf, path)
|
|
149
|
+
for name in waveforms:
|
|
150
|
+
waveforms[name] = _enrich_metadata_from_session(
|
|
151
|
+
waveforms[name], session_metadata, str(path)
|
|
152
|
+
)
|
|
153
|
+
except Exception:
|
|
154
|
+
# Session metadata is optional
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
return waveforms
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _validate_tss_file(path: Path) -> None:
|
|
161
|
+
"""Validate that file exists and is a valid ZIP archive.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
path: Path to validate.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
LoaderError: If file doesn't exist or can't be read.
|
|
168
|
+
FormatError: If file is not a ZIP archive.
|
|
169
|
+
"""
|
|
170
|
+
if not path.exists():
|
|
171
|
+
raise LoaderError(
|
|
172
|
+
"File not found",
|
|
173
|
+
file_path=str(path),
|
|
174
|
+
fix_hint="Ensure the file path is correct and the file exists.",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if not zipfile.is_zipfile(path):
|
|
178
|
+
raise FormatError(
|
|
179
|
+
"Not a valid ZIP archive",
|
|
180
|
+
file_path=str(path),
|
|
181
|
+
expected="Tektronix session file (.tss) is a ZIP archive",
|
|
182
|
+
got="File is not a ZIP file",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
187
|
+
# Test archive integrity
|
|
188
|
+
bad_file = zf.testzip()
|
|
189
|
+
if bad_file is not None:
|
|
190
|
+
raise FormatError(
|
|
191
|
+
f"Corrupted ZIP archive: {bad_file}",
|
|
192
|
+
file_path=str(path),
|
|
193
|
+
expected="Valid ZIP archive",
|
|
194
|
+
got="Corrupted file detected",
|
|
195
|
+
)
|
|
196
|
+
except zipfile.BadZipFile as e:
|
|
197
|
+
raise FormatError(
|
|
198
|
+
"Corrupted or invalid ZIP archive",
|
|
199
|
+
file_path=str(path),
|
|
200
|
+
details=str(e),
|
|
201
|
+
) from e
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _parse_session_metadata(zf: zipfile.ZipFile, path: Path) -> dict[str, Any]:
|
|
205
|
+
"""Parse session.json metadata from the archive.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
zf: Open ZipFile object.
|
|
209
|
+
path: Path to session file (for error messages).
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dictionary containing session metadata.
|
|
213
|
+
Returns empty dict if session.json not found (non-fatal).
|
|
214
|
+
"""
|
|
215
|
+
# Look for session metadata files
|
|
216
|
+
metadata_files = [
|
|
217
|
+
"session.json",
|
|
218
|
+
"Session.json",
|
|
219
|
+
"metadata.json",
|
|
220
|
+
"Metadata.json",
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
for metadata_file in metadata_files:
|
|
224
|
+
try:
|
|
225
|
+
with zf.open(metadata_file) as f:
|
|
226
|
+
data: dict[str, Any] = json.load(f)
|
|
227
|
+
return data
|
|
228
|
+
except KeyError:
|
|
229
|
+
continue
|
|
230
|
+
except json.JSONDecodeError as e:
|
|
231
|
+
# Non-fatal: log warning and continue
|
|
232
|
+
import warnings
|
|
233
|
+
|
|
234
|
+
warnings.warn(
|
|
235
|
+
f"Failed to parse {metadata_file} in {path.name}: {e}",
|
|
236
|
+
stacklevel=2,
|
|
237
|
+
)
|
|
238
|
+
return {}
|
|
239
|
+
|
|
240
|
+
# Session metadata is optional
|
|
241
|
+
return {}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _extract_waveform_list(zf: zipfile.ZipFile) -> list[str]:
|
|
245
|
+
"""Extract list of .wfm files in the session.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
zf: Open ZipFile object.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
List of .wfm file paths within the archive.
|
|
252
|
+
Excludes macOS metadata files (__MACOSX).
|
|
253
|
+
"""
|
|
254
|
+
return [
|
|
255
|
+
name
|
|
256
|
+
for name in zf.namelist()
|
|
257
|
+
if name.lower().endswith(".wfm") and not name.startswith("__MACOSX")
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _load_wfm_from_archive(
|
|
262
|
+
zf: zipfile.ZipFile,
|
|
263
|
+
wfm_name: str,
|
|
264
|
+
path: Path,
|
|
265
|
+
) -> WaveformTrace | DigitalTrace | IQTrace:
|
|
266
|
+
"""Extract and load a .wfm file from the archive.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
zf: Open ZipFile object.
|
|
270
|
+
wfm_name: Name of .wfm file within archive.
|
|
271
|
+
path: Path to session file (for error messages).
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Loaded trace from the waveform file.
|
|
275
|
+
|
|
276
|
+
Raises:
|
|
277
|
+
LoaderError: If waveform cannot be loaded.
|
|
278
|
+
"""
|
|
279
|
+
try:
|
|
280
|
+
# Extract waveform to temporary file
|
|
281
|
+
# (load_tektronix_wfm expects file path, not bytes)
|
|
282
|
+
wfm_bytes = zf.read(wfm_name)
|
|
283
|
+
|
|
284
|
+
with tempfile.NamedTemporaryFile(suffix=".wfm", delete=True) as tmp:
|
|
285
|
+
tmp.write(wfm_bytes)
|
|
286
|
+
tmp.flush()
|
|
287
|
+
|
|
288
|
+
# Use existing Tektronix loader
|
|
289
|
+
from oscura.loaders.tektronix import load_tektronix_wfm
|
|
290
|
+
|
|
291
|
+
trace = load_tektronix_wfm(tmp.name)
|
|
292
|
+
|
|
293
|
+
return trace
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
raise LoaderError(
|
|
297
|
+
f"Failed to load waveform from session: {wfm_name}",
|
|
298
|
+
file_path=str(path),
|
|
299
|
+
details=str(e),
|
|
300
|
+
fix_hint="Waveform file may be corrupted or incompatible.",
|
|
301
|
+
) from e
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _load_all_waveforms(path: Path) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
|
|
305
|
+
"""Load all waveforms from the session file.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
path: Path to .tss session file.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Dictionary mapping channel names to traces.
|
|
312
|
+
"""
|
|
313
|
+
waveforms: dict[str, WaveformTrace | DigitalTrace | IQTrace] = {}
|
|
314
|
+
|
|
315
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
316
|
+
wfm_files = _extract_waveform_list(zf)
|
|
317
|
+
|
|
318
|
+
for wfm_name in sorted(wfm_files): # Sort for consistent ordering
|
|
319
|
+
# Derive channel name from filename
|
|
320
|
+
channel_name = _derive_channel_name(wfm_name)
|
|
321
|
+
|
|
322
|
+
# Load waveform
|
|
323
|
+
trace = _load_wfm_from_archive(zf, wfm_name, path)
|
|
324
|
+
|
|
325
|
+
# Store with normalized channel name
|
|
326
|
+
waveforms[channel_name] = trace
|
|
327
|
+
|
|
328
|
+
return waveforms
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _derive_channel_name(wfm_filename: str) -> str:
|
|
332
|
+
"""Derive channel name from .wfm filename.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
wfm_filename: Filename like "CH1.wfm", "CH2_Voltage.wfm", etc.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Normalized channel name (lowercase, e.g., "ch1", "ch2", "d0").
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
>>> _derive_channel_name("CH1.wfm")
|
|
342
|
+
'ch1'
|
|
343
|
+
>>> _derive_channel_name("subdir/CH2_Voltage.wfm")
|
|
344
|
+
'ch2'
|
|
345
|
+
>>> _derive_channel_name("D0.wfm")
|
|
346
|
+
'd0'
|
|
347
|
+
>>> _derive_channel_name("MATH1.wfm")
|
|
348
|
+
'math1'
|
|
349
|
+
"""
|
|
350
|
+
# Get base filename without path
|
|
351
|
+
basename = Path(wfm_filename).stem # Remove extension
|
|
352
|
+
|
|
353
|
+
# Remove path components if nested
|
|
354
|
+
basename = basename.split("/")[-1].split("\\")[-1]
|
|
355
|
+
|
|
356
|
+
# Extract channel identifier (first part before underscore)
|
|
357
|
+
channel_id = basename.split("_")[0]
|
|
358
|
+
|
|
359
|
+
# Normalize to lowercase
|
|
360
|
+
return channel_id.lower()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _select_channel(
|
|
364
|
+
waveforms: dict[str, WaveformTrace | DigitalTrace | IQTrace],
|
|
365
|
+
channel: str | int | None,
|
|
366
|
+
path: Path,
|
|
367
|
+
) -> tuple[WaveformTrace | DigitalTrace | IQTrace, str]:
|
|
368
|
+
"""Select specific channel from waveforms dictionary.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
waveforms: Dictionary of channel name to trace.
|
|
372
|
+
channel: Channel selector (name, index, or None for first).
|
|
373
|
+
path: Path to session file (for error messages).
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Tuple of (selected_trace, channel_name).
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
LoaderError: If channel not found or index out of range.
|
|
380
|
+
"""
|
|
381
|
+
if channel is None:
|
|
382
|
+
# Default: first channel (alphabetically sorted)
|
|
383
|
+
channel_name = sorted(waveforms.keys())[0]
|
|
384
|
+
return waveforms[channel_name], channel_name
|
|
385
|
+
|
|
386
|
+
if isinstance(channel, int):
|
|
387
|
+
# Select by index
|
|
388
|
+
channel_names = sorted(waveforms.keys())
|
|
389
|
+
if channel < 0 or channel >= len(channel_names):
|
|
390
|
+
raise LoaderError(
|
|
391
|
+
f"Channel index {channel} out of range",
|
|
392
|
+
file_path=str(path),
|
|
393
|
+
fix_hint=f"Available channels: {', '.join(channel_names)} (indices 0-{len(channel_names) - 1})",
|
|
394
|
+
)
|
|
395
|
+
channel_name = channel_names[channel]
|
|
396
|
+
return waveforms[channel_name], channel_name
|
|
397
|
+
|
|
398
|
+
# Select by name (case-insensitive)
|
|
399
|
+
channel_lower = channel.lower()
|
|
400
|
+
for name, trace in waveforms.items():
|
|
401
|
+
if name.lower() == channel_lower:
|
|
402
|
+
return trace, name
|
|
403
|
+
|
|
404
|
+
# Channel not found
|
|
405
|
+
available = ", ".join(sorted(waveforms.keys()))
|
|
406
|
+
raise LoaderError(
|
|
407
|
+
f"Channel '{channel}' not found in session",
|
|
408
|
+
file_path=str(path),
|
|
409
|
+
fix_hint=f"Available channels: {available}",
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _enrich_metadata_from_session(
|
|
414
|
+
trace: WaveformTrace | DigitalTrace | IQTrace,
|
|
415
|
+
session_metadata: dict[str, Any],
|
|
416
|
+
source_file: str,
|
|
417
|
+
) -> WaveformTrace | DigitalTrace | IQTrace:
|
|
418
|
+
"""Enrich waveform metadata with session-level information.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
trace: Original trace from .wfm file.
|
|
422
|
+
session_metadata: Session metadata from session.json.
|
|
423
|
+
source_file: Path to .tss file (for source_file metadata).
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
Trace with enriched metadata.
|
|
427
|
+
"""
|
|
428
|
+
# Create new metadata with session information
|
|
429
|
+
from dataclasses import replace
|
|
430
|
+
|
|
431
|
+
metadata = trace.metadata
|
|
432
|
+
|
|
433
|
+
# Update source file to point to .tss instead of temp .wfm
|
|
434
|
+
metadata = replace(metadata, source_file=source_file)
|
|
435
|
+
|
|
436
|
+
# Add trigger info from session if available
|
|
437
|
+
if "trigger" in session_metadata and metadata.trigger_info is None:
|
|
438
|
+
metadata = replace(metadata, trigger_info=session_metadata["trigger"])
|
|
439
|
+
|
|
440
|
+
# Return trace with updated metadata
|
|
441
|
+
if isinstance(trace, WaveformTrace):
|
|
442
|
+
return WaveformTrace(data=trace.data, metadata=metadata)
|
|
443
|
+
if isinstance(trace, DigitalTrace):
|
|
444
|
+
return DigitalTrace(data=trace.data, metadata=metadata, edges=trace.edges)
|
|
445
|
+
# IQTrace
|
|
446
|
+
return IQTrace(
|
|
447
|
+
i_data=trace.i_data,
|
|
448
|
+
q_data=trace.q_data,
|
|
449
|
+
metadata=metadata,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
__all__ = [
|
|
454
|
+
"load_all_channels_tss",
|
|
455
|
+
"load_tss",
|
|
456
|
+
]
|