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
oscura/jupyter/display.py
CHANGED
|
@@ -103,8 +103,8 @@ class TraceDisplay:
|
|
|
103
103
|
rows.append(("Sample Rate", rate_str))
|
|
104
104
|
|
|
105
105
|
# Channel name
|
|
106
|
-
if hasattr(meta, "
|
|
107
|
-
rows.append(("Channel", meta.
|
|
106
|
+
if hasattr(meta, "channel") and meta.channel:
|
|
107
|
+
rows.append(("Channel", meta.channel))
|
|
108
108
|
|
|
109
109
|
# Source file
|
|
110
110
|
if hasattr(meta, "source_file") and meta.source_file:
|
oscura/jupyter/magic.py
CHANGED
|
@@ -82,7 +82,7 @@ except ImportError:
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
@magics_class
|
|
85
|
-
class OscuraMagics(Magics):
|
|
85
|
+
class OscuraMagics(Magics): # type: ignore[misc]
|
|
86
86
|
"""IPython magics for Oscura analysis.
|
|
87
87
|
|
|
88
88
|
Provides convenient shortcuts for loading traces, running measurements,
|
|
@@ -232,8 +232,8 @@ class OscuraMagics(Magics):
|
|
|
232
232
|
meta = trace.metadata
|
|
233
233
|
if hasattr(meta, "sample_rate"):
|
|
234
234
|
info["sample_rate"] = meta.sample_rate
|
|
235
|
-
if hasattr(meta, "
|
|
236
|
-
info["channel"] = meta.
|
|
235
|
+
if hasattr(meta, "channel"):
|
|
236
|
+
info["channel"] = meta.channel
|
|
237
237
|
|
|
238
238
|
for key, value in info.items():
|
|
239
239
|
print(f"{key}: {value}")
|
oscura/loaders/__init__.py
CHANGED
|
@@ -290,6 +290,10 @@ def load(
|
|
|
290
290
|
return _dispatch_loader(loader_name, path, channel=channel, **kwargs)
|
|
291
291
|
|
|
292
292
|
|
|
293
|
+
# Alias for auto-detection (backward compatibility)
|
|
294
|
+
load_auto = load
|
|
295
|
+
|
|
296
|
+
|
|
293
297
|
def _load_wfm_auto(
|
|
294
298
|
path: Path,
|
|
295
299
|
*,
|
|
@@ -400,8 +404,8 @@ def load_all_channels(
|
|
|
400
404
|
else:
|
|
401
405
|
# For other formats, try loading as single channel
|
|
402
406
|
trace = load(path, format=format)
|
|
403
|
-
|
|
404
|
-
return {
|
|
407
|
+
channel = getattr(trace.metadata, "channel", None) or "ch1"
|
|
408
|
+
return {channel: trace}
|
|
405
409
|
|
|
406
410
|
|
|
407
411
|
def _load_all_channels_tektronix(
|
|
@@ -464,9 +468,9 @@ def _read_tektronix_file(path: Path) -> Any:
|
|
|
464
468
|
except ImportError:
|
|
465
469
|
# Fall back to single channel loading
|
|
466
470
|
trace = load(path, format="tektronix")
|
|
467
|
-
|
|
471
|
+
channel = getattr(trace.metadata, "channel", None) or "ch1"
|
|
468
472
|
# Return a dict-like object to maintain compatibility
|
|
469
|
-
return {"__fallback__": {
|
|
473
|
+
return {"__fallback__": {channel: trace}}
|
|
470
474
|
|
|
471
475
|
try:
|
|
472
476
|
return tm_data_types.read_file(str(path))
|
|
@@ -502,14 +506,14 @@ def _extract_analog_waveforms(
|
|
|
502
506
|
sample_rate = 1.0 / x_increment if x_increment > 0 else 1e6
|
|
503
507
|
vertical_scale = getattr(awfm, "y_scale", None)
|
|
504
508
|
vertical_offset = getattr(awfm, "y_offset", None)
|
|
505
|
-
|
|
509
|
+
channel = getattr(awfm, "name", f"CH{i + 1}")
|
|
506
510
|
|
|
507
511
|
trace = _build_waveform_trace(
|
|
508
512
|
data=data,
|
|
509
513
|
sample_rate=sample_rate,
|
|
510
514
|
vertical_scale=vertical_scale,
|
|
511
515
|
vertical_offset=vertical_offset,
|
|
512
|
-
|
|
516
|
+
channel=channel,
|
|
513
517
|
path=path,
|
|
514
518
|
wfm=awfm,
|
|
515
519
|
)
|
|
@@ -557,23 +561,23 @@ def _extract_direct_waveform(
|
|
|
557
561
|
from oscura.loaders.tektronix import _load_digital_waveform
|
|
558
562
|
|
|
559
563
|
trace = _load_digital_waveform(wfm, path, 0)
|
|
560
|
-
|
|
561
|
-
channels[
|
|
564
|
+
channel = trace.metadata.channel or "d1"
|
|
565
|
+
channels[channel.lower()] = trace
|
|
562
566
|
|
|
563
567
|
elif wfm_type == "IQWaveform" or (
|
|
564
568
|
hasattr(wfm, "i_axis_values") and hasattr(wfm, "q_axis_values")
|
|
565
569
|
):
|
|
566
570
|
# IQWaveform format (RF/SDR data)
|
|
567
571
|
loaded_trace = load(path, format="tektronix")
|
|
568
|
-
|
|
569
|
-
channels[
|
|
572
|
+
channel = loaded_trace.metadata.channel or "iq1"
|
|
573
|
+
channels[channel.lower()] = loaded_trace
|
|
570
574
|
|
|
571
575
|
elif hasattr(wfm, "y_axis_values") or hasattr(wfm, "y_data"):
|
|
572
576
|
# Direct analog waveform
|
|
573
577
|
loaded_trace = load(path, format="tektronix")
|
|
574
578
|
# Add both analog and digital traces to channels
|
|
575
|
-
|
|
576
|
-
channels[
|
|
579
|
+
channel = loaded_trace.metadata.channel or "ch1"
|
|
580
|
+
channels[channel.lower()] = loaded_trace
|
|
577
581
|
|
|
578
582
|
|
|
579
583
|
def get_supported_formats() -> list[str]:
|
|
@@ -649,6 +653,7 @@ __all__ = [
|
|
|
649
653
|
"hdf5",
|
|
650
654
|
"load",
|
|
651
655
|
"load_all_channels",
|
|
656
|
+
"load_auto",
|
|
652
657
|
"load_binary",
|
|
653
658
|
"load_binary_packets",
|
|
654
659
|
"load_lazy",
|
oscura/loaders/binary.py
CHANGED
|
@@ -81,8 +81,8 @@ def load_binary(
|
|
|
81
81
|
# Create metadata
|
|
82
82
|
metadata = TraceMetadata(
|
|
83
83
|
sample_rate=sample_rate,
|
|
84
|
+
channel=f"Channel {channel}",
|
|
84
85
|
source_file=str(path),
|
|
85
|
-
channel_name=f"Channel {channel}",
|
|
86
86
|
)
|
|
87
87
|
|
|
88
88
|
return WaveformTrace(data=data.astype(np.float64), metadata=metadata)
|
oscura/loaders/chipwhisperer.py
CHANGED
|
@@ -399,8 +399,7 @@ def to_waveform_trace(
|
|
|
399
399
|
|
|
400
400
|
metadata = TraceMetadata(
|
|
401
401
|
sample_rate=traceset.sample_rate,
|
|
402
|
-
|
|
403
|
-
channel_name=f"trace_{trace_index}",
|
|
402
|
+
channel=f"trace_{trace_index}",
|
|
404
403
|
)
|
|
405
404
|
|
|
406
405
|
return WaveformTrace(
|
oscura/loaders/configurable.py
CHANGED
|
@@ -1351,7 +1351,7 @@ def extract_channels(
|
|
|
1351
1351
|
# Create metadata with configurable sample rate
|
|
1352
1352
|
metadata = TraceMetadata(
|
|
1353
1353
|
sample_rate=effective_sample_rate,
|
|
1354
|
-
|
|
1354
|
+
channel=ch_name,
|
|
1355
1355
|
)
|
|
1356
1356
|
|
|
1357
1357
|
traces[ch_name] = DigitalTrace(data=data, metadata=metadata)
|
oscura/loaders/csv_loader.py
CHANGED
|
@@ -208,8 +208,8 @@ def _load_with_pandas(
|
|
|
208
208
|
# Create metadata and trace
|
|
209
209
|
metadata = TraceMetadata(
|
|
210
210
|
sample_rate=detected_sample_rate,
|
|
211
|
+
channel=voltage_col_name or "CH1",
|
|
211
212
|
source_file=str(path),
|
|
212
|
-
channel_name=voltage_col_name or "CH1",
|
|
213
213
|
)
|
|
214
214
|
|
|
215
215
|
return WaveformTrace(data=np.asarray(voltage_data, dtype=np.float64), metadata=metadata)
|
|
@@ -429,8 +429,8 @@ def _load_basic(
|
|
|
429
429
|
# Create metadata and trace
|
|
430
430
|
metadata = TraceMetadata(
|
|
431
431
|
sample_rate=detected_sample_rate,
|
|
432
|
+
channel=channel_name,
|
|
432
433
|
source_file=str(path),
|
|
433
|
-
channel_name=channel_name,
|
|
434
434
|
)
|
|
435
435
|
|
|
436
436
|
return WaveformTrace(data=np.array(voltage_data, dtype=np.float64), metadata=metadata)
|
oscura/loaders/hdf5_loader.py
CHANGED
|
@@ -413,8 +413,8 @@ def _build_metadata(
|
|
|
413
413
|
sample_rate=float(detected_sample_rate),
|
|
414
414
|
vertical_scale=float(vertical_scale) if vertical_scale else None,
|
|
415
415
|
vertical_offset=float(vertical_offset) if vertical_offset else None,
|
|
416
|
+
channel=str(channel_name),
|
|
416
417
|
source_file=str(file_path),
|
|
417
|
-
channel_name=str(channel_name),
|
|
418
418
|
)
|
|
419
419
|
|
|
420
420
|
|
oscura/loaders/lazy.py
CHANGED
|
@@ -219,9 +219,14 @@ class LazyWaveformTrace:
|
|
|
219
219
|
"""
|
|
220
220
|
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
221
221
|
|
|
222
|
+
# Handle API migration: channel_name -> channel
|
|
223
|
+
metadata_dict = self._metadata.copy()
|
|
224
|
+
if "channel_name" in metadata_dict:
|
|
225
|
+
metadata_dict["channel"] = metadata_dict.pop("channel_name")
|
|
226
|
+
|
|
222
227
|
metadata = TraceMetadata(
|
|
223
228
|
sample_rate=self._sample_rate,
|
|
224
|
-
**
|
|
229
|
+
**metadata_dict,
|
|
225
230
|
)
|
|
226
231
|
return WaveformTrace(data=self.data, metadata=metadata)
|
|
227
232
|
|
oscura/loaders/mmap_loader.py
CHANGED
oscura/loaders/numpy_loader.py
CHANGED
|
@@ -173,8 +173,8 @@ def _build_npz_metadata(
|
|
|
173
173
|
sample_rate=float(final_sample_rate),
|
|
174
174
|
vertical_scale=float(detected_vertical_scale) if detected_vertical_scale else None,
|
|
175
175
|
vertical_offset=float(detected_vertical_offset) if detected_vertical_offset else None,
|
|
176
|
+
channel=_get_channel_name(npz, channel),
|
|
176
177
|
source_file=str(path),
|
|
177
|
-
channel_name=_get_channel_name(npz, channel),
|
|
178
178
|
)
|
|
179
179
|
|
|
180
180
|
|
|
@@ -444,12 +444,13 @@ def _get_channel_name(
|
|
|
444
444
|
elif isinstance(channel, int):
|
|
445
445
|
return f"CH{channel + 1}"
|
|
446
446
|
|
|
447
|
-
# Try to find channel name in metadata
|
|
447
|
+
# Try to find channel name in metadata (support both old and new API)
|
|
448
448
|
keys = list(npz.keys())
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
449
|
+
for key in ["channel", "channel_name"]:
|
|
450
|
+
if key in keys:
|
|
451
|
+
value = npz[key]
|
|
452
|
+
# NPZ values are always ndarrays
|
|
453
|
+
return str(value.item())
|
|
453
454
|
|
|
454
455
|
return "CH1"
|
|
455
456
|
|
|
@@ -556,8 +557,8 @@ def load_raw_binary(
|
|
|
556
557
|
|
|
557
558
|
metadata = TraceMetadata(
|
|
558
559
|
sample_rate=sample_rate,
|
|
560
|
+
channel="RAW",
|
|
559
561
|
source_file=str(path),
|
|
560
|
-
channel_name="RAW",
|
|
561
562
|
)
|
|
562
563
|
|
|
563
564
|
return WaveformTrace(data=data, metadata=metadata)
|
oscura/loaders/preprocessing.py
CHANGED
|
@@ -293,13 +293,11 @@ def trim_idle(
|
|
|
293
293
|
sample_rate=trace.metadata.sample_rate,
|
|
294
294
|
vertical_scale=trace.metadata.vertical_scale,
|
|
295
295
|
vertical_offset=trace.metadata.vertical_offset,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
source_file=trace.metadata.source_file,
|
|
299
|
-
channel_name=trace.metadata.channel_name,
|
|
296
|
+
channel=trace.metadata.channel,
|
|
297
|
+
units=trace.metadata.units,
|
|
300
298
|
)
|
|
301
299
|
|
|
302
|
-
return DigitalTrace(data=trimmed_data, metadata=new_metadata
|
|
300
|
+
return DigitalTrace(data=trimmed_data, metadata=new_metadata)
|
|
303
301
|
|
|
304
302
|
return trace
|
|
305
303
|
|
oscura/loaders/rigol.py
CHANGED
|
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
|
|
|
26
26
|
|
|
27
27
|
# Try to import RigolWFM for full Rigol support
|
|
28
28
|
try:
|
|
29
|
-
import RigolWFM.wfm as rigol_wfm # type: ignore[import-untyped] # Optional third-party library
|
|
29
|
+
import RigolWFM.wfm as rigol_wfm # type: ignore[import-untyped,import-not-found] # Optional third-party library
|
|
30
30
|
|
|
31
31
|
RIGOL_WFM_AVAILABLE = True
|
|
32
32
|
except ImportError:
|
|
@@ -109,14 +109,28 @@ def _load_with_rigolwfm(
|
|
|
109
109
|
_extract_rigol_channel_data(wfm, channel, str(path))
|
|
110
110
|
)
|
|
111
111
|
|
|
112
|
+
# Extract trigger info if available
|
|
113
|
+
trigger_info = None
|
|
114
|
+
if (
|
|
115
|
+
hasattr(wfm, "trigger_level")
|
|
116
|
+
or hasattr(wfm, "trigger_mode")
|
|
117
|
+
or hasattr(wfm, "trigger_source")
|
|
118
|
+
):
|
|
119
|
+
trigger_info = {}
|
|
120
|
+
if hasattr(wfm, "trigger_level"):
|
|
121
|
+
trigger_info["level"] = wfm.trigger_level
|
|
122
|
+
if hasattr(wfm, "trigger_mode"):
|
|
123
|
+
trigger_info["mode"] = wfm.trigger_mode
|
|
124
|
+
if hasattr(wfm, "trigger_source"):
|
|
125
|
+
trigger_info["source"] = wfm.trigger_source
|
|
126
|
+
|
|
112
127
|
# Build metadata
|
|
113
128
|
metadata = TraceMetadata(
|
|
114
129
|
sample_rate=sample_rate,
|
|
115
130
|
vertical_scale=vertical_scale,
|
|
116
131
|
vertical_offset=vertical_offset,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
trigger_info=_extract_trigger_info(wfm),
|
|
132
|
+
channel=channel_name,
|
|
133
|
+
trigger_info=trigger_info,
|
|
120
134
|
)
|
|
121
135
|
|
|
122
136
|
return WaveformTrace(data=data, metadata=metadata)
|
|
@@ -200,7 +214,7 @@ def _extract_rigol_channel_data(
|
|
|
200
214
|
file_path: File path for error messages.
|
|
201
215
|
|
|
202
216
|
Returns:
|
|
203
|
-
Tuple of (data, sample_rate, vertical_scale, vertical_offset,
|
|
217
|
+
Tuple of (data, sample_rate, vertical_scale, vertical_offset, channel).
|
|
204
218
|
|
|
205
219
|
Raises:
|
|
206
220
|
FormatError: If no waveform data found.
|
|
@@ -212,7 +226,7 @@ def _extract_rigol_channel_data(
|
|
|
212
226
|
sample_rate = wfm.sample_rate if hasattr(wfm, "sample_rate") else 1e6
|
|
213
227
|
vertical_scale = ch.volts_per_div if hasattr(ch, "volts_per_div") else None
|
|
214
228
|
vertical_offset = ch.volt_offset if hasattr(ch, "volt_offset") else None
|
|
215
|
-
channel_name = f"CH{channel + 1}"
|
|
229
|
+
channel_name: str = f"CH{channel + 1}"
|
|
216
230
|
return data, sample_rate, vertical_scale, vertical_offset, channel_name
|
|
217
231
|
|
|
218
232
|
# Single channel format
|
|
@@ -302,7 +316,7 @@ def _load_basic(
|
|
|
302
316
|
vertical_scale=vertical_scale,
|
|
303
317
|
vertical_offset=vertical_offset,
|
|
304
318
|
source_file=str(path),
|
|
305
|
-
|
|
319
|
+
channel=f"CH{channel + 1}",
|
|
306
320
|
)
|
|
307
321
|
|
|
308
322
|
return WaveformTrace(data=data, metadata=metadata)
|
oscura/loaders/sigrok.py
CHANGED
|
@@ -270,19 +270,16 @@ def _build_digital_trace(
|
|
|
270
270
|
Returns:
|
|
271
271
|
DigitalTrace object.
|
|
272
272
|
"""
|
|
273
|
-
|
|
273
|
+
_compute_edges(channel_data, sample_rate)
|
|
274
274
|
|
|
275
275
|
trace_metadata = TraceMetadata(
|
|
276
276
|
sample_rate=sample_rate,
|
|
277
|
-
|
|
278
|
-
channel_name=channel_name,
|
|
279
|
-
trigger_info=metadata_dict.get("trigger"),
|
|
277
|
+
channel=channel_name,
|
|
280
278
|
)
|
|
281
279
|
|
|
282
280
|
return DigitalTrace(
|
|
283
281
|
data=channel_data,
|
|
284
282
|
metadata=trace_metadata,
|
|
285
|
-
edges=edges,
|
|
286
283
|
)
|
|
287
284
|
|
|
288
285
|
|
oscura/loaders/tdms.py
CHANGED
|
@@ -230,14 +230,15 @@ def _build_tdms_metadata(
|
|
|
230
230
|
sample_rate = _get_sample_rate(target_channel, target_group, tdms_file)
|
|
231
231
|
vertical_scale = target_channel.properties.get("NI_Scale[0]_Linear_Slope")
|
|
232
232
|
vertical_offset = target_channel.properties.get("NI_Scale[0]_Linear_Y_Intercept")
|
|
233
|
+
trigger_info = _extract_tdms_properties(target_channel)
|
|
233
234
|
|
|
234
235
|
return TraceMetadata(
|
|
235
236
|
sample_rate=sample_rate,
|
|
236
237
|
vertical_scale=float(vertical_scale) if vertical_scale is not None else None,
|
|
237
238
|
vertical_offset=float(vertical_offset) if vertical_offset is not None else None,
|
|
239
|
+
channel=target_channel.name,
|
|
240
|
+
trigger_info=trigger_info,
|
|
238
241
|
source_file=str(path),
|
|
239
|
-
channel_name=target_channel.name,
|
|
240
|
-
trigger_info=_extract_tdms_properties(target_channel),
|
|
241
242
|
)
|
|
242
243
|
|
|
243
244
|
|
oscura/loaders/tektronix.py
CHANGED
|
@@ -20,7 +20,6 @@ Example:
|
|
|
20
20
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
|
-
import contextlib
|
|
24
23
|
import logging
|
|
25
24
|
from pathlib import Path
|
|
26
25
|
from typing import TYPE_CHECKING, Any, Union
|
|
@@ -80,7 +79,7 @@ def load_tektronix_wfm(
|
|
|
80
79
|
Example:
|
|
81
80
|
>>> trace = load_tektronix_wfm("TEK00001.wfm")
|
|
82
81
|
>>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
|
|
83
|
-
>>> print(f"Channel: {trace.metadata.
|
|
82
|
+
>>> print(f"Channel: {trace.metadata.channel}")
|
|
84
83
|
|
|
85
84
|
>>> # Check trace type
|
|
86
85
|
>>> if isinstance(trace, DigitalTrace):
|
|
@@ -250,7 +249,7 @@ def _load_analog_waveforms_container(
|
|
|
250
249
|
sample_rate = 1.0 / waveform.x_increment if waveform.x_increment > 0 else 1e6
|
|
251
250
|
vertical_scale = getattr(waveform, "y_scale", None)
|
|
252
251
|
vertical_offset = getattr(waveform, "y_offset", None)
|
|
253
|
-
channel_name = getattr(waveform, "name", f"CH{channel + 1}")
|
|
252
|
+
channel_name: str = str(getattr(waveform, "name", None) or f"CH{channel + 1}")
|
|
254
253
|
|
|
255
254
|
# Use original wfm for trigger info (need to get it from parent)
|
|
256
255
|
return _build_waveform_trace(
|
|
@@ -258,7 +257,7 @@ def _load_analog_waveforms_container(
|
|
|
258
257
|
sample_rate=sample_rate,
|
|
259
258
|
vertical_scale=vertical_scale,
|
|
260
259
|
vertical_offset=vertical_offset,
|
|
261
|
-
|
|
260
|
+
channel=channel_name,
|
|
262
261
|
path=path,
|
|
263
262
|
wfm=waveform,
|
|
264
263
|
)
|
|
@@ -288,7 +287,7 @@ def _load_analog_waveform_direct(
|
|
|
288
287
|
x_spacing = float(wfm.x_axis_spacing) if wfm.x_axis_spacing else 1e-6
|
|
289
288
|
sample_rate = 1.0 / x_spacing if x_spacing > 0 else 1e6
|
|
290
289
|
vertical_offset = y_offset
|
|
291
|
-
channel_name = (
|
|
290
|
+
channel_name: str = (
|
|
292
291
|
wfm.source_name if hasattr(wfm, "source_name") and wfm.source_name else f"CH{channel + 1}"
|
|
293
292
|
)
|
|
294
293
|
|
|
@@ -297,7 +296,7 @@ def _load_analog_waveform_direct(
|
|
|
297
296
|
sample_rate=sample_rate,
|
|
298
297
|
vertical_scale=None,
|
|
299
298
|
vertical_offset=vertical_offset,
|
|
300
|
-
|
|
299
|
+
channel=channel_name,
|
|
301
300
|
path=path,
|
|
302
301
|
wfm=wfm,
|
|
303
302
|
)
|
|
@@ -321,14 +320,14 @@ def _load_legacy_y_data(
|
|
|
321
320
|
sample_rate = 1.0 / x_increment if x_increment > 0 else 1e6
|
|
322
321
|
vertical_scale = getattr(wfm, "y_scale", None)
|
|
323
322
|
vertical_offset = getattr(wfm, "y_offset", None)
|
|
324
|
-
|
|
323
|
+
channel = getattr(wfm, "name", "CH1")
|
|
325
324
|
|
|
326
325
|
return _build_waveform_trace(
|
|
327
326
|
data=data,
|
|
328
327
|
sample_rate=sample_rate,
|
|
329
328
|
vertical_scale=vertical_scale,
|
|
330
329
|
vertical_offset=vertical_offset,
|
|
331
|
-
|
|
330
|
+
channel=channel,
|
|
332
331
|
path=path,
|
|
333
332
|
wfm=wfm,
|
|
334
333
|
)
|
|
@@ -339,7 +338,7 @@ def _build_waveform_trace(
|
|
|
339
338
|
sample_rate: float,
|
|
340
339
|
vertical_scale: float | None,
|
|
341
340
|
vertical_offset: float | None,
|
|
342
|
-
|
|
341
|
+
channel: str,
|
|
343
342
|
path: Path,
|
|
344
343
|
wfm: Any,
|
|
345
344
|
) -> WaveformTrace:
|
|
@@ -350,27 +349,38 @@ def _build_waveform_trace(
|
|
|
350
349
|
sample_rate: Sample rate in Hz.
|
|
351
350
|
vertical_scale: Vertical scale in volts/div.
|
|
352
351
|
vertical_offset: Vertical offset in volts.
|
|
353
|
-
|
|
352
|
+
channel: Channel name.
|
|
354
353
|
path: Source file path.
|
|
355
354
|
wfm: Original waveform object for trigger info extraction.
|
|
356
355
|
|
|
357
356
|
Returns:
|
|
358
357
|
Constructed WaveformTrace.
|
|
359
358
|
"""
|
|
359
|
+
# Extract trigger information
|
|
360
|
+
trigger_info = _extract_trigger_info(wfm)
|
|
361
|
+
|
|
360
362
|
# Extract acquisition time if available
|
|
361
363
|
acquisition_time = None
|
|
362
364
|
if hasattr(wfm, "date_time"):
|
|
363
|
-
|
|
364
|
-
|
|
365
|
+
try:
|
|
366
|
+
from datetime import datetime
|
|
367
|
+
|
|
368
|
+
# Handle both datetime objects and other formats
|
|
369
|
+
if isinstance(wfm.date_time, datetime):
|
|
370
|
+
acquisition_time = wfm.date_time
|
|
371
|
+
# Add additional parsing if needed for other formats
|
|
372
|
+
except (ValueError, AttributeError, TypeError):
|
|
373
|
+
# Silently ignore invalid or unparseable times
|
|
374
|
+
pass
|
|
365
375
|
|
|
366
376
|
metadata = TraceMetadata(
|
|
367
377
|
sample_rate=sample_rate,
|
|
368
378
|
vertical_scale=vertical_scale,
|
|
369
379
|
vertical_offset=vertical_offset,
|
|
370
|
-
|
|
380
|
+
channel=channel,
|
|
371
381
|
source_file=str(path),
|
|
372
|
-
|
|
373
|
-
|
|
382
|
+
trigger_info=trigger_info,
|
|
383
|
+
acquisition_time=acquisition_time,
|
|
374
384
|
)
|
|
375
385
|
|
|
376
386
|
return WaveformTrace(data=data, metadata=metadata)
|
|
@@ -407,19 +417,15 @@ def _load_digital_waveform(
|
|
|
407
417
|
sample_rate = _extract_sample_rate(wfm)
|
|
408
418
|
|
|
409
419
|
# Extract channel name
|
|
410
|
-
channel_name =
|
|
420
|
+
channel_name: str = _extract_channel(wfm, channel)
|
|
411
421
|
|
|
412
422
|
# Build metadata
|
|
413
423
|
metadata = TraceMetadata(
|
|
414
424
|
sample_rate=sample_rate,
|
|
415
|
-
|
|
416
|
-
channel_name=channel_name,
|
|
425
|
+
channel=channel_name,
|
|
417
426
|
)
|
|
418
427
|
|
|
419
|
-
|
|
420
|
-
edges = _extract_edges(wfm)
|
|
421
|
-
|
|
422
|
-
return DigitalTrace(data=data, metadata=metadata, edges=edges)
|
|
428
|
+
return DigitalTrace(data=data, metadata=metadata)
|
|
423
429
|
|
|
424
430
|
|
|
425
431
|
def _extract_digital_samples(wfm: Any, path: Path) -> NDArray[np.bool_]:
|
|
@@ -465,7 +471,7 @@ def _extract_sample_rate(wfm: Any) -> float:
|
|
|
465
471
|
return 1.0 / x_spacing if x_spacing > 0 else 1e6
|
|
466
472
|
|
|
467
473
|
|
|
468
|
-
def
|
|
474
|
+
def _extract_channel(wfm: Any, channel: int) -> str:
|
|
469
475
|
"""Extract channel name from waveform object."""
|
|
470
476
|
# Try source_name first
|
|
471
477
|
if hasattr(wfm, "source_name") and wfm.source_name:
|
|
@@ -534,18 +540,19 @@ def _load_iq_waveform(
|
|
|
534
540
|
sample_rate = 1.0 / x_spacing if x_spacing > 0 else 1e6
|
|
535
541
|
|
|
536
542
|
# Extract channel name
|
|
537
|
-
|
|
543
|
+
channel = "IQ1"
|
|
538
544
|
if hasattr(wfm, "source_name") and wfm.source_name:
|
|
539
|
-
|
|
545
|
+
channel = wfm.source_name
|
|
540
546
|
|
|
541
547
|
# Build metadata
|
|
542
548
|
metadata = TraceMetadata(
|
|
543
549
|
sample_rate=sample_rate,
|
|
544
|
-
|
|
545
|
-
channel_name=channel_name,
|
|
550
|
+
channel=channel,
|
|
546
551
|
)
|
|
547
552
|
|
|
548
|
-
|
|
553
|
+
# Create complex I/Q data
|
|
554
|
+
iq_data = i_data + 1j * q_data
|
|
555
|
+
return IQTrace(data=iq_data, metadata=metadata)
|
|
549
556
|
|
|
550
557
|
|
|
551
558
|
def _load_basic(
|
|
@@ -641,14 +648,14 @@ def _parse_wfm003(
|
|
|
641
648
|
# Extract metadata from header
|
|
642
649
|
sample_rate = _extract_sample_interval(file_data, header_size)
|
|
643
650
|
vertical_scale, vertical_offset = _extract_vertical_params(file_data, header_size)
|
|
644
|
-
channel_name = f"CH{channel + 1}"
|
|
651
|
+
channel_name: str = f"CH{channel + 1}"
|
|
645
652
|
|
|
646
653
|
metadata = TraceMetadata(
|
|
647
654
|
sample_rate=sample_rate,
|
|
648
655
|
vertical_scale=vertical_scale,
|
|
649
656
|
vertical_offset=vertical_offset,
|
|
657
|
+
channel=channel_name,
|
|
650
658
|
source_file=str(path),
|
|
651
|
-
channel_name=channel_name,
|
|
652
659
|
)
|
|
653
660
|
|
|
654
661
|
return WaveformTrace(data=data, metadata=metadata)
|
|
@@ -797,8 +804,7 @@ def _parse_wfm_legacy(
|
|
|
797
804
|
sample_rate=sample_rate,
|
|
798
805
|
vertical_scale=vertical_scale,
|
|
799
806
|
vertical_offset=vertical_offset,
|
|
800
|
-
|
|
801
|
-
channel_name=f"CH{channel + 1}",
|
|
807
|
+
channel=f"CH{channel + 1}",
|
|
802
808
|
)
|
|
803
809
|
|
|
804
810
|
return WaveformTrace(data=data, metadata=metadata)
|