API reference#

Top-level#

pyGigEVision: pure-Python GigE Vision protocol library.

Provides GVCP (control channel), GVSP (image streaming), GigE Vision standard register constants, and helpers for fetching the GenICam XML descriptor from a connected camera. Vendor-specific drivers (register maps, calibration, image-format quirks) are built on top of this protocol foundation.

Public API summary:

  • GVCPClient: UDP-based camera control: register read/write, bulk memory access, heartbeat keepalive.

  • GVCPError: raised when the camera returns a non-SUCCESS GVCP status code.

  • GVSPReceiver: background UDP receiver that reassembles image frames into NumPy arrays.

  • discover(): broadcast discovery to find cameras on the network.

  • force_ip(): broadcast a FORCEIP to assign a camera IP by MAC.

  • bootstrap(): single-call convenience: open GVCP, take CCP control, fetch GenICam XML.

  • fetch_genicam_xml(): download and decompress the GenICam XML descriptor from a connected camera.

  • parse_first_url(): parse the REG_FIRST_URL bytes into (filename, addr, size).

  • pyGigEVision.standard: public submodule of GigE Vision register constants (e.g. REG_CCP, REG_HEARTBEAT_TIMEOUT).

Quickstart:

from pyGigEVision import discover, bootstrap, GVSPReceiver

cameras = discover()
client, xml = bootstrap(cameras[0]["ip"])
# ... configure registers, start GVSPReceiver, grab frames
client.disconnect()

The top-level package re-exports the most commonly used symbols from the submodules below for convenience. See the per-module sections for full documentation:

pyGigEVision.discover and pyGigEVision.force_ip are top-level aliases for discover() and force_ip() respectively. pyGigEVision.standard is the public submodule of GigE Vision register constants.

Control protocol (GVCP)#

GigE Vision Control Protocol (GVCP) client.

Implements the UDP-based control protocol from the GigE Vision specification: device discovery (broadcast), register read/write (READREG/WRITEREG), bulk memory access (READMEM), and heartbeat keepalive for the Control Channel Privilege register.

The protocol runs on UDP port 3956. Each packet is an 8-byte header followed by a payload. Header layout:

key(1B=0x42) flag(1B) command(2B) payload_len(2B) req_id(2B)

ACK packets use an 8-byte response header:

status(2B) ack_cmd(2B) length(2B) ack_id(2B)

This module is vendor-agnostic. The same code works against any GigE Vision compliant camera; vendor-specific register addresses and features are layered on top by vendor drivers.

See also

pyGigEVision.gvsp

The streaming counterpart (GVSP).

pyGigEVision.standard

GigE Vision spec register addresses.

pyGigEVision.bootstrap

Convenience helper to perform the standard boot sequence (open GVCP, take CCP, start heartbeat, fetch XML).

exception pyGigEVision.gvcp.GVCPError(message: str, status: int = 0)[source]#

Bases: Exception

GVCP protocol error raised when the camera returns a non-SUCCESS status.

status#

Numeric GVCP status code, e.g. 0x8006 for ACCESS_DENIED.

Type:

int

status_name#

Human-readable name from STATUS_NAMES, or "UNKNOWN_0xXXXX" for unrecognised codes.

Type:

str

Parameters:
  • message (str) – Short description of what operation failed.

  • status (int, optional) – GVCP status code returned by the camera. Default is 0 (SUCCESS), used when the error is locally generated (e.g. timeout after all retries).

Examples

>>> err = GVCPError("Register read failed", 0x8006)
>>> err.status
32774
>>> err.status_name
'ACCESS_DENIED'
class pyGigEVision.gvcp.GVCPClient(camera_ip: str, local_ip: str | None = None, timeout: float = 2.0)[source]#

Bases: object

GigE Vision Control Protocol client for camera register access.

Manages a single UDP socket to a specific camera, handles request/ACK sequencing (including stale-ACK discard and PENDING_ACK extension), takes and releases the Control Channel Privilege (CCP) register, and maintains a background heartbeat thread to keep the session alive.

Parameters:
  • camera_ip (str) – IPv4 address of the camera, e.g. "169.254.67.34".

  • local_ip (str or None, optional) – Local network interface address to bind the GVCP socket to. None (default) lets the OS choose the interface based on the camera’s IP and routing rules.

  • timeout (float, optional) – Socket timeout in seconds for the initial connection and the overall socket. Default is 2.0.

