Skip to content

Waveform

Waveform acquisition and data handling

Waveform acquisition and data processing for Siglent oscilloscopes.

WaveformData dataclass

WaveformData(time: ndarray, voltage: ndarray, channel: Union[int, str], sample_rate: Optional[float] = None, record_length: Optional[int] = None, timebase: Optional[float] = None, voltage_scale: Optional[float] = None, voltage_offset: float = 0.0, source: Optional[str] = None, description: Optional[str] = None)

Container for waveform data and metadata.

Attributes:

Name Type Description
time ndarray

Time values in seconds (numpy array)

voltage ndarray

Voltage values in volts (numpy array)

channel Union[int, str]

Source channel number

sample_rate Optional[float]

Sampling rate in samples/second

record_length Optional[int]

Number of samples

timebase Optional[float]

Timebase setting (seconds/division)

voltage_scale Optional[float]

Voltage scale (volts/division)

voltage_offset float

Voltage offset in volts

Waveform

Waveform(oscilloscope: Oscilloscope)

Waveform acquisition and data processing.

Handles downloading waveform data from oscilloscope channels and converting to voltage/time arrays.

Initialize waveform acquisition.

Parameters:

Name Type Description Default
oscilloscope Oscilloscope

Parent Oscilloscope instance

required
Source code in scpi_control/waveform.py
def __init__(self, oscilloscope: "Oscilloscope"):
    """Initialize waveform acquisition.

    Args:
        oscilloscope: Parent Oscilloscope instance
    """
    self._scope = oscilloscope

acquire

acquire(channel: int, format: str = 'BYTE') -> WaveformData

Acquire waveform data from a channel.

Parameters:

Name Type Description Default
channel int

Channel number (1-4)

required
format str

Data format - 'BYTE' or 'WORD' (default: 'BYTE')

'BYTE'

Returns:

Type Description
WaveformData

WaveformData object with time and voltage arrays

Raises:

Type Description
InvalidParameterError

If channel number is invalid

CommandError

If acquisition fails

Source code in scpi_control/waveform.py
def acquire(self, channel: int, format: str = "BYTE") -> WaveformData:
    """Acquire waveform data from a channel.

    Args:
        channel: Channel number (1-4)
        format: Data format - 'BYTE' or 'WORD' (default: 'BYTE')

    Returns:
        WaveformData object with time and voltage arrays

    Raises:
        InvalidParameterError: If channel number is invalid
        CommandError: If acquisition fails
    """
    if not 1 <= channel <= 4:
        raise exceptions.InvalidParameterError(f"Invalid channel number: {channel}. Must be 1-4.")

    logger.info(f"Acquiring waveform from channel {channel}")

    # Get channel configuration
    ch = f"C{channel}"
    voltage_scale = self._get_voltage_scale(ch)
    voltage_offset = self._get_voltage_offset(ch)
    timebase = self._get_timebase()
    sample_rate = self._get_sample_rate()

    # Request waveform data
    waveform_command = f"{ch}:WF? DAT2"  # DAT2 is binary format
    self._scope.write(waveform_command)

    # Read waveform data header and data
    raw_data = self._scope.read_raw()

    # Parse waveform data
    voltage_data = self._parse_waveform(raw_data, format, waveform_command)
    record_length = len(voltage_data)

    # Convert to voltage using scale and offset
    # Formula: Voltage = (code - code_offset) * code_scale + voltage_offset
    # For 8-bit data: typically code_offset = 127 (or 128), code_scale = voltage_scale / 25
    voltage = self._convert_to_voltage(voltage_data, voltage_scale, voltage_offset)

    # Generate time axis
    time = self._generate_time_axis(record_length, sample_rate, timebase)

    logger.info(f"Acquired {record_length} samples from channel {channel}")

    return WaveformData(
        time=time,
        voltage=voltage,
        channel=channel,
        sample_rate=sample_rate,
        record_length=record_length,
        timebase=timebase,
        voltage_scale=voltage_scale,
        voltage_offset=voltage_offset,
    )

