Skip to main content

pyhikrobot

Thin, zero-copy Python bindings for Hikrobot machine-vision cameras over the MVS SDK. NumPy views without the copy, a cross-platform loader, and errors you can catch.

Status: alpha. Enumeration, open/close, the GenICam node map, streaming and GigE transport tuning are implemented and tested against real hardware. Action commands and the CUDA layer are not written yet. The public API may still change.


Requirements

Python 3.9+
Runtime dependency numpy — nothing else, ever, in the core package
SDK Hikrobot MVS 4.x, installed separately by you
Platforms Linux x86_64 / aarch64 / armv7l, Windows x64

This package does not ship any Hikrobot code. It opens an SDK you installed yourself; get it from the vendor under their terms.

Install

pip install pyhikrobot

The cuda extra (pip install "pyhikrobot[cuda]") pulls in CuPy for the device-side layer, which is not written yet.

The SDK is found through MVCAM_SDK_PATH, falling back to /opt/MVS on Linux and the installer's Common Files\MVS\Runtime on Windows. Nothing is loaded at import time, so import hikrobot works on a machine with no SDK and no camera — a missing SDK raises SDKNotFoundError on first real use.

Quick start

import hikrobot

devices = hikrobot.enumerate_devices()
for device in devices:
    print(device.transport, device.model_name, device.serial_number, device.ip_address)

with hikrobot.Camera(devices[0]) as camera:
    camera.exposure_us = 2500.0
    camera.gain_db = 0.0

    for frame in camera.frames(timeout_ms=1000):
        print(frame.frame_number, frame.data.shape, frame.data.mean())
        break

Camera() touches no SDK state; the context manager opens and closes the device.

Buffer lifetime

This is the one rule that matters. frame.data is a read-only NumPy view onto a node of the driver's buffer pool — not memory you own. The pool is fixed and recycled, so once the node goes back the driver writes the next frame into the same address.

for frame in camera.frames(timeout_ms=1000):
    total = frame.data.sum()  # fine — inside the body
    keep = frame.copy()  # fine — owns its memory, writable
    leaked = frame.data  # a view; its node goes back at the end of the body
    break

frame.data  # BufferReleasedError — the usual mistake, caught
leaked.mean()  # no error, and no longer this frame's pixels

Note the asymmetry. Reaching for frame.data after the release raises, which catches the common mistake in development instead of in the field. An array you already took out cannot be caught — NumPy has no idea the memory changed hands, so it keeps working and quietly returns whatever the driver wrote there next. That is the failure this API is shaped to avoid, and .copy() is the only thing that avoids it.

frames() releases the node in a finally, on every route out including break, return and exceptions. There is deliberately no "hold this one for me" shortcut: a .copy() of a 2 MB frame is visible in a profile, an accidentally retained view is not.

For consumers that outlive the loop body, frames_raw() hands the release over:

camera.start_grabbing(node_count=8)  # own acquisition, or leaving the loop stops it
try:
    for frame in camera.frames_raw(timeout_ms=1000):
        queue.put(frame)  # released later, by whoever drains the queue
finally:
    camera.stop_grabbing()  # reclaims anything still outstanding

The pool holds exactly node_count nodes and the SDK's default is one, so holding a second frame without raising it fails with InsufficientBufferError.

Camera settings

Named properties cover the common SFNC features:

camera.width, camera.height, camera.offset_x, camera.offset_y
camera.exposure_us, camera.gain_db, camera.frame_rate
camera.pixel_format, camera.pixel_formats
camera.payload_size  # read-only
camera.exposure_range_us.min  # FloatRange(value, min, max)
camera.nodes.int_range("Width").inc  # IntRange(value, min, max, inc)

Anything else goes through the node map directly:

camera.nodes.set_enum("TriggerMode", "On")
camera.nodes.set_enum("TriggerSource", "Software")
camera.nodes.execute("TriggerSoftware")
camera.nodes.get_int("GevTimestampTickFrequency")

