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
@@ -97,7 +97,7 @@ class WindowTrigger(Trigger):
97
97
  List of trigger events for window crossings.
98
98
  """
99
99
  data = trace.data
100
- sample_period = trace.metadata.time_base
100
+ sample_period = 1.0 / trace.metadata.sample_rate
101
101
  events: list[TriggerEvent] = []
102
102
 
103
103
  # Determine if each sample is inside the window
@@ -182,7 +182,7 @@ class ZoneTrigger(Trigger):
182
182
  List of trigger events.
183
183
  """
184
184
  data = trace.data
185
- sample_period = trace.metadata.time_base
185
+ sample_period = 1.0 / trace.metadata.sample_rate
186
186
  time_vector = np.arange(len(data)) * sample_period
187
187
  events: list[TriggerEvent] = []
188
188
 
@@ -396,7 +396,7 @@ class MaskTrigger(Trigger):
396
396
  mask_path = Path(self.mask_points)
397
397
 
398
398
  data = trace.data
399
- sample_period = trace.metadata.time_base
399
+ sample_period = 1.0 / trace.metadata.sample_rate
400
400
  time_vector = np.arange(len(data)) * sample_period
401
401
 
402
402
  # Create points array for containment test
@@ -35,7 +35,7 @@ from enum import Enum
35
35
  from typing import Any, Protocol
36
36
 
37
37
  try:
38
- import can # type: ignore[import-untyped]
38
+ import can
39
39
  except ImportError:
40
40
  can = None # type: ignore[assignment]
41
41
 
@@ -46,18 +46,18 @@ except ImportError:
46
46
  # Create module structure for test patching even when pyusb unavailable
47
47
  import types
48
48
 
49
- usb = types.ModuleType("usb") # type: ignore[assignment]
49
+ usb = types.ModuleType("usb")
50
50
  usb.core = None # type: ignore[attr-defined]
51
51
 
52
52
  try:
53
53
  import spidev # type: ignore[import-not-found]
54
54
  except ImportError:
55
- spidev = None # type: ignore[assignment]
55
+ spidev = None
56
56
 
57
57
  try:
58
58
  from smbus2 import SMBus # type: ignore[import-not-found]
59
59
  except ImportError:
60
- SMBus = None # type: ignore[assignment]
60
+ SMBus = None
61
61
 
62
62
  try:
63
63
  import RPi.GPIO as GPIO # type: ignore[import-untyped]
@@ -65,23 +65,23 @@ except ImportError:
65
65
  try:
66
66
  import gpiod # type: ignore[import-not-found]
67
67
 
68
- GPIO = None # type: ignore[assignment]
68
+ GPIO = None
69
69
  except ImportError:
70
- GPIO = None # type: ignore[assignment]
71
- gpiod = None # type: ignore[assignment]
70
+ GPIO = None
71
+ gpiod = None
72
72
 
73
73
  try:
74
74
  from scapy.all import IP, UDP, Packet, wrpcap # type: ignore[attr-defined]
75
75
  except ImportError:
76
- IP = None # type: ignore[assignment]
77
- UDP = None # type: ignore[assignment]
76
+ IP = None
77
+ UDP = None
78
78
  Packet = None # type: ignore[assignment,misc]
79
79
  wrpcap = None # type: ignore[assignment]
80
80
 
81
81
  try:
82
82
  import serial # type: ignore[import-untyped]
83
83
  except ImportError:
84
- serial = None # type: ignore[assignment]
84
+ serial = None
85
85
 
86
86
  from oscura.utils.serial import connect_serial_port
87
87
 
@@ -960,7 +960,7 @@ class HILTester:
960
960
  # Create UDP packet for received data if present
961
961
  if recv_data:
962
962
  pkt = IP(src="192.168.1.1") / UDP(sport=12345) / bytes(recv_data)
963
- pkt.time = timestamp + 0.001 # Slight offset
963
+ pkt.time = timestamp + 0.001
964
964
  packets.append(pkt)
965
965
 
966
966
  if packets:
