oscura 0.0.1__py3-none-any.whl → 0.1.1__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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""Progressive streaming analysis with incremental confidence.
|
|
2
|
+
|
|
3
|
+
Enables real-time analysis with confidence that increases as more data
|
|
4
|
+
is processed, allowing early stopping when confidence is sufficient.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from collections import deque
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
from ..quality.scoring import assess_data_quality
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from numpy.typing import NDArray
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class StreamingProgress:
|
|
27
|
+
"""Progress update from streaming analysis.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
samples_processed: Number of samples processed so far
|
|
31
|
+
total_samples: Total samples expected (None if unknown)
|
|
32
|
+
confidence: Current confidence level (0-1)
|
|
33
|
+
preliminary_results: Current analysis results
|
|
34
|
+
is_complete: Whether analysis is complete
|
|
35
|
+
can_stop_early: Whether confidence is sufficient for early stopping
|
|
36
|
+
message: Human-readable status message
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
samples_processed: int
|
|
40
|
+
total_samples: int | None
|
|
41
|
+
confidence: float
|
|
42
|
+
preliminary_results: dict[str, Any]
|
|
43
|
+
is_complete: bool = False
|
|
44
|
+
can_stop_early: bool = False
|
|
45
|
+
message: str = ""
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def progress_percent(self) -> float | None:
|
|
49
|
+
"""Get progress as percentage if total is known.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Progress percentage (0-100) or None if total unknown
|
|
53
|
+
"""
|
|
54
|
+
if self.total_samples:
|
|
55
|
+
return 100.0 * self.samples_processed / self.total_samples
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class StreamingConfig:
|
|
61
|
+
"""Configuration for streaming analysis.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
chunk_size: Samples per processing chunk
|
|
65
|
+
overlap: Overlap ratio between chunks (0-1)
|
|
66
|
+
min_samples_for_result: Minimum samples before generating results
|
|
67
|
+
early_stop_confidence: Confidence threshold for early stopping
|
|
68
|
+
max_buffer_size: Maximum buffer size to maintain
|
|
69
|
+
update_interval_samples: Samples between progress updates
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
chunk_size: int = 1024
|
|
73
|
+
overlap: float = 0.25
|
|
74
|
+
min_samples_for_result: int = 100
|
|
75
|
+
early_stop_confidence: float = 0.9
|
|
76
|
+
max_buffer_size: int = 100000
|
|
77
|
+
update_interval_samples: int = 512
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ProgressiveAnalyzer:
|
|
81
|
+
"""Streaming analyzer with progressive confidence updates.
|
|
82
|
+
|
|
83
|
+
Processes data in chunks and provides incremental updates with
|
|
84
|
+
increasing confidence as more data is analyzed. Enables early
|
|
85
|
+
stopping when confidence threshold is met.
|
|
86
|
+
|
|
87
|
+
API-004: Real-time streaming analysis
|
|
88
|
+
QUAL-001: Quality scoring with progressive confidence
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> analyzer = ProgressiveAnalyzer(sample_rate=1000.0)
|
|
92
|
+
>>> analyzer.subscribe(lambda p: print(f"Confidence: {p.confidence:.0%}"))
|
|
93
|
+
>>> progress = analyzer.process_chunk(data_chunk)
|
|
94
|
+
>>> if progress.can_stop_early:
|
|
95
|
+
... final = analyzer.finalize()
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
sample_rate: float = 1.0,
|
|
101
|
+
config: StreamingConfig | None = None,
|
|
102
|
+
):
|
|
103
|
+
"""Initialize progressive analyzer.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
sample_rate: Sample rate in Hz
|
|
107
|
+
config: Streaming configuration
|
|
108
|
+
"""
|
|
109
|
+
self.sample_rate = sample_rate
|
|
110
|
+
self.config = config or StreamingConfig()
|
|
111
|
+
|
|
112
|
+
# Internal state
|
|
113
|
+
self._buffer: deque[float] = deque(maxlen=self.config.max_buffer_size)
|
|
114
|
+
self._samples_processed = 0
|
|
115
|
+
self._current_results: dict[str, Any] = {}
|
|
116
|
+
self._confidence = 0.0
|
|
117
|
+
self._callbacks: list[Callable[[StreamingProgress], None]] = []
|
|
118
|
+
|
|
119
|
+
# Running statistics (incremental calculation)
|
|
120
|
+
self._sum = 0.0
|
|
121
|
+
self._sum_sq = 0.0
|
|
122
|
+
self._min_val = float("inf")
|
|
123
|
+
self._max_val = float("-inf")
|
|
124
|
+
|
|
125
|
+
# Frequency estimation state
|
|
126
|
+
self._zero_crossings: list[int] = []
|
|
127
|
+
self._last_sign = 0
|
|
128
|
+
|
|
129
|
+
def reset(self) -> None:
|
|
130
|
+
"""Reset analyzer state to initial conditions."""
|
|
131
|
+
self._buffer.clear()
|
|
132
|
+
self._samples_processed = 0
|
|
133
|
+
self._current_results = {}
|
|
134
|
+
self._confidence = 0.0
|
|
135
|
+
self._sum = 0.0
|
|
136
|
+
self._sum_sq = 0.0
|
|
137
|
+
self._min_val = float("inf")
|
|
138
|
+
self._max_val = float("-inf")
|
|
139
|
+
self._zero_crossings = []
|
|
140
|
+
self._last_sign = 0
|
|
141
|
+
|
|
142
|
+
def subscribe(self, callback: Callable[[StreamingProgress], None]) -> None:
|
|
143
|
+
"""Subscribe to progress updates.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
callback: Function called with StreamingProgress updates
|
|
147
|
+
"""
|
|
148
|
+
self._callbacks.append(callback)
|
|
149
|
+
|
|
150
|
+
def process_chunk(self, chunk: NDArray[np.float64]) -> StreamingProgress:
|
|
151
|
+
"""Process a chunk of data.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
chunk: Data chunk to process
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
StreamingProgress with current state
|
|
158
|
+
"""
|
|
159
|
+
chunk_arr = np.asarray(chunk).flatten()
|
|
160
|
+
|
|
161
|
+
# Handle empty chunks
|
|
162
|
+
if len(chunk_arr) == 0:
|
|
163
|
+
return StreamingProgress(
|
|
164
|
+
samples_processed=self._samples_processed,
|
|
165
|
+
total_samples=None,
|
|
166
|
+
confidence=self._confidence,
|
|
167
|
+
preliminary_results=self._current_results.copy(),
|
|
168
|
+
is_complete=False,
|
|
169
|
+
can_stop_early=self._confidence >= self.config.early_stop_confidence,
|
|
170
|
+
message=self._get_status_message(),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Add to buffer
|
|
174
|
+
for val in chunk_arr:
|
|
175
|
+
self._buffer.append(val)
|
|
176
|
+
|
|
177
|
+
# Update running statistics
|
|
178
|
+
self._update_statistics(chunk_arr)
|
|
179
|
+
|
|
180
|
+
# Update frequency estimation
|
|
181
|
+
self._update_frequency_estimation(chunk_arr)
|
|
182
|
+
|
|
183
|
+
self._samples_processed += len(chunk_arr)
|
|
184
|
+
|
|
185
|
+
# Calculate current confidence
|
|
186
|
+
self._update_confidence()
|
|
187
|
+
|
|
188
|
+
# Generate preliminary results
|
|
189
|
+
self._update_results()
|
|
190
|
+
|
|
191
|
+
# Create progress update
|
|
192
|
+
progress = StreamingProgress(
|
|
193
|
+
samples_processed=self._samples_processed,
|
|
194
|
+
total_samples=None,
|
|
195
|
+
confidence=self._confidence,
|
|
196
|
+
preliminary_results=self._current_results.copy(),
|
|
197
|
+
is_complete=False,
|
|
198
|
+
can_stop_early=self._confidence >= self.config.early_stop_confidence,
|
|
199
|
+
message=self._get_status_message(),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Notify subscribers
|
|
203
|
+
if self._samples_processed % self.config.update_interval_samples < len(chunk_arr):
|
|
204
|
+
self._notify(progress)
|
|
205
|
+
|
|
206
|
+
return progress
|
|
207
|
+
|
|
208
|
+
def finalize(self) -> StreamingProgress:
|
|
209
|
+
"""Finalize analysis and return final results.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Final StreamingProgress with complete results
|
|
213
|
+
"""
|
|
214
|
+
# Do final calculations with all buffered data
|
|
215
|
+
if self._buffer:
|
|
216
|
+
buffer_array = np.array(list(self._buffer))
|
|
217
|
+
|
|
218
|
+
# Enhanced frequency detection with full buffer
|
|
219
|
+
self._current_results["frequency_final"] = self._detect_frequency_fft(buffer_array)
|
|
220
|
+
|
|
221
|
+
# Final quality assessment
|
|
222
|
+
data_quality = assess_data_quality(buffer_array, self.sample_rate)
|
|
223
|
+
self._current_results["data_quality"] = {
|
|
224
|
+
"snr_db": data_quality.snr_db,
|
|
225
|
+
"sample_count": data_quality.sample_count,
|
|
226
|
+
"completeness": data_quality.completeness,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
self._confidence = min(1.0, self._confidence * 1.1) # Boost for finalization
|
|
230
|
+
|
|
231
|
+
progress = StreamingProgress(
|
|
232
|
+
samples_processed=self._samples_processed,
|
|
233
|
+
total_samples=self._samples_processed,
|
|
234
|
+
confidence=self._confidence,
|
|
235
|
+
preliminary_results=self._current_results.copy(),
|
|
236
|
+
is_complete=True,
|
|
237
|
+
can_stop_early=False,
|
|
238
|
+
message="Analysis complete",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
self._notify(progress)
|
|
242
|
+
return progress
|
|
243
|
+
|
|
244
|
+
def _update_statistics(self, chunk: NDArray[np.float64]) -> None:
|
|
245
|
+
"""Update running statistics with new chunk."""
|
|
246
|
+
self._sum += float(np.sum(chunk))
|
|
247
|
+
self._sum_sq += float(np.sum(chunk**2))
|
|
248
|
+
self._min_val = min(self._min_val, float(np.min(chunk)))
|
|
249
|
+
self._max_val = max(self._max_val, float(np.max(chunk)))
|
|
250
|
+
|
|
251
|
+
def _update_frequency_estimation(self, chunk: NDArray[np.float64]) -> None:
|
|
252
|
+
"""Update frequency estimation from zero crossings."""
|
|
253
|
+
# Track zero crossings
|
|
254
|
+
mean_val = self._sum / max(1, self._samples_processed)
|
|
255
|
+
|
|
256
|
+
for i, val in enumerate(chunk):
|
|
257
|
+
current_sign = 1 if val > mean_val else -1
|
|
258
|
+
if self._last_sign != 0 and current_sign != self._last_sign:
|
|
259
|
+
self._zero_crossings.append(self._samples_processed - len(chunk) + i)
|
|
260
|
+
self._last_sign = current_sign
|
|
261
|
+
|
|
262
|
+
def _update_confidence(self) -> None:
|
|
263
|
+
"""Update confidence based on data processed."""
|
|
264
|
+
# Base confidence from sample count (logarithmic)
|
|
265
|
+
sample_factor = min(1.0, np.log10(max(1, self._samples_processed)) / 4.0)
|
|
266
|
+
|
|
267
|
+
# Frequency confidence from consistent zero crossings
|
|
268
|
+
freq_factor = 0.5
|
|
269
|
+
if len(self._zero_crossings) > 10:
|
|
270
|
+
intervals = np.diff(self._zero_crossings)
|
|
271
|
+
if len(intervals) > 5:
|
|
272
|
+
cv = np.std(intervals) / (np.mean(intervals) + 1e-10)
|
|
273
|
+
freq_factor = min(1.0, 1.0 - cv)
|
|
274
|
+
|
|
275
|
+
# Combine factors - ensure monotonic increase by taking max with previous
|
|
276
|
+
new_confidence = 0.4 * sample_factor + 0.4 * freq_factor + 0.2
|
|
277
|
+
self._confidence = max(self._confidence, new_confidence)
|
|
278
|
+
|
|
279
|
+
def _update_results(self) -> None:
|
|
280
|
+
"""Update preliminary results."""
|
|
281
|
+
n = self._samples_processed
|
|
282
|
+
if n < self.config.min_samples_for_result:
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
# Running mean and std
|
|
286
|
+
mean = self._sum / n
|
|
287
|
+
variance = (self._sum_sq / n) - (mean**2)
|
|
288
|
+
std = np.sqrt(max(0, variance))
|
|
289
|
+
|
|
290
|
+
self._current_results.update(
|
|
291
|
+
{
|
|
292
|
+
"mean": mean,
|
|
293
|
+
"std": std,
|
|
294
|
+
"min": self._min_val,
|
|
295
|
+
"max": self._max_val,
|
|
296
|
+
"amplitude": self._max_val - self._min_val,
|
|
297
|
+
"sample_count": n,
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Frequency from zero crossings
|
|
302
|
+
if len(self._zero_crossings) > 4:
|
|
303
|
+
intervals = np.diff(self._zero_crossings)
|
|
304
|
+
avg_half_period = np.mean(intervals) / self.sample_rate
|
|
305
|
+
if avg_half_period > 0:
|
|
306
|
+
self._current_results["frequency_estimate"] = 0.5 / avg_half_period
|
|
307
|
+
|
|
308
|
+
def _detect_frequency_fft(self, data: NDArray[np.float64]) -> float | None:
|
|
309
|
+
"""Detect frequency using FFT on full buffer.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
data: Signal data
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Dominant frequency in Hz or None if detection failed
|
|
316
|
+
"""
|
|
317
|
+
try:
|
|
318
|
+
data_ac = data - np.mean(data)
|
|
319
|
+
fft_result = np.fft.rfft(data_ac)
|
|
320
|
+
freqs = np.fft.rfftfreq(len(data_ac), d=1.0 / self.sample_rate)
|
|
321
|
+
magnitude = np.abs(fft_result[1:])
|
|
322
|
+
|
|
323
|
+
if len(magnitude) > 0:
|
|
324
|
+
peak_idx = np.argmax(magnitude)
|
|
325
|
+
return float(freqs[1:][peak_idx])
|
|
326
|
+
except Exception:
|
|
327
|
+
pass
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
def _get_status_message(self) -> str:
|
|
331
|
+
"""Generate status message.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Human-readable status string
|
|
335
|
+
"""
|
|
336
|
+
if self._samples_processed < self.config.min_samples_for_result:
|
|
337
|
+
return f"Collecting data... ({self._samples_processed} samples)"
|
|
338
|
+
|
|
339
|
+
if self._confidence < 0.5:
|
|
340
|
+
return f"Low confidence ({self._confidence:.0%}), collecting more data..."
|
|
341
|
+
elif self._confidence < 0.8:
|
|
342
|
+
return f"Medium confidence ({self._confidence:.0%}), analysis in progress"
|
|
343
|
+
else:
|
|
344
|
+
return f"High confidence ({self._confidence:.0%}), results reliable"
|
|
345
|
+
|
|
346
|
+
def _notify(self, progress: StreamingProgress) -> None:
|
|
347
|
+
"""Notify all subscribers.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
progress: Progress update to send
|
|
351
|
+
"""
|
|
352
|
+
for callback in self._callbacks:
|
|
353
|
+
try:
|
|
354
|
+
callback(progress)
|
|
355
|
+
except Exception as e:
|
|
356
|
+
logger.debug(f"Callback error: {e}")
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def create_progressive_analyzer(
|
|
360
|
+
sample_rate: float = 1.0,
|
|
361
|
+
chunk_size: int = 1024,
|
|
362
|
+
early_stop_confidence: float = 0.9,
|
|
363
|
+
) -> ProgressiveAnalyzer:
|
|
364
|
+
"""Create a progressive analyzer with common settings.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
sample_rate: Sample rate in Hz
|
|
368
|
+
chunk_size: Samples per chunk
|
|
369
|
+
early_stop_confidence: Confidence threshold for early stopping
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Configured ProgressiveAnalyzer
|
|
373
|
+
|
|
374
|
+
Example:
|
|
375
|
+
>>> analyzer = create_progressive_analyzer(
|
|
376
|
+
... sample_rate=1000.0,
|
|
377
|
+
... chunk_size=512,
|
|
378
|
+
... early_stop_confidence=0.85
|
|
379
|
+
... )
|
|
380
|
+
"""
|
|
381
|
+
config = StreamingConfig(
|
|
382
|
+
chunk_size=chunk_size,
|
|
383
|
+
early_stop_confidence=early_stop_confidence,
|
|
384
|
+
)
|
|
385
|
+
return ProgressiveAnalyzer(sample_rate, config)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
__all__ = [
|
|
389
|
+
"ProgressiveAnalyzer",
|
|
390
|
+
"StreamingConfig",
|
|
391
|
+
"StreamingProgress",
|
|
392
|
+
"create_progressive_analyzer",
|
|
393
|
+
]
|