oscura 0.8.0__py3-none-any.whl → 0.10.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.
Files changed (151) hide show
  1. oscura/__init__.py +19 -19
  2. oscura/analyzers/__init__.py +2 -0
  3. oscura/analyzers/digital/extraction.py +2 -3
  4. oscura/analyzers/digital/quality.py +1 -1
  5. oscura/analyzers/digital/timing.py +1 -1
  6. oscura/analyzers/patterns/__init__.py +66 -0
  7. oscura/analyzers/power/basic.py +3 -3
  8. oscura/analyzers/power/soa.py +1 -1
  9. oscura/analyzers/power/switching.py +3 -3
  10. oscura/analyzers/signal_classification.py +529 -0
  11. oscura/analyzers/signal_integrity/sparams.py +3 -3
  12. oscura/analyzers/statistics/basic.py +10 -7
  13. oscura/analyzers/validation.py +1 -1
  14. oscura/analyzers/waveform/measurements.py +200 -156
  15. oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
  16. oscura/analyzers/waveform/spectral.py +164 -73
  17. oscura/api/dsl/commands.py +15 -6
  18. oscura/api/server/templates/base.html +137 -146
  19. oscura/api/server/templates/export.html +84 -110
  20. oscura/api/server/templates/home.html +248 -267
  21. oscura/api/server/templates/protocols.html +44 -48
  22. oscura/api/server/templates/reports.html +27 -35
  23. oscura/api/server/templates/session_detail.html +68 -78
  24. oscura/api/server/templates/sessions.html +62 -72
  25. oscura/api/server/templates/waveforms.html +54 -64
  26. oscura/automotive/__init__.py +1 -1
  27. oscura/automotive/can/session.py +1 -1
  28. oscura/automotive/dbc/generator.py +638 -23
  29. oscura/automotive/uds/decoder.py +99 -6
  30. oscura/cli/analyze.py +8 -2
  31. oscura/cli/batch.py +36 -5
  32. oscura/cli/characterize.py +18 -4
  33. oscura/cli/export.py +47 -5
  34. oscura/cli/main.py +2 -0
  35. oscura/cli/onboarding/wizard.py +10 -6
  36. oscura/cli/pipeline.py +585 -0
  37. oscura/cli/visualize.py +6 -4
  38. oscura/convenience.py +400 -32
  39. oscura/core/measurement_result.py +286 -0
  40. oscura/core/progress.py +1 -1
  41. oscura/core/types.py +232 -239
  42. oscura/correlation/multi_protocol.py +1 -1
  43. oscura/export/legacy/__init__.py +11 -0
  44. oscura/export/legacy/wav.py +75 -0
  45. oscura/exporters/__init__.py +19 -0
  46. oscura/exporters/wireshark.py +809 -0
  47. oscura/hardware/acquisition/file.py +5 -19
  48. oscura/hardware/acquisition/saleae.py +10 -10
  49. oscura/hardware/acquisition/socketcan.py +4 -6
  50. oscura/hardware/acquisition/synthetic.py +1 -5
  51. oscura/hardware/acquisition/visa.py +6 -6
  52. oscura/hardware/security/side_channel_detector.py +5 -508
  53. oscura/inference/message_format.py +686 -1
  54. oscura/jupyter/display.py +2 -2
  55. oscura/jupyter/magic.py +3 -3
  56. oscura/loaders/__init__.py +17 -12
  57. oscura/loaders/binary.py +1 -1
  58. oscura/loaders/chipwhisperer.py +1 -2
  59. oscura/loaders/configurable.py +1 -1
  60. oscura/loaders/csv_loader.py +2 -2
  61. oscura/loaders/hdf5_loader.py +1 -1
  62. oscura/loaders/lazy.py +6 -1
  63. oscura/loaders/mmap_loader.py +0 -1
  64. oscura/loaders/numpy_loader.py +8 -7
  65. oscura/loaders/preprocessing.py +3 -5
  66. oscura/loaders/rigol.py +21 -7
  67. oscura/loaders/sigrok.py +2 -5
  68. oscura/loaders/tdms.py +3 -2
  69. oscura/loaders/tektronix.py +38 -32
  70. oscura/loaders/tss.py +20 -27
  71. oscura/loaders/vcd.py +13 -8
  72. oscura/loaders/wav.py +1 -6
  73. oscura/pipeline/__init__.py +76 -0
  74. oscura/pipeline/handlers/__init__.py +165 -0
  75. oscura/pipeline/handlers/analyzers.py +1045 -0
  76. oscura/pipeline/handlers/decoders.py +899 -0
  77. oscura/pipeline/handlers/exporters.py +1103 -0
  78. oscura/pipeline/handlers/filters.py +891 -0
  79. oscura/pipeline/handlers/loaders.py +640 -0
  80. oscura/pipeline/handlers/transforms.py +768 -0
  81. oscura/reporting/formatting/measurements.py +55 -14
  82. oscura/reporting/templates/enhanced/protocol_re.html +504 -503
  83. oscura/side_channel/__init__.py +38 -57
  84. oscura/utils/builders/signal_builder.py +5 -5
  85. oscura/utils/comparison/compare.py +7 -9
  86. oscura/utils/comparison/golden.py +1 -1
  87. oscura/utils/filtering/convenience.py +2 -2
  88. oscura/utils/math/arithmetic.py +38 -62
  89. oscura/utils/math/interpolation.py +20 -20
  90. oscura/utils/pipeline/__init__.py +4 -17
  91. oscura/utils/progressive.py +1 -4
  92. oscura/utils/triggering/edge.py +1 -1
  93. oscura/utils/triggering/pattern.py +2 -2
  94. oscura/utils/triggering/pulse.py +2 -2
  95. oscura/utils/triggering/window.py +3 -3
  96. oscura/validation/hil_testing.py +11 -11
  97. oscura/visualization/__init__.py +46 -284
  98. oscura/visualization/batch.py +72 -433
  99. oscura/visualization/plot.py +542 -53
  100. oscura/visualization/styles.py +184 -318
  101. oscura/workflows/batch/advanced.py +1 -1
  102. oscura/workflows/batch/aggregate.py +7 -8
  103. oscura/workflows/complete_re.py +251 -23
  104. oscura/workflows/digital.py +27 -4
  105. oscura/workflows/multi_trace.py +136 -17
  106. oscura/workflows/waveform.py +11 -6
  107. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
  108. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/RECORD +111 -136
  109. oscura/side_channel/dpa.py +0 -1025
  110. oscura/utils/optimization/__init__.py +0 -19
  111. oscura/utils/optimization/parallel.py +0 -443
  112. oscura/utils/optimization/search.py +0 -532
  113. oscura/utils/pipeline/base.py +0 -338
  114. oscura/utils/pipeline/composition.py +0 -248
  115. oscura/utils/pipeline/parallel.py +0 -449
  116. oscura/utils/pipeline/pipeline.py +0 -375
  117. oscura/utils/search/__init__.py +0 -16
  118. oscura/utils/search/anomaly.py +0 -424
  119. oscura/utils/search/context.py +0 -294
  120. oscura/utils/search/pattern.py +0 -288
  121. oscura/utils/storage/__init__.py +0 -61
  122. oscura/utils/storage/database.py +0 -1166
  123. oscura/visualization/accessibility.py +0 -526
  124. oscura/visualization/annotations.py +0 -371
  125. oscura/visualization/axis_scaling.py +0 -305
  126. oscura/visualization/colors.py +0 -451
  127. oscura/visualization/digital.py +0 -436
  128. oscura/visualization/eye.py +0 -571
  129. oscura/visualization/histogram.py +0 -281
  130. oscura/visualization/interactive.py +0 -1035
  131. oscura/visualization/jitter.py +0 -1042
  132. oscura/visualization/keyboard.py +0 -394
  133. oscura/visualization/layout.py +0 -400
  134. oscura/visualization/optimization.py +0 -1079
  135. oscura/visualization/palettes.py +0 -446
  136. oscura/visualization/power.py +0 -508
  137. oscura/visualization/power_extended.py +0 -955
  138. oscura/visualization/presets.py +0 -469
  139. oscura/visualization/protocols.py +0 -1246
  140. oscura/visualization/render.py +0 -223
  141. oscura/visualization/rendering.py +0 -444
  142. oscura/visualization/reverse_engineering.py +0 -838
  143. oscura/visualization/signal_integrity.py +0 -989
  144. oscura/visualization/specialized.py +0 -643
  145. oscura/visualization/spectral.py +0 -1226
  146. oscura/visualization/thumbnails.py +0 -340
  147. oscura/visualization/time_axis.py +0 -351
  148. oscura/visualization/waveform.py +0 -454
  149. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
  150. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
  151. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,394 +0,0 @@