@@ -1,330 +1,93 @@
1
1
  """Visualization module for Oscura.
2
2
 
3
- Provides plotting functions for waveforms, spectra, and other signal data,
4
- plus optimization utilities, style presets, and intelligent rendering.
3
+ Provides simple matplotlib-based plotting functions for waveforms, spectra,
4
+ and digital signals. All functions accept and return matplotlib axes for
5
+ composability.
5
6
 
6
7
  **Requires matplotlib:**
7
- This module requires matplotlib to be installed. Install with:
8
- pip install oscura[visualization] # Just visualization
9
- pip install oscura[standard] # Recommended
10
- pip install oscura[all] # Everything
8
+ Install with: pip install oscura[visualization]
11
9
 
12
10
  Example:
13
- >>> from oscura.visualization import plot_waveform, plot_spectrum
14
- >>> from oscura.visualization import plot_timing, plot_eye
15
- >>> from oscura.visualization import plot_bode, plot_histogram
16
- >>> from oscura.visualization import apply_style_preset
17
11
  >>> import matplotlib.pyplot as plt
18
- >>> with apply_style_preset("publication"):
19
- ... plot_waveform(trace, time_unit="us")
20
- ... plt.savefig("figure.pdf")
12
+ >>> import oscura as osc
13
+ >>> from oscura.visualization import plot_waveform, plot_fft
14
+ >>>
15
+ >>> trace = osc.load("signal.wfm")
16
+ >>> fig, (ax1, ax2) = plt.subplots(2, 1)
17
+ >>> plot_waveform(ax1, trace)
18
+ >>> plot_fft(ax2, trace)
19
+ >>> plt.savefig("analysis.png")
21
20
  """
22
21
 
