Inference Runner (executor of S1)
Part of the OpenRAL public-symbol inventory. Hand-curated;
(LNN)markers are refreshed bytools/refresh_methods_linenos.py.
The hardware-side counterpart to openral_sim — closes
WorldState → Skill.step → SafetyClient.check → HAL.send_action at the runner
tick rate. DeployScene is the deploy/workcell YAML used by the ROS deploy
graph; openral_runner remains the library executor behind runtime nodes.
python/runner/src/openral_runner/clock.py
High-precision cadence helpers for the inference runner.
precise_sleep(duration_s: float) -> None— Hybrid sleep:time.sleepfor the bulk, busy-wait onperf_counterfor the final ~1 ms; non-positiveduration_sis a no-op. (L29)sleep_until(deadline_perf_counter_s: float) -> None— Convenience wrapper taking an absolutetime.perf_counterdeadline. Used byInferenceRunnerBase.runto enforce cadence. (L60)- module constant
_BUSY_LOOP_THRESHOLD_S = 1e-3— Busy-wait threshold used byprecise_sleep. (L26)
python/runner/src/openral_runner/protocol.py
Inference runner Protocol. The structural contract every runner shape satisfies.
class InferenceRunner(Protocol)—@runtime_checkableProtocol. (L24)- attr
rate_hz: float— foreground tick rate. activate() -> None— open sensors / HAL / executor. (L38)tick() -> TickResult— run one tick (record), no cadence enforcement here. (L42)run(max_ticks: int | None = None) -> RunResult— rate-limited loop returning the aggregate. (L52)deactivate() -> None— release resources opened byactivate; idempotent. (L62)
python/runner/src/openral_runner/world_cloud_bridge.py
rclpy → OTLP bridge rendering the octomap occupied-voxel cloud (/octomap_point_cloud_centers) as a robot-frame oblique "chase-view" PNG for the dashboard world.pointcloud card. Pure render core is rclpy-free (tested without ROS).
- module constant
WORLD_CLOUD_TOPIC_DEFAULT = "/octomap_point_cloud_centers"— default occupied-voxel-centers PointCloud2 topic (octomap_server). (L48) - module constant
_DEFAULT_PUBLISH_INTERVAL_S = 1.0(L53) — throttle so a busy octomap run doesn't flood the OTLP pipeline. - module constant
_DROP_WARN_BURST = 3(L57) — oversize-cloud warnings emitted verbosely before dropping to a rate-limited cadence. - module constant
_DROP_WARN_RATE = 100(L58) — the 1-in-N rate after_DROP_WARN_BURSTis exceeded. - module constant
_CAM_BACK_M = 2.2(L63) — oblique chase-camera offset behind the robot inbase_link. - module constant
_CAM_UP_M = 1.6(L64) — oblique chase-camera offset above the robot inbase_link. - module constant
_CAM_PITCH_DOWN_RAD = 0.45(L65) — oblique chase-camera downward tilt. - module constant
_FOCAL_PX = 320.0(L66) — chase-camera focal length in pixels, tuned so a ~2 m local box fills a 480x360 frame. - module constant
_MIN_CAM_DEPTH_M = 1e-3(L68) — camera-forward depth below which a point is treated as behind the lens. - module constant
_BG_RGB = (16, 20, 28)(L70) — chase-view canvas background colour. - module constant
_ORIGIN_RGB = (240, 240, 255)(L71) — chase-view origin-marker colour. crop_points_to_box(points, *, xy_m, z_min, z_max) -> NDArray[float32]— keep(N,3)points inside the local box around base_link. (L74)distance_to_rgb(dist_m, *, range_max_m) -> tuple[int,int,int]— near=warm→far=cool color ramp. (L90)encode_world_cloud_png(points_base, *, range_max_m=4.0, image_w=480, image_h=360, xy_m=2.0, z_min=-0.2, z_max=2.0) -> str— crop→oblique-pinhole project→rasterize→base64 PNG. Pure; PIL-only. (L137)world_cloud_span_attributes(*, points_base, frame_id, source_node, range_max_m, xy_m, z_min, z_max) -> dict[str,Any]— assemble theopenral.world_cloud.*span attributes. (L210)class WorldCloudBridge— Subscribes the voxel cloud, TF2-transforms intobase_frame(defaultbase_link; pass the manifest's ownbase_framefor a fixed-base arm, or lookups silently drop every cloud), throttles to 1 Hz, emits aworld.pointcloudspan.latched=Truematches octomap's TRANSIENT_LOCAL topic,Falsematches nvblox's VOLATILE ESDF cloud — mismatched durability receives nothing; an unreadable frame is warned and skipped rather than crashing the executor. (L256)destroy() -> None(L333) — Releases the ROS subscription; safe to call multiple times.
python/runner/src/openral_runner/dataset_recorder_bridge.py
Bus-attached LeRobot/rosbag recorder for the deploy graph (mirrors WorldCloudBridge).
decode_inline_frame(frame: SensorFrame) -> np.ndarray | None(L110) — Decode one aggregatorSensorFramewith inlinedatainto anHxWxCarray whose dtype comes fromframe.encoding(DEPTH16→ uint16 millimetres;BGR8/RGB8/MONO8/RAW→ uint8;JPEG/PNGdecoded with Pillow to RGB, or mono whenchannels == 1).Nonesilently for topic/handle delivery (data is None);Noneplus arunner.frame_skippedwarning (sensor, encoding, reason) for anything it cannot decode — inline bytes on a device-handle encoding (CUDA_*), a corrupt or wrongly-sized compressed image, a raw payload whose byte count is off. Wrapped bydecode_policy_image, which is whatdecode_recordable_images— keeping only(H, W, 3) uint8frames, the contract ofDatasetRecorder.record_frame— and the runner's_decode_image_frames(raisesROSPerceptionStalewhen the skipped sensor feeds a required policy slot) both call. Reading everything as uint8 aborted the first real-hardware OpenArm dispatch (lab Orin, 2026-09-22): the ZED depth sensor's16UC11280x720 frame is two bytes per pixel andreshape(720, 1280, 1)raisedValueError: cannot reshape array of size 1843200.decode_policy_image(frame: SensorFrame) -> np.ndarray | None(L154) —decode_inline_framewith aBGR8frame reversed to RGB (contiguous: torch rejects negative strides); every other encoding, andNone, passes through. The one decoder the skill runner's_decode_image_framesanddecode_recordable_imagesshare, so the bag holds the frame the policy saw: OpenCV readers and the ZED'sbgra8stream deliverBGR8, and the recorder used to write the raw bytes untagged whileopenral dataset from-bagread them back as RGB — every real-camera dataset came out channel-swapped.-
decode_recordable_images(image_frames, sensor_to_slot) -> dict[str, np.ndarray](L187) —WorldState.image_frames→RolloutRecorder.record_frameimages: each frame throughdecode_policy_image, keyed by VLA slot (sensor_name_to_slot, unmapped sensors keep their name), keeping only(H, W, 3) uint8so a depth/mono frame beside them is dropped rather than rejected per frame. Capture resolution (the aggregator's native frame, never the sensor leg's downscaled topic). The one image path forDatasetRecorderBridgeandDeployRunner's attached recorder. -
module constant
_PHASE_START = 0(L68) —Episode.phaseenum value; mirrorspackages/msgs/msg/Episode.msg. - module constant
_PHASE_END = 1(L69) —Episode.phaseenum value; mirrorspackages/msgs/msg/Episode.msg. - module constant
ACTION_TOPIC_DEFAULT = "/openral/candidate_action"(L71) — defaultActionChunktopic. - module constant
EPISODE_TOPIC_DEFAULT = "/openral/episode"(L72) — defaultEpisodemarker topic. class DatasetRecorderBridge(node, *, robot, aggregator, recorder, output_path=None, action_topic="/openral/candidate_action", episode_topic="/openral/episode")— SubscribesEpisode(drivesrecorder.episode_start/end) andActionChunk, joins each tick's action with theWorldStateAggregatorsnapshot, and writes frames viaRosbag2Sink. Logsdataset_recorder.nothing_recordedatdestroy()if no episode marker ever fired, so an empty recording is never silent. (L238)destroy() -> None(L335) — Flushes the pending tick, closes any open episode (marking it a failure), finalizes the recorder, releases the subscriptions; idempotent.
python/runner/src/openral_runner/sensor_reader.py
SensorReader Protocol — seam between per-sensor capture backends and the inference runner.
class SensorReader(Protocol)—@runtime_checkableProtocol; concrete backends live underopenral_runner.backends. (L29)- attr
sensor_id: str— matchesSensorReaderConfig.sensor_id. - attr
is_open: bool— True betweenopen()andclose(). open() -> None— Acquire device, start background workers. Idempotent. (L55)close() -> None— Release device, join workers. Idempotent. (L64)read_latest(max_age_ms: int | None = None) -> SensorFrame— Non-blocking peek at the most recent buffered frame; raisesROSPerceptionStaleif no frame yet or freshest exceeds budget. (L71)
python/runner/src/openral_runner/backends/opencv_thread.py
OpenCVThreadSensorReader — default backend. Mirrors lerobot's per-camera-thread pattern.
- module constant
_COLOR_NDIM = 3— Number of dims for an OpenCV colour frame ((H, W, 3)); mono is(H, W). Used to deriveSensorFrame.channels. (L39) - module constant
_CROP_LEN = 4(L41) — A crop is(x, y, width, height). _validated_crop(sensor_id, crop) -> tuple[int, int, int, int] | None— Normalises a crop (tuple or YAML list) and rejects an invalid length, origin, or extent at construction, before any I/O.class OpenCVThreadSensorReader— Per-camera background-thread reader on top ofcv2.VideoCapture. Importscv2lazily insideopen()(theopencvoptional-extra). (L84)__init__(*, sensor_id, device, fps=30, width=None, height=None, encoding=BGR8, crop=None, default_max_age_ms=100)— Stash config; rejects non-positivefps/default_max_age_msand a malformedcrop.crophandles side-by-side stereo (e.g. a ZED Mini's doubled-width UVC frame): without it the extra width is silently out-of-distribution, not an error, since the shape is still a valid image. Applied in the capture thread at zero hot-path cost (the copy happens later, attobytes())._apply_crop(frame) -> frame | None— Returns the cropped sub-rectangle, orNone(frame dropped, logged) if the device changed mode mid-stream; never raises, so the reader stays recoverable instead of dying silently.open() -> None(L161) — Opens the capture, validates anycropagainst the mode the device actually negotiated, and spawns the reader thread; idempotent. A crop that doesn't fit raises here, so a bad deploy refuses to start rather than run blind.close() -> None— Stop event, join thread (2 s timeout), release capture. Idempotent. (L218)__enter__() / __exit__()— Context-manager sugar; callsopen/close. (L236)read_latest(max_age_ms: int | None = None) -> SensorFrame— Lock-protected snapshot with inlined raw bytes; raisesROSPerceptionStaleif no frame yet or stale,RuntimeErrorif closed. (L247)_read_loop()— Background daemon:cv2.VideoCapture.read→_latest_frame + _latest_stamp_*_nsunder lock; sleeps1/fpson read failure / EOF. (L330)
python/runner/src/openral_runner/backends/ros2_image.py
Ros2ImageSensorReader — backend for a stream a device only publishes over ROS rather than emits over USB directly (e.g. ZED SDK depth, RealSense aligned depth). Subscribes rather than opening a device.
- module constant
_DIRECT_ENCODINGS(L58) — Maps supportedsensor_msgs/Imageencodings to(FrameEncoding, dtype, channels); anything unlisted is refused by name, not misread as pixels. - module constant
_ALPHA_ENCODINGS: Final[dict[str, str]] = {"bgra8": "bgr8", "rgba8": "rgb8"}(L77) — alpha-channel encodings accepted by dropping the alpha byte to their_DIRECT_ENCODINGSbase. - module constant
_FLOAT_DEPTH_ENCODINGS = {"32FC1"}(L82) — float metre depth (what the ZED SDK publishes), converted on the way in. - module constant
_DEPTH16_MAX_MM = 65535(L86) — the uint16-millimetre ceiling ofFrameEncoding.DEPTH16. _depth32f_to_depth16(metres) -> NDArray[uint16](L89) — Converts float32 metres to uint16 millimetres, the format every downstream consumer expects. Non-finite or out-of-range samples become 0 (ROS's "no reading") rather than wrapping to a confident, wrong, near distance.class Ros2ImageSensorReader(L119) — Subscribes to a driver topic, keeps the newest message in a one-slot buffer, serves it through the same non-blockingread_lateststaleness contract as every other backend. Owns no device.__init__(*, sensor_id, topic, default_max_age_ms=100, reliability="best_effort", qos_depth=5, node=None)(L147) — Config only; rejects emptytopicor unknownreliability. Defaults toBEST_EFFORT/VOLATILE/KEEP_LAST=5because aRELIABLEsubscriber receives nothing from aBEST_EFFORTpublisher — a mismatch that looks exactly like a dead camera.open() -> None(L190) — Creates the subscription and, if it owns the node, spins an executor thread. Idempotent; setsis_openlast so a failure mid-setup is still cleaned up.close() -> None(L254) — Destroys the subscription; callsrclpy.shutdown()only if this reader initialised it. Idempotent, guarding per-resource rather than onis_open.read_latest(max_age_ms=None) -> SensorFrame(L303) — Lock-protected snapshot;ROSPerceptionStaleon no-frame-yet or staleness,RuntimeErroron a closed reader. Frames carry inlineddata, not atopicreference._on_image(msg) -> None(L345) — Subscription callback; conversion failures are counted and logged, never raised, since an exception here would kill the spin loop and silently stop the camera._rows(raw, dtype, msg, height, width, channels) -> NDArray(L447) — Unpacks anImagepayload honouringmsg.step(row stride), needed when a publisher hands out pitch-aligned buffers (e.g. Isaac/NITROS) or a cropped ROI. An implausiblestepfalls back to the packed stride, so a wrong value fails loud rather than yielding a skewed image._byte_order(msg) -> str(L488) — HonoursImage.is_bigendian; a 16-bit depth image from a big-endian publisher read little-endian is byte-swapped garbage.
python/runner/src/openral_runner/backends/galaxea_a1_camera_bridge.py
Real-deploy reader for the public A1 Runtime paired-frame bridge. It never opens a camera device or imports the Runtime checkout; the A1 camera monitor stays the only RealSense owner.
- module constant
_PROTOCOL_VERSION = 2— wire version stamped on every request to the camera-bridge session. (L22) - module constant
_SOCKET_NAME = "a1-camera-bridge.sock"— the shared session's Unix-socket name underruntime_socket_path. (L23) - module constant
_MAX_RESPONSE_BYTES = 128 * 1024 * 1024— response-size ceiling passed toRuntimeLocalClient. (L24) - module constant
_SHA256_HEX_LENGTH = 64— expected length of the session'scontract_digest. (L25) - module constant
_COLOR_NDIM = 3— expected rank of a*_color_shapetuple. (L26) - module constant
_COLOR_CHANNELS = 3— expected channel count of a*_color_shapetuple. (L27) class GalaxeaA1CameraBridgeReader(L187) — Reads either thefrontorwristmember of a native versioned Unix-socket session and returns an inline RGB8SensorFrame. The client discovers and pins the Runtime contract digest and camera shapes before reading; stale pairs, wrong shapes, and drift fail explicitly.open() -> None— Acquire the shared Runtime camera connection; idempotent. (L207)close() -> None— Release this view and close the connection after the last owner; idempotent. (L214)read_latest(max_age_ms: int | None = None) -> SensorFrame— Return the latest source-timestamped RGB frame. (L223)
python/runner/src/openral_runner/backends/galaxea_a1_ipc.py
Shared native transport for public A1 Runtime local services.
- module constant
_PACKET_LENGTH = struct.Struct("!I")— the length-prefix codec shared by send/receive. (L16) runtime_socket_path(name) -> Path(L19) — Resolves a private per-user Runtime endpoint fromA1_PROCESS_STATE_ROOTor the standard runtime directory.class RuntimeLocalClient(L32) — Bounded, synchronous length-prefixed MessagePack client with typed connect-time and request-time failures.connect(*, timeout_s: float) -> None— Connect to the Runtime-owned Unix socket. (L50)call(request: dict[str, Any], *, timeout_s: float) -> dict[str, Any]— Perform one bounded request and require an explicit success reply. (L68)close() -> None— Close the local connection idempotently. (L99)encode_array(value) -> dict[str, Any](L106) — Encode one contiguous ndarray with an explicit shape and dtype.decode_array(value, *, shape, dtype, label) -> NDArray(L116) — Decode an exact ndarray payload and reject protocol drift.
python/runner/src/openral_runner/backends/__init__.py
Per-backend SensorReader implementations. Default OpenCVThreadSensorReader is always available; GStreamerSensorReader gates on PyGObject and Ros2ImageSensorReader on a ROS 2 install (both lazy-imported at open()).
OpenCVThreadSensorReader— Lazy-exported via PEP 562 so importing the gstreamer backend doesn't eagerly pull incv2, whose glib init can segfault a subsequentrclpy.Node()in some ROS-enabled processes.__getattr__(name) -> Any— PEP 562 attribute hook; resolvesOpenCVThreadSensorReaderon first access viaimportlib.import_module. (L28)
python/runner/src/openral_runner/backends/gstreamer/pipeline.py
GStreamer pipeline-string builder + platform detection. Pure-Python — does not import gi at module load.
TEE_NAME: Final[str](L56) —"openral_cam_tee". Name of the per-cameratee— the perception-bus attach pointTeeManageruses to add pads for reasoner-activated consumers at runtime.LEAKY_BRANCH_QUEUE: Final[str](L63) —"queue leaky=downstream max-size-buffers=2". The single definition of the per-branch isolation policy, shared by the static builder and the runtimeTeeManager.leaky_branch(elements, *, tee_name=TEE_NAME) -> str(L66) — Returns oneteebranch<tee>. ! <leaky queue> ! <elements>. The shared branch-construction primitive so the static builder and the dynamicTeeManagerbuild branches identically.- module constant
_DEFAULT_APPSINK_NAME: Final[str] = "bh_sink"(L48) — default name attached to the trailing appsink so the reader can look it up viaGst.Bin.get_by_name. - module constant
_GST_INSPECT_TIMEOUT_S: Final[float] = 5.0(L93) — timeout for thegst-inspect-1.0probe. - module constant
_NVVIDEOCONVERT: Final[str] = "nvvideoconvert"(L97) — the DeepStream NVMM colour-convert element. - module constant
_NVVIDCONV: Final[str] = "nvvidconv"(L98) — the Tegra / L4T multimedia-stack NVMM colour-convert element. - module constant
_VIDEOCONVERT: Final[str] = "videoconvert"(L99) — the stock system-memory / CPU colour-convert element. - module constant
_NVVIDCONV_BGR_BRIDGE_FORMAT: Final[str] = "BGRx"(L103) — the closest packed formatnvvidconvadvertises; bridged toBGRviabgr_convert_chain. class PipelineSpec(BaseModel)(L163) — Validated description of a GStreamer ingest pipeline;jpeg(MJPG UVC, USB-only) is exclusive withencoded, andevent_appsink_nameis validated as a legal GStreamer element name.class Platform(str, Enum)(L106) — GStreamer platform tier:TEGRA/NVIDIA_DEEPSTREAM/NVIDIA_DESKTOP/CPU_ONLY.NVIDIA_DEEPSTREAMis the x86ds-onimage, decoding and converting NVMM-native on GPU.class Source(str, Enum)(L139) —USB | CSI | RTSP | FILE | TESTSRC.detect_platform() -> Platform(L268) — Cached; detects the platform viaopenral_core.is_tegra_host()then probinggst-inspect-1.0for DeepStream/nvcodec elements.inspect_element_present(element_name) -> bool(L303) — Genericgst-inspect-1.0 --existsprobe with timeout.nvmm_convert_element() -> str | None(L333) — Probes for the host's NVMM colour-convert element:nvvideoconvert(DeepStream/x86) preferred, elsenvvidconv(Tegra/L4T), elseNone.bgr_convert_chain(convert) -> str(L353) — Bridges a converter element to system-memoryBGR: passthrough fornvvideoconvert/videoconvert, butnvvidconvneeds aBGRx→videoconvertbridge since pinningformat=BGRon it fails to link on a plain-L4T Jetson.ensure_appsink_name(pipeline, name) -> str(L397) — Rewrites a trailingappsinkto carryname=<name>.build_pipeline_string(spec, platform=None) -> str(L449) — Builds the pipeline string, adding ateeleg per enabledros/eventbranch vialeaky_branchso a stalled observability or detector branch never backpressures the policy leg._build_event_tee_branch(spec, platform) -> str(L722) — Returns the event leg of thetee: lifts NVMM to system memory, pinsformat=BGR, rate-caps viavideoratetoevent_rate_hz, terminates inappsink name=event_sink._build_ros_tee_branch(spec, platform) -> str(L706) — Returns the observability leg (system memory BGRappsink name=ros_sink)._platform_convert_element(platform) -> str(L609) — The bare colour-convert element for a platform:nvvidconv(Tegra) /nvvideoconvert(DeepStream) /videoconvert(else). Shared by the policy leg and the tee legs._use_nvmm(spec, platform) -> bool(L633) — Single definition of "does the policy leg negotiatememory:NVMM", shared by_build_convertand_build_capsso converter and caps can never disagree._build_convert(spec, platform) -> str(L644) — The policy leg's conversion stage: the bare element on the NVMM path (NV12/RGBAare on its own src template),bgr_convert_chain(...)on the system-memoryBGRpath._lift_convert(platform) -> str(L747) — The chain a tee leg uses to lift NVMM → system-memory BGR:_platform_convert_elementpassed throughbgr_convert_chain, so Tegra legs bridge viaBGRxwhile DeepStream legs stay direct-to-BGR.
python/runner/src/openral_runner/backends/gstreamer/reader.py
GStreamer-backed SensorReader (CPU appsink path + NVMM zero-copy path). Mirrors the latest-only contract of OpenCVThreadSensorReader. Gst.init runs immediately after the gi import, before anything else, to avoid a Fast-DDS/GStreamer thread-init SIGSEGV.
- module constant
_BUS_POLL_TIMEOUT_NS: Final[int] = 100_000_000(L71) — bus poll timeout (100 ms) when listening for ERROR / EOS. - module constant
_DEFAULT_MAX_AGE_MS: Final[int] = 100(L74) — default staleness budget; matches the OpenCV reader default. - module constant
_GST_FORMAT_TO_ENCODING: Final[dict[str, FrameEncoding]](L80) — maps GStreamer capsformat=...(BGR/RGB/GRAY8) toFrameEncodingfor the CPU path; NV12 is deliberately absent (handled by the NVMM path instead). class GStreamerSensorReader(L87) —SensorReaderbacked by a GStreamer pipeline; construct from an explicitpipeline=string or a generatedspec=(PipelineSpec). CPU path deliversSensorFrame(data=bytes); NVMM/CUDA zero-copy populateshandle+encoding∈{CUDA_NV12, CUDA_RGBA}.ros_topic/ros_rate_hz/ros_frame_id/ros_camera_infoconfigure the optional ROS tee (RosImagePublisher).open() -> None(L210) — Initialise GStreamer, parse the pipeline, start the ROS tee (if enabled) and the bus-drain thread, transition to PLAYING. Idempotent.close() -> None(L316) — Stop the ROS publisher (if any), tear down the pipeline, join the bus thread, release the latched frame/handle. Idempotent.__enter__() / __exit__()(L348) — Context-manager sugar; callsopen/close.read_latest(max_age_ms: int | None = None) -> SensorFrame(L364) — Non-blocking snapshot of the latched frame; raisesROSRuntimeErroron a bus-reported error,ROSPerceptionStaleon no-frame-yet or staleness,RuntimeErroron a closed reader._start_ros_publisher()(L276) — Look up theros_sinkappsink and start aRosImagePublisher; tears the pipeline back down with an actionableROSConfigErrorifrclpyis unavailable._on_new_sample(appsink) -> int(L445) — Streaming-thread callback; branches onmemory:NVMMcaps features to the zero-copy or CPU handler._handle_cpu_buffer(buffer, structure) -> int(L474) — Map → copy → latchdata; unsupported format latches a bus error._handle_nvmm_buffer(buffer, structure) -> int(L516) — Map → wrap as anNvBufSurfaceHandle(lazyopenral_pro_trt.nvbufsurfaceimport) → DtoD-mirror into a reader-ownedStableSurfaceMirror→ latchhandle; an absent/unloadable NVMM backend latches a bus error rather than silently falling back._bus_loop()(L623) — Background thread draining the GStreamer bus for ERROR / EOS._teardown_pipeline()(L655) — Drop the pipeline and join the bus thread; shared byclose()andopen()'s rollback path._wrap_in_pipeline(element) -> Gst.Pipeline(L667) [@staticmethod] — Wraps a bareGst.ElementfromGst.parse_launchin aPipelinebin (single-element strings only).- module constant
_GST_INIT_LOCK(L680) — one-shot-init guard lock. - module constant
_GST_INITIALISED(L681) — one-shot-init guard flag. _ensure_gst_initialised() -> None(L684) — CallsGst.initexactly once per process, thread-safely.
python/runner/src/openral_runner/backends/gstreamer/ros_tee.py
ROS 2 image-publisher tee for GStreamerSensorReader. Republishes the ros_sink appsink branch as sensor_msgs/Image on a configurable topic, independently rate-limited from the inference loop, plus — when manifest intrinsics are supplied — a companion sensor_msgs/CameraInfo on the sibling topic built by openral_sensors.ros_publisher.build_camera_info_msg. rclpy is lazy-imported inside start() so the module is import-safe without a sourced ROS env.
- module constant
_DEFAULT_QOS_DEPTH: Final[int] = 5(L52) — default QoS depth for the image publisher (mirrors gscam2's shallow,BEST_EFFORT-friendly default). class RosImagePublisher(L55) —__init__(*, sensor_id, appsink, topic, rate_hz=None, node_name=None, qos_depth=_DEFAULT_QOS_DEPTH, frame_id=None, camera_info=None)—frame_id(defaultsensor_id) stamps both headers;camera_infoenables theCameraInfocompanion oncamera_info_topic_for(topic). Validatestopicis absolute andrate_hzis positive orNone; no ROS I/O untilstart.is_started[@property] (L128) —Truebetweenstartandstop.start() -> None(L132) — Initialise rclpy (if needed), create thesensor_msgs/Imagepublisher (BEST_EFFORT+VOLATILE+KEEP_LAST), plus aCameraInfopublisher (RELIABLE+VOLATILE+KEEP_LAST=1) whencamera_infois set, hook the appsink; raisesRuntimeErrorifrclpyis unavailable.stop() -> None(L206) — Disconnect the signal, destroy the publisher, shut down rclpy if this instance initialised it. Idempotent._on_new_sample(appsink) -> int(L244) — Rate-gate → map → buildsensor_msgs/Image→ publish, then the matchingCameraInfo(frame width/height, same stamp) when enabled._claim_rate_slot() -> bool(L313) — Monotonic-clock token gate enforcingrate_hz._extract_image_payload(appsink, gst) -> tuple[bytes, int, int, str] | None(L328) — Pull the latest sample;Noneon malformed sample / unsupported format / map failure._gst_format_to_ros_encoding(gst_format) -> str | None(L370) — Maps a GStreamer capsformat(BGR/RGB/GRAY8) to a ROSImage.encoding(bgr8/rgb8/mono8).
python/runner/src/openral_runner/backends/gstreamer/perception_tee.py
Perception event tee for GStreamerSensorReader. Pulls frames from the event leg's appsink, runs EventDetectors, publishes openral_msgs/PromptStamped on /openral/perception/<kind>. rclpy lazy-imported in start() so the module stays import-safe on hosts without a sourced ROS env.
- module constant
TOPIC_PREFIX: Final[str] = "/openral/perception"(L56) — Fixed value; full topic isf"{TOPIC_PREFIX}/{detector.kind}". - module constant
_DEFAULT_QOS_DEPTH: Final[int] = 10(L61) — default QoS depth for the per-kindPromptStampedpublisher (BEST_EFFORT+VOLATILE+KEEP_LAST). - module constant
_DEFAULT_RATE_HZ: Final[float] = 5.0(L66) — default per-detector token-bucket rate cap. class EventDetector(Protocol)(L69) —kind: str.detect(frame_bgr, width, height, sensor_id) -> PerceptionEventMetadata | None(L88) — Run one detection pass;Nonemeans no event this frame.summarise(metadata) -> str(L97) — Human-readablePromptStamped.textformetadata.class MotionDetector(L101) — Pure-Python frame-diff motion detector over a BGR appsink (BT.601 luma, mean abs delta). Numpy lazy-imported indetect.__init__(*, threshold=0.02, downsample=1).detect(frame_bgr, width, height, sensor_id) -> PerceptionEventMetadata | None(L139) — Mean abs luma delta vs the previous frame; emitsMotionMetadataon threshold cross, with a tight axis-aligned bbox around moving pixels.summarise(metadata) -> str(L195) — One-line motion-event summary.class SceneChangeDetector(L209) — Grayscale-histogram scene-change detector (chisqr_altdistance, 32 bins).__init__(*, threshold=0.5).detect(frame_bgr, width, height, sensor_id) -> PerceptionEventMetadata | None(L243) — Chi-square-alt distance between consecutive 32-bin grayscale histograms; emitsSceneChangeMetadataon threshold cross.summarise(metadata) -> str(L286) — One-line scene-change-event summary.class _TokenBucket(L296) — Per-(sensor, kind)rate-limit primitive; mirrorsopenral_observability.failure_bus._TokenBucketbut independently implemented to keep the runner free of an observability-package dep.class PerceptionEventPublisher(L328) — Owns one event-sink appsink for one sensor; fans out to onePublisherper detector kind. Constructor enforces uniquekinds, absolutetopic_prefix, positiverate_hz. QoS:BEST_EFFORT + VOLATILE + KEEP_LAST=10.is_started[@property] (L418) —Truebetweenstartandstop.dropped_counts[@property] (L423) — Per-kind count of detections suppressed by the rate-limit gate.start() -> None(L427) — Initialise rclpy (if needed), create one publisher per detector kind, hook the appsink.stop() -> None(L482) — Disconnect the signal, destroy publishers, shut down rclpy (if this instance owns it). Idempotent.
python/runner/src/openral_runner/backends/gstreamer/tee_manager.py
Runtime tee-branch manager for the GStreamer perception bus. Attaches / detaches consumer branches on a running pipeline's named tee (pipeline.TEE_NAME) via dynamic pad add/remove — the mechanism the S2 reasoner drives through ExecuteRskill. Imports gi at load (requires the gstreamer extra).
- module constant
_REQUEST_PAD_TEMPLATE: Final[str] = "src_%u"(L56) — tee request-pad template name passed torequest_pad_simple. - module constant
_DETACH_TIMEOUT_S: Final[float] = 5.0(L61) — how longdetachwaits for the IDLE probe before raising (pipeline presumed stalled). class BranchHandle(L65) — Opaque dataclass handle to an attached branch (name+ the privateteepad / branch bin); returned byattach, passed back todetach.class TeeManager(L81) —__init__(pipeline, *, tee_name=TEE_NAME)— raisesROSConfigErrorif the tee is absent.branch_count[@property] (L115) — Number of currently attached branches.attach(elements, *, name) -> BranchHandle(L120) — requests a tee pad, parsesLEAKY_BRANCH_QUEUE ! <elements>into a bin, links + syncs it live; rolls back on link failure.detach(handle) -> None(L190) — IDLE-probe unlink + release-pad + NULL teardown; idempotent, blocks until removed (bounded by_DETACH_TIMEOUT_S).
python/runner/src/openral_runner/backends/gstreamer/objects_detector.py
CPU-tier object detector for the perception event tee. Implements EventDetector via ONNXRuntime on system-memory BGR frames (RT-DETR / D-FINE ONNX signature). onnxruntime lazy-imported at construction time. Zero-copy NVMM tiers are a planned follow-up; requesting them raises ROSConfigError.
- module constant
_DETECTOR_TIERS_GROUP = "openral.detector_tiers"(L74) — entry-point group name theNVMM_AGGREGATORtier resolves against (registered by the privateopenral-pro-trtpackage). class DetectorTier(str, Enum)(L80) — Execution tier for the object detector:CPU_ONNX,NVINFER,NVMM_AGGREGATOR,VLM_SIDECAR(out-of-process open-vocab VLM),ZEROSHOT_HF(in-process Transformers zero-shot over a fixed vocabulary). The latter two reuse theCPU_ONNXBGR appsink branch.select_detector_tier(platform=None) -> DetectorTier(L125) — Probesgst-inspect-1.0 nvinfer(→NVINFER), then checks forPlatform.TEGRA(→NVMM_AGGREGATOR), elseCPU_ONNX.nvinferprobe always wins over explicitplatform.identify_rtdetr_outputs(named_shapes: list[tuple[str, tuple[Any, ...]]]) -> tuple[str, str](L213) — Tier-agnostic helper: from a list of(name, shape)output pairs, returns(logits_name, boxes_name). Among 3-D outputs, the one with last-dim==4 is boxes; if both (or neither) end in 4, falls back to index order (0=logits, 1=boxes). RaisesROSConfigErrorif fewer than two 3-D outputs are present.postprocess_rtdetr(logits, boxes, *, labels, model_id, sensor_id, score_threshold, frame_width, frame_height) -> ObjectsMetadata | None(L251) — Tier-agnostic RT-DETR decode: sigmoid→argmax→threshold, cxcywh→xyxy pixels, degenerate-bbox guard, sorts descending by confidence, returnsNoneon zero survivors.class ObjectsDetector(L350) —EventDetectorimplementation.__init__(onnx_path, *, labels, model_id, input_size=(640,640), score_threshold=0.5, device="cpu"). Delegates logits/boxes identification toidentify_rtdetr_outputs.detect(frame_bgr, width, height, sensor_id) -> ObjectsMetadata | None(L456) — BGR→RGB, NN-resize, float32/255, NCHW, ORT inference, delegates postprocessing topostprocess_rtdetr.summarise(metadata) -> str(L512) — Aggregates label counts as"Nx label"string.make_objects_detector(onnx_path, *, labels, model_id, tier=None, **kwargs) -> ObjectsDetector | object(L553) — Resolves a detector by tier (auto-selected viaselect_detector_tierwhen unset):CPU_ONNX→ObjectsDetector;NVMM_AGGREGATOR→ theopenral.detector_tiersentry point (privateopenral-pro-trt);NVINFERand unknown tiers raiseROSConfigError.
Moved to OpenRAL Pro: the NVMM zero-copy consumers —
nvbufsurface.py,cuda_context.py,trt_nvmm.py(TrtNvmmExecutor),nvmm_detector.py(NvmmObjectsDetector),nvmm_vision_encoder.py,act_nvmm.py— now live in the privateopenral-pro-trtpackage asopenral_pro_trt.*; theNVMM_AGGREGATORtier resolves via theopenral.detector_tiersentry-point group. The open perception bus (pipeline.py,reader.py,tee_manager.py, CPU/VLM detector tiers) is unchanged.
python/runner/src/openral_runner/backends/gstreamer/detector_factory.py
gi-free dispatch seam so the manifest→detector-backend selection is unit-testable without a live pipeline. DetectorRunner delegates construction here. No gi/onnxruntime/zmq/torch at import; the pytorch and zeroshot_hf branches lazy-import their detector classes.
weights_source_from_manifest(manifest) -> str(L97) — Resolves the HF repo the backend loads: prefers anhf://weights_uri, falls back tosource_repo, elsenvidia/LocateAnything-3B; strips only thehf://scheme and keeps any@revision. RaisesROSConfigErrorwhen atrust_remote_codemanifest does not pin an@<sha>revision.build_manifest_detector(manifest, *, onnx_path=None, tier=None) -> tuple[Any, DetectorTier](L125) — Dispatches onmanifest.detector.engine(thenmanifest.runtime) to the matching backend — thezeroshot_hf/pytorchsidecar/VLM tiers need noonnx_path,onnx/tensorrtdo. RaisesROSConfigErrorfor a non-detector manifest or a missingonnx_pathwhen required.class DetectorNodeWiring(frozen dataclass) (L50) —run_continuous_leg: bool,serve_on_demand: bool; the perception node's wiring decision, pure and rclpy-free.detector_node_wiring(mode: DetectorMode) -> DetectorNodeWiring(L69) — pure (rclpy-free, unit-testable) policy the perception node consumes:continuous→run_continuous_leg=True, serve_on_demand=False(publish leg, no query service);on_demand→run_continuous_leg=False, serve_on_demand=True(locate_in_view service +detector_querytopic, no continuous publishing).
python/runner/src/openral_runner/backends/gstreamer/omdet_turbo_detector.py
In-process Transformers open-vocabulary detector — omlab/omdet-turbo-swin-tiny-hf (Apache-2.0). One backend serves both detector modes (declared by the manifest's detector.mode): continuous (fixed labels, unprompted) or on_demand (prompted via set_query/detect_with_query). Same interface as ObjectsDetector, so it reuses the CPU BGR appsink branch; loads under the runtime's own transformers>=5, no sidecar.
- module constant
_MAX_AREA_FRAC = 0.98(L55) — degenerate-box guard: drops detections covering ≥98% of the frame. build_objects_metadata_from_results(*, labels, scores, boxes_xyxy, width, height, model_id, sensor_id, score_threshold) -> ObjectsMetadata | None(L58) — Pure (no torch): from decoded per-detectionlabels/scores/pixelboxes_xyxy, drops sub-threshold + degenerate/near-full-image (≥98%) boxes, clips + corner-orders to frame, sorts descending by confidence;Noneon zero survivors. RaisesROSConfigErroron length mismatch.query_to_classes(query) -> list[str](L143) — Pure: parse a free-text on-demand query into OmDet's multi-label class list (split on commas /</c>; a single phrase is one class; whitespace dropped). RaisesROSConfigErrorif empty.class OmDetTurboDetector(L175) —__init__(*, labels, model_id, weights_source, score_threshold=0.3, nms_threshold=0.5, device="auto")— stores config; model/processor load deferred to firstdetect()(lazy, side-effect-free;device="auto"→ CUDA when available else CPU).set_query(text) -> None(L298) — Retarget the persistent vocabulary (thedetector_querytopic; on-demand), parsed viaquery_to_classes.detect(frame_bgr, width, height, sensor_id) -> ObjectsMetadata | None(L310) — Detect over the current (persistent) vocabulary.detect_with_query(frame_bgr, width, height, sensor_id, query) -> ObjectsMetadata | None(L327) — One-shot detect forqueryWITHOUT mutating the persistent vocabulary (the read-onlylocate_in_viewservice). Bothdetectanddetect_with_querydelegate to_detect_classes(BGR→RGB PIL, processor over the class list,model(**inputs)underno_grad,post_process_grounded_object_detection, →build_objects_metadata_from_results).close() -> None(L383) — Releases the model +cuda.empty_cache()if loaded on GPU; idempotent.
python/runner/src/openral_runner/backends/gstreamer/segmenter_factory.py
gi-free dispatch seam for kind: segmenter rSkills — the sibling of detector_factory.py. Dispatch keys on the required manifest.segmenter.engine (no legacy runtime-keyed fallback for this kind). Reuses weights_source_from_manifest from the detector factory rather than reimplementing hf:// stripping.
build_manifest_segmenter(manifest, *, device="auto") -> Sam2Segmenter(L28) —engine: sam2_hf→Sam2Segmenterconfigured from the contract'smax_prompt_points/multimask/min_mask_area_px. RaisesROSConfigErrorif the manifest is not akind:segmenterwith a segmenter block, or names an engine with no backend.deviceis host posture, not manifest contract (the same weights run on either):"auto"picks CUDA when available, and an explicit"cpu"is the honest setting on a pre-sm_70 dev GPU where the CUDA wheels ship no kernels for the card andtorch.cuda.is_available()alone would mislead the auto path.segmenter_nodeforwards itsdeviceparameter here.
python/runner/src/openral_runner/backends/gstreamer/sam2_segmenter.py
In-process Transformers SAM 2.1 promptable segmenter — facebook/sam2.1-hiera-small (Apache-2.0), the backend for engine: sam2_hf. Answers a geometric prompt (a point) with binary masks, where a detector answers a semantic one (a label) with scored boxes — no label vocabulary, no thresholdable confidence. Held resident and warmed at node activate, since a cold call would not fit inside the HAL's deferred-ack barrier.
- module constant
_DEPTH_EPS = 1e-6(L65) — a point closer than this to the camera's optical plane has no meaningful projection. class MaskCandidate(L69) — Frozen dataclass: one mask hypothesis —mask((H, W)bool at the source frame's resolution),score_advisory(the model's own IoU estimate, advisory only, never thresholded),area_px.project_point_to_pixel(point_xyz, intrinsics) -> tuple[float, float] | None(L92) — Pure: project a camera optical-frame point (REP-103) to continuous pixels.Nonewhen behind/on the optical plane or outside the image — the caller then has no usable prompt rather than a guessed one. This is where intrinsics are applied; theSegmentInViewprompt crosses the HAL boundary as a 3-D point precisely so they stay on this side.build_mask_candidates(masks, *, scores, min_mask_area_px) -> list[MaskCandidate](L340) — Pure (no torch): drops masks belowmin_mask_area_pxas degenerate and sorts survivors by area ascending (SAM 2's nested subpart → part → whole), never by score. RaisesROSConfigErroron masks/scores length mismatch.class Sam2Segmenter(L133) —__init__(*, model_id, weights_source, max_prompt_points=8, multimask=True, min_mask_area_px=64, device="auto")— stores config; theSam2Processor+Sam2Modelload is deferred to first use (bf16 on CUDA, fp32 on CPU).model_id[@property] (L191) — Identifier recorded alongside every emitted mask.warm_up(*, width=320, height=240) -> None(L222) — Load and burn one forward pass at node activate, moving the ~742 ms cold call off the first real attach event; not optional.segment(frame_bgr, width, height, *, positive_points, negative_points=()) -> list[MaskCandidate](L246) — One BGR frame plus pixel prompts (positive at the TCP, negative typically at the jaw tips) → surviving candidates, area ascending. Returns[]on a caps-mismatched buffer; raisesROSConfigErrorwith no positive point or overmax_prompt_points,ROSRuntimeErrorwithout the wheels.close() -> None(L331) — Releases the model +cuda.empty_cache()if loaded on GPU.
Candidates are handed to the consumer rather than resolved here by score, because only geometry can pick between them: a mis-aimed point prompt on a real frame returned a mask covering 59.8% of the image at this model's top score of 0.977.
python/runner/src/openral_runner/backends/gstreamer/_zmq_sidecar.py
Private. Shared ZMQ REQ/REP transport mixin for the two sidecar-client backends below — _connect/_rpc/_try_ping were byte-identical between LocateAnythingDetector and QwenSceneVlm; consolidated here (see docs/methods/14-duplication-watch.md). _spawn_and_wait, _ensure_ready and close stay per-class — they genuinely differ (CLI args, error messages, ports).
class ZmqSidecarMixin(L17) — Shared transport mixin:_connect()opens a freshzmq.REQsocket totcp://{host}:{port}._rpc()does a msgpack round-trip and recreates the socket on a timeout (REQ/REP can't recover from a missed reply);_try_ping()is a best-effort liveness check.
python/runner/src/openral_runner/backends/gstreamer/locateanything_detector.py
Open-vocabulary detector backend for the LocateAnything-3B sidecar. Same interface as ObjectsDetector, so it reuses the CPU BGR appsink branch. Connects lazily on first detect() and auto-spawns the sidecar (ping → Popen → poll → close); the model runs in an isolated transformers==4.57.1 venv, this module is the ZMQ/msgpack client.
- module constant
_TOKEN_RE(L45) — compiled regex matching<ref>label</ref>or a 4-coord<box>token (point boxes are ignored). - module constant
_MIN_SIDE_FRAC = 0.02(L50) — drops boxes thinner than 2% of the image in either axis (degenerate-box guard). - module constant
_MAX_AREA_FRAC = 0.85(L51) — drops boxes covering more than 85% of the image (degenerate-box guard). parse_grounding_answer(answer, *, fallback_label="object", norm=1000) -> list[tuple[str, tuple[int,int,int,int]]](L54) — Parses<ref>label</ref>+<box>tokens in document order, binding each box to the most recent ref; coords stay normalized[0,norm]. Drops exact duplicates and degenerate boxes — the repeated-box tail a looping decode emits.build_objects_metadata(answer, *, width, height, model_id, sensor_id, fallback_label="object", norm=1000) -> ObjectsMetadata | None(L96) — Scalesparse_grounding_answerboxes intowidth×heightpixels (clipped), buildsObjectDetection2Datconfidence=1.0(a grounding model has no per-box score);Noneif no valid detections.class LocateAnythingDetector(ZmqSidecarMixin)(L150) —__init__(*, labels, model_id, weights_source="nvidia/LocateAnything-3B", host="127.0.0.1", port=5757, query=None, auto_spawn=True, boot_timeout_s=1200.0, request_timeout_s=180.0, max_side=1024, max_new_tokens=1024, mode="hybrid")— stores config; static defaultquery = "</c>".join(labels); no connection (lazy).set_query(text) -> None(L262) — Runtime open-vocab override for the continuous leg.detect(frame_bgr, width, height, sensor_id) -> ObjectsMetadata | None(L268) — One-shot detect of the persistent query (delegates todetect_with_query).detect_with_query(frame_bgr, width, height, sensor_id, query) -> ObjectsMetadata | None(L274) — One-shot detect forqueryWITHOUT mutating the persistent query; used by thelocate_in_viewservice so an on-demand reasoner query doesn't change what the continuous leg grounds.close() -> None(L314) — Closes the socket and terminates the sidecar if spawned; idempotent.
python/runner/src/openral_runner/backends/gstreamer/qwen_scene_vlm.py
Scene-VLM backend backed by the Qwen3.5-4B sidecar — the scene-reasoning counterpart of LocateAnythingDetector. Returns text, not ObjectsMetadata (a reasoning aid for task-progress / success verification, not a localizer). Same ZMQ lifecycle (lazy connect, auto-spawn, teardown only the child), transport shared via ZmqSidecarMixin. No zmq/numpy/PIL at import (all lazy).
class QwenSceneVlm(ZmqSidecarMixin)(L47) —__init__(*, model_id, weights_source="Qwen/Qwen3.5-4B", host="127.0.0.1", port=5759, auto_spawn=True, boot_timeout_s=1200.0, request_timeout_s=180.0, max_side=1024, max_new_tokens=256)— stores config; no connection (lazy).query(frame_bgr, width, height, question) -> str(L143) — Encode BGR→PNG, RPC{"op":"query",...}, return the whitespace-stripped answer; raisesROSConfigErroron empty question or sidecar error.close() -> None(L184) — Closes the socket + terminates the spawned sidecar; idempotent.build_scene_vlm(manifest, *, host="127.0.0.1", port=5759) -> QwenSceneVlm(L200) — build from akind:"vlm"manifest;model_id=manifest.name,weights_sourcefromweights_uri(the deployable pre-quant checkpoint) stripped ofhf:///@rev. RaisesROSConfigErrorifmanifest.kind != "vlm". Lazy.
python/runner/src/openral_runner/backends/reward/frame_source.py
Transport-agnostic rolling frame buffer for the reward monitor. Pure Python + stdlib only (no numpy/torch), so it unit-tests without ROS, torch, or a GPU.
- module constant
_NS_PER_S = 1_000_000_000(L20) — nanosecond/second conversion factor. class Frame(frozen dataclass) (L24) — one buffered camera frame:stamp_ns: int,bgr: bytes,width: int,height: int.class RollingFrameBuffer(L40) —__init__(*, window_s, max_frames=256, stale_after_s=3.0)— transport-agnostic node-side ring of recent frames (sim + real).push(frame) -> None(L79) — Append + evict frames older thanwindow_srelative to the newest / overmax_frames.window(seconds) -> list[Frame](L88) — Frames within the lastseconds(capped towindow_s).is_stale(now_ns) -> bool(L99) — True if no fresh frame withinstale_after_s.__len__() -> int(L105) — Number of frames currently buffered.- module constant
_MIN_POINTS_FOR_SLOPE = 2(L111) — a least-squares slope needs at least two points. trend(series: list[float]) -> float(L114) — least-squares slope per sample (0.0 for < 2 points); used for progress/success trend +stalled.- module constant
_STALL_TREND_EPS = 0.002(L133) — |progress trend per sample| below this reads as "stalled"; shared byRobometerInProcessReward.assessandTOPRewardMonitor.assess. assess_from_score(progress, success, *, success_threshold, frames_seen) -> dict(L136) — Sharedassess()dict builder, consolidated out ofRobometerInProcessReward.assessandTOPRewardMonitor.assess(seedocs/methods/14-duplication-watch.md).
python/runner/src/openral_runner/backends/reward/robometer_reward.py
Robometer reward-monitor backend — loads lerobot 0.6.0's native Robometer model inside reward_monitor_node and scores clips on demand. Nothing here imports torch / transformers / numpy at module load.
critic_score_from_assessment(assessment, *, threshold) -> tuple[float, float](L37) — Pure mapping from a reward assessment result to a genericopenral_msgs/CriticScore(score, threshold): usesprogress_now(higher-is-better) as the score, clamped to[0, 1], defaulting a missing/non-numeric/bool value to0.0. Letsreward_monitor_nodefeed the Tier-C critic producer. Pure, ROS-free, unit-tested.class RobometerInProcessReward(L147) —__init__(*, model_id, weights_source="OpenRAL/rskill-robometer_4b-any-general-nf4", num_bins=100, success_threshold=0.5, max_frames=8, device="cuda")— default Robometer backend forreward_monitor_node: lazily importstools/_robometer_scorer.py::_Scorer, meta-loads the prequantized NF4 checkpoint in the reward-monitor process, and scores BGR frames via the same native lerobot 0.6.0_compute_rbm_logits+decode_progress_outputspath.score(frames, task) -> (progress, success)(L179) — Validates empty task/clip + frame sizes, evenly subsamples tomax_frames, converts BGR→RGB, returns per-frame normalized arrays.assess(frames, task) -> dict(L191) — Scoresframesand summarizes the window for the Reasoner; mirrors the reasoner contract.close() -> None(L200) — Releases the model +cuda.empty_cache()if loaded on GPU.build_reward_monitor(manifest) -> RobometerInProcessReward | TOPRewardMonitor(L209) — build from akind:"reward"manifest; dispatchesreward.backend=="topreward"toTOPRewardMonitorand defaults Robometer toRobometerInProcessReward. RaisesROSConfigErrorifmanifest.kind != "reward". Lazy.
python/runner/src/openral_runner/backends/reward/topreward_reward.py
In-process TOPReward (arXiv 2602.19313) reward monitor — a zero-shot reward reading P("True" | video, instruction) from an off-the-shelf Qwen3-VL VLM, no fine-tuned checkpoint and no ZMQ sidecar. Nothing here imports torch / transformers at module load.
class TOPRewardMonitor(L35) —__init__(*, model_id, weights_source, success_threshold=0.8, max_frames=8, num_samples=6, fps=2.0, device="cuda")— stores config; the VLM is loaded lazily on firstscore.score(frames, task) -> (progress, success)(L132) — Prefix-sweep: scoresframes[:L]atnum_samplesanchor lengths, min-max normalizes raw log-probs within the queried window, then interpolates to one value per frame.successmirrorsprogress(TOPReward has a single head).assess(frames, task) -> dict(L182) — Scoresframesand summarizes the window for the Reasoner; same keys asRobometerInProcessReward.assess.close() -> None(L195) — Releases the in-process model + frees CUDA memory.build_topreward_monitor(manifest, *, device="cuda") -> TOPRewardMonitor(L210) — Build aTOPRewardMonitorfrom areward.backend == "topreward"manifest (weights source, success threshold, target fps). RaisesROSConfigErroron a non-reward manifest.
python/runner/src/openral_runner/backends/gstreamer/detector_runner.py
Runtime glue that wires a kind: detector rSkill to a live camera pipeline — loads the DetectorContract, delegates backend construction to build_manifest_detector (ONNX CPU/NVMM tiers or the VLM_SIDECAR open-vocab tier), attaches the appropriate branch to the bus tee via TeeManager, and fires the on_detection callback for each non-None ObjectsMetadata. Imports gi + DetectorTier/build_manifest_detector + nvmm_convert_element eagerly at load.
class DetectorRunner(L59) —__init__(pipeline, manifest, *, onnx_path=None, sensor_id, on_detection, tee_name=TEE_NAME, tier=None)— validatesmanifest.kind == "detector", caches NVMM caps dims fromDetectorContract.input_size, delegates backend construction tobuild_manifest_detector, and creates aTeeManager.start() -> None(L156) — Attaches the tee branch matching the tier:NVMM_AGGREGATORgets the NVMM RGBA appsink, every other tier a BGR appsink; raisesROSRuntimeErrorif the appsink isn't found after attach._on_sample_bgr(appsink) -> int(L220) — pulls BGR sample, format assert, buffer.map/unmap, callsdetector.detect, fireson_detectionon non-None; errors guarded._on_sample_nvmm(appsink) -> int(L279) — pulls NVMM sample,wrap_buffer(lazy import from the privateopenral_pro_trt.nvbufsurface), calls the entry-point-resolved NVMM detector'sdetect_nvmm, fireson_detection; always unmaps; errors guarded.stop() -> None(L336) — disconnects signal + detaches branch + callsdetector.close()if present; idempotent.
python/runner/src/openral_runner/__init__.py
Public surface of the inference runner. Imports are PEP 562 lazy: heavy symbols (InferenceRunnerBase, factory.*, DeployRunner, safety.*) are resolved on first attribute access so importing any subpackage does not eagerly drag in torch or trigger downstream glib conflicts.
- light eager imports:
precise_sleep,sleep_until,InferenceRunner(Protocol),SensorReader(Protocol). _LAZY_ATTRS: dict[str, tuple[str, str]]—attr → (module, name)map driving the__getattr__resolver. (L70)__getattr__(name) -> Any— Resolves heavy symbols on first access (torch / glib-sensitive deferral). (L84)
python/runner/src/openral_runner/factory.py
Library deploy runner used by runtime nodes; the public deploy CLI now shells the ROS graph from a DeployScene.
SKILL_REGISTRY: dict[str, Callable[[dict[str, object]], rSkillBase]]—vla.id→ skill factory. Today:hello,gpu_passthrough. (L98)SENSOR_BACKEND_REGISTRY: dict[str, Callable[[SensorReaderConfig], SensorReader]]—backendid → reader factory. Today:opencv_thread,ros2_image,gstreamer,galaxea_a1_camera_bridge. (ros2_imagewas in theSensorReaderBackendenum but absent here, so selecting it raisedunknown sensor reader backend.) (L387)_to_int(value, *, field, sensor_id) -> int— YAMLobject→intcoercion helper used across factories; rejects bools explicitly. (L48)_make_gpu_passthrough_skill(extra) -> rSkillBase— BuildsGpuPassthroughSkill; recognisedextra:sensor_id(default"wrist_rgb"),n_joints,horizon,device(default"cuda", raises if unavailable). (L75)_make_opencv_thread_reader(cfg) -> SensorReader— BuildsOpenCVThreadSensorReaderfrom aSensorReaderConfig; requiresbackend_params.device, forwards optionalfps/width/height/crop([x, y, width, height]); an invalid value raisesROSConfigError._make_ros2_image_reader(cfg) -> SensorReader— BuildsRos2ImageSensorReader; requiresbackend_params.topic(e.g./zed/depth/depth_registered), optionalreliability(best_effortdefault /reliable) andqos_depth(default 5).cfg.max_age_msbecomes the reader's staleness budget. Imported lazily so the factory module stays importable without ROS. (L350)_make_gstreamer_reader(cfg) -> SensorReader— BuildsGStreamerSensorReaderfrom aSensorReaderConfig. Translatespublish_to_ros/publish_topic/publish_rate_hz→PipelineSpec.enable_ros_tee, and forwardspublish_frame_id/publish_camera_infoto the reader's ROS tee. (L159)_make_galaxea_a1_camera_bridge_reader(cfg) -> SensorReader— Builds the native A1 Runtime paired-camera connector. Accepts onlycamera; unknown values are rejected.make_sensor_readers(configs) -> list[SensorReader](L270) — Batch constructor that preserves config order and shares one A1 paired-camera session across both views. Other backends still dispatch throughSENSOR_BACKEND_REGISTRY.
python/runner/src/openral_runner/deploy_runner.py
DeployRunner — concrete InferenceRunnerBase subclass composing HAL + Skill + WorldStateAggregator + SensorReaders + SafetyClient.
- module constant
_THUMBNAIL_HZ = 25.0(L66) — fixed cadence for the dashboard JPEG thumbnail emission. class DeployRunner(InferenceRunnerBase)— Closes theWorldState → Skill → safety → HALloop on real hardware / digital twins. It is the safety-supervisor boundary: catchesROSSafetyViolationfrom theSafetyClient, records it on theTickResult, and withholdsHAL.send_actionrather than re-raising — withholding is today's mitigation. (L71)__init__(*, hal, skill, aggregator, sensor_readers=(), safety_client=None, recorder=None, **base_kwargs)— Caller must pre-configure()+activate()the skill; runner manages HAL + reader open/close. Defaultssafety_clienttoNullSafetyClient. Dashboard JPEG thumbnails are emitted at a private fixed cadence. Optionalrecorderis aopenral_dataset.RolloutRecorder; when set,episode_start/episode_enddrive its lifecycle and every tick fans out viarecord_framewith the snapshot's camera frames (decode_recordable_images; it wrote 1x1 zero placeholders until #354). (L114)episode_start(task_string: str) -> int— Open a new episode on the attached recorder; returns the newepisode_idx(or-1when no recorder is attached). RaisesRuntimeErrorif called twice withoutepisode_end. (L176)episode_end(*, success: bool) -> None— Close the current recorder episode with the success flag. No-op when no recorder is attached. RaisesRuntimeErrorif called withoutepisode_start. (L204)activate() -> None—super().activate()+hal.connect()+ open everySensorReader. (L230)deactivate() -> None— Close everySensorReader(best-effort; logs + continues),hal.disconnect(),super().deactivate(). (L248)_tick_impl(tick_idx) -> TickResult— Five-phase tick: sensors → world_state → inference → safety → hal, each phase timed onto theTickResult/OTel span. CatchesROSPerceptionStaleper reader andROSSafetyViolationat the supervisor boundary, recording each rather than letting it crash the tick. (L319)_tracer[@property] — Per-calltrace.get_tracer("openral")(never cached at__init__, would bind to the provider live at construction time). (L221)_hal_adapter_label— Lower-cased class name of the HAL adapter, used as the closed-setopenral.hal.adaptervalue on spans + metrics. (L160)
python/runner/src/openral_runner/safety.py
SafetyClient stub — Python-side seam for the future C++ safety kernel.
class SafetyClient(Protocol)—@runtime_checkableProtocol.check_action(action)returnsNoneto allow or raisesROSSafetyViolationto reject; the inference runner catches it only at its supervisor boundary, never silently. (L42)- attr
envelope: SafetyEnvelope— the envelope checked against. check_action(action: Action) -> None(L58)class NullSafetyClient— no-op stub that always allows. Every call opens asafety.checkOTel span atseverity="info"carryingcontrol_mode,horizon,envelope_max_ee_speed_m_s,envelope_max_force_n. Used by digital-twin runs and pre-hardware tests so traces show the seam is wired before the C++ kernel arrives. (L74)__init__(envelope: SafetyEnvelope | None = None)— defaults to a stockSafetyEnvelope. (L99)check_action(action: Action) -> None(L103)
python/runner/src/openral_runner/base.py
Shared base for inference runners. Subclasses override _tick_impl.
- module constant
_DEADLINE_LOG_PERIOD_S = 5.0(L44) — minimum spacing between consecutive deadline-miss WARN log lines (rate-limit period consumed by_deadline_log_due). _percentile(samples: list[float], q: float) -> float— Linear-interpolation percentile (0.0for empty list). Used by_build_run_result. (L47)class InferenceRunnerBase(ABC)— Owns the rate-limited loop,rskill.tickOTel parent span,RunResultaggregation, deadline-overrun policy. (L65)__init__(*, rate_hz=30.0, deadline_overrun_policy=WARN, runner_name="inference_runner", latency_budget_ms=None, save_dir=None)— Rejectrate_hz <= 0. (L101)activate() -> None— Reset tick counter; mark active. (L128)deactivate() -> None— Stop ticking; idempotent. (L133)_tick_impl(tick_idx: int) -> TickResult[@abstractmethod] — Subclass hook; the base wraps it in arskill.tickspan. (L140)episode_start(task_string: str) -> int— Optional explicit episode boundary; default raisesNotImplementedError.DeployRunneroverrides to drive the recorder;SimRunneroverrides as a no-op (sim derives episode boundaries fromenv.stepflags). (L177)episode_end(*, success: bool) -> None— Optional explicit episode boundary; default raisesNotImplementedError. Seeepisode_start. (L202)_should_terminate() -> bool— Subclass early-exit hook (default False) consulted after each tick insiderun().SimRunneroverrides to stop oncen_episodescomplete. (L154)tick() -> TickResult— Span-wrapped single-tick entry; attaches per-stage timings asskill.{tick_ms, inference_ms, sensors_ms, world_state_ms, safety_ms, hal_ms, action_applied, safety_violations}attributes plus sim-onlyskill.{step_idx, episode_idx, reward, terminated, truncated}when set, plusopenral.tick.idx. Recordsopenral.tick.duration/openral.inference.durationhistograms (label:skill.id) and incrementsopenral.safety.violations{check_name="runtime", severity="violation"}for each violation on the tick. (L221)run(max_ticks: int | None = None) -> RunResult— Rate-limited loop usingsleep_until. AppliesDeadlineOverrunPolicy(warn/drop/raise). Recordslatency_budget_msviolations and incrementsopenral.tick.budget_violationsper violation. Honors_should_terminate()after each tick. (L308)_on_deadline_overrun(result: TickResult) -> None— Apply policy: structlog warn / drop / raiseROSDeadlineMissed. Always incrementsopenral.tick.deadline_missesand emitsopenral.event.deadline_missedon the current parent span; onRAISE, also callsrecord_exception+set_status(ERROR)on the parent span before re-raising. TheWARN/DROPlog lines are rate-limited via_deadline_log_due; the metric and the span event are not. (L393)_deadline_log_due(tick_ms: float) -> tuple[bool, int, float]— Rate-limits the deadline-miss WARN log to one line per_DEADLINE_LOG_PERIOD_S(5 s) per runner, since a genuinely slow host misses every tick and would otherwise flood the dashboard Event Log. No miss is lost — suppressed counts and the window'sworst_tick_msfold into the next logged line, andopenral.tick.deadline_missesstill counts every miss regardless._build_run_result(results, *, budget_violations, trace_id) -> RunResult— Aggregate per-tick records intoRunResult(mean / p99). (L454)