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,9 +5,9 @@ This module enables efficient pattern matching, anomaly detection, and
|
|
|
5
5
|
context extraction for debugging and analysis workflows.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from oscura.search.anomaly import find_anomalies
|
|
9
|
-
from oscura.search.context import extract_context
|
|
10
|
-
from oscura.search.pattern import find_pattern
|
|
8
|
+
from oscura.utils.search.anomaly import find_anomalies
|
|
9
|
+
from oscura.utils.search.context import extract_context
|
|
10
|
+
from oscura.utils.search.pattern import find_pattern
|
|
11
11
|
|
|
12
12
|
__all__ = [
|
|
13
13
|
"extract_context",
|
|
@@ -94,36 +94,33 @@ def find_anomalies(
|
|
|
94
94
|
if anomaly_type not in valid_types:
|
|
95
95
|
raise ValueError(f"Invalid anomaly_type '{anomaly_type}'. Must be one of: {valid_types}")
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
return _dispatch_anomaly_detection(
|
|
98
|
+
trace, anomaly_type, threshold, min_width, max_width, sample_rate, context_samples
|
|
99
|
+
)
|
|
98
100
|
|
|
101
|
+
|
|
102
|
+
def _dispatch_anomaly_detection(
|
|
103
|
+
trace: NDArray[np.float64],
|
|
104
|
+
anomaly_type: str,
|
|
105
|
+
threshold: float | None,
|
|
106
|
+
min_width: float | None,
|
|
107
|
+
max_width: float | None,
|
|
108
|
+
sample_rate: float | None,
|
|
109
|
+
context_samples: int,
|
|
110
|
+
) -> list[dict[str, Any]]:
|
|
111
|
+
"""Dispatch to appropriate anomaly detection method."""
|
|
99
112
|
if anomaly_type == "glitch":
|
|
100
|
-
|
|
101
|
-
trace,
|
|
102
|
-
threshold=threshold,
|
|
103
|
-
min_width=min_width,
|
|
104
|
-
max_width=max_width,
|
|
105
|
-
sample_rate=sample_rate,
|
|
106
|
-
context_samples=context_samples,
|
|
113
|
+
return _detect_glitches(
|
|
114
|
+
trace, threshold, min_width, max_width, sample_rate, context_samples
|
|
107
115
|
)
|
|
108
116
|
|
|
109
|
-
|
|
117
|
+
if anomaly_type == "timing":
|
|
110
118
|
if sample_rate is None:
|
|
111
119
|
raise ValueError("sample_rate required for timing anomaly detection")
|
|
120
|
+
return _detect_timing_violations(trace, sample_rate, min_width, max_width, context_samples)
|
|
112
121
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
sample_rate=sample_rate,
|
|
116
|
-
min_width=min_width,
|
|
117
|
-
max_width=max_width,
|
|
118
|
-
context_samples=context_samples,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
elif anomaly_type == "protocol":
|
|
122
|
-
# Protocol error detection would integrate with protocol decoders
|
|
123
|
-
# For now, return empty list with note
|
|
124
|
-
anomalies = []
|
|
125
|
-
|
|
126
|
-
return anomalies
|
|
122
|
+
# Protocol error detection would integrate with protocol decoders
|
|
123
|
+
return []
|
|
127
124
|
|
|
128
125
|
|
|
129
126
|
def _detect_glitches(
|
|
@@ -134,70 +131,175 @@ def _detect_glitches(
|
|
|
134
131
|
sample_rate: float | None,
|
|
135
132
|
context_samples: int,
|
|
136
133
|
) -> list[dict[str, Any]]:
|
|
137
|
-
"""Detect voltage glitches using derivative method.
|
|
138
|
-
|
|
134
|
+
"""Detect voltage glitches using derivative method.
|
|
135
|
+
|
|
136
|
+
Uses median absolute deviation (MAD) for robust auto-thresholding,
|
|
137
|
+
groups consecutive derivative spikes, and filters by duration.
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
Example:
|
|
140
|
+
>>> trace = np.array([1.0, 1.0, 5.0, 1.0, 1.0]) # Glitch at index 2
|
|
141
|
+
>>> glitches = _detect_glitches(trace, None, None, None, 1000.0, 5)
|
|
142
|
+
>>> len(glitches) > 0
|
|
143
|
+
True
|
|
144
|
+
"""
|
|
145
|
+
if len(trace) < 2:
|
|
146
|
+
return []
|
|
147
147
|
|
|
148
148
|
# Compute derivative to find rapid changes
|
|
149
|
-
|
|
150
|
-
abs_derivative = np.abs(derivative)
|
|
149
|
+
abs_derivative = np.abs(np.diff(trace))
|
|
151
150
|
|
|
152
|
-
#
|
|
153
|
-
|
|
151
|
+
# Determine threshold
|
|
152
|
+
threshold_value = _compute_glitch_threshold(threshold, abs_derivative)
|
|
154
153
|
|
|
154
|
+
# Find glitch candidate points
|
|
155
|
+
glitch_candidates = np.where(abs_derivative > threshold_value)[0]
|
|
155
156
|
if len(glitch_candidates) == 0:
|
|
156
|
-
return
|
|
157
|
+
return []
|
|
157
158
|
|
|
158
159
|
# Group consecutive points into glitch events
|
|
159
|
-
glitch_groups =
|
|
160
|
-
|
|
160
|
+
glitch_groups = _group_consecutive_indices(glitch_candidates)
|
|
161
|
+
|
|
162
|
+
# Compute baseline once for performance
|
|
163
|
+
baseline = _compute_baseline(trace)
|
|
164
|
+
|
|
165
|
+
# Convert groups to glitch dictionaries
|
|
166
|
+
return _build_glitch_results(
|
|
167
|
+
glitch_groups,
|
|
168
|
+
trace,
|
|
169
|
+
baseline,
|
|
170
|
+
threshold_value,
|
|
171
|
+
min_width,
|
|
172
|
+
max_width,
|
|
173
|
+
sample_rate,
|
|
174
|
+
context_samples,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _compute_glitch_threshold(
|
|
179
|
+
threshold: float | None, abs_derivative: NDArray[np.float64]
|
|
180
|
+
) -> float:
|
|
181
|
+
"""Compute threshold for glitch detection using MAD.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
threshold: User-provided threshold, or None for auto-threshold.
|
|
185
|
+
abs_derivative: Absolute derivative of signal.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Threshold value for glitch detection.
|
|
189
|
+
"""
|
|
190
|
+
if threshold is not None:
|
|
191
|
+
return threshold
|
|
192
|
+
|
|
193
|
+
# Use median absolute deviation (MAD) for robust auto-thresholding
|
|
194
|
+
median_deriv = np.median(abs_derivative)
|
|
195
|
+
mad = np.median(np.abs(abs_derivative - median_deriv))
|
|
196
|
+
|
|
197
|
+
# Convert MAD to equivalent std (1.4826 is the constant for normal distribution)
|
|
198
|
+
if mad > 0:
|
|
199
|
+
return float(median_deriv + 3 * 1.4826 * mad)
|
|
200
|
+
|
|
201
|
+
# Fallback: use 75th percentile to avoid catching glitches in threshold
|
|
202
|
+
p75 = np.percentile(abs_derivative, 75)
|
|
203
|
+
if p75 > 0:
|
|
204
|
+
return float(p75)
|
|
205
|
+
|
|
206
|
+
# Last resort: use any non-zero derivative
|
|
207
|
+
return 0.0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _group_consecutive_indices(indices: NDArray[np.int64]) -> list[list[int]]:
|
|
211
|
+
"""Group consecutive indices into separate lists.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
indices: Sorted array of indices.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
List of groups, where each group contains consecutive indices.
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
>>> indices = np.array([1, 2, 3, 7, 8, 10])
|
|
221
|
+
>>> _group_consecutive_indices(indices)
|
|
222
|
+
[[1, 2, 3], [7, 8], [10]]
|
|
223
|
+
"""
|
|
224
|
+
if len(indices) == 0:
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
groups = []
|
|
228
|
+
current_group = [int(indices[0])]
|
|
161
229
|
|
|
162
|
-
for idx in
|
|
230
|
+
for idx in indices[1:]:
|
|
163
231
|
if idx == current_group[-1] + 1:
|
|
164
|
-
current_group.append(idx)
|
|
232
|
+
current_group.append(int(idx))
|
|
165
233
|
else:
|
|
166
|
-
|
|
167
|
-
current_group = [idx]
|
|
234
|
+
groups.append(current_group)
|
|
235
|
+
current_group = [int(idx)]
|
|
168
236
|
|
|
169
237
|
if current_group:
|
|
170
|
-
|
|
238
|
+
groups.append(current_group)
|
|
239
|
+
|
|
240
|
+
return groups
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _compute_baseline(trace: NDArray[np.float64]) -> float:
|
|
244
|
+
"""Compute baseline value using median.
|
|
171
245
|
|
|
172
|
-
|
|
173
|
-
|
|
246
|
+
For very large arrays (>1M samples), uses percentile approximation
|
|
247
|
+
for performance.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
trace: Signal trace.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Baseline value (median).
|
|
254
|
+
"""
|
|
174
255
|
if len(trace) > 1_000_000:
|
|
175
256
|
# Fast approximation: 50th percentile with linear interpolation
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
257
|
+
return float(np.percentile(trace, 50, method="linear"))
|
|
258
|
+
return float(np.median(trace))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _build_glitch_results(
|
|
262
|
+
glitch_groups: list[list[int]],
|
|
263
|
+
trace: NDArray[np.float64],
|
|
264
|
+
baseline: float,
|
|
265
|
+
threshold_value: float,
|
|
266
|
+
min_width: float | None,
|
|
267
|
+
max_width: float | None,
|
|
268
|
+
sample_rate: float | None,
|
|
269
|
+
context_samples: int,
|
|
270
|
+
) -> list[dict[str, Any]]:
|
|
271
|
+
"""Build glitch result dictionaries from detected groups.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
glitch_groups: Groups of consecutive glitch indices.
|
|
275
|
+
trace: Original signal trace.
|
|
276
|
+
baseline: Signal baseline value.
|
|
277
|
+
threshold_value: Detection threshold.
|
|
278
|
+
min_width: Minimum glitch duration (seconds), or None.
|
|
279
|
+
max_width: Maximum glitch duration (seconds), or None.
|
|
280
|
+
sample_rate: Sample rate (Hz), or None.
|
|
281
|
+
context_samples: Number of context samples to include.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
List of glitch dictionaries with metadata.
|
|
285
|
+
"""
|
|
286
|
+
glitches: list[dict[str, Any]] = []
|
|
179
287
|
|
|
180
|
-
# Filter by width if specified
|
|
181
288
|
for group in glitch_groups:
|
|
182
289
|
start_idx = group[0]
|
|
183
290
|
end_idx = group[-1] + 1
|
|
184
291
|
duration_samples = end_idx - start_idx
|
|
185
292
|
|
|
186
293
|
# Check width constraints
|
|
187
|
-
if
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if min_width is not None and duration_seconds < min_width:
|
|
191
|
-
continue
|
|
192
|
-
if max_width is not None and duration_seconds > max_width:
|
|
193
|
-
continue
|
|
294
|
+
if not _check_width_constraints(duration_samples, min_width, max_width, sample_rate):
|
|
295
|
+
continue
|
|
194
296
|
|
|
195
297
|
# Extract context
|
|
196
298
|
ctx_start = max(0, start_idx - context_samples)
|
|
197
299
|
ctx_end = min(len(trace), end_idx + context_samples)
|
|
198
300
|
context = trace[ctx_start:ctx_end].copy()
|
|
199
301
|
|
|
200
|
-
# Compute amplitude deviation
|
|
302
|
+
# Compute amplitude deviation
|
|
201
303
|
amplitude = np.max(np.abs(trace[start_idx:end_idx] - baseline))
|
|
202
304
|
|
|
203
305
|
# Severity: normalized amplitude
|
|
@@ -218,6 +320,34 @@ def _detect_glitches(
|
|
|
218
320
|
return glitches
|
|
219
321
|
|
|
220
322
|
|
|
323
|
+
def _check_width_constraints(
|
|
324
|
+
duration_samples: int,
|
|
325
|
+
min_width: float | None,
|
|
326
|
+
max_width: float | None,
|
|
327
|
+
sample_rate: float | None,
|
|
328
|
+
) -> bool:
|
|
329
|
+
"""Check if glitch duration meets width constraints.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
duration_samples: Glitch duration in samples.
|
|
333
|
+
min_width: Minimum duration (seconds), or None.
|
|
334
|
+
max_width: Maximum duration (seconds), or None.
|
|
335
|
+
sample_rate: Sample rate (Hz), or None.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
True if glitch meets constraints, False otherwise.
|
|
339
|
+
"""
|
|
340
|
+
if sample_rate is None:
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
duration_seconds = duration_samples / sample_rate
|
|
344
|
+
|
|
345
|
+
if min_width is not None and duration_seconds < min_width:
|
|
346
|
+
return False
|
|
347
|
+
|
|
348
|
+
return not (max_width is not None and duration_seconds > max_width)
|
|
349
|
+
|
|
350
|
+
|
|
221
351
|
def _detect_timing_violations(
|
|
222
352
|
trace: NDArray[np.float64],
|
|
223
353
|
sample_rate: float,
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Context extraction around points of interest.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This module provides efficient extraction of signal context around
|
|
5
|
+
events, maintaining original time references for debugging workflows.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from numpy.typing import NDArray
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_context(
|
|
15
|
+
trace: NDArray[np.float64],
|
|
16
|
+
index: int | list[int] | NDArray[np.int_],
|
|
17
|
+
*,
|
|
18
|
+
before: int = 100,
|
|
19
|
+
after: int = 100,
|
|
20
|
+
sample_rate: float | None = None,
|
|
21
|
+
include_metadata: bool = True,
|
|
22
|
+
) -> dict[str, Any] | list[dict[str, Any]]:
|
|
23
|
+
"""Extract signal context around a point of interest.
|
|
24
|
+
|
|
25
|
+
: Context extraction with time reference preservation.
|
|
26
|
+
Supports batch extraction for multiple indices and optional protocol data.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
trace: Input signal trace
|
|
30
|
+
index: Sample index or list of indices to extract context around.
|
|
31
|
+
Can be int, list of ints, or numpy array.
|
|
32
|
+
before: Number of samples to include before index (default: 100)
|
|
33
|
+
after: Number of samples to include after index (default: 100)
|
|
34
|
+
sample_rate: Optional sample rate in Hz for time calculations
|
|
35
|
+
include_metadata: Include metadata dict with context info (default: True)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
If index is scalar: Single context dictionary
|
|
39
|
+
If index is list/array: List of context dictionaries
|
|
40
|
+
|
|
41
|
+
Each context dictionary contains:
|
|
42
|
+
- data: Extracted sub-trace array
|
|
43
|
+
- start_index: Starting index in original trace
|
|
44
|
+
- end_index: Ending index in original trace
|
|
45
|
+
- center_index: Center index (original query index)
|
|
46
|
+
- time_reference: Time offset if sample_rate provided
|
|
47
|
+
- length: Number of samples in context
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: If index is out of bounds
|
|
51
|
+
ValueError: If before or after are negative
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
>>> # Extract context around a glitch
|
|
55
|
+
>>> trace = np.random.randn(1000)
|
|
56
|
+
>>> glitch_index = 500
|
|
57
|
+
>>> context = extract_context(
|
|
58
|
+
... trace,
|
|
59
|
+
... glitch_index,
|
|
60
|
+
... before=50,
|
|
61
|
+
... after=50,
|
|
62
|
+
... sample_rate=1e6
|
|
63
|
+
... )
|
|
64
|
+
>>> print(f"Context length: {len(context['data'])}")
|
|
65
|
+
>>> print(f"Time reference: {context['time_reference']*1e6:.2f} µs")
|
|
66
|
+
|
|
67
|
+
>>> # Batch extraction for multiple events
|
|
68
|
+
>>> event_indices = [100, 200, 300]
|
|
69
|
+
>>> contexts = extract_context(
|
|
70
|
+
... trace,
|
|
71
|
+
... event_indices,
|
|
72
|
+
... before=25,
|
|
73
|
+
... after=25
|
|
74
|
+
... )
|
|
75
|
+
>>> print(f"Extracted {len(contexts)} contexts")
|
|
76
|
+
|
|
77
|
+
Notes:
|
|
78
|
+
- Handles edge cases at trace boundaries automatically
|
|
79
|
+
- Context may be shorter than before+after at boundaries
|
|
80
|
+
- Time reference is relative to start of extracted context
|
|
81
|
+
- Original trace is not modified
|
|
82
|
+
|
|
83
|
+
References:
|
|
84
|
+
SRCH-003: Context Extraction
|
|
85
|
+
"""
|
|
86
|
+
# Phase 1: Input validation
|
|
87
|
+
_validate_context_params(before, after, trace)
|
|
88
|
+
|
|
89
|
+
# Phase 2: Normalize indices
|
|
90
|
+
indices, return_single = _normalize_indices(index, trace)
|
|
91
|
+
|
|
92
|
+
# Phase 3: Extract contexts
|
|
93
|
+
contexts = [
|
|
94
|
+
_extract_single_context(trace, idx, before, after, sample_rate, include_metadata)
|
|
95
|
+
for idx in indices
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
# Return single context or list
|
|
99
|
+
return contexts[0] if return_single else contexts
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _validate_context_params(before: int, after: int, trace: NDArray[np.float64]) -> None:
|
|
103
|
+
"""Validate context extraction parameters.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
before: Samples before index.
|
|
107
|
+
after: Samples after index.
|
|
108
|
+
trace: Input trace.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValueError: If parameters are invalid.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> trace = np.array([1.0, 2.0, 3.0])
|
|
115
|
+
>>> _validate_context_params(10, 10, trace)
|
|
116
|
+
>>> _validate_context_params(-1, 10, trace)
|
|
117
|
+
Traceback (most recent call last):
|
|
118
|
+
ValueError: before and after must be non-negative
|
|
119
|
+
"""
|
|
120
|
+
if before < 0 or after < 0:
|
|
121
|
+
raise ValueError("before and after must be non-negative")
|
|
122
|
+
|
|
123
|
+
if trace.size == 0:
|
|
124
|
+
raise ValueError("Trace cannot be empty")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _normalize_indices(
|
|
128
|
+
index: int | list[int] | NDArray[np.int_], trace: NDArray[np.float64]
|
|
129
|
+
) -> tuple[list[int], bool]:
|
|
130
|
+
"""Normalize index input to list of integers.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
index: Input index (int, list, or array).
|
|
134
|
+
trace: Trace to validate against.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Tuple of (normalized_indices, return_single_flag).
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ValueError: If any index is out of bounds.
|
|
141
|
+
|
|
142
|
+
Example:
|
|
143
|
+
>>> trace = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
|
|
144
|
+
>>> _normalize_indices(2, trace)
|
|
145
|
+
([2], True)
|
|
146
|
+
>>> _normalize_indices([1, 3], trace)
|
|
147
|
+
([1, 3], False)
|
|
148
|
+
"""
|
|
149
|
+
# Handle single index vs multiple indices
|
|
150
|
+
if isinstance(index, int | np.integer):
|
|
151
|
+
indices = [int(index)]
|
|
152
|
+
return_single = True
|
|
153
|
+
else:
|
|
154
|
+
indices = [int(i) for i in index]
|
|
155
|
+
return_single = False
|
|
156
|
+
|
|
157
|
+
# Validate indices
|
|
158
|
+
for idx in indices:
|
|
159
|
+
if idx < 0 or idx >= len(trace):
|
|
160
|
+
raise ValueError(f"Index {idx} out of bounds for trace of length {len(trace)}")
|
|
161
|
+
|
|
162
|
+
return indices, return_single
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _extract_single_context(
|
|
166
|
+
trace: NDArray[np.float64],
|
|
167
|
+
idx: int,
|
|
168
|
+
before: int,
|
|
169
|
+
after: int,
|
|
170
|
+
sample_rate: float | None,
|
|
171
|
+
include_metadata: bool,
|
|
172
|
+
) -> dict[str, Any]:
|
|
173
|
+
"""Extract context for a single index.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
trace: Input signal trace.
|
|
177
|
+
idx: Center index to extract around.
|
|
178
|
+
before: Samples before index.
|
|
179
|
+
after: Samples after index.
|
|
180
|
+
sample_rate: Optional sample rate.
|
|
181
|
+
include_metadata: Include metadata dict.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Context dictionary with extracted data and metadata.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
>>> trace = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
|
|
188
|
+
>>> ctx = _extract_single_context(trace, 2, 1, 1, None, False)
|
|
189
|
+
>>> ctx['data']
|
|
190
|
+
array([2., 3., 4.])
|
|
191
|
+
"""
|
|
192
|
+
# Calculate window bounds with boundary handling
|
|
193
|
+
start_idx, end_idx = _calculate_window_bounds(idx, before, after, len(trace))
|
|
194
|
+
|
|
195
|
+
# Extract data
|
|
196
|
+
data = trace[start_idx:end_idx].copy()
|
|
197
|
+
|
|
198
|
+
# Build context dictionary
|
|
199
|
+
context: dict[str, Any] = {
|
|
200
|
+
"data": data,
|
|
201
|
+
"start_index": start_idx,
|
|
202
|
+
"end_index": end_idx,
|
|
203
|
+
"center_index": idx,
|
|
204
|
+
"length": len(data),
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Add time information if requested
|
|
208
|
+
if sample_rate is not None:
|
|
209
|
+
_add_time_information(context, start_idx, sample_rate, len(data))
|
|
210
|
+
|
|
211
|
+
# Add metadata if requested
|
|
212
|
+
if include_metadata:
|
|
213
|
+
_add_boundary_metadata(context, idx, start_idx, end_idx, len(trace))
|
|
214
|
+
|
|
215
|
+
return context
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _calculate_window_bounds(idx: int, before: int, after: int, trace_len: int) -> tuple[int, int]:
|
|
219
|
+
"""Calculate window boundaries with edge handling.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
idx: Center index.
|
|
223
|
+
before: Samples before center.
|
|
224
|
+
after: Samples after center.
|
|
225
|
+
trace_len: Length of trace.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Tuple of (start_idx, end_idx).
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
>>> _calculate_window_bounds(50, 10, 10, 100)
|
|
232
|
+
(40, 61)
|
|
233
|
+
>>> _calculate_window_bounds(5, 10, 10, 100)
|
|
234
|
+
(0, 16)
|
|
235
|
+
"""
|
|
236
|
+
start_idx = max(0, idx - before)
|
|
237
|
+
end_idx = min(trace_len, idx + after + 1)
|
|
238
|
+
return start_idx, end_idx
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _add_time_information(
|
|
242
|
+
context: dict[str, Any], start_idx: int, sample_rate: float, data_len: int
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Add time reference information to context.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
context: Context dictionary to update.
|
|
248
|
+
start_idx: Start index in original trace.
|
|
249
|
+
sample_rate: Sample rate in Hz.
|
|
250
|
+
data_len: Length of extracted data.
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> ctx = {}
|
|
254
|
+
>>> _add_time_information(ctx, 100, 1e6, 50)
|
|
255
|
+
>>> ctx['time_reference']
|
|
256
|
+
0.0001
|
|
257
|
+
>>> ctx['sample_rate']
|
|
258
|
+
1000000.0
|
|
259
|
+
"""
|
|
260
|
+
time_offset = start_idx / sample_rate
|
|
261
|
+
context["time_reference"] = time_offset
|
|
262
|
+
context["sample_rate"] = sample_rate
|
|
263
|
+
|
|
264
|
+
# Time array for the context
|
|
265
|
+
dt = 1.0 / sample_rate
|
|
266
|
+
context["time_array"] = np.arange(data_len) * dt + time_offset
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _add_boundary_metadata(
|
|
270
|
+
context: dict[str, Any], idx: int, start_idx: int, end_idx: int, trace_len: int
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Add boundary metadata to context.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
context: Context dictionary to update.
|
|
276
|
+
idx: Center index.
|
|
277
|
+
start_idx: Window start index.
|
|
278
|
+
end_idx: Window end index.
|
|
279
|
+
trace_len: Total trace length.
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
>>> ctx = {}
|
|
283
|
+
>>> _add_boundary_metadata(ctx, 5, 0, 11, 100)
|
|
284
|
+
>>> ctx['metadata']['at_start_boundary']
|
|
285
|
+
True
|
|
286
|
+
>>> ctx['metadata']['samples_before']
|
|
287
|
+
5
|
|
288
|
+
"""
|
|
289
|
+
context["metadata"] = {
|
|
290
|
+
"samples_before": idx - start_idx,
|
|
291
|
+
"samples_after": end_idx - idx - 1,
|
|
292
|
+
"at_start_boundary": start_idx == 0,
|
|
293
|
+
"at_end_boundary": end_idx == trace_len,
|
|
294
|
+
}
|