oscura 0.1.0__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 +14 -14
- oscura/__main__.py +9 -9
- oscura/analyzers/__init__.py +1 -1
- oscura/analyzers/packet/daq.py +1 -1
- oscura/analyzers/patterns/__init__.py +2 -2
- oscura/analyzers/patterns/clustering.py +1 -1
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/periodic.py +1 -1
- oscura/analyzers/patterns/sequences.py +1 -1
- oscura/analyzers/power/__init__.py +1 -1
- oscura/analyzers/power/ac_power.py +1 -1
- oscura/analyzers/power/basic.py +1 -1
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/efficiency.py +1 -1
- oscura/analyzers/power/ripple.py +1 -1
- oscura/analyzers/power/soa.py +1 -1
- oscura/analyzers/power/switching.py +1 -1
- oscura/api/__init__.py +1 -1
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/can/checksum.py +2 -2
- oscura/automotive/can/session.py +1 -1
- oscura/automotive/can/state_machine.py +1 -1
- oscura/automotive/dbc/generator.py +1 -1
- oscura/automotive/dbc/parser.py +1 -1
- oscura/automotive/loaders/pcap.py +1 -1
- oscura/batch/__init__.py +1 -1
- oscura/batch/aggregate.py +2 -2
- oscura/batch/analyze.py +3 -3
- oscura/builders/__init__.py +7 -7
- oscura/builders/signal_builder.py +4 -4
- oscura/cli/__init__.py +1 -1
- oscura/cli/batch.py +1 -1
- oscura/cli/characterize.py +2 -2
- oscura/cli/compare.py +4 -4
- oscura/cli/decode.py +1 -1
- oscura/cli/main.py +8 -8
- oscura/cli/shell.py +19 -19
- oscura/comparison/__init__.py +1 -1
- oscura/comparison/compare.py +1 -1
- oscura/comparison/golden.py +1 -1
- oscura/comparison/limits.py +1 -1
- oscura/comparison/mask.py +1 -1
- oscura/comparison/trace_diff.py +1 -1
- oscura/compliance/__init__.py +2 -2
- oscura/compliance/reporting.py +2 -2
- oscura/component/__init__.py +1 -1
- oscura/component/impedance.py +1 -1
- oscura/component/reactive.py +1 -1
- oscura/component/transmission_line.py +1 -1
- oscura/config/__init__.py +1 -1
- oscura/config/loader.py +1 -1
- oscura/config/memory.py +3 -3
- oscura/config/migration.py +1 -1
- oscura/config/preferences.py +1 -1
- oscura/config/schema.py +3 -3
- oscura/config/settings.py +1 -1
- oscura/convenience.py +13 -13
- oscura/core/__init__.py +6 -6
- oscura/core/cache.py +10 -10
- oscura/core/cancellation.py +1 -1
- oscura/core/confidence.py +1 -1
- oscura/core/config.py +1 -1
- oscura/core/correlation.py +1 -1
- oscura/core/debug.py +4 -4
- oscura/core/edge_cases.py +1 -1
- oscura/core/exceptions.py +14 -14
- oscura/core/gpu_backend.py +5 -5
- oscura/core/lazy.py +3 -3
- oscura/core/logging.py +6 -6
- oscura/core/logging_advanced.py +1 -1
- oscura/core/memoize.py +3 -3
- oscura/core/memory_check.py +1 -1
- oscura/core/memory_guard.py +1 -1
- oscura/core/memory_limits.py +1 -1
- oscura/core/memory_monitor.py +1 -1
- oscura/core/memory_progress.py +1 -1
- oscura/core/memory_warnings.py +1 -1
- oscura/core/progress.py +1 -1
- oscura/core/provenance.py +5 -5
- oscura/core/results.py +3 -3
- oscura/core/types.py +1 -1
- oscura/core/uncertainty.py +1 -1
- oscura/discovery/__init__.py +4 -4
- oscura/discovery/comparison.py +1 -1
- oscura/dsl/__init__.py +1 -1
- oscura/dsl/commands.py +22 -22
- oscura/dsl/interpreter.py +4 -4
- oscura/dsl/parser.py +4 -4
- oscura/dsl/repl.py +4 -4
- oscura/exceptions.py +3 -3
- oscura/export/__init__.py +2 -2
- oscura/export/wireshark/__init__.py +1 -1
- oscura/export/wireshark/generator.py +2 -2
- oscura/export/wireshark/templates/dissector.lua.j2 +1 -1
- oscura/export/wireshark/type_mapping.py +8 -8
- oscura/exporters/__init__.py +3 -3
- oscura/exporters/csv.py +1 -1
- oscura/exporters/html_export.py +5 -5
- oscura/exporters/json_export.py +9 -9
- oscura/exporters/markdown_export.py +3 -3
- oscura/exporters/npz_export.py +1 -1
- oscura/exporters/spice_export.py +1 -1
- oscura/extensibility/__init__.py +1 -1
- oscura/extensibility/docs.py +1 -1
- oscura/extensibility/extensions.py +7 -7
- oscura/extensibility/measurements.py +12 -12
- oscura/extensibility/plugins.py +12 -12
- oscura/extensibility/registry.py +12 -12
- oscura/extensibility/templates.py +16 -16
- oscura/extensibility/validation.py +3 -3
- oscura/filtering/__init__.py +1 -1
- oscura/filtering/base.py +1 -1
- oscura/filtering/convenience.py +1 -1
- oscura/filtering/design.py +1 -1
- oscura/filtering/introspection.py +1 -1
- oscura/guidance/__init__.py +1 -1
- oscura/guidance/recommender.py +1 -1
- oscura/guidance/wizard.py +1 -1
- oscura/inference/__init__.py +1 -1
- oscura/inference/adaptive_tuning.py +3 -3
- oscura/inference/logic.py +5 -5
- oscura/inference/protocol.py +5 -5
- oscura/inference/signal_intelligence.py +19 -19
- oscura/inference/spectral.py +6 -6
- oscura/integrations/__init__.py +1 -1
- oscura/integrations/llm.py +6 -6
- oscura/jupyter/__init__.py +3 -3
- oscura/jupyter/display.py +1 -1
- oscura/jupyter/magic.py +31 -31
- oscura/loaders/__init__.py +10 -10
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/pcap.py +1 -1
- oscura/math/__init__.py +1 -1
- oscura/math/arithmetic.py +1 -1
- oscura/math/interpolation.py +1 -1
- oscura/onboarding/__init__.py +1 -1
- oscura/onboarding/help.py +18 -18
- oscura/onboarding/tutorials.py +29 -29
- oscura/onboarding/wizard.py +22 -22
- oscura/pipeline/composition.py +15 -15
- oscura/pipeline/pipeline.py +10 -10
- oscura/plugins/__init__.py +2 -2
- oscura/plugins/base.py +5 -5
- oscura/plugins/discovery.py +6 -6
- oscura/plugins/registry.py +6 -6
- oscura/plugins/versioning.py +2 -2
- oscura/quality/__init__.py +1 -1
- oscura/quality/scoring.py +1 -1
- oscura/quality/warnings.py +1 -1
- oscura/reporting/__init__.py +1 -1
- oscura/reporting/advanced.py +1 -1
- oscura/reporting/analyze.py +1 -1
- oscura/reporting/auto_report.py +2 -2
- oscura/reporting/batch.py +3 -3
- oscura/reporting/chart_selection.py +1 -1
- oscura/reporting/comparison.py +1 -1
- oscura/reporting/core.py +4 -4
- oscura/reporting/export.py +1 -1
- oscura/reporting/formatting.py +1 -1
- oscura/reporting/html.py +3 -3
- oscura/reporting/multichannel.py +1 -1
- oscura/reporting/output.py +1 -1
- oscura/reporting/pdf.py +2 -2
- oscura/reporting/pptx_export.py +1 -1
- oscura/reporting/sections.py +1 -1
- oscura/reporting/standards.py +2 -2
- oscura/reporting/summary_generator.py +1 -1
- oscura/reporting/tables.py +1 -1
- oscura/reporting/template_system.py +1 -1
- oscura/reporting/templates/index.html +2 -2
- oscura/schemas/__init__.py +2 -2
- oscura/schemas/bus_configuration.json +1 -1
- oscura/schemas/device_mapping.json +1 -1
- oscura/schemas/packet_format.json +1 -1
- oscura/schemas/protocol_definition.json +1 -1
- oscura/search/__init__.py +1 -1
- oscura/session/__init__.py +4 -4
- oscura/session/history.py +7 -7
- oscura/session/session.py +2 -2
- oscura/streaming/chunked.py +7 -7
- oscura/testing/__init__.py +1 -1
- oscura/triggering/__init__.py +1 -1
- oscura/triggering/base.py +1 -1
- oscura/triggering/edge.py +1 -1
- oscura/triggering/pattern.py +1 -1
- oscura/triggering/pulse.py +1 -1
- oscura/triggering/window.py +1 -1
- oscura/ui/__init__.py +1 -1
- oscura/ui/formatters.py +1 -1
- oscura/ui/progressive_display.py +1 -1
- oscura/utils/__init__.py +1 -1
- oscura/utils/memory.py +3 -3
- oscura/utils/memory_extensions.py +1 -1
- oscura/visualization/__init__.py +1 -1
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/keyboard.py +1 -1
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/spectral.py +9 -9
- oscura/visualization/waveform.py +4 -4
- oscura/workflows/__init__.py +5 -5
- oscura/workflows/compliance.py +5 -5
- oscura/workflows/digital.py +5 -5
- oscura/workflows/multi_trace.py +25 -25
- oscura/workflows/power.py +7 -7
- oscura/workflows/protocol.py +5 -5
- oscura/workflows/reverse_engineering.py +5 -5
- oscura/workflows/signal_integrity.py +6 -6
- {oscura-0.1.0.dist-info → oscura-0.1.1.dist-info}/METADATA +11 -11
- {oscura-0.1.0.dist-info → oscura-0.1.1.dist-info}/RECORD +212 -212
- {oscura-0.1.0.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
- {oscura-0.1.0.dist-info → oscura-0.1.1.dist-info}/entry_points.txt +0 -0
- {oscura-0.1.0.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +0 -0
oscura/jupyter/magic.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""IPython magic commands for
|
|
1
|
+
"""IPython magic commands for Oscura.
|
|
2
2
|
|
|
3
3
|
This module provides IPython/Jupyter magic commands for convenient
|
|
4
4
|
trace analysis in notebooks.
|
|
@@ -82,8 +82,8 @@ except ImportError:
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
@magics_class
|
|
85
|
-
class
|
|
86
|
-
"""IPython magics for
|
|
85
|
+
class OscuraMagics(Magics): # type: ignore[misc]
|
|
86
|
+
"""IPython magics for Oscura analysis.
|
|
87
87
|
|
|
88
88
|
Provides convenient shortcuts for loading traces, running measurements,
|
|
89
89
|
and displaying results in Jupyter notebooks.
|
|
@@ -91,7 +91,7 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
91
91
|
|
|
92
92
|
@line_magic # type: ignore[misc, untyped-decorator]
|
|
93
93
|
def oscura(self, line: str) -> Any:
|
|
94
|
-
"""
|
|
94
|
+
"""Oscura line magic for quick operations.
|
|
95
95
|
|
|
96
96
|
Usage:
|
|
97
97
|
%oscura load <filename> - Load a trace file
|
|
@@ -106,7 +106,7 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
106
106
|
Returns:
|
|
107
107
|
Command result or None
|
|
108
108
|
"""
|
|
109
|
-
import oscura as
|
|
109
|
+
import oscura as osc
|
|
110
110
|
|
|
111
111
|
parts = line.strip().split()
|
|
112
112
|
if not parts:
|
|
@@ -129,7 +129,7 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
129
129
|
return self._show_trace_info()
|
|
130
130
|
|
|
131
131
|
elif cmd == "formats":
|
|
132
|
-
formats =
|
|
132
|
+
formats = osc.get_supported_formats()
|
|
133
133
|
print("Supported formats:")
|
|
134
134
|
for fmt in formats:
|
|
135
135
|
print(f" - {fmt}")
|
|
@@ -144,10 +144,10 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
144
144
|
|
|
145
145
|
def _load_trace(self, filename: str) -> Any:
|
|
146
146
|
"""Load a trace file and store as current."""
|
|
147
|
-
import oscura as
|
|
147
|
+
import oscura as osc
|
|
148
148
|
|
|
149
149
|
try:
|
|
150
|
-
trace =
|
|
150
|
+
trace = osc.load(filename)
|
|
151
151
|
set_current_trace(trace, filename)
|
|
152
152
|
|
|
153
153
|
# Display summary
|
|
@@ -169,7 +169,7 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
169
169
|
|
|
170
170
|
def _run_measurements(self, measurement_names: list[str] | None) -> dict[str, Any]:
|
|
171
171
|
"""Run measurements on current trace."""
|
|
172
|
-
import oscura as
|
|
172
|
+
import oscura as osc
|
|
173
173
|
|
|
174
174
|
trace = get_current_trace()
|
|
175
175
|
if trace is None:
|
|
@@ -180,9 +180,9 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
180
180
|
# Run specific measurements
|
|
181
181
|
results = {}
|
|
182
182
|
for name in measurement_names:
|
|
183
|
-
if hasattr(
|
|
183
|
+
if hasattr(osc, name):
|
|
184
184
|
try:
|
|
185
|
-
func = getattr(
|
|
185
|
+
func = getattr(osc, name)
|
|
186
186
|
results[name] = func(trace)
|
|
187
187
|
except Exception as e:
|
|
188
188
|
results[name] = f"Error: {e}"
|
|
@@ -191,7 +191,7 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
191
191
|
else:
|
|
192
192
|
# Run all measurements
|
|
193
193
|
try:
|
|
194
|
-
results =
|
|
194
|
+
results = osc.measure(trace)
|
|
195
195
|
except Exception as e:
|
|
196
196
|
print(f"Error running measurements: {e}")
|
|
197
197
|
return {}
|
|
@@ -243,7 +243,7 @@ class TracekitMagics(Magics): # type: ignore[misc]
|
|
|
243
243
|
def _show_help(self) -> None:
|
|
244
244
|
"""Show magic command help."""
|
|
245
245
|
help_text = """
|
|
246
|
-
|
|
246
|
+
Oscura Magic Commands
|
|
247
247
|
=======================
|
|
248
248
|
|
|
249
249
|
%oscura load <filename> Load a trace file
|
|
@@ -282,23 +282,23 @@ Example:
|
|
|
282
282
|
|
|
283
283
|
All oscura functions are auto-imported in the cell namespace.
|
|
284
284
|
"""
|
|
285
|
-
import oscura as
|
|
285
|
+
import oscura as osc
|
|
286
286
|
|
|
287
287
|
# Build execution namespace with oscura imports
|
|
288
288
|
namespace = {
|
|
289
|
-
"
|
|
290
|
-
"load":
|
|
291
|
-
"measure":
|
|
292
|
-
"fft":
|
|
293
|
-
"psd":
|
|
294
|
-
"thd":
|
|
295
|
-
"snr":
|
|
296
|
-
"rise_time":
|
|
297
|
-
"fall_time":
|
|
298
|
-
"frequency":
|
|
299
|
-
"amplitude":
|
|
300
|
-
"low_pass":
|
|
301
|
-
"high_pass":
|
|
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
302
|
}
|
|
303
303
|
|
|
304
304
|
# Add current trace if available
|
|
@@ -314,19 +314,19 @@ Example:
|
|
|
314
314
|
|
|
315
315
|
|
|
316
316
|
def load_ipython_extension(ipython: InteractiveShell) -> None:
|
|
317
|
-
"""Load
|
|
317
|
+
"""Load Oscura IPython extension.
|
|
318
318
|
|
|
319
319
|
Called when user runs: %load_ext oscura
|
|
320
320
|
|
|
321
321
|
Args:
|
|
322
322
|
ipython: The IPython shell instance
|
|
323
323
|
"""
|
|
324
|
-
ipython.register_magics(
|
|
325
|
-
print("
|
|
324
|
+
ipython.register_magics(OscuraMagics)
|
|
325
|
+
print("Oscura magics loaded. Type '%oscura help' for usage.")
|
|
326
326
|
|
|
327
327
|
|
|
328
328
|
def unload_ipython_extension(ipython: InteractiveShell) -> None:
|
|
329
|
-
"""Unload
|
|
329
|
+
"""Unload Oscura IPython extension.
|
|
330
330
|
|
|
331
331
|
Args:
|
|
332
332
|
ipython: The IPython shell instance
|
oscura/loaders/__init__.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Oscura data loaders for various file formats.
|
|
2
2
|
|
|
3
3
|
This module provides a unified load() function that auto-detects file formats
|
|
4
4
|
and delegates to the appropriate loader.
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> import oscura as
|
|
9
|
-
>>> trace =
|
|
8
|
+
>>> import oscura as osc
|
|
9
|
+
>>> trace = osc.load("capture.wfm")
|
|
10
10
|
>>> print(f"Loaded {len(trace.data)} samples")
|
|
11
11
|
|
|
12
12
|
>>> # Load all channels from multi-channel file
|
|
13
|
-
>>> channels =
|
|
13
|
+
>>> channels = osc.load_all_channels("multi_channel.wfm")
|
|
14
14
|
>>> for name, trace in channels.items():
|
|
15
15
|
... print(f"{name}: {len(trace.data)} samples")
|
|
16
16
|
"""
|
|
@@ -135,12 +135,12 @@ def load(
|
|
|
135
135
|
FileNotFoundError: If the file does not exist.
|
|
136
136
|
|
|
137
137
|
Example:
|
|
138
|
-
>>> import oscura as
|
|
139
|
-
>>> trace =
|
|
138
|
+
>>> import oscura as osc
|
|
139
|
+
>>> trace = osc.load("oscilloscope_capture.wfm")
|
|
140
140
|
>>> print(f"Loaded {len(trace.data)} samples at {trace.metadata.sample_rate} Hz")
|
|
141
141
|
|
|
142
142
|
>>> # Force specific loader
|
|
143
|
-
>>> trace =
|
|
143
|
+
>>> trace = osc.load("data.bin", format="tektronix")
|
|
144
144
|
|
|
145
145
|
>>> # Check if digital trace
|
|
146
146
|
>>> if isinstance(trace, DigitalTrace):
|
|
@@ -306,8 +306,8 @@ def load_all_channels(
|
|
|
306
306
|
FileNotFoundError: If the file does not exist.
|
|
307
307
|
|
|
308
308
|
Example:
|
|
309
|
-
>>> import oscura as
|
|
310
|
-
>>> channels =
|
|
309
|
+
>>> import oscura as osc
|
|
310
|
+
>>> channels = osc.load_all_channels("multi_channel.wfm")
|
|
311
311
|
>>> for name, trace in channels.items():
|
|
312
312
|
... print(f"{name}: {len(trace.data)} samples")
|
|
313
313
|
ch1: 10000 samples
|
|
@@ -472,7 +472,7 @@ def load_lazy(path: str | PathLike[str], **kwargs: Any) -> LazyWaveformTrace | W
|
|
|
472
472
|
LazyWaveformTrace or WaveformTrace.
|
|
473
473
|
|
|
474
474
|
Example:
|
|
475
|
-
>>> trace =
|
|
475
|
+
>>> trace = osc.loaders.load_lazy("huge_trace.npy", sample_rate=1e9)
|
|
476
476
|
>>> print(f"Length: {trace.length}") # Metadata available immediately
|
|
477
477
|
|
|
478
478
|
References:
|
oscura/loaders/mmap_loader.py
CHANGED
|
@@ -7,7 +7,7 @@ file into memory but access it in chunks on-demand via the OS page cache.
|
|
|
7
7
|
Key features:
|
|
8
8
|
- Zero-copy data access via numpy.memmap
|
|
9
9
|
- Chunked iteration for processing huge files
|
|
10
|
-
- Integration with existing
|
|
10
|
+
- Integration with existing Oscura loader infrastructure
|
|
11
11
|
- Support for common binary formats (raw, NPY, structured)
|
|
12
12
|
- Automatic fallback to regular loading for small files
|
|
13
13
|
|
oscura/loaders/pcap.py
CHANGED
oscura/math/__init__.py
CHANGED
oscura/math/arithmetic.py
CHANGED
oscura/math/interpolation.py
CHANGED
oscura/onboarding/__init__.py
CHANGED
oscura/onboarding/help.py
CHANGED
|
@@ -67,7 +67,7 @@ Common scales:
|
|
|
67
67
|
- 1 MHz = 1,000,000 cycles/second (radio, slow digital)
|
|
68
68
|
- 1 GHz = 1,000,000,000 cycles/second (fast digital, RF)
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
Oscura finds frequency by detecting repeated patterns in your signal.
|
|
71
71
|
""",
|
|
72
72
|
"when_to_use": [
|
|
73
73
|
"Verifying clock frequency",
|
|
@@ -143,7 +143,7 @@ harmonics (1x, 3x, 5x, etc. of the fundamental).
|
|
|
143
143
|
"related": ["psd", "thd", "snr", "spectrogram"],
|
|
144
144
|
},
|
|
145
145
|
"load": {
|
|
146
|
-
"summary": "Load a trace file -
|
|
146
|
+
"summary": "Load a trace file - Oscura's starting point",
|
|
147
147
|
"plain_english": """
|
|
148
148
|
load() reads waveform data from a file. It auto-detects the format,
|
|
149
149
|
so you don't need to specify whether it's CSV, WFM, HDF5, etc.
|
|
@@ -157,7 +157,7 @@ Supported formats: CSV, Tektronix WFM, Rigol WFM, NumPy NPZ,
|
|
|
157
157
|
HDF5, Sigrok sessions, VCD, TDMS, and more.
|
|
158
158
|
""",
|
|
159
159
|
"when_to_use": [
|
|
160
|
-
"Starting any
|
|
160
|
+
"Starting any Oscura analysis",
|
|
161
161
|
"Loading oscilloscope captures",
|
|
162
162
|
"Importing logic analyzer data",
|
|
163
163
|
],
|
|
@@ -189,7 +189,7 @@ Results are returned in a dictionary for easy access.
|
|
|
189
189
|
|
|
190
190
|
|
|
191
191
|
def get_help(topic: str) -> str | None:
|
|
192
|
-
"""Get plain English help for a
|
|
192
|
+
"""Get plain English help for a Oscura function or concept.
|
|
193
193
|
|
|
194
194
|
Args:
|
|
195
195
|
topic: Function name or concept to get help for
|
|
@@ -222,10 +222,10 @@ def get_help(topic: str) -> str | None:
|
|
|
222
222
|
|
|
223
223
|
# Try to get docstring
|
|
224
224
|
try:
|
|
225
|
-
import oscura as
|
|
225
|
+
import oscura as osc
|
|
226
226
|
|
|
227
|
-
if hasattr(
|
|
228
|
-
func = getattr(
|
|
227
|
+
if hasattr(osc, topic):
|
|
228
|
+
func = getattr(osc, topic)
|
|
229
229
|
if func.__doc__:
|
|
230
230
|
return f"Help for {topic}:\n\n{func.__doc__}"
|
|
231
231
|
except Exception:
|
|
@@ -467,29 +467,29 @@ def get_example(function_name: str) -> str | None:
|
|
|
467
467
|
examples = {
|
|
468
468
|
"load": """
|
|
469
469
|
# Load a trace file
|
|
470
|
-
import oscura as
|
|
471
|
-
trace =
|
|
470
|
+
import oscura as osc
|
|
471
|
+
trace = osc.load("capture.csv")
|
|
472
472
|
print(f"Loaded {len(trace.data)} samples")
|
|
473
473
|
""",
|
|
474
474
|
"rise_time": """
|
|
475
475
|
# Measure rise time
|
|
476
|
-
import oscura as
|
|
477
|
-
trace =
|
|
478
|
-
rt =
|
|
476
|
+
import oscura as osc
|
|
477
|
+
trace = osc.load("signal.csv")
|
|
478
|
+
rt = osc.rise_time(trace)
|
|
479
479
|
print(f"Rise time: {rt*1e9:.2f} ns")
|
|
480
480
|
""",
|
|
481
481
|
"fft": """
|
|
482
482
|
# Compute FFT spectrum
|
|
483
|
-
import oscura as
|
|
484
|
-
trace =
|
|
485
|
-
freq, mag =
|
|
483
|
+
import oscura as osc
|
|
484
|
+
trace = osc.load("signal.csv")
|
|
485
|
+
freq, mag = osc.fft(trace)
|
|
486
486
|
print(f"Frequency resolution: {freq[1]:.2f} Hz")
|
|
487
487
|
""",
|
|
488
488
|
"measure": """
|
|
489
489
|
# Run all measurements
|
|
490
|
-
import oscura as
|
|
491
|
-
trace =
|
|
492
|
-
results =
|
|
490
|
+
import oscura as osc
|
|
491
|
+
trace = osc.load("signal.csv")
|
|
492
|
+
results = osc.measure(trace)
|
|
493
493
|
for name, value in results.items():
|
|
494
494
|
print(f"{name}: {value}")
|
|
495
495
|
""",
|
oscura/onboarding/tutorials.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Interactive tutorial system for
|
|
1
|
+
"""Interactive tutorial system for Oscura.
|
|
2
2
|
|
|
3
3
|
This module provides step-by-step interactive tutorials for new users,
|
|
4
4
|
covering common analysis workflows.
|
|
@@ -11,7 +11,7 @@ covering common analysis workflows.
|
|
|
11
11
|
Example:
|
|
12
12
|
>>> from oscura.onboarding import run_tutorial
|
|
13
13
|
>>> run_tutorial("getting_started")
|
|
14
|
-
Welcome to
|
|
14
|
+
Welcome to Oscura!
|
|
15
15
|
Step 1/5: Loading a trace file
|
|
16
16
|
...
|
|
17
17
|
"""
|
|
@@ -74,17 +74,17 @@ def _register_getting_started() -> None:
|
|
|
74
74
|
TutorialStep(
|
|
75
75
|
title="Loading a Trace File",
|
|
76
76
|
description="""
|
|
77
|
-
|
|
77
|
+
Oscura can load waveform data from many file formats.
|
|
78
78
|
The simplest way is to use the load() function, which auto-detects the format.
|
|
79
79
|
|
|
80
80
|
Think of a trace like a recording of an electrical signal over time -
|
|
81
81
|
similar to how an audio file stores sound waves.
|
|
82
82
|
""",
|
|
83
83
|
code="""
|
|
84
|
-
import oscura as
|
|
84
|
+
import oscura as osc
|
|
85
85
|
|
|
86
86
|
# Load a waveform file (replace with your file path)
|
|
87
|
-
trace =
|
|
87
|
+
trace = osc.load("signal.csv")
|
|
88
88
|
|
|
89
89
|
# See basic info
|
|
90
90
|
print(f"Loaded {len(trace.data)} samples")
|
|
@@ -107,20 +107,20 @@ Once you have a trace, you can measure things like:
|
|
|
107
107
|
These are the same measurements an oscilloscope would show you!
|
|
108
108
|
""",
|
|
109
109
|
code="""
|
|
110
|
-
import oscura as
|
|
110
|
+
import oscura as osc
|
|
111
111
|
|
|
112
|
-
trace =
|
|
112
|
+
trace = osc.load("signal.csv")
|
|
113
113
|
|
|
114
114
|
# Measure rise time (10% to 90% transition)
|
|
115
|
-
rt =
|
|
115
|
+
rt = osc.rise_time(trace)
|
|
116
116
|
print(f"Rise time: {rt*1e9:.2f} nanoseconds")
|
|
117
117
|
|
|
118
118
|
# Measure frequency
|
|
119
|
-
freq =
|
|
119
|
+
freq = osc.frequency(trace)
|
|
120
120
|
print(f"Frequency: {freq/1e6:.2f} MHz")
|
|
121
121
|
|
|
122
122
|
# Get all measurements at once
|
|
123
|
-
results =
|
|
123
|
+
results = osc.measure(trace)
|
|
124
124
|
for name, value in results.items():
|
|
125
125
|
print(f"{name}: {value}")
|
|
126
126
|
""",
|
|
@@ -142,12 +142,12 @@ This is useful for:
|
|
|
142
142
|
It's like looking at a music equalizer that shows bass, mid, and treble!
|
|
143
143
|
""",
|
|
144
144
|
code="""
|
|
145
|
-
import oscura as
|
|
145
|
+
import oscura as osc
|
|
146
146
|
|
|
147
|
-
trace =
|
|
147
|
+
trace = osc.load("signal.csv")
|
|
148
148
|
|
|
149
149
|
# Compute FFT (Fast Fourier Transform)
|
|
150
|
-
freq, magnitude =
|
|
150
|
+
freq, magnitude = osc.fft(trace)
|
|
151
151
|
|
|
152
152
|
# Find the dominant frequency
|
|
153
153
|
import numpy as np
|
|
@@ -155,8 +155,8 @@ peak_idx = np.argmax(magnitude)
|
|
|
155
155
|
print(f"Dominant frequency: {freq[peak_idx]/1e6:.2f} MHz")
|
|
156
156
|
|
|
157
157
|
# Measure signal quality
|
|
158
|
-
thd_value =
|
|
159
|
-
snr_value =
|
|
158
|
+
thd_value = osc.thd(trace)
|
|
159
|
+
snr_value = osc.snr(trace)
|
|
160
160
|
print(f"THD: {thd_value:.1f} dB")
|
|
161
161
|
print(f"SNR: {snr_value:.1f} dB")
|
|
162
162
|
""",
|
|
@@ -170,15 +170,15 @@ print(f"SNR: {snr_value:.1f} dB")
|
|
|
170
170
|
title="Protocol Decoding",
|
|
171
171
|
description="""
|
|
172
172
|
If your signal is a digital communication protocol like UART, SPI, or I2C,
|
|
173
|
-
|
|
173
|
+
Oscura can decode it to show you the actual data being transmitted.
|
|
174
174
|
|
|
175
175
|
Think of it like translating Morse code back into text!
|
|
176
176
|
""",
|
|
177
177
|
code="""
|
|
178
|
-
import oscura as
|
|
178
|
+
import oscura as osc
|
|
179
179
|
|
|
180
180
|
# Load a UART signal
|
|
181
|
-
trace =
|
|
181
|
+
trace = osc.load("uart_signal.csv")
|
|
182
182
|
|
|
183
183
|
# Decode UART (auto-detects baud rate!)
|
|
184
184
|
from oscura.analyzers.protocols import decode_uart
|
|
@@ -197,7 +197,7 @@ for pkt in packets[:5]: # First 5 packets
|
|
|
197
197
|
TutorialStep(
|
|
198
198
|
title="Auto-Discovery for Beginners",
|
|
199
199
|
description="""
|
|
200
|
-
Not sure what your signal is?
|
|
200
|
+
Not sure what your signal is? Oscura can analyze it automatically!
|
|
201
201
|
|
|
202
202
|
The characterize_signal() function examines your trace and tells you:
|
|
203
203
|
- What type of signal it likely is
|
|
@@ -207,10 +207,10 @@ The characterize_signal() function examines your trace and tells you:
|
|
|
207
207
|
It's like having an expert look at your signal and give you hints!
|
|
208
208
|
""",
|
|
209
209
|
code="""
|
|
210
|
-
import oscura as
|
|
210
|
+
import oscura as osc
|
|
211
211
|
from oscura.discovery import characterize_signal
|
|
212
212
|
|
|
213
|
-
trace =
|
|
213
|
+
trace = osc.load("mystery_signal.csv")
|
|
214
214
|
|
|
215
215
|
# Auto-characterize the signal
|
|
216
216
|
result = characterize_signal(trace)
|
|
@@ -236,9 +236,9 @@ else:
|
|
|
236
236
|
|
|
237
237
|
tutorial = Tutorial(
|
|
238
238
|
id="getting_started",
|
|
239
|
-
title="Getting Started with
|
|
239
|
+
title="Getting Started with Oscura",
|
|
240
240
|
description="""
|
|
241
|
-
Welcome to
|
|
241
|
+
Welcome to Oscura! This tutorial will teach you the basics of
|
|
242
242
|
signal analysis in 5 easy steps:
|
|
243
243
|
|
|
244
244
|
1. Loading trace files
|
|
@@ -266,11 +266,11 @@ The Fast Fourier Transform (FFT) converts a time-domain signal into
|
|
|
266
266
|
its frequency components. Think of it as breaking a chord into individual notes.
|
|
267
267
|
""",
|
|
268
268
|
code="""
|
|
269
|
-
import oscura as
|
|
269
|
+
import oscura as osc
|
|
270
270
|
import numpy as np
|
|
271
271
|
|
|
272
|
-
trace =
|
|
273
|
-
freq, mag =
|
|
272
|
+
trace = osc.load("signal.csv")
|
|
273
|
+
freq, mag = osc.fft(trace)
|
|
274
274
|
|
|
275
275
|
# Magnitude is in dB (decibels)
|
|
276
276
|
# 0 dB = full scale, -20 dB = 10x smaller, -40 dB = 100x smaller
|
|
@@ -287,10 +287,10 @@ PSD is normalized per Hz, making it easier to compare signals with
|
|
|
287
287
|
different durations or sample rates.
|
|
288
288
|
""",
|
|
289
289
|
code="""
|
|
290
|
-
import oscura as
|
|
290
|
+
import oscura as osc
|
|
291
291
|
|
|
292
|
-
trace =
|
|
293
|
-
freq, psd =
|
|
292
|
+
trace = osc.load("signal.csv")
|
|
293
|
+
freq, psd = osc.psd(trace)
|
|
294
294
|
|
|
295
295
|
# Find where most power is concentrated
|
|
296
296
|
import numpy as np
|
oscura/onboarding/wizard.py
CHANGED
|
@@ -99,7 +99,7 @@ class AnalysisWizard:
|
|
|
99
99
|
title="Signal Type Detection",
|
|
100
100
|
question="What type of analysis do you want to perform?",
|
|
101
101
|
options=[
|
|
102
|
-
"Auto-detect (let
|
|
102
|
+
"Auto-detect (let Oscura figure it out)",
|
|
103
103
|
"Digital signal analysis",
|
|
104
104
|
"Analog/waveform analysis",
|
|
105
105
|
"Protocol decoding",
|
|
@@ -157,7 +157,7 @@ class AnalysisWizard:
|
|
|
157
157
|
WizardResult with all collected data
|
|
158
158
|
"""
|
|
159
159
|
print("\n" + "=" * 60)
|
|
160
|
-
print("
|
|
160
|
+
print("Oscura Analysis Wizard")
|
|
161
161
|
print("=" * 60)
|
|
162
162
|
print("Let's analyze your signal step by step.\n")
|
|
163
163
|
|
|
@@ -287,7 +287,7 @@ class AnalysisWizard:
|
|
|
287
287
|
|
|
288
288
|
def _handle_measurements(self, choice: int) -> None:
|
|
289
289
|
"""Handle measurement selection."""
|
|
290
|
-
import oscura as
|
|
290
|
+
import oscura as osc
|
|
291
291
|
|
|
292
292
|
if choice == 4: # Skip
|
|
293
293
|
print("\nSkipping measurements.")
|
|
@@ -297,22 +297,22 @@ class AnalysisWizard:
|
|
|
297
297
|
|
|
298
298
|
try:
|
|
299
299
|
if choice == 1: # All
|
|
300
|
-
results =
|
|
300
|
+
results = osc.measure(self.trace)
|
|
301
301
|
elif choice == 2: # Timing only
|
|
302
302
|
results = {
|
|
303
|
-
"rise_time":
|
|
304
|
-
"fall_time":
|
|
305
|
-
"frequency":
|
|
306
|
-
"period":
|
|
307
|
-
"duty_cycle":
|
|
303
|
+
"rise_time": osc.rise_time(self.trace),
|
|
304
|
+
"fall_time": osc.fall_time(self.trace),
|
|
305
|
+
"frequency": osc.frequency(self.trace),
|
|
306
|
+
"period": osc.period(self.trace),
|
|
307
|
+
"duty_cycle": osc.duty_cycle(self.trace),
|
|
308
308
|
}
|
|
309
309
|
elif choice == 3: # Amplitude only
|
|
310
310
|
results = {
|
|
311
|
-
"amplitude":
|
|
312
|
-
"rms":
|
|
313
|
-
"mean":
|
|
314
|
-
"overshoot":
|
|
315
|
-
"undershoot":
|
|
311
|
+
"amplitude": osc.amplitude(self.trace),
|
|
312
|
+
"rms": osc.rms(self.trace),
|
|
313
|
+
"mean": osc.mean(self.trace),
|
|
314
|
+
"overshoot": osc.overshoot(self.trace),
|
|
315
|
+
"undershoot": osc.undershoot(self.trace),
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
self.result.measurements.update(results)
|
|
@@ -331,7 +331,7 @@ class AnalysisWizard:
|
|
|
331
331
|
"""Handle spectral analysis selection."""
|
|
332
332
|
import numpy as np
|
|
333
333
|
|
|
334
|
-
import oscura as
|
|
334
|
+
import oscura as osc
|
|
335
335
|
|
|
336
336
|
if choice == 4: # Skip
|
|
337
337
|
print("\nSkipping spectral analysis.")
|
|
@@ -341,14 +341,14 @@ class AnalysisWizard:
|
|
|
341
341
|
|
|
342
342
|
try:
|
|
343
343
|
if choice in (1, 3): # FFT
|
|
344
|
-
freq, mag =
|
|
344
|
+
freq, mag = osc.fft(self.trace) # type: ignore[misc]
|
|
345
345
|
peak_idx = np.argmax(mag)
|
|
346
346
|
self.result.measurements["fft_peak_freq"] = freq[peak_idx]
|
|
347
347
|
self.result.measurements["fft_peak_mag"] = mag[peak_idx]
|
|
348
348
|
print(f" FFT peak: {freq[peak_idx] / 1e6:.3f} MHz at {mag[peak_idx]:.1f} dB")
|
|
349
349
|
|
|
350
350
|
if choice in (2, 3): # PSD
|
|
351
|
-
freq, _psd_vals =
|
|
351
|
+
freq, _psd_vals = osc.psd(self.trace)
|
|
352
352
|
self.result.measurements["psd_computed"] = True
|
|
353
353
|
print(f" PSD computed over {len(freq)} frequency bins")
|
|
354
354
|
|
|
@@ -357,7 +357,7 @@ class AnalysisWizard:
|
|
|
357
357
|
|
|
358
358
|
def _handle_quality(self, choice: int) -> None:
|
|
359
359
|
"""Handle quality assessment selection."""
|
|
360
|
-
import oscura as
|
|
360
|
+
import oscura as osc
|
|
361
361
|
|
|
362
362
|
if choice == 4: # Skip
|
|
363
363
|
print("\nSkipping quality assessment.")
|
|
@@ -367,8 +367,8 @@ class AnalysisWizard:
|
|
|
367
367
|
|
|
368
368
|
try:
|
|
369
369
|
if choice in (1, 3): # THD and SNR
|
|
370
|
-
thd_val =
|
|
371
|
-
snr_val =
|
|
370
|
+
thd_val = osc.thd(self.trace)
|
|
371
|
+
snr_val = osc.snr(self.trace)
|
|
372
372
|
self.result.measurements["thd"] = thd_val
|
|
373
373
|
self.result.measurements["snr"] = snr_val
|
|
374
374
|
print(f" THD: {thd_val:.1f} dB")
|
|
@@ -457,9 +457,9 @@ def run_wizard(trace: Any, interactive: bool = True) -> WizardResult:
|
|
|
457
457
|
WizardResult with measurements, recommendations, and summary
|
|
458
458
|
|
|
459
459
|
Example:
|
|
460
|
-
>>> import oscura as
|
|
460
|
+
>>> import oscura as osc
|
|
461
461
|
>>> from oscura.onboarding import run_wizard
|
|
462
|
-
>>> trace =
|
|
462
|
+
>>> trace = osc.load("signal.csv")
|
|
463
463
|
>>> result = run_wizard(trace)
|
|
464
464
|
"""
|
|
465
465
|
wizard = AnalysisWizard(trace)
|