Streaming and buffer recording#

pyTelops supports two acquisition modes: live streaming (frames sent to the PC in real time) and buffer recording (frames recorded to the camera’s internal 16 GB memory at full sensor speed, then downloaded to the PC afterwards). Choose the right mode for your measurement.

Live streaming#

In streaming mode the camera sends each frame over Ethernet as it is captured. GigE bandwidth limits throughput to roughly 125 MB/s, which supports up to approximately 760 fps at full resolution (320x256).

Start and stop streaming with pyTelops.Camera.acquisition_start() and pyTelops.Camera.acquisition_stop(), or use the context manager shorthand:

with cam.acquisition():
    while running:
        frame = cam.read_frame(timeout=0.1, latest=True)
        if frame is not None:
            process_and_display(frame)

Pass latest=True to pyTelops.Camera.read_frame() in display loops to always show the most recent frame rather than processing a growing backlog. See Troubleshooting for details on the growing-lag symptom.

For short captures you can use the convenience methods directly:

frame  = cam.grab()          # single frame -> numpy (H, W)
frames = cam.acquire(100)    # 100 consecutive frames -> numpy (N, H, W)

Packet delay tuning#

At the camera’s default packet_delay of 0, all packets of a frame are sent back-to-back in a ~1.4 ms burst. At higher frame rates this can overflow the host UDP receive buffer. If you see packets unrecoverable warnings, spread the burst:

cam.packet_delay = 1000   # ~8 us between packets; safe up to ~400 fps

Start with 1000 and increase to 2000 or 5000 under heavy host load. Packet delay does not affect buffer recording: the camera fills its internal buffer at full speed regardless. It does pace buffer download, where raising it inserts gaps between packets and can remove dropped frames on a host or adapter that cannot keep up at full rate, at some cost to peak throughput. See the buffer-download section below.

Buffer recording#

The onboard 16 GB buffer lets the camera record at the full sensor speed (up to 95k fps) independently of GigE bandwidth. The workflow is: configure the buffer, record, then download.

from pyTelops import Camera

with Camera() as cam:
    cam.frame_rate = 2000.0
    cam.integration_time = 30.0

    # Allocate three sequences of 5 seconds each
    cam.buffer_configure(n_sequences=3, duration=5.0,
                         moi_source="software")

    # Record all sequences in one call
    cam.buffer_record()    # arms, fires MOI for each, waits, stops

    # Inspect what was recorded
    print(cam.buffer_info())
    # {'status': 'IDLE', 'n_sequences': 3, 'recorded': [10000, 10000, 10000], ...}

    # Download selected sequences
    data_0 = cam.buffer_download(sequence=0)
    data_2 = cam.buffer_download(sequence=2)

    cam.buffer_clear()

pyTelops.Camera.buffer_record() prints per-sequence progress:

Arming (seq 1/3)... Recording... Done (10000 frames)
Firing (seq 2/3)... Recording... Done (10000 frames)
Firing (seq 3/3)... Recording... Done (10000 frames)

pyTelops.Camera.buffer_download() shows a tqdm progress bar and an integrity check:

Downloading: 100%|██████████| 10000/10000 [00:36<00:00, 271.84frame/s]
Downloaded 10000 frames in 36.8s (271 fps, 44.8 MB/s)
Data check: OK, 10000 frames, range [24.9, 36.2], mean 28.1

Download integrity and recovery#

pyTelops.Camera.buffer_download() checks that every frame arrived whole. By default (max_dropped_frames=0) it raises pyTelops.FrameIntegrityError if any frame is still incomplete after recovery, so an unnoticed gap cannot slip into your data. Pass max_dropped_frames=N to tolerate up to N incomplete frames and get the array back anyway:

from pyTelops import FrameIntegrityError

try:
    data = cam.buffer_download(sequence=0)
except FrameIntegrityError as exc:
    print(f"{exc.stats.n_incomplete} frame(s) incomplete")
    # Or tolerate a few drops:
    data = cam.buffer_download(sequence=0, max_dropped_frames=5)

The download self-recovers before it gives up: frames that arrive incomplete or never arrive are re-streamed at a paced lower bitrate until they are complete, controlled by retries. resend= toggles GVSP packet resends, which are off by default because resend requests can congest a healthy link.

Auto-tune#

Auto-tune is on by default. On the first download of a connection it probes once whether the path carries jumbo frames, using them when supported and falling back to 1500 otherwise, and it learns a starting bitrate from how complete each download is, lowering the bitrate after drops. You do not need to hand-pick a packet size. Passing an explicit packet_size= or bitrate_mbps= disables auto-tune for that call, and cam.auto_tune = False disables it entirely:

data = cam.buffer_download(sequence=0, bitrate_mbps=500)   # manual override

Download statistics#

Every call attaches cam.last_download_stats, a pyTelops.DownloadStats with fields such as n_frames, n_incomplete, incomplete_frame_ids, throughput_mbps, packet_size_used, and bitrate_used. Callers can check transfer quality without inspecting pixel values:

data = cam.buffer_download(sequence=0)
stats = cam.last_download_stats
print(f"{stats.n_incomplete} incomplete, {stats.throughput_mbps:.1f} MB/s, "
      f"packet_size={stats.packet_size_used}")

If downloads are repeatedly slow or incomplete, pyTelops.tune_connection() probes the link and sweeps download settings, returning a pyTelops.ConnectionReport. Its .apply(cam) method stores the recommended configuration on the camera for later downloads:

from pyTelops import tune_connection

report = tune_connection(cam)   # camera must have frames recorded first
report.apply(cam)
data = cam.buffer_download(sequence=0)

See Troubleshooting (buffer-download section) for diagnosing a host or adapter that cannot keep up at full rate.

External trigger#

For triggered recording from an external BNC signal:

with Camera() as cam:
    cam.configure_trigger(source="external", activation="rising")

    cam.buffer_configure(n_sequences=1, frames_per_seq=5000,
                         pre_moi=1000,
                         moi_source="external")

    cam.buffer_arm()               # arm and wait for trigger
    cam.buffer_wait(timeout=60.0)  # blocks until recording completes
    data = cam.buffer_download()

For manual control with a software MOI instead of pyTelops.Camera.buffer_record():

cam.buffer_arm()
cam.buffer_fire_moi()
cam.buffer_wait(timeout=30.0)
data = cam.buffer_download()

Resolution and frame rate#

Reducing the sensor window (subwindow) directly increases the maximum frame rate. Width steps are 64 pixels (64–320); height steps are 4 pixels (4–256). Heights are in usable pixels; the driver adds 2 header rows internally.

cam.resolution = (128, 64)    # 128x64 pixels
cam.roi_offset = (96, 96)     # offset within full sensor

cam.frame_rate_max            # check achievable fps for current settings
cam.valid_widths              # [64, 128, 192, 256, 320]
cam.valid_heights             # [4, 8, 12, ..., 252, 256]

Example frame rates at a 10 us integration time:

Resolution

Int. time

Max FPS

320x256

10 us

3,115

320x128

10 us

5,973

320x64

10 us

11,034

128x64

10 us

17,836

64x32

10 us

36,676

64x4

10 us

64,491

64x4

5 us

95,184

Warning

Cycling resolution rapidly (e.g., changing it in a tight loop) can crash the camera firmware. Always allow at least 1 second between resolution changes, or power-cycle the camera to recover. See Troubleshooting.