Notes

The constructor does not perform any network I/O. Call connect() (or use the context-manager form) to acquire control privilege and start the heartbeat thread.

The client is safe for concurrent use: read_reg(), write_reg(), read_mem(), read_float(), write_float(), and send_packetresend() all acquire the internal _lock before touching the socket.

Examples

Using the context manager (recommended):

with GVCPClient("169.254.67.34") as cam:
    width = cam.read_reg(0xD300)
    exposure = cam.read_float(0xE808)
    cam.write_float(0xE808, 100.0)

Manual lifecycle:

client = GVCPClient("169.254.67.34")
client.connect()
try:
    width = client.read_reg(0xD300)
finally:
    client.disconnect()
static discover(interface_ip: str = '', timeout: float = 2.0) list[dict][source]#

Broadcast a GVCP discovery packet and return all responding cameras.

When interface_ip is empty (the default), discovery sweeps every host interface: it binds a dedicated socket per NIC and sends both the global broadcast (255.255.255.255) and the per-subnet broadcast for each interface, then merges the results. When interface_ip is given, a single socket bound to that interface is used. All sends go to UDP port 3956 and each discovery ACK is parsed into a dictionary.

Both the standard GigE Vision discovery ACK layout and the extended layout (used by some cameras that prepend 24 extra bytes before the string fields) are handled. Duplicate responses from the same IP are deduplicated.

Parameters:
  • interface_ip (str, optional) – Local interface IPv4 address to bind the socket to, e.g. "169.254.0.1". Empty string (default) lets the OS choose, which sends the broadcast on all active interfaces.

  • timeout (float, optional) – How long to wait for discovery responses, in seconds. Default is 2.0.

Returns:

One entry per discovered camera. Each dict has keys:

"ip"

IPv4 address string of the camera.

"spec_version"

GigE Vision spec version the camera reports, e.g. "1.2".

"manufacturer"

Manufacturer name string.

"model"

Model name string.

"device_version"

Firmware / device version string.

"manufacturer_info"

Additional manufacturer info string.

"serial"

Serial number string.

"user_name"

User-assigned name string (may be empty).

"interface_ip"

IPv4 address of the local host interface whose socket received this camera’s discovery reply, or "" when the OS chose the socket. Lets a caller bind the same interface for follow-up unicast control.

Return type:

list of dict

Examples

>>> cameras = GVCPClient.discover(interface_ip="169.254.0.1", timeout=1.0)
>>> for cam in cameras:
...     print(cam["ip"], cam["model"])
static force_ip(mac, ip: str, mask: str, gateway: str = '0.0.0.0', interface_ip: str = '') None[source]#

Broadcast a GVCP FORCEIP command to assign an IP to a camera by MAC.

Re-homes a camera that is on the wrong subnet (or fell back to link-local) without touching host NIC configuration. The camera reboots its IP stack, so no ACK is expected.

When interface_ip is empty (the default), the FORCEIP packet is sent on every active host interface (mirroring discover()), because the target camera may be reachable only via a specific NIC in a multi-NIC link-local setup. When interface_ip is given, a single socket bound to that interface is used.

Parameters:
  • mac (str or bytes) – Target camera MAC, as "aa:bb:cc:dd:ee:ff" or 6 raw bytes.

  • ip (str) – New IPv4 address and subnet mask for the camera.

  • mask (str) – New IPv4 address and subnet mask for the camera.

  • gateway (str, optional) – Default gateway. Default "0.0.0.0" (none).

  • interface_ip (str, optional) – Local interface IPv4 address to bind the socket to, e.g. "169.254.0.1". Empty string (default) sweeps every active interface.

Notes

FORCEIP is a broadcast command and is not acknowledged by the camera. The camera reboots its IP stack to apply the new address, so allow a short delay (about one second) before re-running discovery to see it at the new IP. The assignment is not persistent; power-cycling the camera returns it to its configured startup behavior (typically DHCP then link-local).

connect(force: bool = True) None[source]#

Open the UDP socket, take CCP control, and start the heartbeat thread.