1
- """Keyboard navigation support for Oscura interactive visualizations.
2
-
3
- This module provides keyboard navigation handlers for interactive plots
4
- following WCAG 2.1 accessibility guidelines.
5
-
6
-
7
- Example:
8
- >>> from oscura.visualization.keyboard import KeyboardNavigator
9
- >>> navigator = KeyboardNavigator(fig, ax)
10
- >>> navigator.connect()
11
-
12
- References:
13
- - WCAG 2.1 Guideline 2.1: Keyboard Accessible
14
- - WAI-ARIA Authoring Practices for interactive widgets
15
- """
16
-
17
- from __future__ import annotations
18
-
19
- from typing import TYPE_CHECKING, Any
20
-
21
- from matplotlib.axes import Axes
22
-
23
- if TYPE_CHECKING:
24
- from matplotlib.backend_bases import KeyEvent
25
- from matplotlib.figure import Figure
26
- from matplotlib.text import Text
27
-
28
-
29
- class KeyboardNavigator:
30
- """Keyboard navigation handler for interactive plots.
31
-
32
- : Interactive visualizations are fully keyboard-navigable.
33
- Provides keyboard shortcuts for panning, zooming, and navigation.
34
-
35
- Keyboard shortcuts:
36
- - Arrow keys: Pan left/right/up/down
37
- - +/-: Zoom in/out
38
- - Home: Reset to full view
39
- - Tab: Cycle through subplots
40
- - Escape: Cancel current operation
41
- - ?: Show help
42
-
43
- Args:
44
- fig: Matplotlib figure
45
- axes: Matplotlib axes or list of axes
46
- pan_step: Pan step as fraction of current range (default: 0.1)
47
- zoom_factor: Zoom factor for +/- keys (default: 1.2)
48
-
49
- Example:
50
- >>> import matplotlib.pyplot as plt
51
- >>> from oscura.visualization.keyboard import KeyboardNavigator
52
- >>> fig, ax = plt.subplots()
53
- >>> ax.plot([1, 2, 3], [1, 4, 2])
54
- >>> nav = KeyboardNavigator(fig, ax)
55
- >>> nav.connect()
56
- >>> plt.show()
57
-
58
- References:
59
- ACC-003: Keyboard Navigation for Interactive Plots
60
- """
61
-
62
- def __init__(
63
- self,
64
- fig: Figure,
65
- axes: Axes | list[Axes],
66
- *,
67
- pan_step: float = 0.1,
68
- zoom_factor: float = 1.2,
69
- ) -> None:
70
- """Initialize keyboard navigator.
71
-
72
- Args:
73
- fig: Matplotlib figure
74
- axes: Single axes or list of axes
75
- pan_step: Pan step as fraction of range
76
- zoom_factor: Zoom factor for zoom operations
77
- """
78
- self.fig = fig
79
- self.axes_list = [axes] if isinstance(axes, Axes) else list(axes)
80
- self.current_axes_index = 0
81
- self.pan_step = pan_step
82
- self.zoom_factor = zoom_factor
83
-
84
- # Store original limits for reset
85
- self.original_limits = {}
86
- for i, ax in enumerate(self.axes_list):
87
- self.original_limits[i] = {
88
- "xlim": ax.get_xlim(),
89
- "ylim": ax.get_ylim(),
90
- }
91
-
92
- self._connection_id: int | None = None
93
- self._help_text: Text | None = None
94
-
95
- def connect(self) -> None:
96
- """Connect keyboard event handler to the figure.
97
-
98
- : Tab navigates between plot elements.
99
- Registers keyboard event callback with matplotlib.
100
-
101
- Example:
102
- >>> nav.connect()
103
- >>> # Now keyboard events are handled
104
-
105
- References:
106
- ACC-003: Keyboard Navigation for Interactive Plots
107
- """
108
- self._connection_id = self.fig.canvas.mpl_connect("key_press_event", self._on_key) # type: ignore[arg-type]
109
- self._highlight_active_axes()
110
-
111
- def disconnect(self) -> None:
112
- """Disconnect keyboard event handler.
113
-
114
- Example:
115
- >>> nav.disconnect()
116
-
117
- References:
118
- ACC-003: Keyboard Navigation for Interactive Plots
119
- """
120
- if self._connection_id is not None:
121
- self.fig.canvas.mpl_disconnect(self._connection_id)
122
- self._connection_id = None
123
-
124
- def _on_key(self, event: KeyEvent) -> None:
125
- """Handle keyboard events.
126
-
127
- : Arrow keys move cursors, Enter selects/activates, Escape closes.
128
-
129
- Args:
130
- event: Matplotlib keyboard event
131
-
132
- References:
133
- ACC-003: Keyboard Navigation for Interactive Plots
134
- """
135
- if event.key is None:
136
- return
137
-
138
- ax = self.axes_list[self.current_axes_index]
139
-
140
- # Arrow keys: Pan
141
- if event.key == "left":
142
- self._pan(ax, dx=-self.pan_step, dy=0)
143
- elif event.key == "right":
144
- self._pan(ax, dx=self.pan_step, dy=0)
145
- elif event.key == "up":
146
- self._pan(ax, dx=0, dy=self.pan_step)
147
- elif event.key == "down":
148
- self._pan(ax, dx=0, dy=-self.pan_step)
149
-
150
- # Zoom
151
- elif event.key == "+" or event.key == "=":
152
- self._zoom(ax, factor=1.0 / self.zoom_factor)
153
- elif event.key == "-" or event.key == "_":
154
- self._zoom(ax, factor=self.zoom_factor)
155
-
156
- # Reset view
157
- elif event.key == "home":
158
- self._reset_view(ax)
159
-
160
- # Tab: Cycle through axes
161
- elif event.key == "tab":
162
- self._cycle_axes(reverse=event.key == "shift+tab")
163
-
164
- # Help
165
- elif event.key == "?":
166
- self._show_help()
167
-
168
- # Escape: Close help or reset
169
- elif event.key == "escape":
170
- self._hide_help()
171
-
172
- else:
173
- return # Unhandled key
174
-
175
- self.fig.canvas.draw_idle()
176
-
177
- def _pan(self, ax: Axes, dx: float, dy: float) -> None:
178
- """Pan the axes view.
179
-
180
- Args:
181
- ax: Axes to pan
182
- dx: Horizontal pan as fraction of range
183
- dy: Vertical pan as fraction of range
184
-
185
- References:
186
- ACC-003: Arrow keys move cursors
187
- """
188
- xlim = ax.get_xlim()
189
- ylim = ax.get_ylim()
190
-
191
- x_range = xlim[1] - xlim[0]
192
- y_range = ylim[1] - ylim[0]
193
-
194
- x_shift = dx * x_range
195
- y_shift = dy * y_range
196
-
197
- ax.set_xlim(xlim[0] + x_shift, xlim[1] + x_shift)
198
- ax.set_ylim(ylim[0] + y_shift, ylim[1] + y_shift)
199
-
200
- def _zoom(self, ax: Axes, factor: float) -> None:
201
- """Zoom the axes view.
202
-
203
- Args:
204
- ax: Axes to zoom
205
- factor: Zoom factor (>1 zooms out, <1 zooms in)
206
-
207
- References:
208
- ACC-003: +/- keys zoom in/out
209
- """
210
- xlim = ax.get_xlim()
211
- ylim = ax.get_ylim()
212
-
213
- x_center = (xlim[0] + xlim[1]) / 2
214
- y_center = (ylim[0] + ylim[1]) / 2
215
-
216
- x_range = (xlim[1] - xlim[0]) * factor
217
- y_range = (ylim[1] - ylim[0]) * factor
218
-
219
- ax.set_xlim(x_center - x_range / 2, x_center + x_range / 2)
220
- ax.set_ylim(y_center - y_range / 2, y_center + y_range / 2)
221
-
222
- def _reset_view(self, ax: Axes) -> None:
223
- """Reset axes to original view.
224
-
225
- Args:
226
- ax: Axes to reset
227
-
228
- References:
229
- ACC-003: Home resets to full view
230
- """
231
- idx = self.axes_list.index(ax)
232
- original = self.original_limits[idx]
233
- ax.set_xlim(original["xlim"])
234
- ax.set_ylim(original["ylim"])
235
-
236
- def _cycle_axes(self, reverse: bool = False) -> None:
237
- """Cycle through axes with Tab key.
238
-
239
- Args:
240
- reverse: Cycle backwards (Shift+Tab)
241
-
242
- References:
243
- ACC-003: Tab navigates between plot elements
244
- """
245
- if len(self.axes_list) <= 1:
246
- return
247
-
248
- # Remove highlight from current axes
249
- self._unhighlight_axes(self.axes_list[self.current_axes_index])
250
-
251
- # Move to next/previous axes
252
- if reverse:
253
- self.current_axes_index = (self.current_axes_index - 1) % len(self.axes_list)
254
- else:
255
- self.current_axes_index = (self.current_axes_index + 1) % len(self.axes_list)
256
-
257
- # Highlight new axes
258
- self._highlight_active_axes()
259
-
260
- def _highlight_active_axes(self) -> None:
261
- """Highlight the currently active axes.
262
-
263
- : Focus indicators for selected element.
264
-
265
- References:
266
- ACC-003: Focus indicators for selected element
267
- """
268
- ax = self.axes_list[self.current_axes_index]
269
- # Add visual focus indicator
270
- for spine in ax.spines.values():
271
- spine.set_edgecolor("red")
272
- spine.set_linewidth(2)
273
-
274
- def _unhighlight_axes(self, ax: Axes) -> None:
275
- """Remove highlight from axes.
276
-
277
- Args:
278
- ax: Axes to unhighlight
279
-
280
- References:
281
- ACC-003: Focus indicators for selected element
282
- """
283
- for spine in ax.spines.values():
284
- spine.set_edgecolor("black")
285
- spine.set_linewidth(1)
286
-
287
- def _show_help(self) -> None:
288
- """Show keyboard shortcuts help.
289
-
290
- : ? key shows keyboard shortcuts help.
291
-
292
- References:
293
- ACC-003: ? key shows keyboard shortcuts help
294
- """
295
- if self._help_text is not None:
296
- return # Already showing
297
-
298
- help_message = """
299
- Keyboard Navigation Help
300
- ========================
301
-
302
- Pan:
303
- ←/→/↑/↓ Pan left/right/up/down
304
-
305
- Zoom:
306
- +/- Zoom in/out
307
-
308
- View:
309
- Home Reset to full view
310
-
311
- Navigation:
312
- Tab Next subplot
313
- Shift+Tab Previous subplot
314
-
315
- Help:
316
- ? Show this help
317
- Esc Close help
318
-
319
- Press Esc to close this help.
320
- """
321
- # Add text box to figure
322
- self._help_text = self.fig.text(
323
- 0.5,
324
- 0.5,
325
- help_message,
326
- ha="center",
327
- va="center",
328
- fontsize=10,
329
- family="monospace",
330
- bbox={
331
- "boxstyle": "round",
332
- "facecolor": "wheat",
333
- "alpha": 0.95,
334
- "edgecolor": "black",
335
- "linewidth": 2,
336
- },
337
- zorder=1000,
338
- )
339
- self.fig.canvas.draw_idle()
340
-
341
- def _hide_help(self) -> None:
342
- """Hide keyboard shortcuts help.
343
-
344
- : Escape closes modals/menus.
345
-
346
- References:
347
- ACC-003: Escape closes modals/menus
348
- """
349
- if self._help_text is not None:
350
- self._help_text.remove()
351
- self._help_text = None
352
- self.fig.canvas.draw_idle()
353
-
354
-
355
- def enable_keyboard_navigation(
356
- fig: Figure,
357
- axes: Axes | list[Axes] | None = None,
358
- **kwargs: Any,
359
- ) -> KeyboardNavigator:
360
- """Enable keyboard navigation for a figure.
361
-
362
- Convenience function to create and connect a KeyboardNavigator.
363
-
364
- Args:
365
- fig: Matplotlib figure
366
- axes: Axes to navigate (default: all axes in figure)
367
- **kwargs: Additional arguments passed to KeyboardNavigator
368
-
369
- Returns:
370
- Connected KeyboardNavigator instance
371
-
372
- Example:
373
- >>> import matplotlib.pyplot as plt
374
- >>> from oscura.visualization.keyboard import enable_keyboard_navigation
375
- >>> fig, ax = plt.subplots()
376
- >>> ax.plot([1, 2, 3], [1, 4, 2])
377
- >>> nav = enable_keyboard_navigation(fig)
378
- >>> plt.show()
379
-
380
- References:
381
- ACC-003: Keyboard Navigation for Interactive Plots
382
- """
383
- if axes is None:
384
- axes = fig.get_axes()
385
-
386
- navigator = KeyboardNavigator(fig, axes, **kwargs)
387
- navigator.connect()
388
- return navigator
389
-
390
-
391
- __all__ = [
392
- "KeyboardNavigator",
393
- "enable_keyboard_navigation",
394
- ]