oscura 0.8.0__py3-none-any.whl → 0.11.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 (161) hide show
  1. oscura/__init__.py +19 -19
  2. oscura/__main__.py +4 -0
  3. oscura/analyzers/__init__.py +2 -0
  4. oscura/analyzers/digital/extraction.py +2 -3
  5. oscura/analyzers/digital/quality.py +1 -1
  6. oscura/analyzers/digital/timing.py +1 -1
  7. oscura/analyzers/ml/signal_classifier.py +6 -0
  8. oscura/analyzers/patterns/__init__.py +66 -0
  9. oscura/analyzers/power/basic.py +3 -3
  10. oscura/analyzers/power/soa.py +1 -1
  11. oscura/analyzers/power/switching.py +3 -3
  12. oscura/analyzers/signal_classification.py +529 -0
  13. oscura/analyzers/signal_integrity/sparams.py +3 -3
  14. oscura/analyzers/statistics/basic.py +10 -7
  15. oscura/analyzers/validation.py +1 -1
  16. oscura/analyzers/waveform/measurements.py +200 -156
  17. oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
  18. oscura/analyzers/waveform/spectral.py +182 -84
  19. oscura/api/dsl/commands.py +15 -6
  20. oscura/api/server/templates/base.html +137 -146
  21. oscura/api/server/templates/export.html +84 -110
  22. oscura/api/server/templates/home.html +248 -267
  23. oscura/api/server/templates/protocols.html +44 -48
  24. oscura/api/server/templates/reports.html +27 -35
  25. oscura/api/server/templates/session_detail.html +68 -78
  26. oscura/api/server/templates/sessions.html +62 -72
  27. oscura/api/server/templates/waveforms.html +54 -64
  28. oscura/automotive/__init__.py +1 -1
  29. oscura/automotive/can/session.py +1 -1
  30. oscura/automotive/dbc/generator.py +638 -23
  31. oscura/automotive/dtc/data.json +17 -102
  32. oscura/automotive/flexray/fibex.py +9 -1
  33. oscura/automotive/uds/decoder.py +99 -6
  34. oscura/cli/analyze.py +8 -2
  35. oscura/cli/batch.py +36 -5
  36. oscura/cli/characterize.py +18 -4
  37. oscura/cli/export.py +47 -5
  38. oscura/cli/main.py +2 -0
  39. oscura/cli/onboarding/wizard.py +10 -6
  40. oscura/cli/pipeline.py +585 -0
  41. oscura/cli/visualize.py +6 -4
  42. oscura/convenience.py +400 -32
  43. oscura/core/measurement_result.py +286 -0
  44. oscura/core/progress.py +1 -1
  45. oscura/core/schemas/device_mapping.json +2 -8
  46. oscura/core/schemas/packet_format.json +4 -24
  47. oscura/core/schemas/protocol_definition.json +2 -12
  48. oscura/core/types.py +232 -239
  49. oscura/correlation/multi_protocol.py +1 -1
  50. oscura/export/legacy/__init__.py +11 -0
  51. oscura/export/legacy/wav.py +75 -0
  52. oscura/exporters/__init__.py +19 -0
  53. oscura/exporters/wireshark.py +809 -0
  54. oscura/hardware/acquisition/file.py +5 -19
  55. oscura/hardware/acquisition/saleae.py +10 -10
  56. oscura/hardware/acquisition/socketcan.py +4 -6
  57. oscura/hardware/acquisition/synthetic.py +1 -5
  58. oscura/hardware/acquisition/visa.py +6 -6
  59. oscura/hardware/security/side_channel_detector.py +5 -508
  60. oscura/inference/message_format.py +686 -1
  61. oscura/jupyter/display.py +2 -2
  62. oscura/jupyter/magic.py +3 -3
  63. oscura/loaders/__init__.py +17 -12
  64. oscura/loaders/binary.py +1 -1
  65. oscura/loaders/chipwhisperer.py +1 -2
  66. oscura/loaders/configurable.py +1 -1
  67. oscura/loaders/csv_loader.py +2 -2
  68. oscura/loaders/hdf5_loader.py +1 -1
  69. oscura/loaders/lazy.py +6 -1
  70. oscura/loaders/mmap_loader.py +0 -1
  71. oscura/loaders/numpy_loader.py +8 -7
  72. oscura/loaders/preprocessing.py +3 -5
  73. oscura/loaders/rigol.py +21 -7
  74. oscura/loaders/sigrok.py +2 -5
  75. oscura/loaders/tdms.py +3 -2
  76. oscura/loaders/tektronix.py +38 -32
  77. oscura/loaders/tss.py +20 -27
  78. oscura/loaders/validation.py +17 -10
  79. oscura/loaders/vcd.py +13 -8
  80. oscura/loaders/wav.py +1 -6
  81. oscura/pipeline/__init__.py +76 -0
  82. oscura/pipeline/handlers/__init__.py +165 -0
  83. oscura/pipeline/handlers/analyzers.py +1045 -0
  84. oscura/pipeline/handlers/decoders.py +899 -0
  85. oscura/pipeline/handlers/exporters.py +1103 -0
  86. oscura/pipeline/handlers/filters.py +891 -0
  87. oscura/pipeline/handlers/loaders.py +640 -0
  88. oscura/pipeline/handlers/transforms.py +768 -0
  89. oscura/reporting/formatting/measurements.py +55 -14
  90. oscura/reporting/templates/enhanced/protocol_re.html +504 -503
  91. oscura/sessions/legacy.py +49 -1
  92. oscura/side_channel/__init__.py +38 -57
  93. oscura/utils/builders/signal_builder.py +5 -5
  94. oscura/utils/comparison/compare.py +7 -9
  95. oscura/utils/comparison/golden.py +1 -1
  96. oscura/utils/filtering/convenience.py +2 -2
  97. oscura/utils/math/arithmetic.py +38 -62
  98. oscura/utils/math/interpolation.py +20 -20
  99. oscura/utils/pipeline/__init__.py +4 -17
  100. oscura/utils/progressive.py +1 -4
  101. oscura/utils/triggering/edge.py +1 -1
  102. oscura/utils/triggering/pattern.py +2 -2
  103. oscura/utils/triggering/pulse.py +2 -2
  104. oscura/utils/triggering/window.py +3 -3
  105. oscura/validation/hil_testing.py +11 -11
  106. oscura/visualization/__init__.py +46 -284
  107. oscura/visualization/batch.py +72 -433
  108. oscura/visualization/plot.py +542 -53
  109. oscura/visualization/styles.py +184 -318
  110. oscura/workflows/batch/advanced.py +1 -1
  111. oscura/workflows/batch/aggregate.py +12 -9
  112. oscura/workflows/complete_re.py +251 -23
  113. oscura/workflows/digital.py +27 -4
  114. oscura/workflows/multi_trace.py +136 -17
  115. oscura/workflows/waveform.py +11 -6
  116. oscura-0.11.0.dist-info/METADATA +460 -0
  117. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/RECORD +120 -145
  118. oscura/side_channel/dpa.py +0 -1025
  119. oscura/utils/optimization/__init__.py +0 -19
  120. oscura/utils/optimization/parallel.py +0 -443
  121. oscura/utils/optimization/search.py +0 -532
  122. oscura/utils/pipeline/base.py +0 -338
  123. oscura/utils/pipeline/composition.py +0 -248
  124. oscura/utils/pipeline/parallel.py +0 -449
  125. oscura/utils/pipeline/pipeline.py +0 -375
  126. oscura/utils/search/__init__.py +0 -16
  127. oscura/utils/search/anomaly.py +0 -424
  128. oscura/utils/search/context.py +0 -294
  129. oscura/utils/search/pattern.py +0 -288
  130. oscura/utils/storage/__init__.py +0 -61
  131. oscura/utils/storage/database.py +0 -1166
  132. oscura/visualization/accessibility.py +0 -526
  133. oscura/visualization/annotations.py +0 -371
  134. oscura/visualization/axis_scaling.py +0 -305
  135. oscura/visualization/colors.py +0 -451
  136. oscura/visualization/digital.py +0 -436
  137. oscura/visualization/eye.py +0 -571
  138. oscura/visualization/histogram.py +0 -281
  139. oscura/visualization/interactive.py +0 -1035
  140. oscura/visualization/jitter.py +0 -1042
  141. oscura/visualization/keyboard.py +0 -394
  142. oscura/visualization/layout.py +0 -400
  143. oscura/visualization/optimization.py +0 -1079
  144. oscura/visualization/palettes.py +0 -446
  145. oscura/visualization/power.py +0 -508
  146. oscura/visualization/power_extended.py +0 -955
  147. oscura/visualization/presets.py +0 -469
  148. oscura/visualization/protocols.py +0 -1246
  149. oscura/visualization/render.py +0 -223
  150. oscura/visualization/rendering.py +0 -444
  151. oscura/visualization/reverse_engineering.py +0 -838
  152. oscura/visualization/signal_integrity.py +0 -989
  153. oscura/visualization/specialized.py +0 -643
  154. oscura/visualization/spectral.py +0 -1226
  155. oscura/visualization/thumbnails.py +0 -340
  156. oscura/visualization/time_axis.py +0 -351
  157. oscura/visualization/waveform.py +0 -454
  158. oscura-0.8.0.dist-info/METADATA +0 -661
  159. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/WHEEL +0 -0
  160. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/entry_points.txt +0 -0
  161. {oscura-0.8.0.dist-info → oscura-0.11.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
- ]