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
@@ -61,28 +61,41 @@ def rise_time(
61
61
  IEEE 181-2011 Annex B (measurement uncertainty)
62
62
  """
63
63
  # Get the measurement value
64
- value = meas.rise_time(trace, ref_levels=ref_levels)
64
+ result = meas.rise_time(trace, ref_levels=ref_levels)
65
65
 
66
- if not include_uncertainty or np.isnan(value):
67
- return MeasurementWithUncertainty(value=float(value), uncertainty=float(np.nan), unit="s")
66
+ # Extract value from MeasurementResult
67
+ if not result["applicable"]:
68
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="s")
69
+
70
+ value = result["value"]
71
+
72
+ if value is None:
73
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="s")
74
+
75
+ if not include_uncertainty:
76
+ return MeasurementWithUncertainty(
77
+ value=float(value),
78
+ uncertainty=float(np.nan),
79
+ unit="s",
80
+ )
68
81
 
69
82
  # Estimate uncertainty components
70
83
  uncertainties = []
71
84
 
72
85
  # 1. Time base uncertainty (Type B)
73
86
  if (
74
- trace.metadata.calibration_info is not None
75
- and trace.metadata.calibration_info.timebase_accuracy is not None
87
+ trace.metadata.calibration is not None
88
+ and trace.metadata.calibration.timebase_accuracy is not None
76
89
  ):
77
90
  # Use calibration timebase accuracy if available
78
- timebase_ppm = trace.metadata.calibration_info.timebase_accuracy
91
+ timebase_ppm = trace.metadata.calibration.timebase_accuracy
79
92
  u_timebase = UncertaintyEstimator.time_base_uncertainty(
80
93
  trace.metadata.sample_rate, timebase_ppm
81
94
  )
82
95
  # Rise time involves 2 samples (start and stop), so uncertainty scales
83
96
  u_timebase_rise = u_timebase * np.sqrt(2)
84
97
  uncertainties.append(u_timebase_rise)
85
- elif trace.metadata.calibration_info is not None:
98
+ elif trace.metadata.calibration is not None:
86
99
  # Use conservative estimate if calibration present but no timebase accuracy
87
100
  timebase_ppm = 25.0 # Typical scope: 25-50 ppm
88
101
  u_timebase = UncertaintyEstimator.time_base_uncertainty(
@@ -97,7 +110,7 @@ def rise_time(
97
110
 
98
111
  # 2. Interpolation uncertainty (Type B - rectangular distribution)
99
112
  # Linear interpolation error: typically ±0.5 samples worst case
100
- sample_period = trace.metadata.time_base
113
+ sample_period = 1.0 / trace.metadata.sample_rate
101
114
  u_interp = UncertaintyEstimator.type_b_rectangular(0.5 * sample_period)
102
115
  uncertainties.append(u_interp)
103
116
 
@@ -147,20 +160,33 @@ def fall_time(
147
160
  References:
148
161
  IEEE 181-2011 Section 5.2
149
162
  """
150
- value = meas.fall_time(trace, ref_levels=ref_levels)
163
+ result = meas.fall_time(trace, ref_levels=ref_levels)
164
+
165
+ # Extract value from MeasurementResult
166
+ if not result["applicable"]:
167
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="s")
151
168
 
152
- if not include_uncertainty or np.isnan(value):
153
- return MeasurementWithUncertainty(value=float(value), uncertainty=float(np.nan), unit="s")
169
+ value = result["value"]
170
+
171
+ if value is None:
172
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="s")
173
+
174
+ if not include_uncertainty:
175
+ return MeasurementWithUncertainty(
176
+ value=float(value),
177
+ uncertainty=float(np.nan),
178
+ unit="s",
179
+ )
154
180
 
155
181
  # Similar uncertainty calculation as rise_time
156
182
  uncertainties = []
157
183
 
158
184
  # Time base uncertainty
159
185
  if (
160
- trace.metadata.calibration_info is not None
161
- and trace.metadata.calibration_info.timebase_accuracy is not None
186
+ trace.metadata.calibration is not None
187
+ and trace.metadata.calibration.timebase_accuracy is not None
162
188
  ):