If another application (or a stale previous session) holds the CCP control privilege and force is True, this method polls with 1-second intervals until the remote heartbeat timeout expires and the lock is released automatically by the camera. This handles the common scenario where a previous Python session crashed without calling disconnect().

A call on an already-connected client is a no-op.

Parameters:

force (bool, optional) – If True (default), retry on ACCESS_DENIED for up to 15 seconds, printing a warning on the first retry. If False, raise GVCPError immediately when access is denied.

Raises:
  • GVCPError – If the CCP register write fails for any reason other than ACCESS_DENIED, or if force is True but the 15-second retry window expires without gaining control.

  • OSError – If the UDP socket cannot be created or bound.

disconnect() None[source]#

Stop the heartbeat, release CCP control, and close the socket.

Releases the Control Channel Privilege register (writes 0x00000000 to REG_CCP) so other applications can immediately connect without waiting for the heartbeat timeout. Any errors during the release are silently suppressed to ensure the socket is always closed.

A call on a client that is not connected is a no-op.

read_reg(addr: int) int[source]#

Read a single 32-bit register from the camera.

Sends a READREG command and returns the raw unsigned 32-bit value. Thread-safe: acquires the internal socket lock before sending.

Parameters:

addr (int) – Register address (32-bit unsigned). Use constants from pyGigEVision.standard for spec-defined registers, or vendor-specific addresses from the camera’s GenICam XML.

Returns:

Raw register value as a 32-bit unsigned integer.

Return type:

int

Raises:

GVCPError – If the camera returns a non-SUCCESS status, or if all retry attempts time out.

Examples

>>> with GVCPClient("169.254.67.34") as cam:
...     width = cam.read_reg(0xD300)
...     print(f"Width: {width}")
read_float(addr: int) float[source]#

Read a register and interpret it as an IEEE 754 big-endian float.

Reads the raw 32-bit value from addr via read_reg() and reinterprets the bit pattern as a single-precision float.

Parameters:

addr (int) – Register address (32-bit unsigned) of the float-valued register.

Returns:

The register value reinterpreted as a 32-bit IEEE 754 single-precision float.

Return type:

float

Raises:

GVCPError – If the underlying read_reg() call fails.

Examples

>>> with GVCPClient("169.254.67.34") as cam:
...     exposure_us = cam.read_float(0xE808)
write_reg(addr: int, value: int) None[source]#

Write a 32-bit unsigned integer to a camera register.

Sends a WRITEREG command. Thread-safe: acquires the internal socket lock before sending.

Parameters:
  • addr (int) – Register address (32-bit unsigned).

  • value (int) – Value to write (32-bit unsigned, 0–4294967295).

Raises:

GVCPError – If the camera returns a non-SUCCESS status (e.g. WRITE_PROTECT, ACCESS_DENIED), or if all retry attempts time out.

Examples

>>> with GVCPClient("169.254.67.34") as cam:
...     cam.write_reg(0xD300, 640)
write_float(addr: int, value: float) None[source]#

Write a float value to a camera register as IEEE 754 big-endian.

Packs value as a 32-bit single-precision float and writes the raw bit pattern to the register at addr via write_reg().

Parameters:
  • addr (int) – Register address (32-bit unsigned) of the float-valued register.

  • value (float) – Value to write. The float is packed with big-endian byte order before transmission.

Raises:

GVCPError – If the underlying write_reg() call fails.

Examples

>>> with GVCPClient("169.254.67.34") as cam:
...     cam.write_float(0xE808, 1000.0)  # set exposure to 1000 µs
read_mem(addr: int, size: int) bytes[source]#

Read a contiguous block of camera memory.

Splits the request into chunks of at most READMEM_CHUNK bytes (512 bytes, safe for standard Ethernet) and concatenates the results. Each chunk is read with the lock held, so concurrent register operations may interleave between chunks.

The GigE Vision spec requires every READMEM byte-count to be a multiple of 4, and some cameras reject unaligned reads. Each chunk count is therefore rounded up to a 4-byte multiple on the wire and the returned bytes are trimmed back to the exact size requested.

Parameters:
  • addr (int) – Start address of the memory block (32-bit unsigned).

  • size (int) – Number of bytes to read. If size is 0, an empty bytes object is returned immediately.

Returns:

Raw memory contents, exactly size bytes.

Return type:

bytes

Raises:

