oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ order calculation from specifications.
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Example:
|
|
9
|
-
>>> from oscura.filtering.design import LowPassFilter, design_filter
|
|
9
|
+
>>> from oscura.utils.filtering.design import LowPassFilter, design_filter
|
|
10
10
|
>>> # Simple filter creation
|
|
11
11
|
>>> lpf = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
|
|
12
12
|
>>> # Spec-based design
|
|
@@ -25,12 +25,164 @@ import numpy as np
|
|
|
25
25
|
from scipy import signal
|
|
26
26
|
|
|
27
27
|
from oscura.core.exceptions import AnalysisError
|
|
28
|
-
from oscura.filtering.base import IIRFilter
|
|
28
|
+
from oscura.utils.filtering.base import IIRFilter
|
|
29
29
|
|
|
30
30
|
FilterType = Literal["butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"]
|
|
31
31
|
BandType = Literal["lowpass", "highpass", "bandpass", "bandstop"]
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def _normalize_cutoff(
|
|
35
|
+
cutoff: float | tuple[float, float],
|
|
36
|
+
sample_rate: float,
|
|
37
|
+
analog: bool,
|
|
38
|
+
) -> float | tuple[float, float]:
|
|
39
|
+
"""Normalize cutoff frequency for digital filters.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
cutoff: Cutoff frequency in Hz.
|
|
43
|
+
sample_rate: Sample rate in Hz.
|
|
44
|
+
analog: If True, return as-is (analog filter).
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Normalized cutoff frequency (0-1 for digital, Hz for analog).
|
|
48
|
+
"""
|
|
49
|
+
if analog:
|
|
50
|
+
return cutoff
|
|
51
|
+
|
|
52
|
+
nyquist = sample_rate / 2
|
|
53
|
+
if isinstance(cutoff, tuple):
|
|
54
|
+
return (cutoff[0] / nyquist, cutoff[1] / nyquist)
|
|
55
|
+
else:
|
|
56
|
+
return cutoff / nyquist
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _validate_normalized_cutoff(
|
|
60
|
+
Wn: float | tuple[float, float],
|
|
61
|
+
cutoff: float | tuple[float, float],
|
|
62
|
+
sample_rate: float,
|
|
63
|
+
analog: bool,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Validate normalized cutoff frequency is in valid range.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
Wn: Normalized cutoff frequency.
|
|
69
|
+
cutoff: Original cutoff in Hz.
|
|
70
|
+
sample_rate: Sample rate in Hz.
|
|
71
|
+
analog: If True, skip validation (analog filter).
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
AnalysisError: If cutoff is out of range.
|
|
75
|
+
"""
|
|
76
|
+
if analog:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
nyquist = sample_rate / 2
|
|
80
|
+
if isinstance(Wn, tuple):
|
|
81
|
+
if not (0 < Wn[0] < 1 and 0 < Wn[1] < 1):
|
|
82
|
+
raise AnalysisError(
|
|
83
|
+
f"Normalized cutoff must be in (0, 1), got {Wn}. "
|
|
84
|
+
f"Cutoff {cutoff} Hz must be less than Nyquist {nyquist} Hz."
|
|
85
|
+
)
|
|
86
|
+
elif not 0 < Wn < 1:
|
|
87
|
+
raise AnalysisError(
|
|
88
|
+
f"Normalized cutoff must be in (0, 1), got {Wn}. "
|
|
89
|
+
f"Cutoff {cutoff} Hz must be less than Nyquist {nyquist} Hz."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _design_butterworth(
|
|
94
|
+
order: int,
|
|
95
|
+
Wn: float | tuple[float, float],
|
|
96
|
+
btype: BandType,
|
|
97
|
+
analog: bool,
|
|
98
|
+
output: Literal["sos", "ba"],
|
|
99
|
+
sample_rate: float,
|
|
100
|
+
) -> IIRFilter:
|
|
101
|
+
"""Design Butterworth filter."""
|
|
102
|
+
if output == "sos":
|
|
103
|
+
sos = signal.butter(order, Wn, btype=btype, analog=analog, output="sos")
|
|
104
|
+
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
105
|
+
else:
|
|
106
|
+
b, a = signal.butter(order, Wn, btype=btype, analog=analog, output="ba")
|
|
107
|
+
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _design_chebyshev1(
|
|
111
|
+
order: int,
|
|
112
|
+
ripple_db: float,
|
|
113
|
+
Wn: float | tuple[float, float],
|
|
114
|
+
btype: BandType,
|
|
115
|
+
analog: bool,
|
|
116
|
+
output: Literal["sos", "ba"],
|
|
117
|
+
sample_rate: float,
|
|
118
|
+
) -> IIRFilter:
|
|
119
|
+
"""Design Chebyshev Type I filter."""
|
|
120
|
+
if output == "sos":
|
|
121
|
+
sos = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="sos")
|
|
122
|
+
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
123
|
+
else:
|
|
124
|
+
b, a = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="ba")
|
|
125
|
+
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _design_chebyshev2(
|
|
129
|
+
order: int,
|
|
130
|
+
stopband_atten_db: float,
|
|
131
|
+
Wn: float | tuple[float, float],
|
|
132
|
+
btype: BandType,
|
|
133
|
+
analog: bool,
|
|
134
|
+
output: Literal["sos", "ba"],
|
|
135
|
+
sample_rate: float,
|
|
136
|
+
) -> IIRFilter:
|
|
137
|
+
"""Design Chebyshev Type II filter."""
|
|
138
|
+
if output == "sos":
|
|
139
|
+
sos = signal.cheby2(order, stopband_atten_db, Wn, btype=btype, analog=analog, output="sos")
|
|
140
|
+
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
141
|
+
else:
|
|
142
|
+
b, a = signal.cheby2(order, stopband_atten_db, Wn, btype=btype, analog=analog, output="ba")
|
|
143
|
+
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _design_bessel(
|
|
147
|
+
order: int,
|
|
148
|
+
Wn: float | tuple[float, float],
|
|
149
|
+
btype: BandType,
|
|
150
|
+
analog: bool,
|
|
151
|
+
output: Literal["sos", "ba"],
|
|
152
|
+
sample_rate: float,
|
|
153
|
+
) -> IIRFilter:
|
|
154
|
+
"""Design Bessel filter."""
|
|
155
|
+
if output == "sos":
|
|
156
|
+
sos = signal.bessel(order, Wn, btype=btype, analog=analog, output="sos", norm="phase")
|
|
157
|
+
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
158
|
+
else:
|
|
159
|
+
b, a = signal.bessel(order, Wn, btype=btype, analog=analog, output="ba", norm="phase")
|
|
160
|
+
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _design_elliptic(
|
|
164
|
+
order: int,
|
|
165
|
+
ripple_db: float,
|
|
166
|
+
stopband_atten_db: float,
|
|
167
|
+
Wn: float | tuple[float, float],
|
|
168
|
+
btype: BandType,
|
|
169
|
+
analog: bool,
|
|
170
|
+
output: Literal["sos", "ba"],
|
|
171
|
+
sample_rate: float,
|
|
172
|
+
) -> IIRFilter:
|
|
173
|
+
"""Design Elliptic filter."""
|
|
174
|
+
if output == "sos":
|
|
175
|
+
sos = signal.ellip(
|
|
176
|
+
order, ripple_db, stopband_atten_db, Wn, btype=btype, analog=analog, output="sos"
|
|
177
|
+
)
|
|
178
|
+
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
179
|
+
else:
|
|
180
|
+
b, a = signal.ellip(
|
|
181
|
+
order, ripple_db, stopband_atten_db, Wn, btype=btype, analog=analog, output="ba"
|
|
182
|
+
)
|
|
183
|
+
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
184
|
+
|
|
185
|
+
|
|
34
186
|
def design_filter(
|
|
35
187
|
filter_type: FilterType,
|
|
36
188
|
cutoff: float | tuple[float, float],
|
|
@@ -71,27 +223,11 @@ def design_filter(
|
|
|
71
223
|
References:
|
|
72
224
|
scipy.signal.iirfilter, butter, cheby1, cheby2, ellip, bessel
|
|
73
225
|
"""
|
|
74
|
-
# Normalize cutoff
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
else:
|
|
78
|
-
Wn = cutoff if analog else cutoff / (sample_rate / 2) # type: ignore[assignment]
|
|
79
|
-
|
|
80
|
-
# Validate normalized frequency
|
|
81
|
-
if not analog:
|
|
82
|
-
if isinstance(Wn, tuple):
|
|
83
|
-
if not (0 < Wn[0] < 1 and 0 < Wn[1] < 1):
|
|
84
|
-
raise AnalysisError(
|
|
85
|
-
f"Normalized cutoff must be in (0, 1), got {Wn}. "
|
|
86
|
-
f"Cutoff {cutoff} Hz must be less than Nyquist {sample_rate / 2} Hz."
|
|
87
|
-
)
|
|
88
|
-
elif not 0 < Wn < 1: # type: ignore[unreachable]
|
|
89
|
-
raise AnalysisError(
|
|
90
|
-
f"Normalized cutoff must be in (0, 1), got {Wn}. "
|
|
91
|
-
f"Cutoff {cutoff} Hz must be less than Nyquist {sample_rate / 2} Hz."
|
|
92
|
-
)
|
|
226
|
+
# Normalize and validate cutoff
|
|
227
|
+
Wn = _normalize_cutoff(cutoff, sample_rate, analog)
|
|
228
|
+
_validate_normalized_cutoff(Wn, cutoff, sample_rate, analog)
|
|
93
229
|
|
|
94
|
-
#
|
|
230
|
+
# Validate filter type
|
|
95
231
|
ftype_map = {
|
|
96
232
|
"butterworth": "butter",
|
|
97
233
|
"chebyshev1": "cheby1",
|
|
@@ -99,85 +235,25 @@ def design_filter(
|
|
|
99
235
|
"bessel": "bessel",
|
|
100
236
|
"elliptic": "ellip",
|
|
101
237
|
}
|
|
102
|
-
|
|
103
|
-
if ftype is None:
|
|
238
|
+
if filter_type not in ftype_map:
|
|
104
239
|
raise AnalysisError(f"Unknown filter type: {filter_type}")
|
|
105
240
|
|
|
241
|
+
# Design filter
|
|
106
242
|
try:
|
|
107
243
|
if filter_type == "butterworth":
|
|
108
|
-
|
|
109
|
-
sos = signal.butter(order, Wn, btype=btype, analog=analog, output="sos")
|
|
110
|
-
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
111
|
-
else:
|
|
112
|
-
b, a = signal.butter(order, Wn, btype=btype, analog=analog, output="ba")
|
|
113
|
-
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
114
|
-
|
|
244
|
+
return _design_butterworth(order, Wn, btype, analog, output, sample_rate)
|
|
115
245
|
elif filter_type == "chebyshev1":
|
|
116
|
-
|
|
117
|
-
sos = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="sos")
|
|
118
|
-
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
119
|
-
else:
|
|
120
|
-
b, a = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="ba")
|
|
121
|
-
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
122
|
-
|
|
246
|
+
return _design_chebyshev1(order, ripple_db, Wn, btype, analog, output, sample_rate)
|
|
123
247
|
elif filter_type == "chebyshev2":
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
stopband_atten_db,
|
|
128
|
-
Wn,
|
|
129
|
-
btype=btype,
|
|
130
|
-
analog=analog,
|
|
131
|
-
output="sos",
|
|
132
|
-
)
|
|
133
|
-
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
134
|
-
else:
|
|
135
|
-
b, a = signal.cheby2(
|
|
136
|
-
order,
|
|
137
|
-
stopband_atten_db,
|
|
138
|
-
Wn,
|
|
139
|
-
btype=btype,
|
|
140
|
-
analog=analog,
|
|
141
|
-
output="ba",
|
|
142
|
-
)
|
|
143
|
-
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
144
|
-
|
|
248
|
+
return _design_chebyshev2(
|
|
249
|
+
order, stopband_atten_db, Wn, btype, analog, output, sample_rate
|
|
250
|
+
)
|
|
145
251
|
elif filter_type == "bessel":
|
|
146
|
-
|
|
147
|
-
sos = signal.bessel(
|
|
148
|
-
order, Wn, btype=btype, analog=analog, output="sos", norm="phase"
|
|
149
|
-
)
|
|
150
|
-
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
151
|
-
else:
|
|
152
|
-
b, a = signal.bessel(
|
|
153
|
-
order, Wn, btype=btype, analog=analog, output="ba", norm="phase"
|
|
154
|
-
)
|
|
155
|
-
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
156
|
-
|
|
252
|
+
return _design_bessel(order, Wn, btype, analog, output, sample_rate)
|
|
157
253
|
elif filter_type == "elliptic":
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
ripple_db,
|
|
162
|
-
stopband_atten_db,
|
|
163
|
-
Wn,
|
|
164
|
-
btype=btype,
|
|
165
|
-
analog=analog,
|
|
166
|
-
output="sos",
|
|
167
|
-
)
|
|
168
|
-
return IIRFilter(sample_rate=sample_rate, sos=sos)
|
|
169
|
-
else:
|
|
170
|
-
b, a = signal.ellip(
|
|
171
|
-
order,
|
|
172
|
-
ripple_db,
|
|
173
|
-
stopband_atten_db,
|
|
174
|
-
Wn,
|
|
175
|
-
btype=btype,
|
|
176
|
-
analog=analog,
|
|
177
|
-
output="ba",
|
|
178
|
-
)
|
|
179
|
-
return IIRFilter(sample_rate=sample_rate, ba=(b, a))
|
|
180
|
-
|
|
254
|
+
return _design_elliptic(
|
|
255
|
+
order, ripple_db, stopband_atten_db, Wn, btype, analog, output, sample_rate
|
|
256
|
+
)
|
|
181
257
|
else:
|
|
182
258
|
raise AnalysisError(f"Unsupported filter type: {filter_type}")
|
|
183
259
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Filter convenience functions namespace.
|
|
2
2
|
|
|
3
3
|
This module provides a namespace for filter functions to support:
|
|
4
|
-
from oscura.filtering import filters
|
|
4
|
+
from oscura.utils.filtering import filters
|
|
5
5
|
filters.low_pass(trace, cutoff=1000)
|
|
6
6
|
|
|
7
7
|
Re-exports convenience functions from the filtering package.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from oscura.filtering.convenience import (
|
|
10
|
+
from oscura.utils.filtering.convenience import (
|
|
11
11
|
band_pass,
|
|
12
12
|
band_stop,
|
|
13
13
|
high_pass,
|
|
@@ -5,7 +5,7 @@ step response, and pole-zero diagrams.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.filtering import LowPassFilter, plot_bode
|
|
8
|
+
>>> from oscura.utils.filtering import LowPassFilter, plot_bode
|
|
9
9
|
>>> filt = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
|
|
10
10
|
>>> fig = plot_bode(filt)
|
|
11
11
|
>>> plt.show()
|
|
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
|
|
21
21
|
from matplotlib.figure import Figure
|
|
22
22
|
from numpy.typing import NDArray
|
|
23
23
|
|
|
24
|
-
from oscura.filtering.base import Filter, IIRFilter
|
|
24
|
+
from oscura.utils.filtering.base import Filter, IIRFilter
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class FilterIntrospection:
|
oscura/utils/geometry.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Geometric utility functions for visualization.
|
|
2
|
+
|
|
3
|
+
This module provides geometric calculation utilities used across visualization modules.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_leader_line(
|
|
8
|
+
anchor: tuple[float, float],
|
|
9
|
+
label: tuple[float, float],
|
|
10
|
+
) -> list[tuple[float, float]]:
|
|
11
|
+
"""Generate orthogonal leader line from anchor to label.
|
|
12
|
+
|
|
13
|
+
Creates an L-shaped leader line connecting an anchor point to a label position.
|
|
14
|
+
The line is orthogonal (horizontal then vertical or vice versa).
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
anchor: Anchor point (x, y)
|
|
18
|
+
label: Label position (x, y)
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List of points for leader line [(x1, y1), (x2, y2), ...]
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> generate_leader_line((0, 0), (10, 5))
|
|
25
|
+
[(0, 0), (10, 0), (10, 5)]
|
|
26
|
+
"""
|
|
27
|
+
ax, ay = anchor
|
|
28
|
+
lx, ly = label
|
|
29
|
+
|
|
30
|
+
# Create L-shaped line: horizontal then vertical
|
|
31
|
+
return [(ax, ay), (lx, ay), (lx, ly)]
|
oscura/utils/imports.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Lazy import utilities for optional dependencies.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for handling optional dependencies gracefully,
|
|
4
|
+
with helpful error messages directing users to install missing extras.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MissingOptionalDependency(ImportError):
|
|
11
|
+
"""Raised when an optional dependency is required but not installed."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def require_matplotlib() -> Any:
|
|
15
|
+
"""Import and return matplotlib, or raise helpful error if not installed.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
matplotlib module
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
MissingOptionalDependency: If matplotlib is not installed
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> plt = require_matplotlib().pyplot
|
|
25
|
+
>>> plt.plot([1, 2, 3])
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
import matplotlib
|
|
29
|
+
|
|
30
|
+
return matplotlib
|
|
31
|
+
except ImportError as e:
|
|
32
|
+
raise MissingOptionalDependency(
|
|
33
|
+
"Visualization features require matplotlib.\n\n"
|
|
34
|
+
"Install with:\n"
|
|
35
|
+
" pip install oscura[visualization] # Just matplotlib\n"
|
|
36
|
+
" pip install oscura[standard] # Recommended for most users\n"
|
|
37
|
+
" pip install oscura[all] # Everything\n\n"
|
|
38
|
+
"Or install matplotlib directly:\n"
|
|
39
|
+
" pip install matplotlib\n"
|
|
40
|
+
) from e
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def require_pandas() -> Any:
|
|
44
|
+
"""Import and return pandas, or raise helpful error if not installed.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
pandas module
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
MissingOptionalDependency: If pandas is not installed
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> pd = require_pandas()
|
|
54
|
+
>>> df = pd.DataFrame({'a': [1, 2, 3]})
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
import pandas
|
|
58
|
+
|
|
59
|
+
return pandas
|
|
60
|
+
except ImportError as e:
|
|
61
|
+
raise MissingOptionalDependency(
|
|
62
|
+
"DataFrame features require pandas.\n\n"
|
|
63
|
+
"Install with:\n"
|
|
64
|
+
" pip install oscura[dataframes] # Pandas + Excel export\n"
|
|
65
|
+
" pip install oscura[standard] # Recommended for most users\n"
|
|
66
|
+
" pip install oscura[all] # Everything\n\n"
|
|
67
|
+
"Or install pandas directly:\n"
|
|
68
|
+
" pip install pandas\n"
|
|
69
|
+
) from e
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def require_psutil() -> Any:
|
|
73
|
+
"""Import and return psutil, or raise helpful error if not installed.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
psutil module
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
MissingOptionalDependency: If psutil is not installed
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> psutil = require_psutil()
|
|
83
|
+
>>> psutil.virtual_memory()
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
import psutil
|
|
87
|
+
|
|
88
|
+
return psutil
|
|
89
|
+
except ImportError as e:
|
|
90
|
+
raise MissingOptionalDependency(
|
|
91
|
+
"System monitoring features require psutil.\n\n"
|
|
92
|
+
"Install with:\n"
|
|
93
|
+
" pip install oscura[system] # System utilities\n"
|
|
94
|
+
" pip install oscura[standard] # Recommended for most users\n"
|
|
95
|
+
" pip install oscura[all] # Everything\n\n"
|
|
96
|
+
"Or install psutil directly:\n"
|
|
97
|
+
" pip install psutil\n"
|
|
98
|
+
) from e
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def require_jinja2() -> Any:
|
|
102
|
+
"""Import and return jinja2, or raise helpful error if not installed.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
jinja2 module
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
MissingOptionalDependency: If jinja2 is not installed
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
>>> jinja2 = require_jinja2()
|
|
112
|
+
>>> template = jinja2.Template("Hello {{ name }}")
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
import jinja2
|
|
116
|
+
|
|
117
|
+
return jinja2
|
|
118
|
+
except ImportError as e:
|
|
119
|
+
raise MissingOptionalDependency(
|
|
120
|
+
"Report generation features require jinja2.\n\n"
|
|
121
|
+
"Install with:\n"
|
|
122
|
+
" pip install oscura[reporting] # Report generation\n"
|
|
123
|
+
" pip install oscura[standard] # Recommended for most users\n"
|
|
124
|
+
" pip install oscura[all] # Everything\n\n"
|
|
125
|
+
"Or install jinja2 directly:\n"
|
|
126
|
+
" pip install jinja2\n"
|
|
127
|
+
) from e
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Convenience functions for checking availability without raising errors
|
|
131
|
+
def has_matplotlib() -> bool:
|
|
132
|
+
"""Check if matplotlib is available.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if matplotlib can be imported, False otherwise
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
import matplotlib # noqa: F401
|
|
139
|
+
|
|
140
|
+
return True
|
|
141
|
+
except ImportError:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def has_pandas() -> bool:
|
|
146
|
+
"""Check if pandas is available.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
True if pandas can be imported, False otherwise
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
import pandas # noqa: F401
|
|
153
|
+
|
|
154
|
+
return True
|
|
155
|
+
except ImportError:
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def has_psutil() -> bool:
|
|
160
|
+
"""Check if psutil is available.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
True if psutil can be imported, False otherwise
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
import psutil # noqa: F401
|
|
167
|
+
|
|
168
|
+
return True
|
|
169
|
+
except ImportError:
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def has_jinja2() -> bool:
|
|
174
|
+
"""Check if jinja2 is available.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
True if jinja2 can be imported, False otherwise
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
import jinja2 # noqa: F401
|
|
181
|
+
|
|
182
|
+
return True
|
|
183
|
+
except ImportError:
|
|
184
|
+
return False
|
oscura/utils/lazy.py
CHANGED
|
@@ -120,7 +120,7 @@ class LazyArray(LazyProxy[NDArray[np.floating[Any]]]):
|
|
|
120
120
|
|
|
121
121
|
def shape(self) -> tuple[int, ...]:
|
|
122
122
|
"""Get shape (triggers computation)."""
|
|
123
|
-
return self.compute().shape
|
|
123
|
+
return self.compute().shape
|
|
124
124
|
|
|
125
125
|
def dtype(self) -> np.dtype[Any]:
|
|
126
126
|
"""Get dtype (triggers computation)."""
|
|
@@ -4,7 +4,7 @@ This module provides waveform math operations including arithmetic,
|
|
|
4
4
|
interpolation, and mathematical transformations.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from oscura.math.arithmetic import (
|
|
7
|
+
from oscura.utils.math.arithmetic import (
|
|
8
8
|
absolute,
|
|
9
9
|
add,
|
|
10
10
|
differentiate,
|
|
@@ -17,7 +17,7 @@ from oscura.math.arithmetic import (
|
|
|
17
17
|
scale,
|
|
18
18
|
subtract,
|
|
19
19
|
)
|
|
20
|
-
from oscura.math.interpolation import (
|
|
20
|
+
from oscura.utils.math.interpolation import (
|
|
21
21
|
align_traces,
|
|
22
22
|
downsample,
|
|
23
23
|
interpolate,
|