23
- # NOTE: Matplotlib is optional - individual functions will check and raise
24
- # helpful errors if matplotlib is not installed when they're called.
25
- # The module itself can be imported without matplotlib.
26
-
27
- # Import plot module as namespace for DSL compatibility
28
- from oscura.visualization import plot
29
- from oscura.visualization.accessibility import (
30
- FAIL_SYMBOL,
31
- LINE_STYLES,
32
- PASS_SYMBOL,
33
- KeyboardHandler,
34
- add_plot_aria_attributes,
35
- format_pass_fail,
36
- generate_alt_text,
37
- get_colorblind_palette,
38
- get_multi_line_styles,
39
- )
40
-
41
- # Phase 30 enhancements
42
- from oscura.visualization.annotations import (
43
- Annotation as EnhancedAnnotation,
44
- )
45
- from oscura.visualization.annotations import (
46
- PlacedAnnotation as EnhancedPlacedAnnotation,
47
- )
48
- from oscura.visualization.annotations import (
49
- create_priority_annotation,
50
- filter_by_zoom_level,
51
- place_annotations,
52
- )
53
- from oscura.visualization.axis_scaling import (
54
- calculate_axis_limits,
55
- calculate_multi_channel_limits,
56
- suggest_tick_spacing,
57
- )
58
- from oscura.visualization.colors import (
59
- COLORBLIND_SAFE_QUALITATIVE,
60
- DIVERGING_COOLWARM,
61
- SEQUENTIAL_VIRIDIS,
62
- select_optimal_palette,
63
- )
64
- from oscura.visualization.digital import (
65
- plot_logic_analyzer,
66
- plot_timing,
67
- )
68
- from oscura.visualization.eye import (
22
+ # Import core plotting functions
23
+ # Import batch utilities
24
+ from oscura.visualization import batch
25
+ from oscura.visualization.batch import fig_to_base64, generate_all_plots
26
+ from oscura.visualization.plot import (
69
27
  plot_bathtub,
70
28
  plot_eye,
71
- )
72
- from oscura.visualization.histogram import (
73
- calculate_bin_edges,
74
- calculate_optimal_bins,
75
- )
76
- from oscura.visualization.interactive import (
77
- CursorMeasurement,
78
- ZoomState,
79
- add_measurement_cursors,
80
- enable_zoom_pan,
81
- plot_bode,
29
+ plot_fft,
82
30
  plot_histogram,
83
- plot_phase,
84
- plot_waterfall,
85
- plot_with_cursors,
86
- )
87
- from oscura.visualization.layout import (
88
- Annotation,
89
- ChannelLayout,
90
- PlacedAnnotation,
91
- layout_stacked_channels,
92
- optimize_annotation_placement,
93
- )
94
- from oscura.visualization.optimization import (
95
- InterestingRegion,
96
- calculate_grid_spacing,
97
- calculate_optimal_x_window,
98
- calculate_optimal_y_range,
99
- decimate_for_display,
100
- detect_interesting_regions,
101
- optimize_db_range,
102
- )
103
- from oscura.visualization.presets import (
104
- DARK_THEME_PRESET,
105
- IEEE_DOUBLE_COLUMN_PRESET,
106
- IEEE_PUBLICATION_PRESET,
107
- VisualizationPreset,
108
- apply_preset,
109
- get_preset_colors,
110
- )
111
- from oscura.visualization.presets import (
112
- create_custom_preset as create_custom_visualization_preset,
113
- )
114
- from oscura.visualization.presets import (
115
- list_presets as list_visualization_presets,
116
- )
117
- from oscura.visualization.protocols import (
118
- plot_can_decode,
119
- plot_i2c_decode,
31
+ plot_logic_analyzer,
32
+ plot_multi_channel,
120
33
  plot_protocol_decode,
121
- plot_spi_decode,
122
- plot_uart_decode,
123
- )
124
- from oscura.visualization.render import (
125
- RenderPreset,
126
- apply_rendering_config,
127
- configure_dpi_rendering,
128
- )
129
- from oscura.visualization.rendering import (
130
- StreamingRenderer,
131
- downsample_for_memory,
132
- estimate_memory_usage,
133
- progressive_render,
134
- render_with_lod,
135
- )
136
-
137
- # Reverse Engineering Visualizations (HIGH-2)
138
- from oscura.visualization.reverse_engineering import (
139
- plot_crc_parameters,
140
- plot_field_confidence_heatmap,
141
- plot_message_field_layout,
142
- plot_message_type_distribution,
143
- plot_pipeline_timing,
144
- plot_protocol_candidates,
145
- plot_re_summary,
146
- )
147
- from oscura.visualization.specialized import (
148
- ProtocolSignal,
149
- StateTransition,
150
- plot_protocol_timing,
151
- plot_state_machine,
152
- )
153
- from oscura.visualization.spectral import (
154
- plot_fft,
155
34
  plot_psd,
156
35
  plot_quality_summary,
157
36
  plot_spectrogram,
158
37
  plot_spectrum,
38
+ plot_state_machine,
159
39
  plot_thd_bars,
160
- )
161
- from oscura.visualization.styles import (
162
- PRESENTATION_PRESET,
163
- PRINT_PRESET,
164
- PUBLICATION_PRESET,
165
- SCREEN_PRESET,
166
- StylePreset,
167
- apply_style_preset,
168
- create_custom_preset,
169
- )
170
- from oscura.visualization.thumbnails import (
171
- render_thumbnail,
172
- render_thumbnail_multichannel,
173
- )
174
- from oscura.visualization.time_axis import (
175
- TimeUnit,
176
- calculate_major_ticks,
177
- convert_time_values,
178
- create_relative_time,
179
- format_cursor_readout,
180
- format_time_labels,
181
- select_time_unit,
182
- )
183
- from oscura.visualization.waveform import (
184
- plot_multi_channel,
40
+ plot_timing,
185
41
  plot_waveform,
186
42
  plot_xy,
187
43
  )
188
44
 
45
+ # Import styling utilities
46
+ from oscura.visualization.styles import (
47
+ COLORBLIND_PALETTE,
48
+ FAIL_SYMBOL,
49
+ IEEE_STYLE,
50
+ LINE_STYLES,
51
+ PASS_SYMBOL,
52
+ apply_ieee_style,
53
+ apply_presentation_style,
54
+ format_pass_fail,
55
+ get_colorblind_cmap,
56
+ get_colorblind_palette,
57
+ get_line_styles,
58
+ get_multi_line_styles,
59
+ )
60
+
189
61
  __all__ = [
190
- "COLORBLIND_SAFE_QUALITATIVE",
191
- "DARK_THEME_PRESET",
192
- "DIVERGING_COOLWARM",
62
+ "COLORBLIND_PALETTE",
193
63
  "FAIL_SYMBOL",
194
- "IEEE_DOUBLE_COLUMN_PRESET",
195
- "IEEE_PUBLICATION_PRESET",
64
+ "IEEE_STYLE",
196
65
  "LINE_STYLES",
197
66
  "PASS_SYMBOL",
198
- "PRESENTATION_PRESET",
199
- "PRINT_PRESET",
200
- "PUBLICATION_PRESET",
201
- "SCREEN_PRESET",
202
- "SEQUENTIAL_VIRIDIS",
203
- "Annotation",
204
- # Layout functions (VIS-015, VIS-016)
205
- "ChannelLayout",
206
- # Interactive (VIS-008)
207
- "CursorMeasurement",
208
- # Phase 30: Enhanced modules
209
- "EnhancedAnnotation",
210
- "EnhancedPlacedAnnotation",
211
- "InterestingRegion",
212
- "KeyboardHandler",
213
- "PlacedAnnotation",
214
- "ProtocolSignal",
215
- "RenderPreset",
216
- "StateTransition",
217
- "StreamingRenderer",
218
- "StylePreset",
219
- "TimeUnit",
220
- "VisualizationPreset",
221
- # Interactive (VIS-007)
222
- "ZoomState",
223
- # Interactive (VIS-008)
224
- "add_measurement_cursors",
225
- "add_plot_aria_attributes",
226
- "apply_preset",
227
- "apply_rendering_config",
228
- # Styles
229
- "apply_style_preset",
230
- "calculate_axis_limits",
231
- "calculate_bin_edges",
232
- "calculate_grid_spacing",
233
- "calculate_major_ticks",
234
- "calculate_multi_channel_limits",
235
- # Histogram
236
- "calculate_optimal_bins",
237
- "calculate_optimal_x_window",
238
- # Optimization functions (VIS-013, VIS-014, VIS-019, VIS-020, VIS-022)
239
- "calculate_optimal_y_range",
240
- # Rendering functions (VIS-017)
241
- "configure_dpi_rendering",
242
- "convert_time_values",
243
- "create_custom_preset",
244
- "create_custom_visualization_preset",
245
- "create_priority_annotation",
246
- "create_relative_time",
247
- "decimate_for_display",
248
- "detect_interesting_regions",
249
- "downsample_for_memory",
250
- # Interactive (VIS-007)
251
- "enable_zoom_pan",
252
- "estimate_memory_usage",
253
- "filter_by_zoom_level",
254
- "format_cursor_readout",
67
+ "apply_ieee_style",
68
+ "apply_presentation_style",
69
+ "batch",
70
+ "fig_to_base64",
255
71
  "format_pass_fail",
256
- "format_time_labels",
257
- "generate_alt_text",
258
- # Accessibility (ACC-001, ACC-002, ACC-003)
72
+ "generate_all_plots",
73
+ "get_colorblind_cmap",
259
74
  "get_colorblind_palette",
75
+ "get_line_styles",
260
76
  "get_multi_line_styles",
261
- "get_preset_colors",
262
- "layout_stacked_channels",
263
- "list_visualization_presets",
264
- "optimize_annotation_placement",
265
- "optimize_db_range",
266
- "place_annotations",
267
- "plot",
268
- # Eye diagram (VIS-006)
269
77
  "plot_bathtub",
270
- # Interactive (VIS-010)
271
- "plot_bode",
272
- # Protocol decode visualization (VIS-030)
273
- "plot_can_decode",
274
- # Reverse Engineering Visualization (HIGH-2)
275
- "plot_crc_parameters",
276
78
  "plot_eye",
277
- # Spectral plotting
278
79
  "plot_fft",
279
- # Reverse Engineering Visualization (HIGH-2)
280
- "plot_field_confidence_heatmap",
281
- # Interactive (VIS-012)
282
80
  "plot_histogram",
283
- # Protocol decode visualization (VIS-030)
284
- "plot_i2c_decode",
285
- # Digital visualization (VIS-005)
286
81
  "plot_logic_analyzer",
287
- # Reverse Engineering Visualization (HIGH-2)
288
- "plot_message_field_layout",
289
- "plot_message_type_distribution",
290
82
  "plot_multi_channel",
291
- # Interactive (VIS-009)
292
- "plot_phase",
293
- # Reverse Engineering Visualization (HIGH-2)
294
- "plot_pipeline_timing",
295
- # Protocol decode visualization (VIS-030)
296
- "plot_protocol_candidates",
297
83
  "plot_protocol_decode",
298
- "plot_protocol_timing",
299
84
  "plot_psd",
300
- # Signal quality summary (IEEE 1241-2010)
301
85
  "plot_quality_summary",
302
- # Reverse Engineering Visualization (HIGH-2)
303
- "plot_re_summary",
304
86
  "plot_spectrogram",
305
87
  "plot_spectrum",
306
- # Protocol decode visualization (VIS-030)
307
- "plot_spi_decode",
308
88
  "plot_state_machine",
309
- # THD harmonic bar chart
310
89
  "plot_thd_bars",
311
90
  "plot_timing",
312
- # Protocol decode visualization (VIS-030)
313
- "plot_uart_decode",
314
- # Interactive (VIS-011)
315
- "plot_waterfall",
316
- # Waveform plotting
317
91
  "plot_waveform",
318
- # Interactive (VIS-008)
319
- "plot_with_cursors",
320
92
  "plot_xy",
321
- "progressive_render",
322
- # Thumbnails
323
- "render_thumbnail",
324
- "render_thumbnail_multichannel",
325
- "render_with_lod",
326
- # Colors
327
- "select_optimal_palette",
328
- "select_time_unit",
329
- "suggest_tick_spacing",
330
93
  ]
@@ -0,0 +1,160 @@
1
+ """Batch plot generation for comprehensive signal visualization.
2
+
3
+ Generate multiple related plots from signal traces in a single operation,
4
+ useful for comprehensive analysis reports.
5
+
6
+ Example:
7
+ >>> from oscura.visualization import batch
8
+ >>> trace = osc.load("signal.wfm")
9
+ >>> plots = batch.generate_all_plots(trace)
10
+ >>> # Returns: {"waveform": <base64>, "fft": <base64>, ...}
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import base64
16
+ from io import BytesIO
17
+ from typing import TYPE_CHECKING
18
+
19
+ import matplotlib
20
+
21
+ matplotlib.use("Agg") # Non-interactive backend
22
+ import matplotlib.pyplot as plt
23
+
24
+ from oscura.core.types import DigitalTrace, WaveformTrace
25
+
26
+ if TYPE_CHECKING:
27
+ from matplotlib.figure import Figure
28
+
29
+ # Plot configuration
30
+ PLOT_DPI = 150
31
+ FIGURE_SIZE = (10, 6)
32
+
33
+
34
+ def fig_to_base64(fig: Figure, *, dpi: int = PLOT_DPI) -> str:
35
+ """Convert matplotlib figure to base64-encoded PNG.
36
+
37
+ Args:
38
+ fig: Matplotlib figure object.
39
+ dpi: Resolution in dots per inch.
40
+
41
+ Returns:
42
+ Base64-encoded PNG image string with data URI prefix.
43
+
44
+ Example:
45
+ >>> fig, ax = plt.subplots()
46
+ >>> ax.plot([1, 2, 3])
47
+ >>> b64 = fig_to_base64(fig)
48
+ """
49
+ buf = BytesIO()
50
+ fig.savefig(buf, format="png", dpi=dpi, bbox_inches="tight", facecolor="white")
51
+ buf.seek(0)
52
+ img_base64 = base64.b64encode(buf.read()).decode("utf-8")
53
+ plt.close(fig)
54
+ return f"data:image/png;base64,{img_base64}"
55
+
56
+
57
+ def generate_all_plots(
58
+ trace: WaveformTrace | DigitalTrace,
59
+ *,
60
+ verbose: bool = True,
61
+ ) -> dict[str, str]:
62
+ """Generate all applicable plots for a signal trace.
63
+
64
+ Automatically detects signal type and generates appropriate plots:
65
+ - Analog: waveform, FFT, histogram, spectrogram
66
+ - Digital: waveform, timing diagram
67
+
68
+ Args:
69
+ trace: WaveformTrace or DigitalTrace to visualize.
70
+ verbose: Print progress messages.
71
+
72
+ Returns:
73
+ Dictionary mapping plot names to base64 image strings.
74
+
75
+ Example:
76
+ >>> trace = osc.load("signal.wfm")
77
+ >>> plots = generate_all_plots(trace)
78
+ >>> len(plots) # 4-5 plots for analog signal
79
+ 4
80
+ """
81
+ from oscura.visualization.plot import (
82
+ plot_fft,
83
+ plot_histogram,
84
+ plot_spectrogram,
85
+ plot_timing,
86
+ plot_waveform,
87
+ )
88
+
89
+ plots = {}
90
+
91
+ # Always generate waveform (handle both analog and digital)
92
+ try:
93
+ fig, ax = plt.subplots(figsize=FIGURE_SIZE)
94
+ if isinstance(trace, WaveformTrace):
95
+ plot_waveform(ax, trace)
96
+ else:
97
+ # Digital trace
98
+ plot_timing(ax, trace)
99
+ plots["waveform"] = fig_to_base64(fig)
100
+ if verbose:
101
+ print(" ✓ Generated waveform plot")
102
+ except Exception as e:
103
+ if verbose:
104
+ print(f" ⚠ Waveform plot failed: {e}")
105
+
106
+ if isinstance(trace, WaveformTrace):
107
+ # FFT spectrum
108
+ try:
109
+ fig, ax = plt.subplots(figsize=FIGURE_SIZE)
110
+ plot_fft(ax, trace)
111
+ plots["fft"] = fig_to_base64(fig)
112
+ if verbose:
113
+ print(" ✓ Generated FFT spectrum")
114
+ except Exception as e:
115
+ if verbose:
116
+ print(f" ⚠ FFT plot failed: {e}")
117
+
118
+ # Histogram
119
+ try:
120
+ fig, ax = plt.subplots(figsize=FIGURE_SIZE)
121
+ plot_histogram(ax, trace.data)
122
+ plots["histogram"] = fig_to_base64(fig)
123
+ if verbose:
124
+ print(" ✓ Generated histogram")
125
+ except Exception as e:
126
+ if verbose:
127
+ print(f" ⚠ Histogram plot failed: {e}")
128
+
129
+ # Spectrogram
130
+ try:
131
+ fig, ax = plt.subplots(figsize=FIGURE_SIZE)
132
+ plot_spectrogram(ax, trace)
133
+ plots["spectrogram"] = fig_to_base64(fig)
134
+ if verbose:
135
+ print(" ✓ Generated spectrogram")
136
+ except Exception as e:
137
+ if verbose:
138
+ print(f" ⚠ Spectrogram plot failed: {e}")
139
+
140
+ elif isinstance(trace, DigitalTrace):
141
+ # Timing diagram
142
+ try:
143
+ fig, ax = plt.subplots(figsize=FIGURE_SIZE)
144
+ plot_timing(ax, trace)
145
+ plots["timing"] = fig_to_base64(fig)
146
+ if verbose:
147
+ print(" ✓ Generated timing diagram")
148
+ except Exception as e:
149
+ if verbose:
150
+ print(f" ⚠ Timing plot failed: {e}")
151
+
152
+ return plots
153
+
154
+
155
+ __all__ = [
156
+ "FIGURE_SIZE",
157
+ "PLOT_DPI",
158
+ "fig_to_base64",
159
+ "generate_all_plots",
160
+ ]