get_waveform_preamble

get_waveform_preamble(channel: int) -> dict

Get waveform preamble information.

Parameters:

Name Type Description Default
channel int

Channel number (1-4)

required

Returns:

Type Description
dict

Dictionary with waveform metadata

Source code in scpi_control/waveform.py
def get_waveform_preamble(self, channel: int) -> dict:
    """Get waveform preamble information.

    Args:
        channel: Channel number (1-4)

    Returns:
        Dictionary with waveform metadata
    """
    if not 1 <= channel <= 4:
        raise exceptions.InvalidParameterError(f"Invalid channel number: {channel}. Must be 1-4.")

    ch = f"C{channel}"

    return {
        "channel": channel,
        "voltage_scale": self._get_voltage_scale(ch),
        "voltage_offset": self._get_voltage_offset(ch),
        "timebase": self._get_timebase(),
        "sample_rate": self._get_sample_rate(),
    }

save_waveform

save_waveform(waveform: WaveformData, filename: str, format: Optional[str] = None, metadata: Optional[dict] = None) -> None

Save waveform data to file.

Parameters:

Name Type Description Default
waveform WaveformData

WaveformData object to save

required
filename str

Output filename

required
format Optional[str]

File format - 'CSV', 'CSV_ENHANCED', 'NPY', 'MAT', 'HDF5' If None, auto-detect from file extension

None
metadata Optional[dict]

Optional metadata dictionary to include in file

None
Supported formats
  • CSV: Simple CSV with time and voltage columns
  • CSV_ENHANCED: CSV with metadata header
  • NPY: NumPy compressed archive (.npz)
  • MAT: MATLAB format (.mat) - requires scipy
  • HDF5: HDF5 format (.h5, .hdf5) - requires h5py
Source code in scpi_control/waveform.py
def save_waveform(
    self,
    waveform: WaveformData,
    filename: str,
    format: Optional[str] = None,
    metadata: Optional[dict] = None,
) -> None:
    """Save waveform data to file.

    Args:
        waveform: WaveformData object to save
        filename: Output filename
        format: File format - 'CSV', 'CSV_ENHANCED', 'NPY', 'MAT', 'HDF5'
               If None, auto-detect from file extension
        metadata: Optional metadata dictionary to include in file

    Supported formats:
        - CSV: Simple CSV with time and voltage columns
        - CSV_ENHANCED: CSV with metadata header
        - NPY: NumPy compressed archive (.npz)
        - MAT: MATLAB format (.mat) - requires scipy
        - HDF5: HDF5 format (.h5, .hdf5) - requires h5py
    """
    # Auto-detect format from extension if not specified
    if format is None:
        import os

        ext = os.path.splitext(filename)[1].lower()
        format_map = {
            ".csv": "CSV",
            ".npz": "NPY",
            ".npy": "NPY",
            ".mat": "MAT",
            ".h5": "HDF5",
            ".hdf5": "HDF5",
        }
        format = format_map.get(ext, "CSV")
        logger.debug(f"Auto-detected format: {format} from extension {ext}")

    format = format.upper()

    if format == "CSV":
        self._save_csv(waveform, filename, include_metadata=False, metadata=metadata)

    elif format == "CSV_ENHANCED":
        self._save_csv(waveform, filename, include_metadata=True, metadata=metadata)

    elif format == "NPY":
        self._save_npy(waveform, filename, metadata=metadata)

    elif format == "MAT":
        self._save_mat(waveform, filename, metadata=metadata)

    elif format == "HDF5":
        self._save_hdf5(waveform, filename, metadata=metadata)

    else:
        raise exceptions.InvalidParameterError(f"Invalid format: {format}. Supported: CSV, CSV_ENHANCED, NPY, MAT, HDF5")

See Also

  • Oscilloscope - Main oscilloscope control class for SCPI communication
  • Channel - Channel configuration and control
  • Analysis - Signal analysis (FFT, THD, SNR)