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 theREG_FIRST_URLbytes 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:
GVCPClient,GVCPError(frompyGigEVision.gvcp)GVSPReceiver(frompyGigEVision.gvsp)fetch_genicam_xml(),parse_first_url()(frompyGigEVision.genicam)
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.gvspThe streaming counterpart (GVSP).
pyGigEVision.standardGigE Vision spec register addresses.
pyGigEVision.bootstrapConvenience 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:
ExceptionGVCP protocol error raised when the camera returns a non-SUCCESS status.
- status_name#
Human-readable name from
STATUS_NAMES, or"UNKNOWN_0xXXXX"for unrecognised codes.- Type:
- Parameters:
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:
objectGigE 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(), andsend_packetresend()all acquire the internal_lockbefore 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:
- 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:
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 callingdisconnect().A call on an already-connected client is a no-op.
- disconnect() None[source]#
Stop the heartbeat, release CCP control, and close the socket.
Releases the
Control Channel Privilegeregister (writes 0x00000000 toREG_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
READREGcommand 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.standardfor 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:
- 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:
- 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
WRITEREGcommand. Thread-safe: acquires the internal socket lock before sending.- Parameters:
- 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:
- 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_CHUNKbytes (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:
- Returns:
Raw memory contents, exactly size bytes.
- Return type:
- Raises:
GVCPError – If any
READMEMchunk 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_PACKETRESENDcommand asking the camera to re-send the specified packet range for a given stream block. Used bypyGigEVision.gvsp.GVSPReceiverto 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.gvcpThe 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:
objectReceive 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
GVCPClientlock, 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 theportproperty.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_ipis read fromgvcp_client.camera_ipwhen 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 is1500(standard Ethernet MTU).byteswap (bool, optional) – Swap the byte order of each pixel after assembly. Set to
Truewhen the camera sends data in the opposite endianness from the host. Default isFalse.camera_ip (str, optional) – IPv4 address of the camera, used as the destination for direct resend requests. Falls back to
gvcp_client.camera_ipwhen 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(), orclose(); those should be called from a single controlling thread.get_frame()andget_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()afterstop):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:
- 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 fromPIXEL_DTYPE, orNoneif 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;
0for perfect frames)."complete"Truewhen every data packet was received,Falsewhen the frame was emitted with one or more packets missing (bool). Equivalent tomissing_packets == 0; lets callers reject partial, zero-filled frames without inspecting the count.
Returns
Noneif 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"])
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:
- 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_URLregister 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
GVCPClientwith control privilege. Must expose aread_mem(addr, size)method that returnsbytes.- 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 fromREG_FIRST_URL).- Return type:
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
CCPcontrol privilege, set the heartbeat timeout, and fetch the GenICam XML descriptor viafetch_genicam_xml().- Parameters:
- Returns:
(client, xml_bytes)where client is a connectedGVCPClientholding exclusive control privilege and xml_bytes is the raw GenICam XML. The caller is responsible for callingclient.disconnect()when done.- Return type:
tuple of (GVCPClient, bytes)
- Raises:
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.