Two behaviours worth knowing, both measured rather than assumed:

  • A missing node and a wrong-typed access return the same status, so both raise GenICamError. When one appears, check the spelling and the type.
  • Float features are quantised to a hardware step the node map does not expose. Writing gain_db = 1.0 reads back as 1.0052. Write, then read, and trust the second value.

GigE transport

The knobs that decide whether streaming works at all:

camera.tune_packet_size()  # probe the path, apply what it carries
camera.enable_resend(True)  # retransmit packets the host missed

camera.start_grabbing(node_count=4)
for frame in camera.frames(timeout_ms=5000):
    ...
    stats = camera.statistics  # only valid while acquisition runs
camera.stop_grabbing()

stats.lost_packets, stats.lost_frames, stats.resent_packets

Incomplete frames almost always mean a packet size the network path cannot carry — start with tune_packet_size(). Counters live only between start_grabbing() and stop_grabbing() and reset on every start.

Errors

Everything derives from HikrobotError, so you can catch one thing. Below it, one class per vendor error range, plus named classes for the codes callers actually branch on:

HikrobotError
├── SDKNotFoundError, SDKLoadError, UnsupportedPlatformError
├── CameraStateError, BufferReleasedError, UnsupportedPixelFormatError
└── StatusError                      .status  .name  .operation
    ├── GeneralError                 InvalidHandleError, CallOrderError, NoDataError,
    │                                IncompleteImageError, InsufficientBufferError, …
    ├── GenICamError                 CameraTimeoutError, ValueOutOfRangeError, …
    ├── GigEError                    AccessDeniedError, DeviceBusyError, NetworkError, …
    ├── USBError
    └── UpgradeError

An untranslated code still arrives as its range's class carrying .status and .name, so nothing is lost:

try:
    camera.open()
except hikrobot.AccessDeniedError:
    ...  # held by another process
except hikrobot.HikrobotError as exc:
    print(exc)  # MV_CC_OpenDevice failed: MV_E_NETER (0x80000206) - network error

Development

pip install -e ".[dev]"

pytest tests/unit              # no SDK, no camera, no CUDA — runs anywhere
pytest tests --hardware        # opt-in; needs one reachable camera
ruff check . && ruff format --check . && mypy

Unit tests run against a fake that sits at the CDLL boundary, so struct packing, argtypes and the status-to-exception mapping stay under test without hardware.

License

Apache-2.0. See LICENSE and NOTICE.

Hikrobot and MVS are trademarks of Hangzhou Hikrobot Co., Ltd. This project is not affiliated with or endorsed by them, and distributes none of their software.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyhikrobot-0.1.0.tar.gz (65.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyhikrobot-0.1.0-py3-none-any.whl (44.3 kB view details)

Uploaded Python 3

File details

Details for the file pyhikrobot-0.1.0.tar.gz.

File metadata

  • Download URL: pyhikrobot-0.1.0.tar.gz
  • Upload date:
  • Size: 65.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pyhikrobot-0.1.0.tar.gz
Algorithm Hash digest
SHA256 07e977606836a96b8bca618884ddc93be9d6f172ab475771fffe54ad018092b3
MD5 43048984cb903721e350e354abc64acf
BLAKE2b-256 9919d088573969de256550558363d089efda25d5aacbeb2f9076adf89e13c8c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyhikrobot-0.1.0.tar.gz:

Publisher: release.yml on Shalimov04/pyhikrobot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyhikrobot-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pyhikrobot-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 44.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pyhikrobot-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e7a23a2b4a5a09440afeea5f5212ea169463440d3648eaf1055fc57dfa8119b
MD5 a3d2136ef62c6fe40913b54d7d48091f
BLAKE2b-256 5e2b0e723392dd4086ddcf71d5b4cf4104e8314703921ff7b4e4cc164698de34

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyhikrobot-0.1.0-py3-none-any.whl:

Publisher: release.yml on Shalimov04/pyhikrobot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.1.1

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page