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,63 +1,44 @@
1
- """Side-channel analysis for cryptographic implementation attacks.
2
-
3
- .. deprecated:: 0.6.0
4
- This module is deprecated. Use :mod:`oscura.analyzers.side_channel` instead.
5
- This module will be removed in v1.0.0.
6
-
7
- This package provides tools for performing side-channel attacks on cryptographic
8
- implementations using power analysis, electromagnetic analysis, timing analysis,
9
- and fault injection techniques.
10
-
11
- Migration Guide:
12
- Old import (deprecated):
13
- >>> from oscura.side_channel.dpa import DPAAnalyzer, PowerTrace
14
-
15
- New import (recommended):
16
- >>> from oscura.analyzers.side_channel import DPAAnalyzer, CPAAnalyzer
17
- >>> from oscura.analyzers.side_channel.power import hamming_weight
18
-
19
- Note:
20
- The oscura.side_channel.dpa module contains a different DPAAnalyzer implementation
21
- than oscura.analyzers.side_channel.power. The older implementation in this module
22
- (oscura.side_channel.dpa.DPAAnalyzer) supports combined DPA/CPA/Template attacks
23
- with a single class. The newer implementation in oscura.analyzers.side_channel
24
- provides separate DPAAnalyzer and CPAAnalyzer classes with cleaner APIs.
25
-
26
- For new code, use the oscura.analyzers.side_channel module.
27
- For existing code using DPAAnalyzer with attack_type parameter, continue using
28
- oscura.side_channel.dpa until migration to the new API.
29
-
30
- Example (new API - recommended):
31
- >>> from oscura.analyzers.side_channel import DPAAnalyzer, CPAAnalyzer
32
- >>> # DPA attack
33
- >>> dpa = DPAAnalyzer(target_bit=0)
34
- >>> result = dpa.analyze(traces, plaintexts)
35
- >>> # CPA attack
36
- >>> cpa = CPAAnalyzer(leakage_model="hamming_weight")
37
- >>> result = cpa.analyze(traces, plaintexts)
38
-
39
- Example (old API - deprecated):
40
- >>> from oscura.side_channel.dpa import DPAAnalyzer, PowerTrace
41
- >>> analyzer = DPAAnalyzer(attack_type="cpa", leakage_model="hamming_weight")
42
- >>> traces = [PowerTrace(timestamp=t, power=p, plaintext=pt) for ...]
43
- >>> result = analyzer.perform_attack(traces, target_byte=0)
1
+ """Side-channel trace loading.
2
+
3
+ This module ONLY provides ChipWhisperer trace loading for integration
4
+ into Oscura workflows.
5
+
6
+ For actual side-channel attacks, use ChipWhisperer directly:
7
+ https://chipwhisperer.com/
8
+
9
+ What's here:
10
+ - ChipWhisperer .npy/.trs trace loading via oscura.loaders.chipwhisperer
11
+
12
+ What's NOT here (use ChipWhisperer instead):
13
+ - DPA/CPA attacks
14
+ - Key recovery
15
+ - Leakage assessment
16
+ - Template attacks
17
+ - Hardware interfacing
18
+
19
+ Example:
20
+ >>> from oscura.loaders.chipwhisperer import load_chipwhisperer
21
+ >>> traceset = load_chipwhisperer("capture_data.npy")
22
+ >>> print(f"Loaded {traceset.n_traces} traces")
23
+
24
+ References:
25
+ ChipWhisperer Project: https://chipwhisperer.com/
26
+ ChipWhisperer Documentation: https://chipwhisperer.readthedocs.io/
44
27
  """
45
28
 
46
29
  from __future__ import annotations
47
30
 
48
- import warnings
49
-
50
- # Issue deprecation warning on import
51
- warnings.warn(
52
- "oscura.side_channel is deprecated and will be removed in v1.0.0. "
53
- "Use oscura.analyzers.side_channel instead. "
54
- "See migration guide in module docstring.",
55
- DeprecationWarning,
56
- stacklevel=2,
31
+ # Re-export ChipWhisperer loader for convenience
32
+ from oscura.loaders.chipwhisperer import (
33
+ ChipWhispererTraceSet,
34
+ load_chipwhisperer,
35
+ load_chipwhisperer_npy,
36
+ load_chipwhisperer_trs,
57
37
  )
58
38
 
59
- # Re-export from the local dpa module for backward compatibility
60
- # The dpa.py module in this directory contains the legacy implementation
61
- from oscura.side_channel.dpa import DPAAnalyzer, DPAResult, PowerTrace
62
-
63
- __all__ = ["DPAAnalyzer", "DPAResult", "PowerTrace"]
39
+ __all__ = [
40
+ "ChipWhispererTraceSet",
41
+ "load_chipwhisperer",
42
+ "load_chipwhisperer_npy",
43
+ "load_chipwhisperer_trs",
44
+ ]
@@ -940,7 +940,7 @@ class SignalBuilder:
940
940
  # Build TraceMetadata
941
941
  trace_metadata = TraceMetadata(
942
942
  sample_rate=self._sample_rate,
943
- channel_name=channel,
943
+ channel=channel,
944
944
  )
945
945
 
946
946
  return WaveformTrace(data=data, metadata=trace_metadata)
@@ -977,12 +977,12 @@ class SignalBuilder:
977
977
 
978
978
  # Build WaveformTrace for each channel
979
979
  traces: dict[str, WaveformTrace] = {}
980
- for channel_name, data in self._channels.items():
980
+ for channel, data in self._channels.items():
981
981
  trace_metadata = TraceMetadata(
982
982
  sample_rate=self._sample_rate,
983
- channel_name=channel_name,
983
+ channel=channel,
984
984
  )
985
- traces[channel_name] = WaveformTrace(data=data, metadata=trace_metadata)
985
+ traces[channel] = WaveformTrace(data=data, metadata=trace_metadata)
986
986
 
987
987
  return traces
988
988
 
@@ -1010,7 +1010,7 @@ class SignalBuilder:
1010
1010
  path,
1011
1011
  data=trace.data,
1012
1012
  sample_rate=trace.metadata.sample_rate,
1013
- channel_name=trace.metadata.channel_name or "ch1",
1013
+ channel=trace.metadata.channel or "ch1",
1014
1014
  )
1015
1015
 
1016
1016
  return trace
@@ -59,7 +59,7 @@ def difference(
59
59
  trace2: WaveformTrace,
60
60
  *,
61
61
  normalize: bool = False,
62
- channel_name: str | None = None,
62
+ channel: str | None = None,
63
63
  ) -> WaveformTrace:
64
64
  """Compute difference between two traces.
