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/reporting/engine.py
CHANGED
|
@@ -88,99 +88,115 @@ class AnalysisEngine:
|
|
|
88
88
|
Raises:
|
|
89
89
|
ValueError: If input type cannot be determined.
|
|
90
90
|
"""
|
|
91
|
-
#
|
|
91
|
+
# Try path-based detection first
|
|
92
92
|
if input_path is not None:
|
|
93
|
-
|
|
93
|
+
path_type = self._detect_from_extension(input_path)
|
|
94
|
+
if path_type is not None:
|
|
95
|
+
return path_type
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
97
|
+
# Fallback to data object characteristics
|
|
98
|
+
data_type = self._detect_from_data_object(data)
|
|
99
|
+
if data_type is not None:
|
|
100
|
+
return data_type
|
|
101
|
+
|
|
102
|
+
raise ValueError("Unable to determine input type from path or data characteristics")
|
|
103
|
+
|
|
104
|
+
def _detect_from_extension(self, input_path: Path) -> InputType | None:
|
|
105
|
+
"""Detect input type from file extension."""
|
|
106
|
+
ext = input_path.suffix.lower()
|
|
107
|
+
|
|
108
|
+
# Waveform formats
|
|
109
|
+
if ext in {".wfm", ".csv", ".npz", ".h5", ".hdf5", ".wav", ".tdms"}:
|
|
110
|
+
return InputType.WAVEFORM
|
|
111
|
+
|
|
112
|
+
# Digital formats
|
|
113
|
+
if ext in {".vcd", ".sr"}:
|
|
114
|
+
return InputType.DIGITAL
|
|
115
|
+
|
|
116
|
+
# Packet formats
|
|
117
|
+
if ext in {".pcap", ".pcapng"}:
|
|
118
|
+
return InputType.PCAP
|
|
119
|
+
|
|
120
|
+
# Binary formats
|
|
121
|
+
if ext in {".bin", ".raw"}:
|
|
122
|
+
return InputType.BINARY
|
|
123
|
+
|
|
124
|
+
# S-parameter/Touchstone formats
|
|
125
|
+
if ext in {".s1p", ".s2p", ".s3p", ".s4p", ".s5p", ".s6p", ".s7p", ".s8p"}:
|
|
126
|
+
return InputType.SPARAMS
|
|
127
|
+
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def _detect_from_data_object(self, data: Any) -> InputType | None:
|
|
131
|
+
"""Detect input type from data object characteristics."""
|
|
132
|
+
# SParameterData
|
|
112
133
|
if hasattr(data, "s_matrix") and hasattr(data, "frequencies"):
|
|
113
|
-
# SParameterData
|
|
114
134
|
return InputType.SPARAMS
|
|
115
|
-
|
|
116
|
-
|
|
135
|
+
|
|
136
|
+
# WaveformTrace or DigitalTrace
|
|
137
|
+
if hasattr(data, "data") and hasattr(data, "metadata"):
|
|
117
138
|
if hasattr(data.metadata, "is_digital") and data.metadata.is_digital:
|
|
118
139
|
return InputType.DIGITAL
|
|
119
140
|
return InputType.WAVEFORM
|
|
120
|
-
|
|
141
|
+
|
|
142
|
+
# Raw binary data
|
|
143
|
+
if isinstance(data, bytes | bytearray):
|
|
121
144
|
return InputType.BINARY
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
|
|
146
|
+
# Packet list
|
|
147
|
+
if isinstance(data, list):
|
|
124
148
|
return InputType.PACKETS
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
|
|
150
|
+
# NumPy array (assume waveform)
|
|
151
|
+
if isinstance(data, np.ndarray):
|
|
127
152
|
return InputType.WAVEFORM
|
|
128
153
|
|
|
129
|
-
|
|
154
|
+
return None
|
|
130
155
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
input_path: Path | None = None,
|
|
134
|
-
data: Any = None,
|
|
135
|
-
progress_callback: Callable[[ProgressInfo], None] | None = None,
|
|
136
|
-
) -> dict[str, Any]:
|
|
137
|
-
"""Run comprehensive analysis on input data.
|
|
156
|
+
def _initialize_engine(self, input_path: Path | None) -> None:
|
|
157
|
+
"""Initialize engine state for analysis run.
|
|
138
158
|
|
|
139
159
|
Args:
|
|
140
|
-
input_path:
|
|
141
|
-
data: Input data object (or None to load from input_path).
|
|
142
|
-
progress_callback: Optional callback for progress updates.
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
Dictionary with keys:
|
|
146
|
-
- 'results': Dict mapping AnalysisDomain to analysis results
|
|
147
|
-
- 'errors': List of AnalysisError objects
|
|
148
|
-
- 'stats': Execution statistics dict
|
|
149
|
-
|
|
150
|
-
Raises:
|
|
151
|
-
ValueError: If neither input_path nor data provided.
|
|
152
|
-
FileNotFoundError: If input_path doesn't exist.
|
|
153
|
-
|
|
154
|
-
Example:
|
|
155
|
-
>>> def progress(info: ProgressInfo):
|
|
156
|
-
... print(f"{info.phase}: {info.percent:.1f}%")
|
|
157
|
-
>>> result = engine.run(input_path=Path("data.wfm"), progress_callback=progress)
|
|
160
|
+
input_path: Input file path (or None for in-memory data).
|
|
158
161
|
"""
|
|
159
|
-
if input_path is None and data is None:
|
|
160
|
-
raise ValueError("Must provide either input_path or data")
|
|
161
|
-
|
|
162
162
|
self._start_time = time.time()
|
|
163
163
|
self._input_path = input_path
|
|
164
|
-
|
|
165
|
-
# Initialize argument preparer with input path and default sample rate
|
|
166
164
|
default_sample_rate = self.config.default_sample_rate or 1e6
|
|
167
165
|
self._arg_preparer = ArgumentPreparer(
|
|
168
166
|
input_path=input_path, default_sample_rate=default_sample_rate
|
|
169
167
|
)
|
|
170
168
|
|
|
171
|
-
|
|
169
|
+
def _check_memory_and_adjust(self) -> None:
|
|
170
|
+
"""Check available memory and adjust parallelism if needed."""
|
|
172
171
|
from oscura.core.memory_guard import check_memory_available
|
|
173
172
|
|
|
174
|
-
min_required_mb = 500
|
|
173
|
+
min_required_mb = 500
|
|
175
174
|
if not check_memory_available(min_required_mb):
|
|
176
175
|
logger.warning(
|
|
177
176
|
f"Low memory available (< {min_required_mb} MB). "
|
|
178
177
|
f"Reducing parallel workers to conserve memory."
|
|
179
178
|
)
|
|
180
|
-
# Temporarily reduce parallelism to conserve memory
|
|
181
179
|
self.config.parallel_domains = False
|
|
182
180
|
|
|
183
|
-
|
|
181
|
+
def _load_input_data(
|
|
182
|
+
self,
|
|
183
|
+
input_path: Path | None,
|
|
184
|
+
data: Any,
|
|
185
|
+
progress_callback: Callable[[ProgressInfo], None] | None,
|
|
186
|
+
) -> Any:
|
|
187
|
+
"""Load input data from file or validate in-memory data.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
input_path: Path to input file (or None).
|
|
191
|
+
data: In-memory data (or None).
|
|
192
|
+
progress_callback: Progress callback.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Loaded or provided data.
|
|
196
|
+
|
|
197
|
+
Raises:
|
|
198
|
+
FileNotFoundError: If file not found.
|
|
199
|
+
"""
|
|
184
200
|
if progress_callback:
|
|
185
201
|
progress_callback(
|
|
186
202
|
ProgressInfo(
|
|
@@ -197,15 +213,22 @@ class AnalysisEngine:
|
|
|
197
213
|
if data is None:
|
|
198
214
|
if input_path is None or not input_path.exists():
|
|
199
215
|
raise FileNotFoundError(f"Input file not found: {input_path}")
|
|
200
|
-
|
|
201
|
-
# Load using oscura loaders
|
|
202
216
|
from oscura.loaders import load
|
|
203
217
|
|
|
204
|
-
|
|
218
|
+
return load(input_path)
|
|
219
|
+
return data
|
|
205
220
|
|
|
206
|
-
|
|
207
|
-
|
|
221
|
+
def _report_detection(
|
|
222
|
+
self,
|
|
223
|
+
input_type: InputType,
|
|
224
|
+
progress_callback: Callable[[ProgressInfo], None] | None,
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Report input type detection progress.
|
|
208
227
|
|
|
228
|
+
Args:
|
|
229
|
+
input_type: Detected input type.
|
|
230
|
+
progress_callback: Progress callback.
|
|
231
|
+
"""
|
|
209
232
|
if progress_callback:
|
|
210
233
|
progress_callback(
|
|
211
234
|
ProgressInfo(
|
|
@@ -219,10 +242,21 @@ class AnalysisEngine:
|
|
|
219
242
|
)
|
|
220
243
|
)
|
|
221
244
|
|
|
222
|
-
|
|
223
|
-
|
|
245
|
+
def _plan_analysis_domains(
|
|
246
|
+
self,
|
|
247
|
+
input_type: InputType,
|
|
248
|
+
progress_callback: Callable[[ProgressInfo], None] | None,
|
|
249
|
+
) -> list[AnalysisDomain]:
|
|
250
|
+
"""Determine enabled analysis domains.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
input_type: Input data type.
|
|
254
|
+
progress_callback: Progress callback.
|
|
224
255
|
|
|
225
|
-
|
|
256
|
+
Returns:
|
|
257
|
+
List of enabled domains.
|
|
258
|
+
"""
|
|
259
|
+
applicable_domains = get_available_analyses(input_type)
|
|
226
260
|
enabled_domains = [d for d in applicable_domains if self.config.is_domain_enabled(d)]
|
|
227
261
|
|
|
228
262
|
if progress_callback:
|
|
@@ -237,80 +271,40 @@ class AnalysisEngine:
|
|
|
237
271
|
estimated_remaining_seconds=None,
|
|
238
272
|
)
|
|
239
273
|
)
|
|
274
|
+
return enabled_domains
|
|
240
275
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
276
|
+
def _execute_domains_parallel(
|
|
277
|
+
self,
|
|
278
|
+
enabled_domains: list[AnalysisDomain],
|
|
279
|
+
data: Any,
|
|
280
|
+
progress_callback: Callable[[ProgressInfo], None] | None,
|
|
281
|
+
) -> tuple[dict[AnalysisDomain, dict[str, Any]], list[AnalysisError]]:
|
|
282
|
+
"""Execute analysis domains in parallel.
|
|
246
283
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
284
|
+
Args:
|
|
285
|
+
enabled_domains: List of domains to analyze.
|
|
286
|
+
data: Input data.
|
|
287
|
+
progress_callback: Progress callback.
|
|
250
288
|
|
|
251
|
-
|
|
252
|
-
|
|
289
|
+
Returns:
|
|
290
|
+
Tuple of (results dict, errors list).
|
|
291
|
+
"""
|
|
292
|
+
import concurrent.futures
|
|
253
293
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
for domain in enabled_domains
|
|
259
|
-
}
|
|
294
|
+
results: dict[AnalysisDomain, dict[str, Any]] = {}
|
|
295
|
+
errors: list[AnalysisError] = []
|
|
296
|
+
total_domains = len(enabled_domains)
|
|
297
|
+
max_workers = min(self.config.max_parallel_workers, len(enabled_domains))
|
|
260
298
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if progress_callback:
|
|
267
|
-
progress_callback(
|
|
268
|
-
ProgressInfo(
|
|
269
|
-
phase="analyzing",
|
|
270
|
-
domain=domain,
|
|
271
|
-
function=None,
|
|
272
|
-
percent=domain_percent,
|
|
273
|
-
message=f"Completed domain: {domain.value}",
|
|
274
|
-
elapsed_seconds=time.time() - self._start_time,
|
|
275
|
-
estimated_remaining_seconds=None,
|
|
276
|
-
)
|
|
277
|
-
)
|
|
299
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
300
|
+
futures = {
|
|
301
|
+
executor.submit(self._execute_domain, domain, data): domain
|
|
302
|
+
for domain in enabled_domains
|
|
303
|
+
}
|
|
278
304
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
domain_results, domain_errors = future.result(timeout=timeout_seconds * 10)
|
|
283
|
-
if domain_results:
|
|
284
|
-
results[domain] = domain_results
|
|
285
|
-
errors.extend(domain_errors)
|
|
286
|
-
except concurrent.futures.TimeoutError:
|
|
287
|
-
logger.error(f"Domain {domain.value} exceeded timeout")
|
|
288
|
-
errors.append(
|
|
289
|
-
AnalysisError(
|
|
290
|
-
domain=domain,
|
|
291
|
-
function=f"{domain.value}.*",
|
|
292
|
-
error_type="TimeoutError",
|
|
293
|
-
error_message="Domain execution exceeded timeout",
|
|
294
|
-
traceback=None,
|
|
295
|
-
duration_ms=timeout_seconds * 10 * 1000,
|
|
296
|
-
)
|
|
297
|
-
)
|
|
298
|
-
except Exception as e:
|
|
299
|
-
logger.error(f"Domain {domain.value} failed: {e}")
|
|
300
|
-
errors.append(
|
|
301
|
-
AnalysisError(
|
|
302
|
-
domain=domain,
|
|
303
|
-
function=f"{domain.value}.*",
|
|
304
|
-
error_type=type(e).__name__,
|
|
305
|
-
error_message=str(e),
|
|
306
|
-
traceback=traceback.format_exc(),
|
|
307
|
-
duration_ms=0.0,
|
|
308
|
-
)
|
|
309
|
-
)
|
|
310
|
-
else:
|
|
311
|
-
# Sequential fallback (existing code)
|
|
312
|
-
for idx, domain in enumerate(enabled_domains):
|
|
313
|
-
domain_percent = 10.0 + (idx / total_domains) * 80.0
|
|
305
|
+
for completed, future in enumerate(concurrent.futures.as_completed(futures), 1):
|
|
306
|
+
domain = futures[future]
|
|
307
|
+
domain_percent = 10.0 + (completed / total_domains) * 80.0
|
|
314
308
|
|
|
315
309
|
if progress_callback:
|
|
316
310
|
progress_callback(
|
|
@@ -319,41 +313,134 @@ class AnalysisEngine:
|
|
|
319
313
|
domain=domain,
|
|
320
314
|
function=None,
|
|
321
315
|
percent=domain_percent,
|
|
322
|
-
message=f"
|
|
316
|
+
message=f"Completed domain: {domain.value}",
|
|
323
317
|
elapsed_seconds=time.time() - self._start_time,
|
|
324
318
|
estimated_remaining_seconds=None,
|
|
325
319
|
)
|
|
326
320
|
)
|
|
327
321
|
|
|
328
|
-
|
|
329
|
-
if domain_results:
|
|
330
|
-
results[domain] = domain_results
|
|
331
|
-
errors.extend(domain_errors)
|
|
322
|
+
self._handle_domain_future(future, domain, results, errors)
|
|
332
323
|
|
|
333
|
-
|
|
334
|
-
total_duration = time.time() - self._start_time
|
|
324
|
+
return results, errors
|
|
335
325
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
326
|
+
def _handle_domain_future(
|
|
327
|
+
self,
|
|
328
|
+
future: Any,
|
|
329
|
+
domain: AnalysisDomain,
|
|
330
|
+
results: dict[AnalysisDomain, dict[str, Any]],
|
|
331
|
+
errors: list[AnalysisError],
|
|
332
|
+
) -> None:
|
|
333
|
+
"""Handle completed domain future.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
future: Completed future.
|
|
337
|
+
domain: Domain being analyzed.
|
|
338
|
+
results: Results accumulator.
|
|
339
|
+
errors: Errors accumulator.
|
|
340
|
+
"""
|
|
341
|
+
import concurrent.futures
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
timeout_seconds = self.config.timeout_per_analysis or 30.0
|
|
345
|
+
domain_results, domain_errors = future.result(timeout=timeout_seconds * 10)
|
|
346
|
+
if domain_results:
|
|
347
|
+
results[domain] = domain_results
|
|
348
|
+
errors.extend(domain_errors)
|
|
349
|
+
except concurrent.futures.TimeoutError:
|
|
350
|
+
logger.error(f"Domain {domain.value} exceeded timeout")
|
|
351
|
+
errors.append(
|
|
352
|
+
AnalysisError(
|
|
353
|
+
domain=domain,
|
|
354
|
+
function=f"{domain.value}.*",
|
|
355
|
+
error_type="TimeoutError",
|
|
356
|
+
error_message="Domain execution exceeded timeout",
|
|
357
|
+
traceback=None,
|
|
358
|
+
duration_ms=timeout_seconds * 10 * 1000,
|
|
346
359
|
)
|
|
347
360
|
)
|
|
361
|
+
except Exception as e:
|
|
362
|
+
logger.error(f"Domain {domain.value} failed: {e}")
|
|
363
|
+
errors.append(
|
|
364
|
+
AnalysisError(
|
|
365
|
+
domain=domain,
|
|
366
|
+
function=f"{domain.value}.*",
|
|
367
|
+
error_type=type(e).__name__,
|
|
368
|
+
error_message=str(e),
|
|
369
|
+
traceback=traceback.format_exc(),
|
|
370
|
+
duration_ms=0.0,
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def _execute_domains_sequential(
|
|
375
|
+
self,
|
|
376
|
+
enabled_domains: list[AnalysisDomain],
|
|
377
|
+
data: Any,
|
|
378
|
+
progress_callback: Callable[[ProgressInfo], None] | None,
|
|
379
|
+
) -> tuple[dict[AnalysisDomain, dict[str, Any]], list[AnalysisError]]:
|
|
380
|
+
"""Execute analysis domains sequentially.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
enabled_domains: List of domains to analyze.
|
|
384
|
+
data: Input data.
|
|
385
|
+
progress_callback: Progress callback.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Tuple of (results dict, errors list).
|
|
389
|
+
"""
|
|
390
|
+
results: dict[AnalysisDomain, dict[str, Any]] = {}
|
|
391
|
+
errors: list[AnalysisError] = []
|
|
392
|
+
total_domains = len(enabled_domains)
|
|
393
|
+
|
|
394
|
+
for idx, domain in enumerate(enabled_domains):
|
|
395
|
+
domain_percent = 10.0 + (idx / total_domains) * 80.0
|
|
396
|
+
|
|
397
|
+
if progress_callback:
|
|
398
|
+
progress_callback(
|
|
399
|
+
ProgressInfo(
|
|
400
|
+
phase="analyzing",
|
|
401
|
+
domain=domain,
|
|
402
|
+
function=None,
|
|
403
|
+
percent=domain_percent,
|
|
404
|
+
message=f"Analyzing domain: {domain.value}",
|
|
405
|
+
elapsed_seconds=time.time() - self._start_time,
|
|
406
|
+
estimated_remaining_seconds=None,
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
domain_results, domain_errors = self._execute_domain(domain, data)
|
|
411
|
+
if domain_results:
|
|
412
|
+
results[domain] = domain_results
|
|
413
|
+
errors.extend(domain_errors)
|
|
414
|
+
|
|
415
|
+
return results, errors
|
|
416
|
+
|
|
417
|
+
def _calculate_statistics(
|
|
418
|
+
self,
|
|
419
|
+
results: dict[AnalysisDomain, dict[str, Any]],
|
|
420
|
+
errors: list[AnalysisError],
|
|
421
|
+
enabled_domains: list[AnalysisDomain],
|
|
422
|
+
input_type: InputType,
|
|
423
|
+
total_duration: float,
|
|
424
|
+
) -> dict[str, Any]:
|
|
425
|
+
"""Calculate execution statistics.
|
|
348
426
|
|
|
349
|
-
|
|
427
|
+
Args:
|
|
428
|
+
results: Analysis results.
|
|
429
|
+
errors: Analysis errors.
|
|
430
|
+
enabled_domains: List of enabled domains.
|
|
431
|
+
input_type: Input data type.
|
|
432
|
+
total_duration: Total execution time.
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Statistics dictionary.
|
|
436
|
+
"""
|
|
350
437
|
total_analyses = sum(len(dr) for dr in results.values())
|
|
351
438
|
successful_analyses = sum(
|
|
352
439
|
1 for dr in results.values() for v in dr.values() if not isinstance(v, Exception)
|
|
353
440
|
)
|
|
354
441
|
failed_analyses = len(errors)
|
|
355
442
|
|
|
356
|
-
|
|
443
|
+
return {
|
|
357
444
|
"input_type": input_type.value,
|
|
358
445
|
"total_domains": len(enabled_domains),
|
|
359
446
|
"total_analyses": total_analyses,
|
|
@@ -365,6 +452,73 @@ class AnalysisEngine:
|
|
|
365
452
|
"duration_seconds": total_duration,
|
|
366
453
|
}
|
|
367
454
|
|
|
455
|
+
def run(
|
|
456
|
+
self,
|
|
457
|
+
input_path: Path | None = None,
|
|
458
|
+
data: Any = None,
|
|
459
|
+
progress_callback: Callable[[ProgressInfo], None] | None = None,
|
|
460
|
+
) -> dict[str, Any]:
|
|
461
|
+
"""Run comprehensive analysis on input data.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
input_path: Path to input file (or None for in-memory data).
|
|
465
|
+
data: Input data object (or None to load from input_path).
|
|
466
|
+
progress_callback: Optional callback for progress updates.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Dictionary with keys:
|
|
470
|
+
- 'results': Dict mapping AnalysisDomain to analysis results
|
|
471
|
+
- 'errors': List of AnalysisError objects
|
|
472
|
+
- 'stats': Execution statistics dict
|
|
473
|
+
|
|
474
|
+
Raises:
|
|
475
|
+
ValueError: If neither input_path nor data provided.
|
|
476
|
+
FileNotFoundError: If input_path doesn't exist.
|
|
477
|
+
|
|
478
|
+
Example:
|
|
479
|
+
>>> def progress(info: ProgressInfo):
|
|
480
|
+
... print(f"{info.phase}: {info.percent:.1f}%")
|
|
481
|
+
>>> result = engine.run(input_path=Path("data.wfm"), progress_callback=progress)
|
|
482
|
+
"""
|
|
483
|
+
if input_path is None and data is None:
|
|
484
|
+
raise ValueError("Must provide either input_path or data")
|
|
485
|
+
|
|
486
|
+
self._initialize_engine(input_path)
|
|
487
|
+
self._check_memory_and_adjust()
|
|
488
|
+
data = self._load_input_data(input_path, data, progress_callback)
|
|
489
|
+
input_type = self.detect_input_type(input_path, data)
|
|
490
|
+
self._report_detection(input_type, progress_callback)
|
|
491
|
+
enabled_domains = self._plan_analysis_domains(input_type, progress_callback)
|
|
492
|
+
|
|
493
|
+
# Execute analyses (parallel or sequential)
|
|
494
|
+
if self.config.parallel_domains and len(enabled_domains) > 1:
|
|
495
|
+
results, errors = self._execute_domains_parallel(
|
|
496
|
+
enabled_domains, data, progress_callback
|
|
497
|
+
)
|
|
498
|
+
else:
|
|
499
|
+
results, errors = self._execute_domains_sequential(
|
|
500
|
+
enabled_domains, data, progress_callback
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# Finalize
|
|
504
|
+
total_duration = time.time() - self._start_time
|
|
505
|
+
if progress_callback:
|
|
506
|
+
progress_callback(
|
|
507
|
+
ProgressInfo(
|
|
508
|
+
phase="complete",
|
|
509
|
+
domain=None,
|
|
510
|
+
function=None,
|
|
511
|
+
percent=100.0,
|
|
512
|
+
message="Analysis complete",
|
|
513
|
+
elapsed_seconds=total_duration,
|
|
514
|
+
estimated_remaining_seconds=0.0,
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
stats = self._calculate_statistics(
|
|
519
|
+
results, errors, enabled_domains, input_type, total_duration
|
|
520
|
+
)
|
|
521
|
+
|
|
368
522
|
return {
|
|
369
523
|
"results": results,
|
|
370
524
|
"errors": errors,
|
|
@@ -797,7 +951,7 @@ class AnalysisEngine:
|
|
|
797
951
|
Result with quality score attached (if applicable).
|
|
798
952
|
"""
|
|
799
953
|
try:
|
|
800
|
-
from oscura.quality import score_analysis_result
|
|
954
|
+
from oscura.validation.quality import score_analysis_result
|
|
801
955
|
|
|
802
956
|
# Extract raw data array for quality assessment
|
|
803
957
|
if hasattr(data, "data"):
|