""" 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)