Coverage for src / tracekit / jupyter / magic.py: 15%
119 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""IPython magic commands for TraceKit.
3This module provides IPython/Jupyter magic commands for convenient
4trace analysis in notebooks.
6 - %tracekit load <file> - Load a trace file
7 - %tracekit measure - Run measurements on current trace
8 - %%analyze - Multi-line analysis cell
9 - Auto-display of results with rich HTML
11Example:
12 In [1]: %load_ext tracekit
14 In [2]: %tracekit load capture.wfm
15 Loaded: WaveformTrace with 10000 samples @ 1 GSa/s
17 In [3]: %tracekit measure rise_time fall_time
18 rise_time: 2.5 ns
19 fall_time: 2.8 ns
21 In [4]: %%analyze
22 ...: trace = load("capture.wfm")
23 ...: print(f"THD: {thd(trace):.2f} dB")
25References:
26 - IPython Magic Commands documentation
27 - Jupyter display architecture
28"""
30from __future__ import annotations
32from typing import TYPE_CHECKING, Any
34if TYPE_CHECKING:
35 from IPython.core.interactiveshell import InteractiveShell
38# Store current trace for magic commands
39_current_trace: Any = None
40_current_file: str | None = None
43def get_current_trace() -> Any:
44 """Get the currently loaded trace from magic commands."""
45 return _current_trace
48def 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
55try:
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
64 IPYTHON_AVAILABLE = True
65except ImportError:
66 IPYTHON_AVAILABLE = False
68 class Magics: # type: ignore[no-redef]
69 """Fallback Magics class when IPython not available."""
71 pass
73 def magics_class(cls: type[Any]) -> type[Any]: # type: ignore[no-redef,misc]
74 """Dummy decorator when IPython not available."""
75 return cls
77 def line_magic(func): # type: ignore[no-untyped-def]
78 """Dummy decorator."""
79 return func
81 def cell_magic(func): # type: ignore[no-untyped-def]
82 """Dummy decorator."""
83 return func
86@magics_class
87class TracekitMagics(Magics): # type: ignore[misc]
88 """IPython magics for TraceKit analysis.
90 Provides convenient shortcuts for loading traces, running measurements,
91 and displaying results in Jupyter notebooks.
92 """
94 @line_magic # type: ignore[misc, untyped-decorator]
95 def tracekit(self, line: str) -> Any:
96 """TraceKit line magic for quick operations.
98 Usage:
99 %tracekit load <filename> - Load a trace file
100 %tracekit measure [names...] - Run measurements
101 %tracekit info - Show current trace info
102 %tracekit formats - List supported formats
103 %tracekit help - Show help
105 Args:
106 line: Magic command arguments
108 Returns:
109 Command result or None
110 """
111 import tracekit as tk
113 parts = line.strip().split()
114 if not parts:
115 return self._show_help()
117 cmd = parts[0].lower()
119 if cmd == "load":
120 if len(parts) < 2:
121 print("Usage: %tracekit load <filename>")
122 return None
123 filename = " ".join(parts[1:])
124 return self._load_trace(filename)
126 elif cmd == "measure":
127 measurements = parts[1:] if len(parts) > 1 else None
128 return self._run_measurements(measurements)
130 elif cmd == "info":
131 return self._show_trace_info()
133 elif cmd == "formats":
134 formats = tk.get_supported_formats()
135 print("Supported formats:")
136 for fmt in formats:
137 print(f" - {fmt}")
138 return formats
140 elif cmd == "help":
141 return self._show_help()
143 else:
144 print(f"Unknown command: {cmd}")
145 return self._show_help()
147 def _load_trace(self, filename: str) -> Any:
148 """Load a trace file and store as current."""
149 import tracekit as tk
151 try:
152 trace = tk.load(filename)
153 set_current_trace(trace, filename)
155 # Display summary
156 info = {
157 "file": filename,
158 "type": type(trace).__name__,
159 "samples": len(trace.data) if hasattr(trace, "data") else "N/A",
160 "sample_rate": f"{trace.metadata.sample_rate / 1e9:.3f} GSa/s"
161 if hasattr(trace, "metadata")
162 else "N/A",
163 }
165 print(f"Loaded: {info['type']} with {info['samples']} samples @ {info['sample_rate']}")
166 return trace
168 except Exception as e:
169 print(f"Error loading {filename}: {e}")
170 return None
172 def _run_measurements(self, measurement_names: list[str] | None) -> dict[str, Any]:
173 """Run measurements on current trace."""
174 import tracekit as tk
176 trace = get_current_trace()
177 if trace is None:
178 print("No trace loaded. Use: %tracekit load <filename>")
179 return {}
181 if measurement_names:
182 # Run specific measurements
183 results = {}
184 for name in measurement_names:
185 if hasattr(tk, name):
186 try:
187 func = getattr(tk, name)
188 results[name] = func(trace)
189 except Exception as e:
190 results[name] = f"Error: {e}"
191 else:
192 results[name] = "Unknown measurement"
193 else:
194 # Run all measurements
195 try:
196 results = tk.measure(trace)
197 except Exception as e:
198 print(f"Error running measurements: {e}")
199 return {}
201 # Display results
202 self._display_measurements(results)
203 return results
205 def _display_measurements(self, results: dict[str, Any]) -> None:
206 """Display measurement results with formatting."""
207 if IPYTHON_AVAILABLE:
208 from tracekit.jupyter.display import display_measurements
210 display_measurements(results)
211 else:
212 for name, value in results.items():
213 if isinstance(value, float):
214 print(f"{name}: {value:.6g}")
215 else:
216 print(f"{name}: {value}")
218 def _show_trace_info(self) -> dict[str, Any] | None:
219 """Show information about current trace."""
220 trace = get_current_trace()
221 if trace is None:
222 print("No trace loaded. Use: %tracekit load <filename>")
223 return None
225 info = {
226 "file": _current_file,
227 "type": type(trace).__name__,
228 }
230 if hasattr(trace, "data"):
231 info["samples"] = len(trace.data) # type: ignore[assignment]
233 if hasattr(trace, "metadata"):
234 meta = trace.metadata
235 if hasattr(meta, "sample_rate"):
236 info["sample_rate"] = meta.sample_rate
237 if hasattr(meta, "channel_name"):
238 info["channel"] = meta.channel_name
240 for key, value in info.items():
241 print(f"{key}: {value}")
243 return info
245 def _show_help(self) -> None:
246 """Show magic command help."""
247 help_text = """
248TraceKit Magic Commands
249=======================
251%tracekit load <filename> Load a trace file
252%tracekit measure [names...] Run measurements on current trace
253%tracekit info Show current trace info
254%tracekit formats List supported file formats
255%tracekit help Show this help
257%%analyze Multi-line analysis cell magic
259Available measurements: rise_time, fall_time, frequency, period,
260 amplitude, rms, overshoot, undershoot, thd, snr, sinad
262Example:
263 %tracekit load capture.wfm
264 %tracekit measure rise_time fall_time
265"""
266 print(help_text)
268 @cell_magic # type: ignore[misc, untyped-decorator]
269 def analyze(self, line: str, cell: str) -> Any:
270 """Cell magic for multi-line analysis.
272 Args:
273 line: Magic command line arguments (unused).
274 cell: Multi-line cell content to execute.
276 Returns:
277 Result from cell execution (if 'result' variable defined).
279 Usage:
280 %%analyze
281 trace = load("capture.wfm")
282 result = measure(trace)
283 print(f"Rise time: {result['rise_time']}")
285 All tracekit functions are auto-imported in the cell namespace.
286 """
287 import tracekit as tk
289 # Build execution namespace with tracekit imports
290 namespace = {
291 "tk": tk,
292 "load": tk.load,
293 "measure": tk.measure,
294 "fft": tk.fft,
295 "psd": tk.psd,
296 "thd": tk.thd,
297 "snr": tk.snr,
298 "rise_time": tk.rise_time,
299 "fall_time": tk.fall_time,
300 "frequency": tk.frequency,
301 "amplitude": tk.amplitude,
302 "low_pass": tk.low_pass,
303 "high_pass": tk.high_pass,
304 }
306 # Add current trace if available
307 trace = get_current_trace()
308 if trace is not None:
309 namespace["trace"] = trace
311 # Execute cell
312 exec(cell, namespace)
314 # Return any result variable if defined
315 return namespace.get("result")
318def load_ipython_extension(ipython: InteractiveShell) -> None:
319 """Load TraceKit IPython extension.
321 Called when user runs: %load_ext tracekit
323 Args:
324 ipython: The IPython shell instance
325 """
326 ipython.register_magics(TracekitMagics)
327 print("TraceKit magics loaded. Type '%tracekit help' for usage.")
330def unload_ipython_extension(ipython: InteractiveShell) -> None:
331 """Unload TraceKit IPython extension.
333 Args:
334 ipython: The IPython shell instance
335 """
336 # IPython handles magic cleanup automatically
337 pass