65
65
 
@@ -70,7 +70,7 @@ def difference(
70
70
  trace1: First trace.
71
71
  trace2: Second trace.
72
72
  normalize: Normalize difference to percentage of reference range.
73
- channel_name: Name for the result trace.
73
+ channel: Name for the result trace.
74
74
 
75
75
  Returns:
76
76
  WaveformTrace containing the difference.
@@ -106,12 +106,10 @@ def difference(
106
106
 
107
107
  new_metadata = TraceMetadata(
108
108
  sample_rate=trace1.metadata.sample_rate,
109
- vertical_scale=None,
110
- vertical_offset=None,
111
- acquisition_time=trace1.metadata.acquisition_time,
112
- trigger_info=trace1.metadata.trigger_info,
113
- source_file=trace1.metadata.source_file,
114
- channel_name=channel_name or "difference",
109
+ vertical_scale=trace1.metadata.vertical_scale,
110
+ vertical_offset=trace1.metadata.vertical_offset,
111
+ channel=channel or "difference",
112
+ units=trace1.metadata.units,
115
113
  )
116
114
 
117
115
  return WaveformTrace(data=diff, metadata=new_metadata)
@@ -482,7 +480,7 @@ def compare_traces(
482
480
  match = _determine_match(method, max_diff, tolerance, tolerance_pct, data1, data2)
483
481
 
484
482
  diff_trace = (
485
- difference(trace1, trace2, channel_name="comparison_diff") if include_difference else None
483
+ difference(trace1, trace2, channel="comparison_diff") if include_difference else None
486
484
  )
487
485
  statistics = _compute_comparison_statistics(diff, violations, min_len, data1, data2)
488
486
 
@@ -235,7 +235,7 @@ def create_golden(
235
235
  description=description,
236
236
  metadata={
237
237
  "source_file": trace.metadata.source_file,
238
- "channel_name": trace.metadata.channel_name,
238
+ "channel": trace.metadata.channel,
239
239
  },
240
240
  )
241
241
 
@@ -496,7 +496,7 @@ def differentiate(
496
496
  if order < 1:
497
497
  raise AnalysisError(f"Derivative order must be positive, got {order}")
498
498
 
499
- sample_period = trace.metadata.time_base
499
+ sample_period = 1.0 / trace.metadata.sample_rate
500
500
  result = trace.data.copy()
501
501
 
502
502
  for _ in range(order):
@@ -530,7 +530,7 @@ def integrate(
530
530
  Example:
531
531
  >>> position = integrate(velocity_trace)
532
532
  """
533
- sample_period = trace.metadata.time_base
533
+ sample_period = 1.0 / trace.metadata.sample_rate
534
534
 
535
535
  if method == "cumtrapz":
536
536
  from scipy.integrate import cumulative_trapezoid
@@ -73,7 +73,7 @@ def add(
73
73
  trace1: WaveformTrace,
74
74
  trace2: TraceOrScalar,
75
75
  *,
76
- channel_name: str | None = None,
76
+ channel: str | None = None,
77
77
  ) -> WaveformTrace:
78
78
  """Add two traces or add a scalar to a trace.
79
79
 
@@ -83,7 +83,7 @@ def add(
83
83
  Args:
84
84
  trace1: First trace (base trace).
85
85
  trace2: Second trace or scalar value to add.
86
- channel_name: Name for the result trace (optional).
86
+ channel: Name for the result trace (optional).
87
87
 
88
88
  Returns:
89
89
  New WaveformTrace containing the sum.
@@ -121,10 +121,7 @@ def add(
121
121
  sample_rate=metadata.sample_rate,
122
122
  vertical_scale=metadata.vertical_scale,
123
123
  vertical_offset=metadata.vertical_offset,
124
- acquisition_time=metadata.acquisition_time,
125
- trigger_info=metadata.trigger_info,
126
- source_file=metadata.source_file,
127
- channel_name=channel_name or f"{metadata.channel_name or 'trace'}_sum",
124
+ channel=channel or f"{metadata.channel or 'trace'}_sum",
128
125
  )
129
126
 
130
127
  return WaveformTrace(data=result_data, metadata=new_metadata)
@@ -134,7 +131,7 @@ def subtract(
134
131
  trace1: WaveformTrace,
135
132
  trace2: TraceOrScalar,
136
133
  *,
137
- channel_name: str | None = None,
134
+ channel: str | None = None,
138
135
  ) -> WaveformTrace:
139
136
  """Subtract second trace from first trace or subtract a scalar.
140
137
 
@@ -144,7 +141,7 @@ def subtract(
144
141
  Args:
145
142
  trace1: Trace to subtract from.
146
143
  trace2: Trace or scalar to subtract.
147
- channel_name: Name for the result trace (optional).
144
+ channel: Name for the result trace (optional).
148
145
 
149
146
  Returns:
150
147
  New WaveformTrace containing the difference.
@@ -178,10 +175,7 @@ def subtract(
178
175
  sample_rate=metadata.sample_rate,
179
176
  vertical_scale=metadata.vertical_scale,
180
177
  vertical_offset=metadata.vertical_offset,
181
- acquisition_time=metadata.acquisition_time,
182
- trigger_info=metadata.trigger_info,
183
- source_file=metadata.source_file,
184
- channel_name=channel_name or f"{metadata.channel_name or 'trace'}_diff",
178
+ channel=channel or f"{metadata.channel or 'trace'}_diff",
185
179
  )
186
180
 
187
181
  return WaveformTrace(data=result_data, metadata=new_metadata)
@@ -191,7 +185,7 @@ def multiply(
191
185
  trace1: WaveformTrace,
192
186
  trace2: TraceOrScalar,
193
187
  *,
194
- channel_name: str | None = None,
188
+ channel: str | None = None,
195
189
  ) -> WaveformTrace:
196
190
  """Multiply two traces or multiply trace by a scalar.
197
191
 
@@ -201,7 +195,7 @@ def multiply(
201
195
  Args:
202
196
  trace1: First trace.
203
197
  trace2: Second trace or scalar multiplier.
204
- channel_name: Name for the result trace (optional).
198
+ channel: Name for the result trace (optional).
205
199
 
206
200
  Returns:
207
201
  New WaveformTrace containing the product.
@@ -235,10 +229,7 @@ def multiply(
235
229
  sample_rate=metadata.sample_rate,
236
230
  vertical_scale=metadata.vertical_scale,
237
231
  vertical_offset=metadata.vertical_offset,
238
- acquisition_time=metadata.acquisition_time,
239
- trigger_info=metadata.trigger_info,
240
- source_file=metadata.source_file,
241
- channel_name=channel_name or f"{metadata.channel_name or 'trace'}_mult",
232
+ channel=channel or f"{metadata.channel or 'trace'}_mult",
242
233
  )
243
234
 
244
235
  return WaveformTrace(data=result_data, metadata=new_metadata)
@@ -248,7 +239,7 @@ def divide(
248
239
  trace1: WaveformTrace,
249
240
  trace2: TraceOrScalar,
250
241
  *,
251
- channel_name: str | None = None,
242
+ channel: str | None = None,
252
243
  fill_value: float = np.nan,
253
244
  ) -> WaveformTrace:
254
245
  """Divide first trace by second trace or by a scalar.
@@ -259,7 +250,7 @@ def divide(
259
250
  Args:
260
251
  trace1: Numerator trace.
261
252
  trace2: Denominator trace or scalar.
262
- channel_name: Name for the result trace (optional).
253
+ channel: Name for the result trace (optional).
263
254
  fill_value: Value to use for division by zero (default NaN).
264
255
 
265
256
  Returns:
@@ -301,10 +292,7 @@ def divide(
301
292
  sample_rate=metadata.sample_rate,
302
293
  vertical_scale=metadata.vertical_scale,
303
294
  vertical_offset=metadata.vertical_offset,
304
- acquisition_time=metadata.acquisition_time,
305
- trigger_info=metadata.trigger_info,
306
- source_file=metadata.source_file,
307
- channel_name=channel_name or f"{metadata.channel_name or 'trace'}_div",
295
+ channel=channel or f"{metadata.channel or 'trace'}_div",
308
296
  )
309
297
 
310
298
  return WaveformTrace(data=result_data, metadata=new_metadata)
@@ -314,7 +302,7 @@ def scale(
314
302
  trace: WaveformTrace,
315
303
  factor: float,
316
304
  *,
317
- channel_name: str | None = None,
305
+ channel: str | None = None,
318
306
  ) -> WaveformTrace:
319
307
  """Scale trace by a constant factor.
320
308
 
@@ -324,7 +312,7 @@ def scale(
324
312
  Args:
325
313
  trace: Input trace.
326
314
  factor: Scale factor to apply.
327
- channel_name: Name for the result trace (optional).
315
+ channel: Name for the result trace (optional).
328
316
 
329
317
  Returns:
330
318
  Scaled WaveformTrace.
@@ -336,7 +324,7 @@ def scale(
336
324
  return multiply(
337
325
  trace,
338
326
  factor,
339
- channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_scaled",
327
+ channel=channel or f"{trace.metadata.channel or 'trace'}_scaled",
340
328
  )
341
329
 
342
330
 
@@ -344,7 +332,7 @@ def offset(
344
332
  trace: WaveformTrace,
345
333
  value: float,
346
334
  *,
347
- channel_name: str | None = None,
335
+ channel: str | None = None,
348
336
  ) -> WaveformTrace:
349
337
  """Add a constant offset to trace.
350
338
 
@@ -353,7 +341,7 @@ def offset(
353
341
  Args:
354
342
  trace: Input trace.
355
343
  value: Offset value to add.
356
- channel_name: Name for the result trace (optional).
344
+ channel: Name for the result trace (optional).
357
345
 
358
346
  Returns:
359
347
  Offset WaveformTrace.
@@ -364,14 +352,14 @@ def offset(
364
352
  return add(
365
353
  trace,
366
354
  value,
367
- channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_offset",
355
+ channel=channel or f"{trace.metadata.channel or 'trace'}_offset",
368
356
  )
369
357
 
370
358
 
371
359
  def invert(
372
360
  trace: WaveformTrace,
373
361
  *,
374
- channel_name: str | None = None,
362
+ channel: str | None = None,
375
363
  ) -> WaveformTrace:
376
364
  """Invert trace polarity (multiply by -1).
377
365
 
@@ -379,7 +367,7 @@ def invert(
379
367
 
380
368
  Args:
381
369
  trace: Input trace.
382
- channel_name: Name for the result trace (optional).
370
+ channel: Name for the result trace (optional).
383
371
 
384
372
  Returns:
385
373
  Inverted WaveformTrace.
@@ -390,14 +378,14 @@ def invert(
390
378
  return scale(
391
379
  trace,
392
380
  -1.0,
393
- channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_inverted",
381
+ channel=channel or f"{trace.metadata.channel or 'trace'}_inverted",
394
382
  )
395
383
 
396
384
 
397
385
  def absolute(
398
386
  trace: WaveformTrace,
399
387
  *,
400
- channel_name: str | None = None,
388
+ channel: str | None = None,
401
389
  ) -> WaveformTrace:
402
390
  """Compute absolute value of trace.
403
391
 
@@ -405,7 +393,7 @@ def absolute(
405
393
 
406
394
  Args:
407
395
  trace: Input trace.
408
- channel_name: Name for the result trace (optional).
396
+ channel: Name for the result trace (optional).
409
397
 
410
398
  Returns:
411
399
  WaveformTrace with absolute values.
@@ -419,10 +407,7 @@ def absolute(
419
407
  sample_rate=trace.metadata.sample_rate,
420
408
  vertical_scale=trace.metadata.vertical_scale,
421
409
  vertical_offset=trace.metadata.vertical_offset,
422
- acquisition_time=trace.metadata.acquisition_time,
423
- trigger_info=trace.metadata.trigger_info,
424
- source_file=trace.metadata.source_file,
425
- channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_abs",
410
+ channel=channel or f"{trace.metadata.channel or 'trace'}_abs",
426
411
  )
427
412
 
428
413
  return WaveformTrace(data=result_data, metadata=new_metadata)
@@ -433,7 +418,7 @@ def differentiate(
433
418
  *,
434
419
  order: int = 1,
435
420
  method: str = "central",
436
- channel_name: str | None = None,
421
+ channel: str | None = None,
437
422
  ) -> WaveformTrace:
438
423
  """Compute numerical derivative of trace.
439
424
 
@@ -447,7 +432,7 @@ def differentiate(
447
432
  - "central": Central difference (default, most accurate)
448
433
  - "forward": Forward difference
449
434
  - "backward": Backward difference
450
- channel_name: Name for the result trace (optional).
435
+ channel: Name for the result trace (optional).
451
436
 
452
437
  Returns:
453
438
  Differentiated WaveformTrace in V/s.
@@ -467,7 +452,7 @@ def differentiate(
467
452
  raise ValueError(f"Order must be positive, got {order}")
468
453
 
469
454
  data = trace.data.astype(np.float64)
470
- dt = trace.metadata.time_base
455
+ dt = 1.0 / trace.metadata.sample_rate
471
456
 
472
457
  if len(data) < order + 1:
473
458
  raise InsufficientDataError(
@@ -500,10 +485,7 @@ def differentiate(
500
485
  sample_rate=trace.metadata.sample_rate,
501
486
  vertical_scale=None, # Units changed
502
487
  vertical_offset=None,
503
- acquisition_time=trace.metadata.acquisition_time,
504
- trigger_info=trace.metadata.trigger_info,
505
- source_file=trace.metadata.source_file,
506
- channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_d{order}",
488
+ channel=channel or f"{trace.metadata.channel or 'trace'}_d{order}",
507
489
  )
508
490
 
509
491
  return WaveformTrace(data=result, metadata=new_metadata)
@@ -514,7 +496,7 @@ def integrate(
514
496
  *,
515
497
  method: str = "trapezoid",
516
498
  initial: float = 0.0,
517
- channel_name: str | None = None,
499
+ channel: str | None = None,
518
500
  ) -> WaveformTrace:
519
501
  """Compute numerical integral of trace.
520
502
 
@@ -528,7 +510,7 @@ def integrate(
528
510
  - "simpson": Simpson's rule (requires odd number of points)
529
511
  - "cumsum": Simple cumulative sum
530
512
  initial: Initial value for cumulative integral (default 0).
531
- channel_name: Name for the result trace (optional).
513
+ channel: Name for the result trace (optional).
532
514
 
533
515
  Returns:
534
516
  Integrated WaveformTrace in V*s.
@@ -545,7 +527,7 @@ def integrate(
545
527
  ARITH-006
546
528
  """
547
529
  data = trace.data.astype(np.float64)
548
- dt = trace.metadata.time_base
530
+ dt = 1.0 / trace.metadata.sample_rate
549
531
 
550
532
  if len(data) < 2:
551
533
  raise InsufficientDataError(
@@ -572,10 +554,7 @@ def integrate(
572
554
  sample_rate=trace.metadata.sample_rate,
573
555
  vertical_scale=None, # Units changed
574
556
  vertical_offset=None,
575
- acquisition_time=trace.metadata.acquisition_time,
576
- trigger_info=trace.metadata.trigger_info,
577
- source_file=trace.metadata.source_file,
578
- channel_name=channel_name or f"{trace.metadata.channel_name or 'trace'}_integral",
557
+ channel=channel or f"{trace.metadata.channel or 'trace'}_integral",
579
558
  )
580
559
 
581
560
  return WaveformTrace(data=result, metadata=new_metadata)
@@ -818,14 +797,14 @@ def _ensure_array_result(result: Any, expected_len: int) -> NDArray[np.float64]:
818
797
 
819
798
 
820
799
  def _build_expression_metadata(
821
- ref_trace: WaveformTrace, expression: str, channel_name: str | None
800
+ ref_trace: WaveformTrace, expression: str, channel: str | None
822
801
  ) -> TraceMetadata:
823
802
  """Build metadata for expression result trace.
824
803
 
825
804
  Args:
826
805
  ref_trace: Reference trace for metadata.
827
806
  expression: Expression string (for default naming).
828
- channel_name: Optional channel name override.
807
+ channel: Optional channel name override.
829
808
 
830
809
  Returns:
831
810
  Metadata for result trace.
@@ -834,10 +813,7 @@ def _build_expression_metadata(
834
813
  sample_rate=ref_trace.metadata.sample_rate,
835
814
  vertical_scale=None,
836
815
  vertical_offset=None,
837
- acquisition_time=ref_trace.metadata.acquisition_time,
838
- trigger_info=ref_trace.metadata.trigger_info,
839
- source_file=ref_trace.metadata.source_file,
840
- channel_name=channel_name or f"expr({expression[:20]})",
816
+ channel=channel or f"expr({expression[:20]})",
841
817
  )
842
818
 
843
819
 
@@ -845,7 +821,7 @@ def math_expression(
845
821
  expression: str,
846
822
  traces: dict[str, WaveformTrace],
847
823
  *,
848
- channel_name: str | None = None,
824
+ channel: str | None = None,
849
825
  ) -> WaveformTrace:
850
826
  """Evaluate a mathematical expression on traces.
851
827
 
@@ -855,7 +831,7 @@ def math_expression(
855
831
  Args:
856
832
  expression: Math expression (e.g., "CH1 + CH2", "abs(CH1 - CH2)").
857
833
  traces: Dictionary mapping variable names to traces.
858
- channel_name: Name for the result trace (optional).
834
+ channel: Name for the result trace (optional).
859
835
 
860
836
  Returns:
861
837
  Result WaveformTrace.
@@ -884,5 +860,5 @@ def math_expression(
884
860
  result = _evaluate_expression(expression, safe_namespace)
885
861
  result = _ensure_array_result(result, len(ref_trace.data))
886
862
 
887
- metadata = _build_expression_metadata(ref_trace, expression, channel_name)
863
+ metadata = _build_expression_metadata(ref_trace, expression, channel)
888
864
  return WaveformTrace(data=result.astype(np.float64), metadata=metadata)