Python API

Complete Python API documentation for HORUS. The Python bindings are built with PyO3, exposing the core Rust framework to Python with zero-copy shared memory IPC.

Python Bindings

Full reference for the HORUS Python bindings:

  • Node — Create nodes with tick, init, and shutdown callbacks
  • Topic — Unified pub/sub with typed messages (CmdVel, Pose2D, Imu, etc.)
  • Scheduler — Node execution with priority ordering and rate control
  • Scheduler configuration — tick_rate, rt, deterministic, watchdog_ms, blackbox_mb, recording, name, cores, max_deadline_misses, verbose, telemetry, net keyword arguments
  • TransformFrame / Transform — Coordinate frame management
  • TensorPool / Tensor — Zero-copy shared memory tensors; .numpy() and .torch() share the buffer via the array interface

Custom Messages

Create your own typed messages in Python:

  • Dict topics — no build step: node.send('robot.status', {'battery': 85, 'ok': True}), node.recv('robot.status') returns a dict
  • Compiled messageshorus.msggen.register_message() + build_messages() (runs maturin develop --release, needs a horus_py source checkout). A real Rust type with named fields, but it still travels over the generic MessagePack path, not the zero-copy backend the built-in types use — see Custom Messages
  • YAML schema support for team projects

Async Nodes

Asynchronous Python nodes for non-blocking I/O operations.


Quick Reference

Core Classes

ClassDescription
NodeComputation unit with tick/init/shutdown callbacks
TopicUnified pub/sub channel with typed messages
SchedulerNode execution orchestrator
Scheduler kwargsKeyword-only constructor arguments: tick_rate (default 1000.0), rt, deterministic, watchdog_ms, blackbox_mb, recording, name, cores, max_deadline_misses, verbose, telemetry, net. horus.run() accepts the same set
NodeStateNode lifecycle states
ParamsRuntime key-value configuration — see Params deep dive
RateDrift-compensated pacing for loops you drive yourself — see Rate
driversHardware driver registry: horus.drivers.register_driver() and horus.drivers.load() — see Write a driver

There is no importable horus.NodeInfo or horus.Priority — neither name is part of the public Python API. Runtime context and logging reach you through the node object passed to init / tick / shutdown: node.log_info(), node.log_warning(), node.log_error(), node.log_debug() (these only emit while the scheduler is driving the node). Node(order=...) sets the registration priority, and Node(priority=...) is the SCHED_FIFO real-time priority (1-99, where higher is more urgent).

order does not sequence ticks in Python. Every Python node carries a rate — 30 Hz by default — which makes it real-time and gives it its own thread, so nodes run concurrently regardless of order. It sequences ticks only under deterministic=True.

Runtime Helpers

NameDescription
budget_remaining()Seconds left in this tick's budget — for a node that wants to bail out before overrunning
rng_float()Random float in [0.0, 1.0) from the deterministic per-tick RNG
timestamp_ns()Current time in nanoseconds, for stamping messages

Exceptions

NameRaised when
HorusNotFoundErrorA topic, frame or node is not found
HorusTimeoutErrorA blocking operation times out
HorusTransformErrorA coordinate transform fails (extrapolation, stale data)

Message Types

ClassFieldsDefault Topic
CmdVellinear, angular"cmd_vel"
Pose2Dx, y, theta"pose"
Imuaccel_x/y/z, gyro_x/y/z"imu"
Odometryx, y, theta, linear_velocity, angular_velocity"odom"
LaserScanranges, angle_min/max, range_min/max"scan"

Transform System

ClassDescription
TransformFrameCoordinate frame tree with transform lookups
Transform3D transformation (translation + quaternion rotation)
TransformFrameConfigTransformFrame configuration with size presets

Perception Types

These live in the horus.perception submodule, which must be imported explicitly — import horus alone does not bind it, so horus.perception.Detection raises AttributeError unless you import the submodule first:

import horus.perception as perception       # or: from horus.perception import Detection
ClassDescription
DetectionObject detection result (bbox, class, confidence)
DetectionListCollection of detections with filtering
BoundingBox2D2D bounding box with IoU calculation
PointCloudBufferPoint cloud with NumPy integration
TrackedObjectObject tracking with velocity estimation
Landmark2D landmark/keypoint for pose estimation
COCOPoseCOCO keypoint indices
PointXYZ3D point (x, y, z)
PointXYZRGB3D point with RGB color

Detection, BoundingBox2D, TrackedObject and Landmark each name two different classes. from horus import Detection is the wire message, which is what a topic carries; from horus.perception import Detection is the helper listed above, which is what the analysis methods live on. They are not interchangeable, and the failure reads as a contradiction: horus.Detection is horus.perception.Detection is False, and passing one where the other is expected raises TypeError: 'Detection' object is not an instance of 'Detection'. DetectionList accepts either.

Tensor System

ClassDescription
TensorPoolShared memory tensor pool allocation
TensorZero-copy tensor with NumPy/PyTorch interop

Networking

The Python bindings do not do networking. Both of the APIs that look like they should are inert:

APIWhat actually happens
Topic(msg, endpoint="topic@host:port")The @ and everything after it is discarded; you get an ordinary shared-memory topic named topic. No socket is opened.
Scheduler(net=True) / horus.run(..., net=True)Accepted, then dropped. At run() the wrapper looks for a native replicator hook that the module does not export, emits a RuntimeWarning, and runs without replication.

The net=True warning is a RuntimeWarning, and Python does print it: the default filters restrict only DeprecationWarning to __main__, so a RuntimeWarning raised from library code shows on stderr once per call site with no -W flag needed. The hazard is milder than being silent, but real — one line in a service's log at startup is easy to miss, and the process then runs without replication for as long as it lives. Grep your startup log for network replication is not available if you depend on net=True.

Cross-machine replication is a Rust-side feature: build with --features net or use horus run --net, and let the replicator mirror the shared-memory topics your Python nodes already publish to. See Network Backends.


Installation

pip install "horus-robotics>=0.4.0"

The floor is not optional. The newest horus-robotics on PyPI is 0.1.9, and it speaks an older shared-memory format than a 0.4.x CLI — so a bare pip install horus-robotics gets you a package that imports, runs, and whose nodes publish topics the CLI cannot see. Nothing reports it; horus topic list simply shows nothing. The floor turns that silence into a resolver error.

Until 0.4.x reaches PyPI, install from the tree the HORUS installer already cached — same tag as your CLI, by construction:

pip install ~/.horus/cache/horus@0.4.0/horus_py

horus --version prints the version to substitute. See Python Support for the build-from-source route.

Minimal Example

import horus

def my_tick(node):
    node.send("greeting", "Hello from Python!")
    msg = node.recv("greeting")
    if msg:
        print(msg)

node = horus.Node(
    name="MyNode",
    pubs=["greeting"],
    subs=["greeting"],
    tick=my_tick,
    rate=10
)

horus.run(node)