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