oscura 0.0.1__py3-none-any.whl → 0.1.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 +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.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""Automated chart type selection for TraceKit reports.
|
|
2
|
+
|
|
3
|
+
This module provides intelligent chart type selection based on data
|
|
4
|
+
characteristics to optimize data visualization in reports.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.reporting import auto_select_chart
|
|
9
|
+
>>> chart_type = auto_select_chart("time_series", (1000, 2))
|
|
10
|
+
>>> print(chart_type) # "line"
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Literal
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from numpy.typing import NDArray
|
|
21
|
+
|
|
22
|
+
ChartType = Literal["line", "scatter", "bar", "histogram", "heatmap", "pie", "spectrum"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def auto_select_chart(
|
|
26
|
+
data_type: str,
|
|
27
|
+
data_shape: tuple[int, ...],
|
|
28
|
+
*,
|
|
29
|
+
data: NDArray[np.float64] | None = None,
|
|
30
|
+
) -> ChartType:
|
|
31
|
+
"""Automatically select appropriate chart type based on data characteristics.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
data_type: Type of data - one of:
|
|
35
|
+
- "time_series": Time-domain waveform data
|
|
36
|
+
- "frequency": Frequency-domain spectral data
|
|
37
|
+
- "distribution": Statistical distribution data
|
|
38
|
+
- "comparison": Comparative measurements
|
|
39
|
+
- "correlation": Correlation or scatter data
|
|
40
|
+
- "categorical": Categorical comparison data
|
|
41
|
+
- "matrix": 2D matrix data
|
|
42
|
+
- "parts": Part-to-whole relationships
|
|
43
|
+
data_shape: Shape of the data array (rows, [columns]).
|
|
44
|
+
data: Optional actual data array for additional analysis.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Recommended chart type: 'line', 'scatter', 'bar', 'histogram',
|
|
48
|
+
'heatmap', 'pie', or 'spectrum'.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> # Time series data → line plot
|
|
52
|
+
>>> auto_select_chart("time_series", (1000, 2))
|
|
53
|
+
'line'
|
|
54
|
+
|
|
55
|
+
>>> # Distribution data → histogram
|
|
56
|
+
>>> auto_select_chart("distribution", (500,))
|
|
57
|
+
'histogram'
|
|
58
|
+
|
|
59
|
+
>>> # Categorical comparison → bar chart
|
|
60
|
+
>>> auto_select_chart("categorical", (5,))
|
|
61
|
+
'bar'
|
|
62
|
+
|
|
63
|
+
>>> # 2D matrix → heatmap
|
|
64
|
+
>>> auto_select_chart("matrix", (100, 100))
|
|
65
|
+
'heatmap'
|
|
66
|
+
|
|
67
|
+
References:
|
|
68
|
+
REPORT-028: Automated Chart Type Selection
|
|
69
|
+
"""
|
|
70
|
+
# Time series → line plot
|
|
71
|
+
if data_type == "time_series":
|
|
72
|
+
return "line"
|
|
73
|
+
|
|
74
|
+
# Frequency data → spectrum plot (log scale)
|
|
75
|
+
if data_type == "frequency":
|
|
76
|
+
return "spectrum"
|
|
77
|
+
|
|
78
|
+
# Distribution → histogram or box plot
|
|
79
|
+
if data_type == "distribution":
|
|
80
|
+
return "histogram"
|
|
81
|
+
|
|
82
|
+
# Categorical comparison → bar chart
|
|
83
|
+
if data_type == "categorical":
|
|
84
|
+
# If very few categories, pie chart might be appropriate
|
|
85
|
+
if len(data_shape) > 0 and data_shape[0] <= 6 and data is not None and np.all(data >= 0):
|
|
86
|
+
# Check if data represents parts of a whole
|
|
87
|
+
total = np.sum(data)
|
|
88
|
+
if total > 0 and np.allclose(data / total * 100, data / total * 100):
|
|
89
|
+
return "pie"
|
|
90
|
+
return "bar"
|
|
91
|
+
|
|
92
|
+
# Comparison (continuous) → scatter plot
|
|
93
|
+
if data_type == "comparison":
|
|
94
|
+
# If 2D data with moderate size, scatter plot
|
|
95
|
+
if len(data_shape) >= 2 and data_shape[0] < 10000:
|
|
96
|
+
return "scatter"
|
|
97
|
+
# Large comparison data → bar chart
|
|
98
|
+
return "bar"
|
|
99
|
+
|
|
100
|
+
# Correlation → scatter plot with potential regression
|
|
101
|
+
if data_type == "correlation":
|
|
102
|
+
return "scatter"
|
|
103
|
+
|
|
104
|
+
# 2D matrix → heatmap
|
|
105
|
+
if data_type == "matrix":
|
|
106
|
+
return "heatmap"
|
|
107
|
+
|
|
108
|
+
# Parts-to-whole → pie chart
|
|
109
|
+
if data_type == "parts":
|
|
110
|
+
return "pie"
|
|
111
|
+
|
|
112
|
+
# Default based on shape
|
|
113
|
+
if len(data_shape) == 1:
|
|
114
|
+
# 1D data: histogram for distributions, bar for small sets
|
|
115
|
+
if data_shape[0] < 20:
|
|
116
|
+
return "bar"
|
|
117
|
+
return "histogram"
|
|
118
|
+
elif len(data_shape) == 2:
|
|
119
|
+
# 2D data: heatmap for square-ish matrices, scatter for point clouds
|
|
120
|
+
if data_shape[0] > 50 and data_shape[1] > 50:
|
|
121
|
+
return "heatmap"
|
|
122
|
+
return "scatter"
|
|
123
|
+
|
|
124
|
+
# Fallback to line plot
|
|
125
|
+
return "line"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def recommend_chart_with_reasoning(
|
|
129
|
+
data_type: str,
|
|
130
|
+
data_shape: tuple[int, ...],
|
|
131
|
+
*,
|
|
132
|
+
data: NDArray[np.float64] | None = None,
|
|
133
|
+
) -> dict[str, str | ChartType]:
|
|
134
|
+
"""Recommend chart type with reasoning explanation.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
data_type: Type of data (see auto_select_chart).
|
|
138
|
+
data_shape: Shape of the data array.
|
|
139
|
+
data: Optional actual data array.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dictionary with 'chart_type' and 'reasoning' keys.
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
>>> result = recommend_chart_with_reasoning("time_series", (1000, 2))
|
|
146
|
+
>>> print(result['chart_type']) # "line"
|
|
147
|
+
>>> print(result['reasoning']) # "Time series data best shown with line plot"
|
|
148
|
+
|
|
149
|
+
References:
|
|
150
|
+
REPORT-028: Automated Chart Type Selection
|
|
151
|
+
"""
|
|
152
|
+
chart_type = auto_select_chart(data_type, data_shape, data=data)
|
|
153
|
+
|
|
154
|
+
# Generate reasoning
|
|
155
|
+
reasoning_map = {
|
|
156
|
+
"line": "Time series or sequential data best visualized with line plot",
|
|
157
|
+
"scatter": "Point data or correlation best shown with scatter plot",
|
|
158
|
+
"bar": "Categorical or discrete comparison best shown with bar chart",
|
|
159
|
+
"histogram": "Distribution data best represented as histogram",
|
|
160
|
+
"heatmap": "2D matrix data best visualized as heatmap",
|
|
161
|
+
"pie": "Part-to-whole relationship best shown with pie chart",
|
|
162
|
+
"spectrum": "Frequency domain data best shown with log-scale spectrum plot",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
reasoning = reasoning_map.get(
|
|
166
|
+
chart_type, f"Data characteristics suggest {chart_type} visualization"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"chart_type": chart_type,
|
|
171
|
+
"reasoning": reasoning,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_axis_scaling(
|
|
176
|
+
data_type: str,
|
|
177
|
+
data: NDArray[np.float64] | None = None,
|
|
178
|
+
) -> dict[str, str]:
|
|
179
|
+
"""Recommend axis scaling (linear vs log) based on data type.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
data_type: Type of data.
|
|
183
|
+
data: Optional actual data array for range analysis.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Dictionary with 'x_scale' and 'y_scale' keys ('linear' or 'log').
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> scaling = get_axis_scaling("frequency")
|
|
190
|
+
>>> print(scaling) # {'x_scale': 'log', 'y_scale': 'log'}
|
|
191
|
+
|
|
192
|
+
References:
|
|
193
|
+
REPORT-028: Automated Chart Type Selection
|
|
194
|
+
"""
|
|
195
|
+
# Default linear scaling
|
|
196
|
+
x_scale = "linear"
|
|
197
|
+
y_scale = "linear"
|
|
198
|
+
|
|
199
|
+
# Frequency data: both axes log
|
|
200
|
+
if data_type == "frequency":
|
|
201
|
+
x_scale = "log"
|
|
202
|
+
y_scale = "log"
|
|
203
|
+
|
|
204
|
+
# Check data range if provided
|
|
205
|
+
if data is not None and len(data) > 0:
|
|
206
|
+
# If data spans > 3 orders of magnitude, use log scale
|
|
207
|
+
data_min = np.min(data[data > 0]) if np.any(data > 0) else 0
|
|
208
|
+
data_max = np.max(data)
|
|
209
|
+
if data_min > 0 and data_max / data_min > 1000:
|
|
210
|
+
y_scale = "log"
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
"x_scale": x_scale,
|
|
214
|
+
"y_scale": y_scale,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
__all__ = [
|
|
219
|
+
"ChartType",
|
|
220
|
+
"auto_select_chart",
|
|
221
|
+
"get_axis_scaling",
|
|
222
|
+
"recommend_chart_with_reasoning",
|
|
223
|
+
]
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Comparison report generation for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for comparing multiple traces or test runs
|
|
4
|
+
and generating comparison reports with diff visualization.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.reporting.comparison import generate_comparison_report
|
|
9
|
+
>>> report = generate_comparison_report(baseline, current, "comparison.pdf")
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any, Literal
|
|
15
|
+
|
|
16
|
+
from oscura.reporting.core import Report, ReportConfig, Section
|
|
17
|
+
from oscura.reporting.tables import create_comparison_table
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate_comparison_report(
|
|
21
|
+
baseline: dict[str, Any],
|
|
22
|
+
current: dict[str, Any],
|
|
23
|
+
*,
|
|
24
|
+
title: str = "Comparison Report",
|
|
25
|
+
mode: Literal["side_by_side", "inline"] = "side_by_side",
|
|
26
|
+
show_only_changes: bool = False,
|
|
27
|
+
highlight_changes: bool = False,
|
|
28
|
+
**kwargs: Any,
|
|
29
|
+
) -> Report:
|
|
30
|
+
"""Generate comparison report between baseline and current results.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
baseline: Baseline results dictionary.
|
|
34
|
+
current: Current results dictionary.
|
|
35
|
+
title: Report title.
|
|
36
|
+
mode: Comparison mode (side_by_side or inline).
|
|
37
|
+
show_only_changes: Only show changed measurements.
|
|
38
|
+
highlight_changes: Highlight changes in output. Reserved for future use.
|
|
39
|
+
**kwargs: Additional report configuration options.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Comparison Report object.
|
|
43
|
+
|
|
44
|
+
References:
|
|
45
|
+
REPORT-008
|
|
46
|
+
"""
|
|
47
|
+
config = ReportConfig(title=title, **kwargs)
|
|
48
|
+
report = Report(config=config)
|
|
49
|
+
|
|
50
|
+
# Add summary
|
|
51
|
+
summary = _generate_comparison_summary(baseline, current)
|
|
52
|
+
report.add_section("Comparison Summary", summary, level=1)
|
|
53
|
+
|
|
54
|
+
# Add change details
|
|
55
|
+
changes_section = _create_changes_section(
|
|
56
|
+
baseline,
|
|
57
|
+
current,
|
|
58
|
+
show_only_changes=show_only_changes,
|
|
59
|
+
)
|
|
60
|
+
report.sections.append(changes_section)
|
|
61
|
+
|
|
62
|
+
# Add violations comparison
|
|
63
|
+
if "violations" in baseline or "violations" in current:
|
|
64
|
+
violations_section = _create_violations_comparison_section(
|
|
65
|
+
baseline,
|
|
66
|
+
current,
|
|
67
|
+
)
|
|
68
|
+
report.sections.append(violations_section)
|
|
69
|
+
|
|
70
|
+
# Add detailed comparison
|
|
71
|
+
if "measurements" in baseline or "measurements" in current:
|
|
72
|
+
detailed_section = _create_detailed_comparison_section(
|
|
73
|
+
baseline.get("measurements", {}),
|
|
74
|
+
current.get("measurements", {}),
|
|
75
|
+
mode=mode,
|
|
76
|
+
)
|
|
77
|
+
report.sections.append(detailed_section)
|
|
78
|
+
|
|
79
|
+
return report
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _generate_comparison_summary(
|
|
83
|
+
baseline: dict[str, Any],
|
|
84
|
+
current: dict[str, Any],
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Generate comparison summary."""
|
|
87
|
+
summary_parts = []
|
|
88
|
+
|
|
89
|
+
# Count changes
|
|
90
|
+
baseline_meas = baseline.get("measurements", {})
|
|
91
|
+
current_meas = current.get("measurements", {})
|
|
92
|
+
|
|
93
|
+
all_params = set(baseline_meas.keys()) | set(current_meas.keys())
|
|
94
|
+
changed_params = []
|
|
95
|
+
improved_params = []
|
|
96
|
+
degraded_params = []
|
|
97
|
+
|
|
98
|
+
for param in all_params:
|
|
99
|
+
base_val = baseline_meas.get(param, {}).get("value")
|
|
100
|
+
curr_val = current_meas.get(param, {}).get("value")
|
|
101
|
+
|
|
102
|
+
if base_val is not None and curr_val is not None:
|
|
103
|
+
if abs(curr_val - base_val) / abs(base_val) > 0.05: # >5% change
|
|
104
|
+
changed_params.append(param)
|
|
105
|
+
|
|
106
|
+
# Determine if improved or degraded
|
|
107
|
+
base_passed = baseline_meas.get(param, {}).get("passed", True)
|
|
108
|
+
curr_passed = current_meas.get(param, {}).get("passed", True)
|
|
109
|
+
|
|
110
|
+
if not base_passed and curr_passed:
|
|
111
|
+
improved_params.append(param)
|
|
112
|
+
elif base_passed and not curr_passed:
|
|
113
|
+
degraded_params.append(param)
|
|
114
|
+
|
|
115
|
+
summary_parts.append(f"Comparing {len(all_params)} parameter(s) between baseline and current.")
|
|
116
|
+
|
|
117
|
+
if changed_params:
|
|
118
|
+
summary_parts.append(f"\n{len(changed_params)} measurement(s) changed significantly (>5%).")
|
|
119
|
+
|
|
120
|
+
if improved_params:
|
|
121
|
+
summary_parts.append(
|
|
122
|
+
f"\n✓ {len(improved_params)} parameter(s) improved (failures → passes)."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if degraded_params:
|
|
126
|
+
summary_parts.append(
|
|
127
|
+
f"\n✗ {len(degraded_params)} parameter(s) degraded (passes → failures)."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Pass/fail comparison
|
|
131
|
+
baseline_pass = baseline.get("pass_count", 0)
|
|
132
|
+
baseline_total = baseline.get("total_count", 0)
|
|
133
|
+
current_pass = current.get("pass_count", 0)
|
|
134
|
+
current_total = current.get("total_count", 0)
|
|
135
|
+
|
|
136
|
+
if baseline_total > 0 and current_total > 0:
|
|
137
|
+
baseline_rate = baseline_pass / baseline_total * 100
|
|
138
|
+
current_rate = current_pass / current_total * 100
|
|
139
|
+
delta = current_rate - baseline_rate
|
|
140
|
+
|
|
141
|
+
summary_parts.append(
|
|
142
|
+
f"\nPass rate: {baseline_rate:.1f}% → {current_rate:.1f}% ({delta:+.1f}% change)"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return "\n".join(summary_parts)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _create_changes_section(
|
|
149
|
+
baseline: dict[str, Any],
|
|
150
|
+
current: dict[str, Any],
|
|
151
|
+
*,
|
|
152
|
+
show_only_changes: bool = False,
|
|
153
|
+
) -> Section:
|
|
154
|
+
"""Create section detailing changes."""
|
|
155
|
+
baseline_meas = baseline.get("measurements", {})
|
|
156
|
+
current_meas = current.get("measurements", {})
|
|
157
|
+
|
|
158
|
+
# Create comparison table
|
|
159
|
+
table = create_comparison_table(
|
|
160
|
+
baseline_meas,
|
|
161
|
+
current_meas,
|
|
162
|
+
format="dict",
|
|
163
|
+
show_delta=True,
|
|
164
|
+
show_percent_change=True,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Filter to only changes if requested
|
|
168
|
+
if show_only_changes:
|
|
169
|
+
filtered_rows = []
|
|
170
|
+
for row in table["data"]: # type: ignore[index]
|
|
171
|
+
# Check if delta is significant
|
|
172
|
+
if len(row) >= 4: # Has delta column
|
|
173
|
+
delta_str = str(row[3])
|
|
174
|
+
if delta_str not in {"-", "0"}:
|
|
175
|
+
filtered_rows.append(row)
|
|
176
|
+
|
|
177
|
+
table["data"] = filtered_rows # type: ignore[index]
|
|
178
|
+
|
|
179
|
+
return Section(
|
|
180
|
+
title="Measurement Changes",
|
|
181
|
+
content=[table],
|
|
182
|
+
level=1,
|
|
183
|
+
visible=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _create_violations_comparison_section(
|
|
188
|
+
baseline: dict[str, Any],
|
|
189
|
+
current: dict[str, Any],
|
|
190
|
+
) -> Section:
|
|
191
|
+
"""Create section comparing violations."""
|
|
192
|
+
baseline_violations = {v.get("parameter") for v in baseline.get("violations", [])}
|
|
193
|
+
current_violations = {v.get("parameter") for v in current.get("violations", [])}
|
|
194
|
+
|
|
195
|
+
content_parts = []
|
|
196
|
+
|
|
197
|
+
# New violations
|
|
198
|
+
new_violations = current_violations - baseline_violations
|
|
199
|
+
if new_violations:
|
|
200
|
+
content_parts.append("**New Violations:**")
|
|
201
|
+
for param in sorted(new_violations):
|
|
202
|
+
content_parts.append(f"- {param}")
|
|
203
|
+
|
|
204
|
+
# Resolved violations
|
|
205
|
+
resolved_violations = baseline_violations - current_violations
|
|
206
|
+
if resolved_violations:
|
|
207
|
+
content_parts.append("\n**Resolved Violations:**")
|
|
208
|
+
for param in sorted(resolved_violations):
|
|
209
|
+
content_parts.append(f"- {param}")
|
|
210
|
+
|
|
211
|
+
# Persistent violations
|
|
212
|
+
persistent_violations = baseline_violations & current_violations
|
|
213
|
+
if persistent_violations:
|
|
214
|
+
content_parts.append("\n**Persistent Violations:**")
|
|
215
|
+
for param in sorted(persistent_violations):
|
|
216
|
+
content_parts.append(f"- {param}")
|
|
217
|
+
|
|
218
|
+
if not content_parts:
|
|
219
|
+
content_parts.append("No violations in either baseline or current.")
|
|
220
|
+
|
|
221
|
+
content = "\n".join(content_parts)
|
|
222
|
+
|
|
223
|
+
return Section(
|
|
224
|
+
title="Violations Comparison",
|
|
225
|
+
content=content,
|
|
226
|
+
level=1,
|
|
227
|
+
visible=True,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _create_detailed_comparison_section(
|
|
232
|
+
baseline_meas: dict[str, Any],
|
|
233
|
+
current_meas: dict[str, Any],
|
|
234
|
+
*,
|
|
235
|
+
mode: str = "side_by_side",
|
|
236
|
+
) -> Section:
|
|
237
|
+
"""Create detailed measurement comparison section."""
|
|
238
|
+
from oscura.reporting.formatting import NumberFormatter
|
|
239
|
+
|
|
240
|
+
formatter = NumberFormatter()
|
|
241
|
+
|
|
242
|
+
content_parts = []
|
|
243
|
+
|
|
244
|
+
all_params = sorted(set(baseline_meas.keys()) | set(current_meas.keys()))
|
|
245
|
+
|
|
246
|
+
for param in all_params:
|
|
247
|
+
base = baseline_meas.get(param, {})
|
|
248
|
+
curr = current_meas.get(param, {})
|
|
249
|
+
|
|
250
|
+
base_val = base.get("value")
|
|
251
|
+
curr_val = curr.get("value")
|
|
252
|
+
unit = base.get("unit", curr.get("unit", ""))
|
|
253
|
+
|
|
254
|
+
if base_val is not None and curr_val is not None:
|
|
255
|
+
delta = curr_val - base_val
|
|
256
|
+
pct_change = (delta / base_val * 100) if base_val != 0 else 0
|
|
257
|
+
|
|
258
|
+
base_str = formatter.format(base_val, unit)
|
|
259
|
+
curr_str = formatter.format(curr_val, unit)
|
|
260
|
+
delta_str = formatter.format(delta, unit)
|
|
261
|
+
|
|
262
|
+
# Determine if improved/degraded
|
|
263
|
+
base_passed = base.get("passed", True)
|
|
264
|
+
curr_passed = curr.get("passed", True)
|
|
265
|
+
|
|
266
|
+
status = ""
|
|
267
|
+
if not base_passed and curr_passed:
|
|
268
|
+
status = " ✓ IMPROVED"
|
|
269
|
+
elif base_passed and not curr_passed:
|
|
270
|
+
status = " ✗ DEGRADED"
|
|
271
|
+
|
|
272
|
+
content_parts.append(
|
|
273
|
+
f"**{param}:** {base_str} → {curr_str} (Δ {delta_str}, {pct_change:+.1f}%){status}"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
content = "\n\n".join(content_parts) if content_parts else "No measurements to compare."
|
|
277
|
+
|
|
278
|
+
return Section(
|
|
279
|
+
title="Detailed Comparison",
|
|
280
|
+
content=content,
|
|
281
|
+
level=1,
|
|
282
|
+
visible=True,
|
|
283
|
+
collapsible=True,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def compare_waveforms(
|
|
288
|
+
baseline_signal: dict[str, Any],
|
|
289
|
+
current_signal: dict[str, Any],
|
|
290
|
+
) -> dict[str, Any]:
|
|
291
|
+
"""Compare two waveforms and extract differences.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
baseline_signal: Baseline waveform data.
|
|
295
|
+
current_signal: Current waveform data.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Dictionary with comparison metrics.
|
|
299
|
+
|
|
300
|
+
References:
|
|
301
|
+
REPORT-008
|
|
302
|
+
"""
|
|
303
|
+
import numpy as np
|
|
304
|
+
|
|
305
|
+
comparison = {
|
|
306
|
+
"correlation": None,
|
|
307
|
+
"rms_difference": None,
|
|
308
|
+
"max_difference": None,
|
|
309
|
+
"mean_difference": None,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
base_data = baseline_signal.get("data")
|
|
313
|
+
curr_data = current_signal.get("data")
|
|
314
|
+
|
|
315
|
+
if base_data is not None and curr_data is not None:
|
|
316
|
+
# Ensure same length
|
|
317
|
+
min_len = min(len(base_data), len(curr_data))
|
|
318
|
+
base_data = base_data[:min_len]
|
|
319
|
+
curr_data = curr_data[:min_len]
|
|
320
|
+
|
|
321
|
+
# Correlation
|
|
322
|
+
comparison["correlation"] = np.corrcoef(base_data, curr_data)[0, 1]
|
|
323
|
+
|
|
324
|
+
# Differences
|
|
325
|
+
diff = curr_data - base_data
|
|
326
|
+
comparison["rms_difference"] = np.sqrt(np.mean(diff**2))
|
|
327
|
+
comparison["max_difference"] = np.max(np.abs(diff))
|
|
328
|
+
comparison["mean_difference"] = np.mean(diff)
|
|
329
|
+
|
|
330
|
+
return comparison
|