GVCPError – If any READMEM chunk fails.

Examples

>>> with GVCPClient("169.254.67.34") as cam:
...     xml_url = cam.read_mem(0x0200, 512)
...     print(xml_url.split(b"\x00")[0].decode())
send_packetresend(block_id: int, first_packet_id: int, last_packet_id: int, stream_channel: int = 0) None[source]#

Request retransmission of missing GVSP stream packets.

Sends a CMD_PACKETRESEND command asking the camera to re-send the specified packet range for a given stream block. Used by pyGigEVision.gvsp.GVSPReceiver to recover from packet loss.

Parameters:
  • block_id (int) – The GVSP block (frame) identifier for which packets are missing.

  • first_packet_id (int) – ID of the first missing packet within the block.

  • last_packet_id (int) – ID of the last missing packet within the block (inclusive).

  • stream_channel (int, optional) – GVSP stream channel index. Default is 0 (the only channel on most cameras).

Raises:

GVCPError – If the camera returns a non-SUCCESS status or the request times out after all retries.

Streaming protocol (GVSP)#

GigE Vision Streaming Protocol (GVSP) receiver.

Receives image frames pushed by a camera over UDP as a sequence of packets:

  • Leader packet: image metadata (dimensions, pixel format, timestamp).

  • Data packets: raw pixel data chunks.

  • Trailer packet: frame complete signal.

The GVSPReceiver runs a background thread that reassembles packets into pre-allocated NumPy buffers, detects gaps in packet sequence numbers, requests resends directly via the receive socket (bypassing the GVCP control channel for lower latency), and pushes completed frames onto a thread-safe queue.

Byte order is configurable via the byteswap constructor parameter (set True when the camera sends data in the opposite endianness from the host; vendor-dependent).

Notes

Implements aravis-inspired patterns: pre-allocated frame buffers (direct offset writes, no dict-and-sort assembly), throttled gap detection (every few packets plus on each socket timeout), three-tier timeouts (initial gap grace, resend interval, frame retention).

See also

pyGigEVision.gvcp

The control counterpart (GVCP).

class pyGigEVision.gvsp.GVSPReceiver(local_ip: str = '', local_port: int = 0, max_queue: int = 30, gvcp_client: object | None = None, packet_size: int = 1500, byteswap: bool = False, camera_ip: str = '', initial_packet_timeout: float = 0.005, frame_retention: float = 0.2)[source]#

Bases: object

Receive GVSP image frames on a UDP socket.

Binds a UDP socket on the given local_ip/local_port, starts a background thread that reassembles GVSP packets into NumPy arrays, and exposes a thread-safe queue via get_frame() / get_frame_with_info().

Packet resends are sent directly from the stream socket to the camera’s GVCP port (3956), bypassing any GVCPClient lock, which keeps the control channel free for register reads during acquisition.

Parameters:
  • local_ip (str, optional) – Local interface IPv4 address to bind to, e.g. "169.254.0.1". Empty string (default) lets the OS choose.

  • local_port (int, optional) – Local UDP port to bind to. 0 (default) asks the OS to assign a free port; read back via the port property.

  • max_queue (int, optional) – Maximum number of completed frames to hold in the internal queue before the oldest is discarded. Default is 30.

  • gvcp_client (GVCPClient or None, optional) – If provided, camera_ip is read from gvcp_client.camera_ip when camera_ip is empty. The client is not otherwise used.

  • packet_size (int, optional) – Expected network packet size in bytes (Ethernet MTU). The data payload per packet is packet_size - 8. Default is 1500 (standard Ethernet MTU).

  • byteswap (bool, optional) – Swap the byte order of each pixel after assembly. Set to True when the camera sends data in the opposite endianness from the host. Default is False.

  • camera_ip (str, optional) – IPv4 address of the camera, used as the destination for direct resend requests. Falls back to gvcp_client.camera_ip when gvcp_client is supplied. Leave empty to disable resends.

  • initial_packet_timeout (float, optional) – Grace period in seconds before the first resend request is issued for a gap. Default is 0.005.

  • frame_retention (float, optional) – Maximum time in seconds to keep an incomplete frame before emitting it with whatever data arrived. Default is 0.200.

Notes

The constructor binds the socket immediately. Call start() to launch the background receiver thread.

