XCAICX docs Product Contact

Quickstart: Python

The deployment path: gateways, NVRs, test rigs and provisioning lines. A ctypes binding over the same C ABI, so it is the same engine with the same behaviour — not a reimplementation that can drift.

Install#

The binding is pure Python over ctypes and needs only the built shared library on the loader path.

bash
pip install -e bindings/python
export XCAICX_LIBRARY=/path/to/libxcaicx.so    # if it is not on the default path
python
import xcaicx
print(xcaicx.version())

An engine#

python
import xcaicx

eng = xcaicx.Engine(
    license_path="unit.token",       # or license_token="XCAICX1.…"
    state_dir="/var/lib/xcaicx",     # writable, survives reboot
    modules=xcaicx.Module.DETECT | xcaicx.Module.PPE,
    cloud_endpoint="https://licensing.xcaicx.com",
    heartbeat_seconds=3600,
    offline=False,
)
ArgumentDefaultNotes
license_tokenNoneThe token string itself. Provide exactly one of token/path.
license_pathNoneFile containing the token.
license_pubkeyNoneOverride the compiled-in verification key. Used for key rotation.
state_dir/var/lib/xcaicxActivation record, clock high-water mark, usage spool.
model_dirNoneModel bundle directory for production backends.
modules0Bitmask. Every bit must be entitled by the licence.
backendBackend.AUTOSee Backends.
num_threads00 = auto.
log_levelLogLevel.INFO
cloud_endpointNoneNone means offline-only; usage still spools locally.
heartbeat_seconds3600
offlineFalseTrue never touches the network.

The constructor raises on an unusable licenceLicenseError or EntitlementError. Failing at startup rather than mid-shift is deliberate.

python
try:
    eng = xcaicx.Engine(license_path="unit.token", state_dir="./state")
except xcaicx.EntitlementError as exc:
    print("SKU does not include a requested module:", exc)
except xcaicx.LicenseError as exc:
    print("licence problem:", exc)

Use it as a context manager so metering is flushed on the way out:

python
with xcaicx.Engine(license_path="unit.token", state_dir="./state") as eng:
    ...

Inspect the licence#

python
lic = eng.license
print(lic.sku, lic.state.name, lic.entitlements)
print("streams <=", lic.max_streams, "fps <=", lic.max_fps_per_stream)
print("perpetual" if lic.perpetual else f"{lic.seconds_remaining}s left")
print("metered so far:", lic.metered_inferences)

if not lic.is_usable:
    raise SystemExit("licence is not usable")
if lic.state is xcaicx.LicenseState.GRACE:
    warn_operator("licence expired, running on grace")

A stream#

python
with eng.stream("line-3",
                modules=xcaicx.Module.DETECT | xcaicx.Module.PPE,
                detect_threshold=0.35,
                nms_threshold=0.45,
                detect_every_n=1) as s:

    s.add_zone("packing-bay",
               [(0.55, 0), (1, 0), (1, 1), (0.55, 1)],
               dwell_seconds=30)
    s.add_line("doorway", (0.5, 0), (0.5, 1))

Coordinates are normalised to [0,1], so they survive a resolution change.

Process frames#

process() accepts anything with the buffer protocol — bytes, bytearray, memoryview, or a numpy array straight out of OpenCV. numpy is not a dependency.

python
result = s.process(frame_bgr, width=1920, height=1080,
                   fmt=xcaicx.PixelFormat.BGR8)

for d in result.detections:
    print(d.class_name, round(d.score, 2), d.track_id, d.box)

for ev in result.events:
    print(ev.kind.name, ev.zone_name, ev.detail)

print(f"{result.inference_ms:.1f}ms inference, {result.total_ms:.1f}ms total")

Box is normalised; convert when you need pixels:

python
x, y, w, h = d.box.to_pixels(1920, 1080)

Asynchronous#

python
s.submit(frame, width=w, height=h, fmt=xcaicx.PixelFormat.NV12)
result = s.poll(timeout_ms=5)
if result is not None:
    handle(result)

submit() raises LimitError on backpressure — treat that as flow control and drop the frame. poll() returns None when nothing is ready.

A complete worked example#

Reads from a camera, draws boxes, prints events, and exits cleanly so metering is flushed.

python
import signal
import cv2
import xcaicx

running = True
signal.signal(signal.SIGINT, lambda *_: globals().__setitem__("running", False))

cap = cv2.VideoCapture(0)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

with xcaicx.Engine(license_path="unit.token", state_dir="./state",
                   modules=xcaicx.Module.DETECT | xcaicx.Module.PPE) as eng:

    print("licence:", eng.license.sku, eng.license.state.name)

    with eng.stream("cam0", detect_every_n=2) as s:
        s.add_line("doorway", (0.5, 0.0), (0.5, 1.0))
        s.add_zone("bay", [(0.6, 0.4), (1.0, 0.4), (1.0, 1.0), (0.6, 1.0)],
                   dwell_seconds=10)

        while running:
            ok, frame = cap.read()
            if not ok:
                break

            result = s.process(frame, width=w, height=h,
                               fmt=xcaicx.PixelFormat.BGR8)

            for d in result.detections:
                x, y, bw, bh = d.box.to_pixels(w, h)
                colour = (40, 200, 90) if d.class_name == "person" else (200, 160, 40)
                cv2.rectangle(frame, (x, y), (x + bw, y + bh), colour, 2)
                tag = f"#{d.track_id} {d.class_name} {d.score:.2f}"
                cv2.putText(frame, tag, (x, max(12, y - 6)),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, colour, 1)

            for ev in result.events:
                print(f"{ev.kind.name:16} {ev.zone_name or '-':14} {ev.detail or ''}")

            cv2.imshow("xcaicx", frame)
            if cv2.waitKey(1) == 27:
                break

    print("metered inferences:", eng.license.metered_inferences)

Errors#

ExceptionRaised when
xcaicx.LicenseErrorAny licence-state failure: missing, malformed, bad signature, expired, wrong device, revoked, clock rollback, activation required
xcaicx.EntitlementErrorA requested module is not in the SKU
xcaicx.LimitErrorStream cap, FPS cap or quota exhausted; also queue-full backpressure
xcaicx.XcaicxErrorBase class, carrying .status and the detail string
python
except xcaicx.XcaicxError as exc:
    log.error("xcaicx failed: status=%s detail=%s", exc.status, exc)

.status is the numeric xcaicx_status, so it maps directly onto the status-code table.

Enumerations#

python
xcaicx.Module.DETECT | xcaicx.Module.DEFECT | xcaicx.Module.PPE | xcaicx.Module.ANPR
xcaicx.PixelFormat.RGB8 / BGR8 / GRAY8 / NV12 / I420
xcaicx.EventKind.ZONE_ENTER / ZONE_EXIT / LINE_CROSS / DWELL_EXCEEDED /
                 PPE_VIOLATION / DEFECT_FOUND / PLATE_READ
xcaicx.LicenseState.INVALID / VALID / GRACE / EXPIRED / REVOKED
xcaicx.Backend.AUTO / REFERENCE / ORT_CPU / ORT_CUDA / TENSORRT / OPENVINO / RKNN / HAILO
xcaicx.LogLevel.OFF / ERROR / WARN / INFO / DEBUG

Full signatures in the Python API reference.

Next#

Commercial software. Use requires a valid XCAICX licence token. Questions an integrator cannot answer from this page belong in an email to [email protected] — and, usually, in a fix to this page.

© 2026 AZMX AI · xcaicx.com