simple-scope-parser/scope_parser/data.py

122 lines
4.1 KiB
Python

"""
Data structures for oscilloscope measurements.
"""
from dataclasses import dataclass
from typing import List, Dict, Optional
import numpy as np
@dataclass
class ChannelData:
"""Represents data from a single oscilloscope channel."""
channel_name: str
voltage_values: np.ndarray # in volts
time_values: np.ndarray # in seconds
metadata: Dict[str, str]
@property
def frequency(self) -> Optional[float]:
"""Extract frequency from metadata if available."""
freq_str = self.metadata.get("Frequency", "")
if freq_str.startswith("F="):
# Extract frequency value and convert units
freq_value = freq_str[2:]
if "kHz" in freq_value:
return float(freq_value.replace("kHz", "")) * 1000
elif "Hz" in freq_value:
return float(freq_value.replace("Hz", ""))
return None
@property
def vpp(self) -> Optional[float]:
"""Peak-to-peak voltage in volts."""
pp_str = self.metadata.get("PK-PK", "")
if pp_str.startswith("Vpp="):
value = float(pp_str[4:].replace("mV", "").replace("V", ""))
# Convert mV to V if needed
if "mV" in pp_str:
return value / 1000.0
return value
return None
@property
def average(self) -> Optional[float]:
"""Average voltage in volts (computed from data)."""
return float(np.mean(self.voltage_values))
@property
def metadata_average(self) -> Optional[float]:
"""Average voltage from metadata in volts."""
avg_str = self.metadata.get("Average", "")
if avg_str.startswith("V="):
value = float(avg_str[2:].replace("mV", "").replace("V", ""))
# Convert mV to V if needed
if "mV" in avg_str:
return value / 1000.0
return value
return None
@property
def time_interval(self) -> Optional[float]:
"""Time interval between samples in seconds."""
interval_str = self.metadata.get("Time interval", "")
if interval_str:
if "uS" in interval_str:
return float(interval_str.replace("uS", "")) * 1e-6
elif "nS" in interval_str:
return float(interval_str.replace("nS", "")) * 1e-9
elif "mS" in interval_str:
return float(interval_str.replace("mS", "")) * 1e-3
elif "S" in interval_str:
return float(interval_str.replace("S", ""))
return None
@dataclass
class ScopeData:
"""Container for all oscilloscope measurement data."""
channels: Dict[str, ChannelData]
metadata: Dict[str, str]
def __getitem__(self, channel_name: str) -> ChannelData:
"""Allow dictionary-like access to channels."""
return self.channels[channel_name]
def __iter__(self):
"""Allow iteration over channels."""
return iter(self.channels.values())
@property
def channel_names(self) -> List[str]:
"""Get list of all channel names."""
return list(self.channels.keys())
@property
def num_channels(self) -> int:
"""Get number of channels."""
return len(self.channels)
def __repr__(self) -> str:
"""String representation showing channel information."""
lines = [f"ScopeData({self.num_channels} channels)"]
for channel_name, channel_data in self.channels.items():
lines.append(f" {channel_name}:")
lines.append(f" Samples: {len(channel_data.voltage_values)}")
lines.append(
f" Time: {channel_data.time_values.min():.6f} to {channel_data.time_values.max():.6f} s"
)
lines.append(
f" Voltage: {channel_data.voltage_values.min():.6f} to {channel_data.voltage_values.max():.6f} V"
)
if channel_data.frequency:
lines.append(f" Frequency: {channel_data.frequency} Hz")
if channel_data.vpp:
lines.append(f" Peak-to-peak: {channel_data.vpp:.6f} V")
lines.append(f" Average: {channel_data.average:.6f} V")
return "\n".join(lines)