Python API reference
A ctypes binding over the same C ABI. Same engine, same behaviour — not a reimplementation that can drift.
import xcaicx
xcaicx.version() # "1.0.0"The shared library is found on the default loader path, or via XCAICX_LIBRARY.
Engine#
xcaicx.Engine(*,
license_token: str | None = None,
license_path: str | None = None,
license_pubkey: str | None = None,
state_dir: str = "/var/lib/xcaicx",
model_dir: str | None = None,
modules: Module | int = 0,
backend: Backend = Backend.AUTO,
num_threads: int = 0,
log_level: LogLevel = LogLevel.INFO,
cloud_endpoint: str | None = None,
heartbeat_seconds: int = 3600,
offline: bool = False)All arguments are keyword-only. Provide exactly one of license_token / license_path.
Raises LicenseError or EntitlementError at construction if the licence is unusable — failing at startup rather than mid-shift is deliberate.
Methods and properties#
| Member | Returns | Notes |
|---|---|---|
license | License | Snapshot of the current licence state |
active_modules() | Module | Modules actually running |
heartbeat() | None | Forces a licensing/metering round trip; no-op when offline |
stream(name, ...) | Stream | Opens a stream |
close() | None | Closes streams and flushes metering |
__enter__ / __exit__ | Use as a context manager |
with xcaicx.Engine(license_path="unit.token", state_dir="./state") as eng:
...close() is idempotent, and __del__ calls it — but relying on the garbage collector to flush your metering is relying on the interpreter's shutdown order. Use the context manager.
Engine.stream()#
eng.stream(name: str = "stream", *,
modules: Module | int = 0,
detect_threshold: float = 0.35,
nms_threshold: float = 0.45,
detect_every_n: int = 1) -> Streamname is used in logs and in metering attribution. modules = 0 means all engine modules.
Stream#
| Method | Signature | |
|---|---|---|
process | process(data, *, width, height, fmt, pts_us=0, frame_id=0) -> Result | |
submit | submit(data, *, width, height, fmt, pts_us=0, frame_id=0) -> None | |
poll | `poll(timeout_ms=0) -> Result | None` |
add_zone | add_zone(name, points, dwell_seconds=0) -> int | |
add_line | add_line(name, a, b) -> int | |
close | close() -> None |
data is anything supporting the buffer protocol: bytes, bytearray, memoryview, or a numpy array. numpy is not a dependency — it simply works if present.
result = s.process(frame_bgr, width=1920, height=1080,
fmt=xcaicx.PixelFormat.BGR8, pts_us=pts, frame_id=n)points for add_zone is a sequence of (x, y) tuples normalised to [0,1]. a and b for add_line are single (x, y) tuples.
poll() returns None when nothing is ready — it does not raise. submit() raises LimitError under backpressure; drop the frame.
Result types#
Result#
| Attribute | Type |
|---|---|
frame_id | int |
pts_us | int |
detections | list[Detection] |
events | list[Event] |
inference_ms | float |
total_ms | float |
Unlike the C API, Python results are copies — they stay valid after the next call.
Detection#
| Attribute | Type | Notes | |
|---|---|---|---|
box | Box | Normalised | |
score | float | ||
class_id | int | ||
class_name | str | person, vehicle, object, defect, ppe_violation, plate | |
track_id | int | -1 if untracked | |
source | Module | Which module produced it | |
text | `str \ | None` | ANPR plate text |
text_score | float |
Box#
box.x, box.y, box.w, box.h # normalised floats
box.to_pixels(width, height) # -> (x, y, w, h) intsEvent#
| Attribute | Type | |
|---|---|---|
kind | EventKind | |
track_id | int | |
zone_id | int | |
zone_name | `str \ | None` |
detail | `str \ | None` |
score | float | |
pts_us | int |
License#
| Attribute | Type | |
|---|---|---|
state | LicenseState | |
license_id, sku, customer_id, device_id | str | |
batch_id | `str \ | None` |
entitlements | Module | |
max_streams, max_fps_per_stream | int | |
issued_at, expires_at, seconds_remaining, grace_days | int | |
metered_inferences, metered_stream_seconds | int | |
is_usable | bool — state is VALID or GRACE | |
perpetual | bool — expires_at == 0 |
lic = eng.license
if not lic.is_usable:
raise SystemExit(f"licence {lic.state.name}")
if lic.state is xcaicx.LicenseState.GRACE:
warn(f"expired, {lic.seconds_remaining}s of grace left")Enumerations#
class Module(enum.IntFlag): DETECT, DEFECT, PPE, ANPR
class PixelFormat(enum.IntEnum): RGB8, BGR8, GRAY8, NV12, I420
class EventKind(enum.IntEnum): NONE, ZONE_ENTER, ZONE_EXIT, LINE_CROSS,
DWELL_EXCEEDED, PPE_VIOLATION, DEFECT_FOUND, PLATE_READ
class LicenseState(enum.IntEnum): INVALID, VALID, GRACE, EXPIRED, REVOKED
class Backend(enum.IntEnum): AUTO, REFERENCE, ORT_CPU, ORT_CUDA, TENSORRT,
OPENVINO, RKNN, HAILO
class LogLevel(enum.IntEnum): OFF, ERROR, WARN, INFO, DEBUGModule is an IntFlag, so entitlements compose and test naturally:
if xcaicx.Module.PPE in eng.license.entitlements:
...
modules = xcaicx.Module.DETECT | xcaicx.Module.PPEExceptions#
RuntimeError
└── XcaicxError .status: int, .detail: str
├── LicenseError any licence-state failure
├── EntitlementError module not in SKU
└── LimitError stream/fps/quota cap, queue-full backpressuretry:
result = s.process(frame, width=w, height=h, fmt=xcaicx.PixelFormat.BGR8)
except xcaicx.LimitError:
dropped += 1 # backpressure or quota
except xcaicx.LicenseError as exc:
disable_analytics() # keep recording
alert(f"licence: {exc} (status {exc.status})").status is the numeric xcaicx_status, so it maps directly onto the status-code table.
Module-level#
xcaicx.version() -> strReturns the library version string. Also the cheapest check that the shared library is loadable at all — call it once at startup and fail loudly if it raises.
Notes on the binding#
- Strings passed into the engine are retained by the binding for the lifetime of the object. ctypes does not keep the temporaries produced by
.encode()alive, and a config struct pointing at freed memory is the kind of bug that reproduces only under load. - Streams are tracked by their engine and closed when the engine closes, so a forgotten
close()on a stream is not a leak. - Every call maps to exactly one C call. There is no caching layer and no reimplemented logic, which is why the binding cannot drift from the C behaviour.
Next#
- Quickstart: Python — worked examples
- C ABI reference — the underlying surface
- Status codes