PyVISA
PyVISADocs

Large Data Transfers

How to transfer multi-megabyte waveforms and spectra from instruments with PyVISA. Binary protocols, chunked reads, and memory-efficient patterns.

Modern oscilloscopes produce 10M+ sample waveforms (40+ MB). Standard PyVISA settings can be 10-100x slower than needed. Binary format and proper chunk sizes fix most of it.

Binary vs ASCII Comparison

import pyvisa
import time
import numpy as np

rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C102912::INSTR")
scope.timeout = 30000

# ASCII: human-readable, slow
start = time.time()
scope.write("FORM:DATA ASC")
ascii_data = scope.query("CURV?")
ascii_values = [float(x) for x in ascii_data.split(",")]
ascii_time = time.time() - start

# Binary: raw bytes, fast
start = time.time()
scope.write("FORM:DATA REAL,32")
binary_values = scope.query_binary_values("CURV?", datatype="f")
binary_time = time.time() - start

print(f"ASCII:  {ascii_time:.2f}s ({len(ascii_values)} points)")
print(f"Binary: {binary_time:.2f}s ({len(binary_values)} points)")
print(f"Speedup: {ascii_time / binary_time:.1f}x")

# Typical result for 1M points:
# ASCII:  12.3s
# Binary: 0.8s
# Speedup: 15.4x

scope.close()
rm.close()

Log measurements automatically

TofuPilot records test results from your PyVISA scripts, tracks pass/fail rates, and generates compliance reports. Free to start.

Fast Waveform Capture

Set binary encoding, increase chunk_size, and use *OPC? to wait for acquisition.

import pyvisa
import numpy as np
import time

rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C102912::INSTR")
scope.timeout = 30000
scope.chunk_size = 1024 * 1024  # 1 MB chunks

scope.write("*RST")
scope.write("*CLS")

# Binary format, 16-bit samples
scope.write("DATA:ENC RIBINARY")
scope.write("DATA:WIDTH 2")
scope.write("DATA:START 1")
scope.write("DATA:STOP 10000000")  # Up to 10M points

# Fastest acquisition mode
scope.write("ACQ:MODE SAMPLE")
scope.write("HOR:RECORDLENGTH 10000000")
scope.write("ACQ:STOPAFTER RUNSTOP")
scope.write("ACQ:STATE ON")

scope.query("*OPC?")

# Read scaling from preamble
scope.write("DATA:SOURCE CH1")
preamble = scope.query("WFMPRE?").split(",")
y_scale = float(preamble[13])
y_offset = float(preamble[14])
x_scale = float(preamble[9])

# Transfer
start = time.time()
raw = scope.query_binary_values("CURVE?", datatype="h")
elapsed = time.time() - start

voltages = np.array(raw) * y_scale + y_offset
time_axis = np.arange(len(voltages)) * x_scale

print(f"{len(voltages)} points in {elapsed:.2f}s")
print(f"Transfer rate: {len(voltages) / elapsed / 1e6:.1f} MSa/s")

scope.close()
rm.close()

Chunked Transfer for Very Large Waveforms

When a waveform exceeds available memory (or you want progress feedback), transfer it in chunks.

import pyvisa
import numpy as np

def chunked_waveform(instrument, total_points, chunk_size=1000000):
    """Transfer a large waveform in chunks."""
    all_data = []

    for start in range(1, total_points + 1, chunk_size):
        end = min(start + chunk_size - 1, total_points)

        instrument.write(f"DATA:START {start}")
        instrument.write(f"DATA:STOP {end}")

        chunk = instrument.query_binary_values("CURVE?", datatype="h")
        all_data.extend(chunk)

        done = len(all_data)
        print(f"{done}/{total_points} points ({100 * done / total_points:.0f}%)")

    return np.array(all_data)

# Usage
rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C102912::INSTR")
scope.timeout = 60000
scope.chunk_size = 1024 * 1024

scope.write("DATA:ENC RIBINARY")
scope.write("DATA:WIDTH 2")

waveform = chunked_waveform(scope, total_points=50000000)
print(f"Total: {len(waveform)} points")

scope.close()
rm.close()

Spectrum Analyzer Trace

Binary format works the same way for spectrum analyzers.

import pyvisa
import numpy as np
import time

rm = pyvisa.ResourceManager()
sa = rm.open_resource("TCPIP::192.168.1.101::INSTR")
sa.timeout = 60000

# Binary format, single sweep
sa.write("FORM:DATA REAL,32")
sa.write("INIT:CONT OFF")
sa.write("SWE:POIN 50001")
sa.write("FREQ:START 1e6")
sa.write("FREQ:STOP 6e9")

sa.write("INIT:IMM")
sa.query("*OPC?")

start = time.time()
trace = sa.query_binary_values("TRAC? TRACE1", datatype="f")
elapsed = time.time() - start

freqs = np.linspace(1e6, 6e9, len(trace))
print(f"{len(trace)} points in {elapsed:.2f}s")

sa.close()
rm.close()

Streaming FFT (Chunked Processing)

For waveforms too large to FFT in one shot, process power spectra per chunk and average.

import numpy as np
import gc

def streaming_fft(instrument, chunk_size=1000000):
    """Compute averaged power spectrum from a large waveform in chunks."""
    total = int(instrument.query("HOR:RECORDLENGTH?"))
    sample_rate = float(instrument.query("HOR:SAMPLERATE?"))

    accumulator = None
    n_chunks = 0

    for start in range(1, total + 1, chunk_size):
        end = min(start + chunk_size - 1, total)
        instrument.write(f"DATA:START {start}")
        instrument.write(f"DATA:STOP {end}")

        chunk = instrument.query_binary_values("CURVE?", datatype="h")
        arr = np.array(chunk, dtype=np.float32)

        power = np.abs(np.fft.fft(arr)) ** 2
        if accumulator is None:
            accumulator = power
        else:
            accumulator += power

        n_chunks += 1
        del chunk, arr, power
        gc.collect()
        print(f"Chunk {n_chunks} processed")

    avg_power = accumulator / n_chunks
    freqs = np.fft.fftfreq(len(avg_power), 1 / sample_rate)

    # Return positive half
    half = len(freqs) // 2
    return freqs[:half], avg_power[:half]

TCP/IP Tuning

For Ethernet instruments, connection parameters affect throughput.

import pyvisa

rm = pyvisa.ResourceManager()

# VXI-11 (standard)
inst = rm.open_resource(
    "TCPIP::192.168.1.100::inst0::INSTR",
    timeout=60000,
    chunk_size=2 * 1024 * 1024,  # 2 MB
)

# Raw socket (lower latency, if instrument supports port 5025)
# inst = rm.open_resource(
#     "TCPIP::192.168.1.100::5025::SOCKET",
#     read_termination="\n",
#     write_termination="\n",
#     timeout=60000,
#     chunk_size=2 * 1024 * 1024,
# )

print(inst.query("*IDN?").strip())

Avoid WiFi for large transfers

Wired Ethernet gives 10-100x more consistent throughput. WiFi latency spikes cause timeout errors on multi-megabyte transfers.

Configuration Checklist

SettingDefaultRecommendedWhy
Data formatASCIIRIBINARY or REAL,3210-50x faster
chunk_size20 KB1-2 MBFewer read calls
timeout2000 ms30000-60000 msLarge transfers take time
Data widthVaries2 (16-bit)Good resolution, half the size of 32-bit
HeaderONHEADER OFFSimplifies binary parsing
ConnectionOpen/close per testKeep openSaves 1-5 s per connection