The receiver is not thread-safe for concurrent calls to start(), stop(), or close(); those should be called from a single controlling thread. get_frame() and get_frame_with_info() are safe to call from any thread.

Examples

Minimal usage, receiving one frame:

receiver = GVSPReceiver(local_ip="169.254.0.1", camera_ip="169.254.67.34")
receiver.start()
# ... configure camera to stream to receiver.port ...
frame = receiver.get_frame(timeout=5.0)
receiver.stop()

Context-manager style (using close() after stop):

receiver = GVSPReceiver(local_ip="169.254.0.1")
receiver.start()
try:
    frame, info = receiver.get_frame_with_info(timeout=10.0)
finally:
    receiver.stop()
    receiver.close()
property port: int#

UDP port the receiver is bound to.

Read the assigned port after construction, especially when local_port was 0 (OS-assigned):

receiver = GVSPReceiver()
print(receiver.port)  # e.g. 49152
Type:

int

start() None[source]#

Start the background receiver thread.

Idempotent. Does nothing if the thread is already running.

Examples

receiver = GVSPReceiver(local_ip="169.254.0.1")
receiver.start()
# ... acquire frames ...
receiver.stop()
stop() None[source]#

Stop the background receiver thread.

Signals the thread to exit and waits up to 5 seconds for it to finish. Clears any in-flight frame buffers. The socket is kept open; call close() to release it.

close() None[source]#

Stop the receiver and close the UDP socket.

After calling close, the receiver must not be used again.

get_frame(timeout: float = 5.0) ndarray | None[source]#

Block until a frame is available and return it as a NumPy array.

Discards the accompanying metadata. Use get_frame_with_info() to retrieve metadata alongside the image.

Parameters:

timeout (float, optional) – Maximum time in seconds to wait for a frame. Default is 5.0.

Returns:

2-D array of shape (height, width) with dtype from PIXEL_DTYPE, or None if no frame arrived within timeout.

Return type:

numpy.ndarray or None

Examples

receiver.start()
frame = receiver.get_frame(timeout=2.0)
if frame is not None:
    print(frame.shape, frame.dtype)
get_frame_with_info(timeout: float = 5.0) tuple[ndarray, dict] | None[source]#

Block until a frame is available and return it with metadata.

Parameters:

timeout (float, optional) – Maximum time in seconds to wait for a frame. Default is 5.0.

Returns:

(frame, info) where frame is a 2-D NumPy array and info is a dict with the following keys:

"block_id"

GVSP block identifier (int).

"timestamp"

Camera timestamp from the leader packet (int, camera ticks).

"pixel_format"

GenICam PFNC pixel format code, e.g. PIXEL_MONO16 (int).

"width"

Image width in pixels (int).

"height"

Image height in pixels (int).

"missing_packets"

Number of data packets that were not recovered before the frame was emitted (int; 0 for perfect frames).

"complete"

True when every data packet was received, False when the frame was emitted with one or more packets missing (bool). Equivalent to missing_packets == 0; lets callers reject partial, zero-filled frames without inspecting the count.

Returns None if no frame arrived within timeout.

Return type:

tuple of (numpy.ndarray, dict) or None

Examples

result = receiver.get_frame_with_info(timeout=3.0)
if result is not None:
    frame, info = result
    print(info["block_id"], info["missing_packets"])
reset_resend_stats() None[source]#

Zero the resend counters (requested/recovered/failed).

The counters otherwise accumulate for the lifetime of the receiver. Call this before a fresh download to get per-download resend figures.

GenICam descriptor download#

GenICam XML descriptor download helper.

Every GigE Vision camera stores its register description XML in on-board memory. The location URL is at bootstrap register 0x0200 (REG_FIRST_URL) and follows the format:

Local:cameralib.xml;0xADDR;0xSIZE

This module reads the URL, downloads the bytes, and decompresses the descriptor if the camera stores it as a ZIP archive (common for larger XML files).

Notes

Parsing the XML itself is left to the vendor driver. Register naming conventions differ enough across vendors that a generic parser would add more abstraction than value. Vendor drivers receive the raw XML bytes from fetch_genicam_xml() and interpret them using their own register maps.

pyGigEVision.genicam.parse_first_url(url_bytes: bytes) tuple[str, int, int][source]#