163
- timebase_ppm = trace.metadata.calibration_info.timebase_accuracy
189
+ timebase_ppm = trace.metadata.calibration.timebase_accuracy
164
190
  else:
165
191
  timebase_ppm = 25.0 # Conservative default
166
192
  u_timebase = UncertaintyEstimator.time_base_uncertainty(
@@ -169,7 +195,7 @@ def fall_time(
169
195
  uncertainties.append(u_timebase * np.sqrt(2))
170
196
 
171
197
  # Interpolation uncertainty
172
- sample_period = trace.metadata.time_base
198
+ sample_period = 1.0 / trace.metadata.sample_rate
173
199
  u_interp = UncertaintyEstimator.type_b_rectangular(0.5 * sample_period)
174
200
  uncertainties.append(u_interp)
175
201
 
@@ -208,9 +234,18 @@ def frequency(
208
234
  IEEE 181-2011 Section 5.3
209
235
  IEEE 1057-2017 Section 4.3
210
236
  """
211
- value = meas.frequency(trace)
237
+ result = meas.frequency(trace)
238
+
239
+ # Extract value from MeasurementResult
240
+ if not result["applicable"]:
241
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="Hz")
242
+
243
+ value = result["value"]
212
244
 
213
- if not include_uncertainty or np.isnan(value):
245
+ if value is None:
246
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="Hz")
247
+
248
+ if not include_uncertainty:
214
249
  return MeasurementWithUncertainty(value=float(value), uncertainty=float(np.nan), unit="Hz")
215
250
 
216
251
  # Frequency is 1/period, so uncertainty propagation:
@@ -225,10 +260,10 @@ def frequency(
225
260
 
226
261
  # Time base uncertainty
227
262
  if (
228
- trace.metadata.calibration_info is not None
229
- and trace.metadata.calibration_info.timebase_accuracy is not None
263
+ trace.metadata.calibration is not None
264
+ and trace.metadata.calibration.timebase_accuracy is not None
230
265
  ):
231
- timebase_ppm = trace.metadata.calibration_info.timebase_accuracy
266
+ timebase_ppm = trace.metadata.calibration.timebase_accuracy
232
267
  else:
233
268
  timebase_ppm = 25.0 # Conservative default
234
269
  # Period measurement spans multiple cycles, typically more accurate
@@ -236,7 +271,7 @@ def frequency(
236
271
  uncertainties.append(u_period_timebase)
237
272
 
238
273
  # Interpolation uncertainty for edge detection
239
- sample_period = trace.metadata.time_base
274
+ sample_period = 1.0 / trace.metadata.sample_rate
240
275
  u_interp = UncertaintyEstimator.type_b_rectangular(0.5 * sample_period)
241
276
  # Two edges per period
242
277
  u_period_interp = u_interp * np.sqrt(2)
@@ -246,10 +281,11 @@ def frequency(
246
281
  u_period = UncertaintyEstimator.combined_uncertainty([float(u) for u in uncertainties])
247
282
 
248
283
  # Propagate to frequency: u(f) = |df/dT| * u(T) = f^2 * u(T)
249
- u_frequency = float((value**2) * u_period)
284
+ value_float = float(value) # Already checked for None above
285
+ u_frequency = float((value_float**2) * u_period)
250
286
 
251
287
  return MeasurementWithUncertainty(
252
- value=float(value), uncertainty=u_frequency, unit="Hz", n_samples=len(trace.data)
288
+ value=value_float, uncertainty=u_frequency, unit="Hz", n_samples=len(trace.data)
253
289
  )
254
290
 
255
291
 
@@ -275,9 +311,18 @@ def amplitude(
275
311
  IEEE 1057-2017 Section 4.2 (amplitude measurement)
276
312
  IEEE 1057-2017 Section 4.4 (amplitude accuracy)
277
313
  """
278
- value = meas.amplitude(trace)
314
+ result = meas.amplitude(trace)
315
+
316
+ # Extract value from MeasurementResult
317
+ if not result["applicable"]:
318
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="V")
319
+
320
+ value = result["value"]
279
321
 
280
- if not include_uncertainty or np.isnan(value):
322
+ if value is None:
323
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="V")
324
+
325
+ if not include_uncertainty:
281
326
  return MeasurementWithUncertainty(value=float(value), uncertainty=float(np.nan), unit="V")
282
327
 
283
328
  uncertainties = []
@@ -291,17 +336,18 @@ def amplitude(
291
336
  else:
292
337
  offset_error = 0.001 # 1 mV default
293
338
 
339
+ value_float = float(value) # Already checked for None
294
340
  u_vertical = UncertaintyEstimator.vertical_uncertainty(
295
- float(value), vertical_accuracy_pct, offset_error
341
+ value_float, vertical_accuracy_pct, offset_error
296
342
  )
297
343
  uncertainties.append(u_vertical)
298
344
 
299
345
  # 2. Quantization uncertainty (Type B - rectangular)
300
346
  if (
301
- trace.metadata.calibration_info is not None
302
- and trace.metadata.calibration_info.vertical_resolution is not None
347
+ trace.metadata.calibration is not None
348
+ and trace.metadata.calibration.vertical_resolution is not None
303
349
  ):
304
- bits = trace.metadata.calibration_info.vertical_resolution
350
+ bits = trace.metadata.calibration.vertical_resolution
305
351
  vertical_range = np.ptp(trace.data) # Simplification
306
352
  lsb = vertical_range / (2**bits)
307
353
  u_quant = UncertaintyEstimator.type_b_rectangular(0.5 * lsb)
@@ -321,7 +367,7 @@ def amplitude(
321
367
  total_uncertainty = UncertaintyEstimator.combined_uncertainty(uncertainties)
322
368
 
323
369
  return MeasurementWithUncertainty(
324
- value=float(value),
370
+ value=value_float,
325
371
  uncertainty=total_uncertainty,
326
372
  unit="V",
327
373
  n_samples=len(trace.data),
@@ -347,9 +393,18 @@ def rms(
347
393
  References:
348
394
  IEEE 1057-2017 Section 4.3
349
395
  """
350
- value = meas.rms(trace, ac_coupled=ac_coupled)
396
+ result = meas.rms(trace, ac_coupled=ac_coupled)
351
397
 
352
- if not include_uncertainty or np.isnan(value):
398
+ # Extract value from MeasurementResult
399
+ if not result["applicable"]:
400
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="V")
401
+
402
+ value = result["value"]
403
+
404
+ if value is None:
405
+ return MeasurementWithUncertainty(value=float(np.nan), uncertainty=float(np.nan), unit="V")
406
+
407
+ if not include_uncertainty:
353
408
  return MeasurementWithUncertainty(value=float(value), uncertainty=float(np.nan), unit="V")
354
409
 
355
410
  uncertainties = []
@@ -357,21 +412,22 @@ def rms(
357
412
  # Vertical accuracy
358
413
  vertical_accuracy_pct = 2.0
359
414
  offset_error = 0.001 # 1 mV
415
+ value_float = float(value) # Already checked for None
360
416
  u_vertical = UncertaintyEstimator.vertical_uncertainty(
361
- float(value), vertical_accuracy_pct, offset_error
417
+ value_float, vertical_accuracy_pct, offset_error
362
418
  )
363
419
  uncertainties.append(u_vertical)
364
420
 
365
421
  # Statistical uncertainty (Type A)
366
422
  # RMS of N samples: u(RMS) ≈ RMS / sqrt(2N) for Gaussian noise
367
423
  n = len(trace.data)
368
- u_statistical = value / np.sqrt(2 * n) if n > 0 else 0.0
424
+ u_statistical = value_float / np.sqrt(2 * n) if n > 0 else 0.0
369
425
  uncertainties.append(u_statistical)
370
426
 
371
427
  total_uncertainty = UncertaintyEstimator.combined_uncertainty(uncertainties)
372
428
 
373
429
  return MeasurementWithUncertainty(
374
- value=float(value),
430
+ value=value_float,
375
431
  uncertainty=total_uncertainty,
376
432
  unit="V",
377
433
  n_samples=len(trace.data),