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.
- oscura/__init__.py +19 -19
- oscura/analyzers/__init__.py +2 -0
- oscura/analyzers/digital/extraction.py +2 -3
- oscura/analyzers/digital/quality.py +1 -1
- oscura/analyzers/digital/timing.py +1 -1
- oscura/analyzers/eye/__init__.py +5 -1
- oscura/analyzers/eye/generation.py +501 -0
- oscura/analyzers/jitter/__init__.py +6 -6
- oscura/analyzers/jitter/timing.py +419 -0
- oscura/analyzers/patterns/__init__.py +94 -0
- oscura/analyzers/patterns/reverse_engineering.py +991 -0
- oscura/analyzers/power/__init__.py +35 -12
- oscura/analyzers/power/basic.py +3 -3
- oscura/analyzers/power/soa.py +1 -1
- oscura/analyzers/power/switching.py +3 -3
- oscura/analyzers/signal_classification.py +529 -0
- oscura/analyzers/signal_integrity/sparams.py +3 -3
- oscura/analyzers/statistics/__init__.py +4 -0
- oscura/analyzers/statistics/basic.py +152 -0
- oscura/analyzers/statistics/correlation.py +47 -6
- oscura/analyzers/validation.py +1 -1
- oscura/analyzers/waveform/__init__.py +2 -0
- oscura/analyzers/waveform/measurements.py +329 -163
- oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
- oscura/analyzers/waveform/spectral.py +498 -54
- oscura/api/dsl/commands.py +15 -6
- oscura/api/server/templates/base.html +137 -146
- oscura/api/server/templates/export.html +84 -110
- oscura/api/server/templates/home.html +248 -267
- oscura/api/server/templates/protocols.html +44 -48
- oscura/api/server/templates/reports.html +27 -35
- oscura/api/server/templates/session_detail.html +68 -78
- oscura/api/server/templates/sessions.html +62 -72
- oscura/api/server/templates/waveforms.html +54 -64
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/can/session.py +1 -1
- oscura/automotive/dbc/generator.py +638 -23
- oscura/automotive/dtc/data.json +102 -17
- oscura/automotive/uds/decoder.py +99 -6
- oscura/cli/analyze.py +8 -2
- oscura/cli/batch.py +36 -5
- oscura/cli/characterize.py +18 -4
- oscura/cli/export.py +47 -5
- oscura/cli/main.py +2 -0
- oscura/cli/onboarding/wizard.py +10 -6
- oscura/cli/pipeline.py +585 -0
- oscura/cli/visualize.py +6 -4
- oscura/convenience.py +400 -32
- oscura/core/config/loader.py +0 -1
- oscura/core/measurement_result.py +286 -0
- oscura/core/progress.py +1 -1
- oscura/core/schemas/device_mapping.json +8 -2
- oscura/core/schemas/packet_format.json +24 -4
- oscura/core/schemas/protocol_definition.json +12 -2
- oscura/core/types.py +300 -199
- oscura/correlation/multi_protocol.py +1 -1
- oscura/export/legacy/__init__.py +11 -0
- oscura/export/legacy/wav.py +75 -0
- oscura/exporters/__init__.py +19 -0
- oscura/exporters/wireshark.py +809 -0
- oscura/hardware/acquisition/file.py +5 -19
- oscura/hardware/acquisition/saleae.py +10 -10
- oscura/hardware/acquisition/socketcan.py +4 -6
- oscura/hardware/acquisition/synthetic.py +1 -5
- oscura/hardware/acquisition/visa.py +6 -6
- oscura/hardware/security/side_channel_detector.py +5 -508
- oscura/inference/message_format.py +686 -1
- oscura/jupyter/display.py +2 -2
- oscura/jupyter/magic.py +3 -3
- oscura/loaders/__init__.py +17 -12
- oscura/loaders/binary.py +1 -1
- oscura/loaders/chipwhisperer.py +1 -2
- oscura/loaders/configurable.py +1 -1
- oscura/loaders/csv_loader.py +2 -2
- oscura/loaders/hdf5_loader.py +1 -1
- oscura/loaders/lazy.py +6 -1
- oscura/loaders/mmap_loader.py +0 -1
- oscura/loaders/numpy_loader.py +8 -7
- oscura/loaders/preprocessing.py +3 -5
- oscura/loaders/rigol.py +21 -7
- oscura/loaders/sigrok.py +2 -5
- oscura/loaders/tdms.py +3 -2
- oscura/loaders/tektronix.py +38 -32
- oscura/loaders/tss.py +20 -27
- oscura/loaders/vcd.py +13 -8
- oscura/loaders/wav.py +1 -6
- oscura/pipeline/__init__.py +76 -0
- oscura/pipeline/handlers/__init__.py +165 -0
- oscura/pipeline/handlers/analyzers.py +1045 -0
- oscura/pipeline/handlers/decoders.py +899 -0
- oscura/pipeline/handlers/exporters.py +1103 -0
- oscura/pipeline/handlers/filters.py +891 -0
- oscura/pipeline/handlers/loaders.py +640 -0
- oscura/pipeline/handlers/transforms.py +768 -0
- oscura/reporting/__init__.py +88 -1
- oscura/reporting/automation.py +348 -0
- oscura/reporting/citations.py +374 -0
- oscura/reporting/core.py +54 -0
- oscura/reporting/formatting/__init__.py +11 -0
- oscura/reporting/formatting/measurements.py +320 -0
- oscura/reporting/html.py +57 -0
- oscura/reporting/interpretation.py +431 -0
- oscura/reporting/summary.py +329 -0
- oscura/reporting/templates/enhanced/protocol_re.html +504 -503
- oscura/reporting/visualization.py +542 -0
- oscura/side_channel/__init__.py +38 -57
- oscura/utils/builders/signal_builder.py +5 -5
- oscura/utils/comparison/compare.py +7 -9
- oscura/utils/comparison/golden.py +1 -1
- oscura/utils/filtering/convenience.py +2 -2
- oscura/utils/math/arithmetic.py +38 -62
- oscura/utils/math/interpolation.py +20 -20
- oscura/utils/pipeline/__init__.py +4 -17
- oscura/utils/progressive.py +1 -4
- oscura/utils/triggering/edge.py +1 -1
- oscura/utils/triggering/pattern.py +2 -2
- oscura/utils/triggering/pulse.py +2 -2
- oscura/utils/triggering/window.py +3 -3
- oscura/validation/hil_testing.py +11 -11
- oscura/visualization/__init__.py +47 -284
- oscura/visualization/batch.py +160 -0
- oscura/visualization/plot.py +542 -53
- oscura/visualization/styles.py +184 -318
- oscura/workflows/__init__.py +2 -0
- oscura/workflows/batch/advanced.py +1 -1
- oscura/workflows/batch/aggregate.py +7 -8
- oscura/workflows/complete_re.py +251 -23
- oscura/workflows/digital.py +27 -4
- oscura/workflows/multi_trace.py +136 -17
- oscura/workflows/waveform.py +788 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
- oscura/side_channel/dpa.py +0 -1025
- oscura/utils/optimization/__init__.py +0 -19
- oscura/utils/optimization/parallel.py +0 -443
- oscura/utils/optimization/search.py +0 -532
- oscura/utils/pipeline/base.py +0 -338
- oscura/utils/pipeline/composition.py +0 -248
- oscura/utils/pipeline/parallel.py +0 -449
- oscura/utils/pipeline/pipeline.py +0 -375
- oscura/utils/search/__init__.py +0 -16
- oscura/utils/search/anomaly.py +0 -424
- oscura/utils/search/context.py +0 -294
- oscura/utils/search/pattern.py +0 -288
- oscura/utils/storage/__init__.py +0 -61
- oscura/utils/storage/database.py +0 -1166
- oscura/visualization/accessibility.py +0 -526
- oscura/visualization/annotations.py +0 -371
- oscura/visualization/axis_scaling.py +0 -305
- oscura/visualization/colors.py +0 -451
- oscura/visualization/digital.py +0 -436
- oscura/visualization/eye.py +0 -571
- oscura/visualization/histogram.py +0 -281
- oscura/visualization/interactive.py +0 -1035
- oscura/visualization/jitter.py +0 -1042
- oscura/visualization/keyboard.py +0 -394
- oscura/visualization/layout.py +0 -400
- oscura/visualization/optimization.py +0 -1079
- oscura/visualization/palettes.py +0 -446
- oscura/visualization/power.py +0 -508
- oscura/visualization/power_extended.py +0 -955
- oscura/visualization/presets.py +0 -469
- oscura/visualization/protocols.py +0 -1246
- oscura/visualization/render.py +0 -223
- oscura/visualization/rendering.py +0 -444
- oscura/visualization/reverse_engineering.py +0 -838
- oscura/visualization/signal_integrity.py +0 -989
- oscura/visualization/specialized.py +0 -643
- oscura/visualization/spectral.py +0 -1226
- oscura/visualization/thumbnails.py +0 -340
- oscura/visualization/time_axis.py +0 -351
- oscura/visualization/waveform.py +0 -454
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
"""Input loader handlers for pipeline system.
|
|
2
|
+
|
|
3
|
+
This module provides handlers for loading data from various file formats.
|
|
4
|
+
All handlers follow the standard signature: (inputs, params, step_name) -> outputs.
|
|
5
|
+
|
|
6
|
+
Available Handlers:
|
|
7
|
+
- input.file: Load from file with auto-format detection
|
|
8
|
+
- input.vcd: Load VCD (Value Change Dump) files
|
|
9
|
+
- input.wav: Load WAV audio files
|
|
10
|
+
- input.csv: Load CSV data files
|
|
11
|
+
- input.pcap: Load PCAP network capture files
|
|
12
|
+
- input.binary: Load raw binary data
|
|
13
|
+
- input.hdf5: Load HDF5 files
|
|
14
|
+
- input.numpy: Load NumPy .npz files
|
|
15
|
+
- input.tektronix: Load Tektronix oscilloscope files
|
|
16
|
+
- input.rigol: Load Rigol oscilloscope files
|
|
17
|
+
- input.sigrok: Load Sigrok/PulseView captures
|
|
18
|
+
- input.tdms: Load NI TDMS files
|
|
19
|
+
- input.touchstone: Load Touchstone S-parameter files
|
|
20
|
+
- input.chipwhisperer: Load ChipWhisperer captures
|
|
21
|
+
- input.multi_channel: Load all channels from multi-channel file
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from oscura.core.config.pipeline import PipelineExecutionError
|
|
30
|
+
from oscura.pipeline.handlers import register_handler
|
|
31
|
+
|
|
32
|
+
# Lazy imports to avoid circular dependencies
|
|
33
|
+
_loaders = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_loaders() -> Any:
|
|
37
|
+
"""Lazy import loaders module."""
|
|
38
|
+
global _loaders
|
|
39
|
+
if _loaders is None:
|
|
40
|
+
import oscura.loaders as _loaders_module
|
|
41
|
+
|
|
42
|
+
_loaders = _loaders_module
|
|
43
|
+
return _loaders
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@register_handler("input.file")
|
|
47
|
+
def handle_input_file(
|
|
48
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
"""Load trace from file with auto-format detection.
|
|
51
|
+
|
|
52
|
+
Parameters:
|
|
53
|
+
path (str): File path to load
|
|
54
|
+
format (str, optional): Format override (tektronix, rigol, csv, etc.)
|
|
55
|
+
channel (str|int, optional): Channel name or index for multi-channel files
|
|
56
|
+
lazy (bool, optional): Enable lazy loading for large files
|
|
57
|
+
|
|
58
|
+
Outputs:
|
|
59
|
+
trace: Loaded WaveformTrace, DigitalTrace, or IQTrace
|
|
60
|
+
path: Absolute file path
|
|
61
|
+
format: File extension
|
|
62
|
+
metadata: Trace metadata dict
|
|
63
|
+
"""
|
|
64
|
+
loaders = _get_loaders()
|
|
65
|
+
|
|
66
|
+
path = params.get("path")
|
|
67
|
+
if not path:
|
|
68
|
+
raise PipelineExecutionError(
|
|
69
|
+
"Missing required parameter 'path'. Suggestion: Add 'path: filename.wfm' to params",
|
|
70
|
+
step_name=step_name,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
format_override = params.get("format")
|
|
74
|
+
channel = params.get("channel")
|
|
75
|
+
lazy = params.get("lazy", False)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
trace = loaders.load(path, format=format_override, channel=channel, lazy=lazy)
|
|
79
|
+
except FileNotFoundError as e:
|
|
80
|
+
raise PipelineExecutionError(f"File not found: {path}", step_name=step_name) from e
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise PipelineExecutionError(f"Failed to load file: {e}", step_name=step_name) from e
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"trace": trace,
|
|
86
|
+
"path": str(Path(path).resolve()),
|
|
87
|
+
"format": Path(path).suffix,
|
|
88
|
+
"metadata": trace.metadata.__dict__ if hasattr(trace, "metadata") else {},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@register_handler("input.vcd")
|
|
93
|
+
def handle_input_vcd(
|
|
94
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
95
|
+
) -> dict[str, Any]:
|
|
96
|
+
"""Load VCD (Value Change Dump) file.
|
|
97
|
+
|
|
98
|
+
Parameters:
|
|
99
|
+
path (str): Path to VCD file
|
|
100
|
+
signal_name (str, optional): Specific signal to extract
|
|
101
|
+
|
|
102
|
+
Outputs:
|
|
103
|
+
trace: Loaded DigitalTrace
|
|
104
|
+
path: Absolute file path
|
|
105
|
+
signals: List of available signal names
|
|
106
|
+
"""
|
|
107
|
+
loaders = _get_loaders()
|
|
108
|
+
|
|
109
|
+
path = params.get("path")
|
|
110
|
+
if not path:
|
|
111
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
112
|
+
|
|
113
|
+
signal_name = params.get("signal_name")
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
trace = loaders.load(path, format="vcd", channel=signal_name)
|
|
117
|
+
# Get all signals if needed
|
|
118
|
+
from oscura.loaders.vcd import load_vcd
|
|
119
|
+
|
|
120
|
+
vcd_data = load_vcd(path)
|
|
121
|
+
signals = list(vcd_data.keys()) if isinstance(vcd_data, dict) else []
|
|
122
|
+
except Exception as e:
|
|
123
|
+
raise PipelineExecutionError(f"Failed to load VCD: {e}", step_name=step_name) from e
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"trace": trace,
|
|
127
|
+
"path": str(Path(path).resolve()),
|
|
128
|
+
"signals": signals,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@register_handler("input.wav")
|
|
133
|
+
def handle_input_wav(
|
|
134
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
135
|
+
) -> dict[str, Any]:
|
|
136
|
+
"""Load WAV audio file.
|
|
137
|
+
|
|
138
|
+
Parameters:
|
|
139
|
+
path (str): Path to WAV file
|
|
140
|
+
channel (int, optional): Channel index (0 for mono/left, 1 for right)
|
|
141
|
+
|
|
142
|
+
Outputs:
|
|
143
|
+
trace: Loaded WaveformTrace
|
|
144
|
+
path: Absolute file path
|
|
145
|
+
sample_rate: Audio sample rate in Hz
|
|
146
|
+
num_channels: Number of audio channels
|
|
147
|
+
"""
|
|
148
|
+
loaders = _get_loaders()
|
|
149
|
+
|
|
150
|
+
path = params.get("path")
|
|
151
|
+
if not path:
|
|
152
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
153
|
+
|
|
154
|
+
channel = params.get("channel")
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
trace = loaders.load(path, format="wav", channel=channel)
|
|
158
|
+
sample_rate = trace.metadata.sample_rate
|
|
159
|
+
# WAV files have num_channels in metadata
|
|
160
|
+
num_channels = getattr(trace.metadata, "num_channels", 1)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
raise PipelineExecutionError(f"Failed to load WAV: {e}", step_name=step_name) from e
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
"trace": trace,
|
|
166
|
+
"path": str(Path(path).resolve()),
|
|
167
|
+
"sample_rate": sample_rate,
|
|
168
|
+
"num_channels": num_channels,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@register_handler("input.csv")
|
|
173
|
+
def handle_input_csv(
|
|
174
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
"""Load CSV data file.
|
|
177
|
+
|
|
178
|
+
Parameters:
|
|
179
|
+
path (str): Path to CSV file
|
|
180
|
+
time_column (str, optional): Name of time column
|
|
181
|
+
data_column (str, optional): Name of data column
|
|
182
|
+
delimiter (str, optional): CSV delimiter (default: ',')
|
|
183
|
+
skip_rows (int, optional): Number of header rows to skip
|
|
184
|
+
|
|
185
|
+
Outputs:
|
|
186
|
+
trace: Loaded WaveformTrace
|
|
187
|
+
path: Absolute file path
|
|
188
|
+
columns: List of available column names
|
|
189
|
+
"""
|
|
190
|
+
loaders = _get_loaders()
|
|
191
|
+
|
|
192
|
+
path = params.get("path")
|
|
193
|
+
if not path:
|
|
194
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
195
|
+
|
|
196
|
+
time_col = params.get("time_column")
|
|
197
|
+
data_col = params.get("data_column")
|
|
198
|
+
delimiter = params.get("delimiter", ",")
|
|
199
|
+
skip_rows = params.get("skip_rows", 0)
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
trace = loaders.load(
|
|
203
|
+
path,
|
|
204
|
+
format="csv",
|
|
205
|
+
time_column=time_col,
|
|
206
|
+
data_column=data_col,
|
|
207
|
+
delimiter=delimiter,
|
|
208
|
+
skip_rows=skip_rows,
|
|
209
|
+
)
|
|
210
|
+
# Try to get column names
|
|
211
|
+
import pandas as pd
|
|
212
|
+
|
|
213
|
+
df = pd.read_csv(path, delimiter=delimiter, skiprows=skip_rows, nrows=0)
|
|
214
|
+
columns = list(df.columns)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
raise PipelineExecutionError(f"Failed to load CSV: {e}", step_name=step_name) from e
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
"trace": trace,
|
|
220
|
+
"path": str(Path(path).resolve()),
|
|
221
|
+
"columns": columns,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@register_handler("input.pcap")
|
|
226
|
+
def handle_input_pcap(
|
|
227
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
228
|
+
) -> dict[str, Any]:
|
|
229
|
+
"""Load PCAP network capture file.
|
|
230
|
+
|
|
231
|
+
Parameters:
|
|
232
|
+
path (str): Path to PCAP file
|
|
233
|
+
filter (str, optional): BPF filter expression
|
|
234
|
+
|
|
235
|
+
Outputs:
|
|
236
|
+
trace: Loaded trace with packet data
|
|
237
|
+
path: Absolute file path
|
|
238
|
+
packet_count: Number of packets
|
|
239
|
+
"""
|
|
240
|
+
loaders = _get_loaders()
|
|
241
|
+
|
|
242
|
+
path = params.get("path")
|
|
243
|
+
if not path:
|
|
244
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
245
|
+
|
|
246
|
+
bpf_filter = params.get("filter")
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
trace = loaders.load(path, format="pcap", filter=bpf_filter)
|
|
250
|
+
# Count packets (trace data should be packet array)
|
|
251
|
+
packet_count = len(trace.data) if hasattr(trace, "data") else 0
|
|
252
|
+
except Exception as e:
|
|
253
|
+
raise PipelineExecutionError(f"Failed to load PCAP: {e}", step_name=step_name) from e
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
"trace": trace,
|
|
257
|
+
"path": str(Path(path).resolve()),
|
|
258
|
+
"packet_count": packet_count,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@register_handler("input.binary")
|
|
263
|
+
def handle_input_binary(
|
|
264
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
265
|
+
) -> dict[str, Any]:
|
|
266
|
+
"""Load raw binary data file.
|
|
267
|
+
|
|
268
|
+
Parameters:
|
|
269
|
+
path (str): Path to binary file
|
|
270
|
+
sample_rate (float): Sample rate in Hz
|
|
271
|
+
dtype (str, optional): NumPy dtype (default: 'float32')
|
|
272
|
+
offset (int, optional): Byte offset to start reading
|
|
273
|
+
count (int, optional): Number of samples to read
|
|
274
|
+
|
|
275
|
+
Outputs:
|
|
276
|
+
trace: Loaded WaveformTrace
|
|
277
|
+
path: Absolute file path
|
|
278
|
+
num_samples: Number of samples loaded
|
|
279
|
+
"""
|
|
280
|
+
loaders = _get_loaders()
|
|
281
|
+
|
|
282
|
+
path = params.get("path")
|
|
283
|
+
sample_rate = params.get("sample_rate")
|
|
284
|
+
|
|
285
|
+
if not path:
|
|
286
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
287
|
+
if not sample_rate:
|
|
288
|
+
raise PipelineExecutionError(
|
|
289
|
+
"Missing required parameter 'sample_rate'", step_name=step_name
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
dtype = params.get("dtype", "float32")
|
|
293
|
+
offset = params.get("offset", 0)
|
|
294
|
+
count = params.get("count", -1)
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
trace = loaders.load_binary(
|
|
298
|
+
path, sample_rate=sample_rate, dtype=dtype, offset=offset, count=count
|
|
299
|
+
)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
raise PipelineExecutionError(f"Failed to load binary: {e}", step_name=step_name) from e
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
"trace": trace,
|
|
305
|
+
"path": str(Path(path).resolve()),
|
|
306
|
+
"num_samples": len(trace.data),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@register_handler("input.hdf5")
|
|
311
|
+
def handle_input_hdf5(
|
|
312
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
313
|
+
) -> dict[str, Any]:
|
|
314
|
+
"""Load HDF5 file.
|
|
315
|
+
|
|
316
|
+
Parameters:
|
|
317
|
+
path (str): Path to HDF5 file
|
|
318
|
+
dataset (str, optional): Dataset path within HDF5 file
|
|
319
|
+
|
|
320
|
+
Outputs:
|
|
321
|
+
trace: Loaded WaveformTrace
|
|
322
|
+
path: Absolute file path
|
|
323
|
+
datasets: List of available dataset paths
|
|
324
|
+
"""
|
|
325
|
+
loaders = _get_loaders()
|
|
326
|
+
|
|
327
|
+
path = params.get("path")
|
|
328
|
+
if not path:
|
|
329
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
330
|
+
|
|
331
|
+
dataset = params.get("dataset")
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
trace = loaders.load(path, format="hdf5", dataset=dataset)
|
|
335
|
+
# Get available datasets
|
|
336
|
+
import h5py
|
|
337
|
+
|
|
338
|
+
with h5py.File(path, "r") as f:
|
|
339
|
+
|
|
340
|
+
def list_datasets(group: Any, prefix: str = "") -> list[str]:
|
|
341
|
+
datasets = []
|
|
342
|
+
for key in group:
|
|
343
|
+
item = group[key]
|
|
344
|
+
if isinstance(item, h5py.Dataset):
|
|
345
|
+
datasets.append(f"{prefix}/{key}")
|
|
346
|
+
elif isinstance(item, h5py.Group):
|
|
347
|
+
datasets.extend(list_datasets(item, f"{prefix}/{key}"))
|
|
348
|
+
return datasets
|
|
349
|
+
|
|
350
|
+
datasets = list_datasets(f)
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise PipelineExecutionError(f"Failed to load HDF5: {e}", step_name=step_name) from e
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
"trace": trace,
|
|
356
|
+
"path": str(Path(path).resolve()),
|
|
357
|
+
"datasets": datasets,
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@register_handler("input.numpy")
|
|
362
|
+
def handle_input_numpy(
|
|
363
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
364
|
+
) -> dict[str, Any]:
|
|
365
|
+
"""Load NumPy .npz file.
|
|
366
|
+
|
|
367
|
+
Parameters:
|
|
368
|
+
path (str): Path to .npz file
|
|
369
|
+
array_name (str, optional): Name of array to load (default: first array)
|
|
370
|
+
|
|
371
|
+
Outputs:
|
|
372
|
+
trace: Loaded WaveformTrace
|
|
373
|
+
path: Absolute file path
|
|
374
|
+
arrays: List of available array names
|
|
375
|
+
"""
|
|
376
|
+
loaders = _get_loaders()
|
|
377
|
+
|
|
378
|
+
path = params.get("path")
|
|
379
|
+
if not path:
|
|
380
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
381
|
+
|
|
382
|
+
array_name = params.get("array_name")
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
trace = loaders.load(path, format="numpy", array_name=array_name)
|
|
386
|
+
# Get available arrays
|
|
387
|
+
import numpy as np
|
|
388
|
+
|
|
389
|
+
with np.load(path) as npz:
|
|
390
|
+
arrays = list(npz.keys())
|
|
391
|
+
except Exception as e:
|
|
392
|
+
raise PipelineExecutionError(f"Failed to load NumPy: {e}", step_name=step_name) from e
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
"trace": trace,
|
|
396
|
+
"path": str(Path(path).resolve()),
|
|
397
|
+
"arrays": arrays,
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@register_handler("input.tektronix")
|
|
402
|
+
def handle_input_tektronix(
|
|
403
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
404
|
+
) -> dict[str, Any]:
|
|
405
|
+
"""Load Tektronix oscilloscope file (.wfm, .isf).
|
|
406
|
+
|
|
407
|
+
Parameters:
|
|
408
|
+
path (str): Path to Tektronix file
|
|
409
|
+
channel (str|int, optional): Channel to load
|
|
410
|
+
|
|
411
|
+
Outputs:
|
|
412
|
+
trace: Loaded WaveformTrace
|
|
413
|
+
path: Absolute file path
|
|
414
|
+
"""
|
|
415
|
+
loaders = _get_loaders()
|
|
416
|
+
|
|
417
|
+
path = params.get("path")
|
|
418
|
+
if not path:
|
|
419
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
420
|
+
|
|
421
|
+
channel = params.get("channel")
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
trace = loaders.load(path, format="tektronix", channel=channel)
|
|
425
|
+
except Exception as e:
|
|
426
|
+
raise PipelineExecutionError(
|
|
427
|
+
f"Failed to load Tektronix file: {e}", step_name=step_name
|
|
428
|
+
) from e
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
"trace": trace,
|
|
432
|
+
"path": str(Path(path).resolve()),
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@register_handler("input.rigol")
|
|
437
|
+
def handle_input_rigol(
|
|
438
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
439
|
+
) -> dict[str, Any]:
|
|
440
|
+
"""Load Rigol oscilloscope file.
|
|
441
|
+
|
|
442
|
+
Parameters:
|
|
443
|
+
path (str): Path to Rigol file
|
|
444
|
+
channel (str|int, optional): Channel to load
|
|
445
|
+
|
|
446
|
+
Outputs:
|
|
447
|
+
trace: Loaded WaveformTrace
|
|
448
|
+
path: Absolute file path
|
|
449
|
+
"""
|
|
450
|
+
loaders = _get_loaders()
|
|
451
|
+
|
|
452
|
+
path = params.get("path")
|
|
453
|
+
if not path:
|
|
454
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
455
|
+
|
|
456
|
+
channel = params.get("channel")
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
trace = loaders.load(path, format="rigol", channel=channel)
|
|
460
|
+
except Exception as e:
|
|
461
|
+
raise PipelineExecutionError(f"Failed to load Rigol file: {e}", step_name=step_name) from e
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
"trace": trace,
|
|
465
|
+
"path": str(Path(path).resolve()),
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
@register_handler("input.sigrok")
|
|
470
|
+
def handle_input_sigrok(
|
|
471
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
472
|
+
) -> dict[str, Any]:
|
|
473
|
+
"""Load Sigrok/PulseView capture.
|
|
474
|
+
|
|
475
|
+
Parameters:
|
|
476
|
+
path (str): Path to Sigrok file
|
|
477
|
+
channel (str|int, optional): Channel to load
|
|
478
|
+
|
|
479
|
+
Outputs:
|
|
480
|
+
trace: Loaded DigitalTrace
|
|
481
|
+
path: Absolute file path
|
|
482
|
+
"""
|
|
483
|
+
loaders = _get_loaders()
|
|
484
|
+
|
|
485
|
+
path = params.get("path")
|
|
486
|
+
if not path:
|
|
487
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
488
|
+
|
|
489
|
+
channel = params.get("channel")
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
trace = loaders.load(path, format="sigrok", channel=channel)
|
|
493
|
+
except Exception as e:
|
|
494
|
+
raise PipelineExecutionError(f"Failed to load Sigrok file: {e}", step_name=step_name) from e
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
"trace": trace,
|
|
498
|
+
"path": str(Path(path).resolve()),
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@register_handler("input.tdms")
|
|
503
|
+
def handle_input_tdms(
|
|
504
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
505
|
+
) -> dict[str, Any]:
|
|
506
|
+
"""Load National Instruments TDMS file.
|
|
507
|
+
|
|
508
|
+
Parameters:
|
|
509
|
+
path (str): Path to TDMS file
|
|
510
|
+
group (str, optional): Group name
|
|
511
|
+
channel (str, optional): Channel name
|
|
512
|
+
|
|
513
|
+
Outputs:
|
|
514
|
+
trace: Loaded WaveformTrace
|
|
515
|
+
path: Absolute file path
|
|
516
|
+
"""
|
|
517
|
+
loaders = _get_loaders()
|
|
518
|
+
|
|
519
|
+
path = params.get("path")
|
|
520
|
+
if not path:
|
|
521
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
522
|
+
|
|
523
|
+
group = params.get("group")
|
|
524
|
+
channel = params.get("channel")
|
|
525
|
+
|
|
526
|
+
try:
|
|
527
|
+
trace = loaders.load(path, format="tdms", group=group, channel=channel)
|
|
528
|
+
except Exception as e:
|
|
529
|
+
raise PipelineExecutionError(f"Failed to load TDMS file: {e}", step_name=step_name) from e
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
"trace": trace,
|
|
533
|
+
"path": str(Path(path).resolve()),
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@register_handler("input.touchstone")
|
|
538
|
+
def handle_input_touchstone(
|
|
539
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
540
|
+
) -> dict[str, Any]:
|
|
541
|
+
"""Load Touchstone S-parameter file (.s1p, .s2p, etc.).
|
|
542
|
+
|
|
543
|
+
Parameters:
|
|
544
|
+
path (str): Path to Touchstone file
|
|
545
|
+
|
|
546
|
+
Outputs:
|
|
547
|
+
sparams: S-parameter data object
|
|
548
|
+
path: Absolute file path
|
|
549
|
+
num_ports: Number of ports
|
|
550
|
+
"""
|
|
551
|
+
loaders = _get_loaders()
|
|
552
|
+
|
|
553
|
+
path = params.get("path")
|
|
554
|
+
if not path:
|
|
555
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
556
|
+
|
|
557
|
+
try:
|
|
558
|
+
sparams = loaders.load_touchstone(path)
|
|
559
|
+
num_ports = getattr(sparams, "num_ports", 0)
|
|
560
|
+
except Exception as e:
|
|
561
|
+
raise PipelineExecutionError(
|
|
562
|
+
f"Failed to load Touchstone file: {e}", step_name=step_name
|
|
563
|
+
) from e
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
"sparams": sparams,
|
|
567
|
+
"path": str(Path(path).resolve()),
|
|
568
|
+
"num_ports": num_ports,
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@register_handler("input.chipwhisperer")
|
|
573
|
+
def handle_input_chipwhisperer(
|
|
574
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
575
|
+
) -> dict[str, Any]:
|
|
576
|
+
"""Load ChipWhisperer capture.
|
|
577
|
+
|
|
578
|
+
Parameters:
|
|
579
|
+
path (str): Path to ChipWhisperer file
|
|
580
|
+
|
|
581
|
+
Outputs:
|
|
582
|
+
trace: Loaded WaveformTrace
|
|
583
|
+
path: Absolute file path
|
|
584
|
+
"""
|
|
585
|
+
loaders = _get_loaders()
|
|
586
|
+
|
|
587
|
+
path = params.get("path")
|
|
588
|
+
if not path:
|
|
589
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
590
|
+
|
|
591
|
+
try:
|
|
592
|
+
trace = loaders.load(path, format="chipwhisperer")
|
|
593
|
+
except Exception as e:
|
|
594
|
+
raise PipelineExecutionError(
|
|
595
|
+
f"Failed to load ChipWhisperer file: {e}", step_name=step_name
|
|
596
|
+
) from e
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
"trace": trace,
|
|
600
|
+
"path": str(Path(path).resolve()),
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
@register_handler("input.multi_channel")
|
|
605
|
+
def handle_input_multi_channel(
|
|
606
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
607
|
+
) -> dict[str, Any]:
|
|
608
|
+
"""Load all channels from a multi-channel file.
|
|
609
|
+
|
|
610
|
+
Parameters:
|
|
611
|
+
path (str): Path to multi-channel file
|
|
612
|
+
format (str, optional): Format override
|
|
613
|
+
|
|
614
|
+
Outputs:
|
|
615
|
+
channels: Dict mapping channel names to traces
|
|
616
|
+
path: Absolute file path
|
|
617
|
+
channel_names: List of channel names
|
|
618
|
+
"""
|
|
619
|
+
loaders = _get_loaders()
|
|
620
|
+
|
|
621
|
+
path = params.get("path")
|
|
622
|
+
if not path:
|
|
623
|
+
raise PipelineExecutionError("Missing required parameter 'path'", step_name=step_name)
|
|
624
|
+
|
|
625
|
+
format_override = params.get("format")
|
|
626
|
+
|
|
627
|
+
try:
|
|
628
|
+
# Load all channels
|
|
629
|
+
channels = loaders.load_all_channels(path, format=format_override)
|
|
630
|
+
channel_names = list(channels.keys())
|
|
631
|
+
except Exception as e:
|
|
632
|
+
raise PipelineExecutionError(
|
|
633
|
+
f"Failed to load multi-channel file: {e}", step_name=step_name
|
|
634
|
+
) from e
|
|
635
|
+
|
|
636
|
+
return {
|
|
637
|
+
"channels": channels,
|
|
638
|
+
"path": str(Path(path).resolve()),
|
|
639
|
+
"channel_names": channel_names,
|
|
640
|
+
}
|