Parse the bytes read from REG_FIRST_URL into (filename, addr, size).

Parameters:

url_bytes (bytes) – Raw bytes from client.read_mem(REG_FIRST_URL, 512). The string is null-terminated; everything after the first null byte is ignored. Non-ASCII trailing bytes are replaced with the Unicode replacement character and then discarded by the null split.

Returns:

(filename, addr, size) where filename is the XML or ZIP entry name (e.g. "cameralib.xml"), addr is the start address of the descriptor in camera memory, and size is the byte length.

Numeric fields accept both 0x-prefixed hex (0x10000) and bare hex without a prefix (ff000, emitted by some cameras), as well as plain decimal integers.

Return type:

tuple of (str, int, int)

Raises:

ValueError – If the URL string cannot be split into at least three semicolon-separated parts (malformed descriptor).

Examples

>>> url = b"Local:cameralib.xml;0x10000;0x4000\x00"
>>> filename, addr, size = parse_first_url(url)
>>> filename
'cameralib.xml'
>>> hex(addr)
'0x10000'
>>> size
16384
pyGigEVision.genicam.fetch_genicam_xml(client: object) tuple[bytes, str][source]#

Download the GenICam XML descriptor from a connected camera.

Reads the REG_FIRST_URL register to locate the descriptor, fetches the raw bytes, and decompresses them if the camera stored the XML as a ZIP archive.

Parameters:

client (GVCPClient) – An open GVCPClient with control privilege. Must expose a read_mem(addr, size) method that returns bytes.

Returns:

(xml_bytes, filename) where xml_bytes is the raw (and, if necessary, decompressed) GenICam XML, and filename is the name of the XML entry (from the zip archive if the descriptor was compressed, otherwise the filename from REG_FIRST_URL).

Return type:

tuple of (bytes, str)

Examples

Typical usage with a connected client:

from pyGigEVision import GVCPClient
from pyGigEVision.genicam import fetch_genicam_xml

with GVCPClient("169.254.1.10") as client:
    xml_bytes, filename = fetch_genicam_xml(client)
    print(f"Received {len(xml_bytes)} bytes from '{filename}'")

Bootstrap helper#

Convenience helper to bring a GigE Vision camera up to a usable state.

Performs the standard boot sequence shared by every GigE Vision driver: acquire control privilege, start heartbeat keepalive, fetch the GenICam XML descriptor. Vendor drivers use this to skip boilerplate, or roll their own if they need finer control over each step.

pyGigEVision.bootstrap.bootstrap(camera_ip: str, heartbeat_ms: int = 3000) tuple[GVCPClient, bytes][source]#

Connect to a camera, take control, and fetch its GenICam XML.

Performs the three-step standard boot sequence: open the GVCP socket, write exclusive CCP control privilege, set the heartbeat timeout, and fetch the GenICam XML descriptor via fetch_genicam_xml().

Parameters:
  • camera_ip (str) – IPv4 address of the target camera, e.g. "169.254.1.10".

  • heartbeat_ms (int, optional) – Heartbeat timeout to write to the camera’s REG_HEARTBEAT_TIMEOUT register, in milliseconds. Default is 3000 (3 seconds).

Returns:

(client, xml_bytes) where client is a connected GVCPClient holding exclusive control privilege and xml_bytes is the raw GenICam XML. The caller is responsible for calling client.disconnect() when done.

Return type:

tuple of (GVCPClient, bytes)

Raises:
  • GVCPError – If taking CCP control or reading the GenICam XML fails.

  • OSError – If the UDP socket cannot be created or bound.

Examples

>>> from pyGigEVision import bootstrap
>>> client, xml = bootstrap("169.254.1.10")
>>> print(f"Got {len(xml)} bytes of GenICam XML")
>>> client.disconnect()

GigE Vision standard registers#

GigE Vision standard register addresses.

Defined by the GigE Vision specification and identical on every compliant camera regardless of vendor. These addresses are normative: the spec requires all GigE Vision devices to implement them at the given offsets in the bootstrap register map (starting at address 0x0000). Register names and semantics follow the GigE Vision 1.x/2.x bootstrap register table.

Vendor-specific registers (Width, Height, ExposureTime, and so on) live in each vendor driver and are derived from the camera’s GenICam XML descriptor, not from this module.