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,32 @@
|
|
|
1
|
+
"""Jupyter and IPython integration for Oscura.
|
|
2
|
+
|
|
3
|
+
This package provides IPython magic commands, rich display integration,
|
|
4
|
+
and Jupyter notebook-specific features.
|
|
5
|
+
|
|
6
|
+
- IPython magic commands (%oscura, %%analyze)
|
|
7
|
+
- Rich HTML display for results
|
|
8
|
+
- Inline plot rendering
|
|
9
|
+
- Progress bars (tqdm integration)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from oscura.jupyter.display import (
|
|
13
|
+
MeasurementDisplay,
|
|
14
|
+
TraceDisplay,
|
|
15
|
+
display_measurements,
|
|
16
|
+
display_spectrum,
|
|
17
|
+
display_trace,
|
|
18
|
+
)
|
|
19
|
+
from oscura.jupyter.magic import (
|
|
20
|
+
OscuraMagics,
|
|
21
|
+
load_ipython_extension,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"MeasurementDisplay",
|
|
26
|
+
"OscuraMagics",
|
|
27
|
+
"TraceDisplay",
|
|
28
|
+
"display_measurements",
|
|
29
|
+
"display_spectrum",
|
|
30
|
+
"display_trace",
|
|
31
|
+
"load_ipython_extension",
|
|
32
|
+
]
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Rich display integration for Jupyter notebooks.
|
|
2
|
+
|
|
3
|
+
This module provides rich HTML display for Oscura objects including
|
|
4
|
+
traces, measurements, and spectral data.
|
|
5
|
+
|
|
6
|
+
- HTML tables for measurements
|
|
7
|
+
- Inline plot rendering
|
|
8
|
+
- Interactive result display
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
In [1]: from oscura.jupyter.display import display_trace
|
|
12
|
+
In [2]: display_trace(trace) # Shows rich HTML summary
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from IPython.display import HTML, SVG, display
|
|
21
|
+
|
|
22
|
+
IPYTHON_AVAILABLE = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
IPYTHON_AVAILABLE = False
|
|
25
|
+
|
|
26
|
+
class HTML: # type: ignore[no-redef]
|
|
27
|
+
"""Fallback HTML class when IPython not available."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, data: str) -> None:
|
|
30
|
+
self.data = data
|
|
31
|
+
|
|
32
|
+
class SVG: # type: ignore[no-redef]
|
|
33
|
+
"""Fallback SVG class when IPython not available."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, data: str) -> None:
|
|
36
|
+
self.data = data
|
|
37
|
+
|
|
38
|
+
def display(*args: Any, **kwargs: Any) -> None: # type: ignore[no-redef,misc]
|
|
39
|
+
"""Fallback display when IPython not available."""
|
|
40
|
+
for arg in args:
|
|
41
|
+
print(arg)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TraceDisplay:
|
|
45
|
+
"""Rich display wrapper for trace objects.
|
|
46
|
+
|
|
47
|
+
Provides _repr_html_ for Jupyter notebook rendering.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, trace: Any, title: str = "Trace") -> None:
|
|
51
|
+
"""Initialize trace display.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
trace: WaveformTrace or DigitalTrace object
|
|
55
|
+
title: Display title
|
|
56
|
+
"""
|
|
57
|
+
self.trace = trace
|
|
58
|
+
self.title = title
|
|
59
|
+
|
|
60
|
+
def _repr_html_(self) -> str:
|
|
61
|
+
"""Generate HTML representation for Jupyter."""
|
|
62
|
+
trace = self.trace
|
|
63
|
+
|
|
64
|
+
# Build info rows
|
|
65
|
+
rows = []
|
|
66
|
+
|
|
67
|
+
if hasattr(trace, "data"):
|
|
68
|
+
rows.append(("Samples", f"{len(trace.data):,}"))
|
|
69
|
+
|
|
70
|
+
if hasattr(trace, "metadata"):
|
|
71
|
+
meta = trace.metadata
|
|
72
|
+
if hasattr(meta, "sample_rate") and meta.sample_rate:
|
|
73
|
+
rate = meta.sample_rate
|
|
74
|
+
if rate >= 1e9:
|
|
75
|
+
rate_str = f"{rate / 1e9:.3f} GSa/s"
|
|
76
|
+
elif rate >= 1e6:
|
|
77
|
+
rate_str = f"{rate / 1e6:.3f} MSa/s"
|
|
78
|
+
else:
|
|
79
|
+
rate_str = f"{rate / 1e3:.3f} kSa/s"
|
|
80
|
+
rows.append(("Sample Rate", rate_str))
|
|
81
|
+
|
|
82
|
+
if hasattr(meta, "channel_name") and meta.channel_name:
|
|
83
|
+
rows.append(("Channel", meta.channel_name))
|
|
84
|
+
|
|
85
|
+
if hasattr(meta, "source_file") and meta.source_file:
|
|
86
|
+
rows.append(("Source", meta.source_file))
|
|
87
|
+
|
|
88
|
+
# Calculate duration if possible
|
|
89
|
+
if hasattr(trace, "data") and hasattr(trace, "metadata"):
|
|
90
|
+
if hasattr(trace.metadata, "sample_rate") and trace.metadata.sample_rate:
|
|
91
|
+
duration = len(trace.data) / trace.metadata.sample_rate
|
|
92
|
+
if duration >= 1:
|
|
93
|
+
dur_str = f"{duration:.3f} s"
|
|
94
|
+
elif duration >= 1e-3:
|
|
95
|
+
dur_str = f"{duration * 1e3:.3f} ms"
|
|
96
|
+
elif duration >= 1e-6:
|
|
97
|
+
dur_str = f"{duration * 1e6:.3f} us"
|
|
98
|
+
else:
|
|
99
|
+
dur_str = f"{duration * 1e9:.3f} ns"
|
|
100
|
+
rows.append(("Duration", dur_str))
|
|
101
|
+
|
|
102
|
+
# Data statistics
|
|
103
|
+
if hasattr(trace, "data"):
|
|
104
|
+
import numpy as np
|
|
105
|
+
|
|
106
|
+
data = trace.data
|
|
107
|
+
rows.append(("Min", f"{np.min(data):.4g}"))
|
|
108
|
+
rows.append(("Max", f"{np.max(data):.4g}"))
|
|
109
|
+
rows.append(("Mean", f"{np.mean(data):.4g}"))
|
|
110
|
+
rows.append(("Std Dev", f"{np.std(data):.4g}"))
|
|
111
|
+
|
|
112
|
+
# Build HTML table
|
|
113
|
+
html = f"""
|
|
114
|
+
<div style="border: 1px solid #ccc; border-radius: 4px; padding: 10px; max-width: 400px;">
|
|
115
|
+
<h4 style="margin: 0 0 10px 0; color: #333;">{self.title}</h4>
|
|
116
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
117
|
+
"""
|
|
118
|
+
for label, value in rows:
|
|
119
|
+
html += f"""
|
|
120
|
+
<tr>
|
|
121
|
+
<td style="padding: 4px 8px; border-bottom: 1px solid #eee; font-weight: bold; color: #666;">{label}</td>
|
|
122
|
+
<td style="padding: 4px 8px; border-bottom: 1px solid #eee;">{value}</td>
|
|
123
|
+
</tr>
|
|
124
|
+
"""
|
|
125
|
+
html += """
|
|
126
|
+
</table>
|
|
127
|
+
</div>
|
|
128
|
+
"""
|
|
129
|
+
return html
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class MeasurementDisplay:
|
|
133
|
+
"""Rich display wrapper for measurement results.
|
|
134
|
+
|
|
135
|
+
Provides _repr_html_ for Jupyter notebook rendering.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(self, measurements: dict[str, Any], title: str = "Measurements") -> None:
|
|
139
|
+
"""Initialize measurement display.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
measurements: Dictionary of measurement name -> value
|
|
143
|
+
title: Display title
|
|
144
|
+
"""
|
|
145
|
+
self.measurements = measurements
|
|
146
|
+
self.title = title
|
|
147
|
+
|
|
148
|
+
def _format_value(self, value: Any) -> str:
|
|
149
|
+
"""Format a measurement value with appropriate units."""
|
|
150
|
+
if isinstance(value, float):
|
|
151
|
+
# Determine scale and units for common measurements
|
|
152
|
+
abs_val = abs(value)
|
|
153
|
+
if abs_val == 0:
|
|
154
|
+
return "0"
|
|
155
|
+
elif abs_val >= 1e9:
|
|
156
|
+
return f"{value / 1e9:.3f} G"
|
|
157
|
+
elif abs_val >= 1e6:
|
|
158
|
+
return f"{value / 1e6:.3f} M"
|
|
159
|
+
elif abs_val >= 1e3:
|
|
160
|
+
return f"{value / 1e3:.3f} k"
|
|
161
|
+
elif abs_val >= 1:
|
|
162
|
+
return f"{value:.4f}"
|
|
163
|
+
elif abs_val >= 1e-3:
|
|
164
|
+
return f"{value * 1e3:.3f} m"
|
|
165
|
+
elif abs_val >= 1e-6:
|
|
166
|
+
return f"{value * 1e6:.3f} u"
|
|
167
|
+
elif abs_val >= 1e-9:
|
|
168
|
+
return f"{value * 1e9:.3f} n"
|
|
169
|
+
elif abs_val >= 1e-12:
|
|
170
|
+
return f"{value * 1e12:.3f} p"
|
|
171
|
+
else:
|
|
172
|
+
return f"{value:.3e}"
|
|
173
|
+
else:
|
|
174
|
+
return str(value)
|
|
175
|
+
|
|
176
|
+
def _repr_html_(self) -> str:
|
|
177
|
+
"""Generate HTML representation for Jupyter."""
|
|
178
|
+
html = f"""
|
|
179
|
+
<div style="border: 1px solid #ccc; border-radius: 4px; padding: 10px; max-width: 500px;">
|
|
180
|
+
<h4 style="margin: 0 0 10px 0; color: #333;">{self.title}</h4>
|
|
181
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
182
|
+
<tr style="background-color: #f5f5f5;">
|
|
183
|
+
<th style="padding: 8px; text-align: left; border-bottom: 2px solid #ddd;">Measurement</th>
|
|
184
|
+
<th style="padding: 8px; text-align: right; border-bottom: 2px solid #ddd;">Value</th>
|
|
185
|
+
</tr>
|
|
186
|
+
"""
|
|
187
|
+
for name, value in self.measurements.items():
|
|
188
|
+
formatted = self._format_value(value)
|
|
189
|
+
html += f"""
|
|
190
|
+
<tr>
|
|
191
|
+
<td style="padding: 6px 8px; border-bottom: 1px solid #eee;">{name}</td>
|
|
192
|
+
<td style="padding: 6px 8px; border-bottom: 1px solid #eee; text-align: right; font-family: monospace;">{formatted}</td>
|
|
193
|
+
</tr>
|
|
194
|
+
"""
|
|
195
|
+
html += """
|
|
196
|
+
</table>
|
|
197
|
+
</div>
|
|
198
|
+
"""
|
|
199
|
+
return html
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def display_trace(trace: Any, title: str = "Trace") -> None:
|
|
203
|
+
"""Display a trace with rich HTML formatting.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
trace: WaveformTrace or DigitalTrace object
|
|
207
|
+
title: Display title
|
|
208
|
+
"""
|
|
209
|
+
wrapper = TraceDisplay(trace, title)
|
|
210
|
+
if IPYTHON_AVAILABLE:
|
|
211
|
+
display(HTML(wrapper._repr_html_())) # type: ignore[no-untyped-call]
|
|
212
|
+
else:
|
|
213
|
+
print(wrapper._repr_html_())
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def display_measurements(measurements: dict[str, Any], title: str = "Measurements") -> None:
|
|
217
|
+
"""Display measurements with rich HTML formatting.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
measurements: Dictionary of measurement name -> value
|
|
221
|
+
title: Display title
|
|
222
|
+
"""
|
|
223
|
+
wrapper = MeasurementDisplay(measurements, title)
|
|
224
|
+
if IPYTHON_AVAILABLE:
|
|
225
|
+
display(HTML(wrapper._repr_html_())) # type: ignore[no-untyped-call]
|
|
226
|
+
else:
|
|
227
|
+
for name, value in measurements.items():
|
|
228
|
+
print(f"{name}: {value}")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def display_spectrum(
|
|
232
|
+
frequencies: Any,
|
|
233
|
+
magnitudes: Any,
|
|
234
|
+
title: str = "Spectrum",
|
|
235
|
+
log_scale: bool = True,
|
|
236
|
+
figsize: tuple[int, int] = (10, 4),
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Display a spectrum plot inline in Jupyter.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
frequencies: Frequency array (Hz)
|
|
242
|
+
magnitudes: Magnitude array (dB or linear)
|
|
243
|
+
title: Plot title
|
|
244
|
+
log_scale: Use log scale for x-axis
|
|
245
|
+
figsize: Figure size tuple
|
|
246
|
+
"""
|
|
247
|
+
import matplotlib.pyplot as plt
|
|
248
|
+
import numpy as np
|
|
249
|
+
|
|
250
|
+
_fig, ax = plt.subplots(figsize=figsize)
|
|
251
|
+
|
|
252
|
+
if log_scale and np.min(frequencies[frequencies > 0]) > 0:
|
|
253
|
+
ax.semilogx(frequencies, magnitudes)
|
|
254
|
+
else:
|
|
255
|
+
ax.plot(frequencies, magnitudes)
|
|
256
|
+
|
|
257
|
+
ax.set_xlabel("Frequency (Hz)")
|
|
258
|
+
ax.set_ylabel("Magnitude (dB)")
|
|
259
|
+
ax.set_title(title)
|
|
260
|
+
ax.grid(True, alpha=0.3)
|
|
261
|
+
|
|
262
|
+
plt.tight_layout()
|
|
263
|
+
|
|
264
|
+
if IPYTHON_AVAILABLE:
|
|
265
|
+
# Display inline
|
|
266
|
+
plt.show()
|
|
267
|
+
else:
|
|
268
|
+
plt.show()
|
oscura/jupyter/magic.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""IPython magic commands for Oscura.
|
|
2
|
+
|
|
3
|
+
This module provides IPython/Jupyter magic commands for convenient
|
|
4
|
+
trace analysis in notebooks.
|
|
5
|
+
|
|
6
|
+
- %oscura load <file> - Load a trace file
|
|
7
|
+
- %oscura measure - Run measurements on current trace
|
|
8
|
+
- %%analyze - Multi-line analysis cell
|
|
9
|
+
- Auto-display of results with rich HTML
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
In [1]: %load_ext oscura
|
|
13
|
+
|
|
14
|
+
In [2]: %oscura load capture.wfm
|
|
15
|
+
Loaded: WaveformTrace with 10000 samples @ 1 GSa/s
|
|
16
|
+
|
|
17
|
+
In [3]: %oscura measure rise_time fall_time
|
|
18
|
+
rise_time: 2.5 ns
|
|
19
|
+
fall_time: 2.8 ns
|
|
20
|
+
|
|
21
|
+
In [4]: %%analyze
|
|
22
|
+
...: trace = load("capture.wfm")
|
|
23
|
+
...: print(f"THD: {thd(trace):.2f} dB")
|
|
24
|
+
|
|
25
|
+
References:
|
|
26
|
+
- IPython Magic Commands documentation
|
|
27
|
+
- Jupyter display architecture
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from typing import TYPE_CHECKING, Any
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Store current trace for magic commands
|
|
39
|
+
_current_trace: Any = None
|
|
40
|
+
_current_file: str | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_current_trace() -> Any:
|
|
44
|
+
"""Get the currently loaded trace from magic commands."""
|
|
45
|
+
return _current_trace
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def set_current_trace(trace: Any, filename: str | None = None) -> None:
|
|
49
|
+
"""Set the current trace for magic commands."""
|
|
50
|
+
global _current_trace, _current_file
|
|
51
|
+
_current_trace = trace
|
|
52
|
+
_current_file = filename
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
from IPython.core.magic import (
|
|
57
|
+
Magics,
|
|
58
|
+
cell_magic,
|
|
59
|
+
line_magic,
|
|
60
|
+
magics_class,
|
|
61
|
+
)
|
|
62
|
+
from IPython.display import HTML, display # noqa: F401
|
|
63
|
+
|
|
64
|
+
IPYTHON_AVAILABLE = True
|
|
65
|
+
except ImportError:
|
|
66
|
+
IPYTHON_AVAILABLE = False
|
|
67
|
+
|
|
68
|
+
class Magics: # type: ignore[no-redef]
|
|
69
|
+
"""Fallback Magics class when IPython not available."""
|
|
70
|
+
|
|
71
|
+
def magics_class(cls: type[Any]) -> type[Any]: # type: ignore[no-redef,misc]
|
|
72
|
+
"""Dummy decorator when IPython not available."""
|
|
73
|
+
return cls
|
|
74
|
+
|
|
75
|
+
def line_magic(func): # type: ignore[no-untyped-def]
|
|
76
|
+
"""Dummy decorator."""
|
|
77
|
+
return func
|
|
78
|
+
|
|
79
|
+
def cell_magic(func): # type: ignore[no-untyped-def]
|
|
80
|
+
"""Dummy decorator."""
|
|
81
|
+
return func
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@magics_class
|
|
85
|
+
class OscuraMagics(Magics): # type: ignore[misc]
|
|
86
|
+
"""IPython magics for Oscura analysis.
|
|
87
|
+
|
|
88
|
+
Provides convenient shortcuts for loading traces, running measurements,
|
|
89
|
+
and displaying results in Jupyter notebooks.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
@line_magic # type: ignore[misc, untyped-decorator]
|
|
93
|
+
def oscura(self, line: str) -> Any:
|
|
94
|
+
"""Oscura line magic for quick operations.
|
|
95
|
+
|
|
96
|
+
Usage:
|
|
97
|
+
%oscura load <filename> - Load a trace file
|
|
98
|
+
%oscura measure [names...] - Run measurements
|
|
99
|
+
%oscura info - Show current trace info
|
|
100
|
+
%oscura formats - List supported formats
|
|
101
|
+
%oscura help - Show help
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
line: Magic command arguments
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Command result or None
|
|
108
|
+
"""
|
|
109
|
+
import oscura as osc
|
|
110
|
+
|
|
111
|
+
parts = line.strip().split()
|
|
112
|
+
if not parts:
|
|
113
|
+
return self._show_help()
|
|
114
|
+
|
|
115
|
+
cmd = parts[0].lower()
|
|
116
|
+
|
|
117
|
+
if cmd == "load":
|
|
118
|
+
if len(parts) < 2:
|
|
119
|
+
print("Usage: %oscura load <filename>")
|
|
120
|
+
return None
|
|
121
|
+
filename = " ".join(parts[1:])
|
|
122
|
+
return self._load_trace(filename)
|
|
123
|
+
|
|
124
|
+
elif cmd == "measure":
|
|
125
|
+
measurements = parts[1:] if len(parts) > 1 else None
|
|
126
|
+
return self._run_measurements(measurements)
|
|
127
|
+
|
|
128
|
+
elif cmd == "info":
|
|
129
|
+
return self._show_trace_info()
|
|
130
|
+
|
|
131
|
+
elif cmd == "formats":
|
|
132
|
+
formats = osc.get_supported_formats()
|
|
133
|
+
print("Supported formats:")
|
|
134
|
+
for fmt in formats:
|
|
135
|
+
print(f" - {fmt}")
|
|
136
|
+
return formats
|
|
137
|
+
|
|
138
|
+
elif cmd == "help":
|
|
139
|
+
return self._show_help()
|
|
140
|
+
|
|
141
|
+
else:
|
|
142
|
+
print(f"Unknown command: {cmd}")
|
|
143
|
+
return self._show_help()
|
|
144
|
+
|
|
145
|
+
def _load_trace(self, filename: str) -> Any:
|
|
146
|
+
"""Load a trace file and store as current."""
|
|
147
|
+
import oscura as osc
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
trace = osc.load(filename)
|
|
151
|
+
set_current_trace(trace, filename)
|
|
152
|
+
|
|
153
|
+
# Display summary
|
|
154
|
+
info = {
|
|
155
|
+
"file": filename,
|
|
156
|
+
"type": type(trace).__name__,
|
|
157
|
+
"samples": len(trace.data) if hasattr(trace, "data") else "N/A",
|
|
158
|
+
"sample_rate": f"{trace.metadata.sample_rate / 1e9:.3f} GSa/s"
|
|
159
|
+
if hasattr(trace, "metadata")
|
|
160
|
+
else "N/A",
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
print(f"Loaded: {info['type']} with {info['samples']} samples @ {info['sample_rate']}")
|
|
164
|
+
return trace
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
print(f"Error loading {filename}: {e}")
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
def _run_measurements(self, measurement_names: list[str] | None) -> dict[str, Any]:
|
|
171
|
+
"""Run measurements on current trace."""
|
|
172
|
+
import oscura as osc
|
|
173
|
+
|
|
174
|
+
trace = get_current_trace()
|
|
175
|
+
if trace is None:
|
|
176
|
+
print("No trace loaded. Use: %oscura load <filename>")
|
|
177
|
+
return {}
|
|
178
|
+
|
|
179
|
+
if measurement_names:
|
|
180
|
+
# Run specific measurements
|
|
181
|
+
results = {}
|
|
182
|
+
for name in measurement_names:
|
|
183
|
+
if hasattr(osc, name):
|
|
184
|
+
try:
|
|
185
|
+
func = getattr(osc, name)
|
|
186
|
+
results[name] = func(trace)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
results[name] = f"Error: {e}"
|
|
189
|
+
else:
|
|
190
|
+
results[name] = "Unknown measurement"
|
|
191
|
+
else:
|
|
192
|
+
# Run all measurements
|
|
193
|
+
try:
|
|
194
|
+
results = osc.measure(trace)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f"Error running measurements: {e}")
|
|
197
|
+
return {}
|
|
198
|
+
|
|
199
|
+
# Display results
|
|
200
|
+
self._display_measurements(results)
|
|
201
|
+
return results
|
|
202
|
+
|
|
203
|
+
def _display_measurements(self, results: dict[str, Any]) -> None:
|
|
204
|
+
"""Display measurement results with formatting."""
|
|
205
|
+
if IPYTHON_AVAILABLE:
|
|
206
|
+
from oscura.jupyter.display import display_measurements
|
|
207
|
+
|
|
208
|
+
display_measurements(results)
|
|
209
|
+
else:
|
|
210
|
+
for name, value in results.items():
|
|
211
|
+
if isinstance(value, float):
|
|
212
|
+
print(f"{name}: {value:.6g}")
|
|
213
|
+
else:
|
|
214
|
+
print(f"{name}: {value}")
|
|
215
|
+
|
|
216
|
+
def _show_trace_info(self) -> dict[str, Any] | None:
|
|
217
|
+
"""Show information about current trace."""
|
|
218
|
+
trace = get_current_trace()
|
|
219
|
+
if trace is None:
|
|
220
|
+
print("No trace loaded. Use: %oscura load <filename>")
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
info = {
|
|
224
|
+
"file": _current_file,
|
|
225
|
+
"type": type(trace).__name__,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if hasattr(trace, "data"):
|
|
229
|
+
info["samples"] = len(trace.data) # type: ignore[assignment]
|
|
230
|
+
|
|
231
|
+
if hasattr(trace, "metadata"):
|
|
232
|
+
meta = trace.metadata
|
|
233
|
+
if hasattr(meta, "sample_rate"):
|
|
234
|
+
info["sample_rate"] = meta.sample_rate
|
|
235
|
+
if hasattr(meta, "channel_name"):
|
|
236
|
+
info["channel"] = meta.channel_name
|
|
237
|
+
|
|
238
|
+
for key, value in info.items():
|
|
239
|
+
print(f"{key}: {value}")
|
|
240
|
+
|
|
241
|
+
return info
|
|
242
|
+
|
|
243
|
+
def _show_help(self) -> None:
|
|
244
|
+
"""Show magic command help."""
|
|
245
|
+
help_text = """
|
|
246
|
+
Oscura Magic Commands
|
|
247
|
+
=======================
|
|
248
|
+
|
|
249
|
+
%oscura load <filename> Load a trace file
|
|
250
|
+
%oscura measure [names...] Run measurements on current trace
|
|
251
|
+
%oscura info Show current trace info
|
|
252
|
+
%oscura formats List supported file formats
|
|
253
|
+
%oscura help Show this help
|
|
254
|
+
|
|
255
|
+
%%analyze Multi-line analysis cell magic
|
|
256
|
+
|
|
257
|
+
Available measurements: rise_time, fall_time, frequency, period,
|
|
258
|
+
amplitude, rms, overshoot, undershoot, thd, snr, sinad
|
|
259
|
+
|
|
260
|
+
Example:
|
|
261
|
+
%oscura load capture.wfm
|
|
262
|
+
%oscura measure rise_time fall_time
|
|
263
|
+
"""
|
|
264
|
+
print(help_text)
|
|
265
|
+
|
|
266
|
+
@cell_magic # type: ignore[misc, untyped-decorator]
|
|
267
|
+
def analyze(self, line: str, cell: str) -> Any:
|
|
268
|
+
"""Cell magic for multi-line analysis.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
line: Magic command line arguments (unused).
|
|
272
|
+
cell: Multi-line cell content to execute.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Result from cell execution (if 'result' variable defined).
|
|
276
|
+
|
|
277
|
+
Usage:
|
|
278
|
+
%%analyze
|
|
279
|
+
trace = load("capture.wfm")
|
|
280
|
+
result = measure(trace)
|
|
281
|
+
print(f"Rise time: {result['rise_time']}")
|
|
282
|
+
|
|
283
|
+
All oscura functions are auto-imported in the cell namespace.
|
|
284
|
+
"""
|
|
285
|
+
import oscura as osc
|
|
286
|
+
|
|
287
|
+
# Build execution namespace with oscura imports
|
|
288
|
+
namespace = {
|
|
289
|
+
"osc": osc,
|
|
290
|
+
"load": osc.load,
|
|
291
|
+
"measure": osc.measure,
|
|
292
|
+
"fft": osc.fft,
|
|
293
|
+
"psd": osc.psd,
|
|
294
|
+
"thd": osc.thd,
|
|
295
|
+
"snr": osc.snr,
|
|
296
|
+
"rise_time": osc.rise_time,
|
|
297
|
+
"fall_time": osc.fall_time,
|
|
298
|
+
"frequency": osc.frequency,
|
|
299
|
+
"amplitude": osc.amplitude,
|
|
300
|
+
"low_pass": osc.low_pass,
|
|
301
|
+
"high_pass": osc.high_pass,
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# Add current trace if available
|
|
305
|
+
trace = get_current_trace()
|
|
306
|
+
if trace is not None:
|
|
307
|
+
namespace["trace"] = trace
|
|
308
|
+
|
|
309
|
+
# Execute cell
|
|
310
|
+
exec(cell, namespace)
|
|
311
|
+
|
|
312
|
+
# Return any result variable if defined
|
|
313
|
+
return namespace.get("result")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def load_ipython_extension(ipython: InteractiveShell) -> None:
|
|
317
|
+
"""Load Oscura IPython extension.
|
|
318
|
+
|
|
319
|
+
Called when user runs: %load_ext oscura
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
ipython: The IPython shell instance
|
|
323
|
+
"""
|
|
324
|
+
ipython.register_magics(OscuraMagics)
|
|
325
|
+
print("Oscura magics loaded. Type '%oscura help' for usage.")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def unload_ipython_extension(ipython: InteractiveShell) -> None:
|
|
329
|
+
"""Unload Oscura IPython extension.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
ipython: The IPython shell instance
|
|
333
|
+
"""
|
|
334
|
+
# IPython handles magic cleanup automatically
|