Whole-Pipeline API (Platform-Scheduled)
Whole-pipeline facade: one call wiring capture -> inference -> overlay.
Positioning (read this before reaching for InferencePipeline in
pipeline.py): this module is the platform-scheduled pipeline. The
camera daemon feeds frames to the model (InferenceClient.subscribe),
so no pixels ever cross into the app process; results come back over the
stream, get pushed to the hardware overlay via the event bus
(OverlayClient), and the annotated video ships inside the encoded
stream — the app only ever sees lightweight InferenceResult structs.
InferencePipeline, by contrast, is the client-side convenience: it
pulls frames into the app, runs pre->infer->post on the app thread, and
is the right tool when the app needs the pixels (custom preprocessing,
multi-model chains on the same frame, drawing with its own rasterizer).
Choose by where the pixels should live: keep them on the device and take
results — StreamPipeline; need them in your process —
InferencePipeline.
One instance is single-shot: start() once, stop() once; build a
new instance for another run. Typical use:
zones = [{"points": [[0, 0], [1, 0], [1, 1]], "label": "yard"}]
pipe = StreamPipeline(
"main", "yolov5n", fps=10,
min_score=0.5, labels=["person"], polygons=zones,
on_result=lambda r: r if r.count_by_label("person") else None,
)
pipe.start()
for seq, result in pipe.results(): # optional: results in the app too
...
pipe.stop() # clears boxes + zones on the stream
- class neoruntime_ipc_sdk.stream_pipeline.StreamPipelineStatus(running, results_seen, results_annotated, annotate_errors, result_queue_drops, subscribe_dropped, last_error, last_latency_ms, avg_latency_ms, last_skew_us, avg_skew_us)[source]
Bases:
objectPoint-in-time health snapshot from
StreamPipeline.status().results_seencounts every result the worker accepted from the subscription, before theon_resulthook runs — results the hook dropped or replaced are included.last_latency_ms/avg_latency_mslikewise time every accepted result, including ones later dropped by the hook.- running: bool
- results_seen: int
- results_annotated: int
- annotate_errors: int
- result_queue_drops: int
- subscribe_dropped: int
- last_error: str
- last_latency_ms: float
- avg_latency_ms: float
- last_skew_us: int
- avg_skew_us: float
- __init__(running, results_seen, results_annotated, annotate_errors, result_queue_drops, subscribe_dropped, last_error, last_latency_ms, avg_latency_ms, last_skew_us, avg_skew_us)
- class neoruntime_ipc_sdk.stream_pipeline.StreamPipeline(stream, model, *, fps=10, session_id='', raw_output_only=False, max_consecutive_failures=10, queue_size=100, draw=True, overlay_config=None, ttl_ms=None, min_score=0.0, labels=None, polygons=None, on_result=None, result_queue_size=100, inference=None, overlay=None)[source]
Bases:
objectOne-call pipeline: subscribe, draw on the hardware overlay, observe.
- Parameters:
stream (str) – camera stream id the model runs on (e.g.
"main").model (str) – registered model id.
fps (int) – result-rate cap for the subscription.
session_id (str) – optional inference session id (observability/logging).
raw_output_only (bool) – skip post-processing, hand back raw tensors.
max_consecutive_failures (int | None) – consecutive failed frames tolerated before the worker gives up (0/None = unlimited); see
InferenceClient.subscribe().queue_size (int | None) – bound for the subscribe-side result queue.
draw (bool) – push results to the hardware overlay.
Falsealso suppresses staticpolygons— the encoded stream stays clean; results still flow toresults().overlay_config (OverlayConfig | None) – optional
OverlayConfigapplied at start (labels, colors, strict frame-lock, …).ttl_ms (int | None) – positive int, per-event validity override forwarded to every annotate call; omitted = the daemon derives it from the stream fps.
min_score (float) – draw only detections with
score >= min_score.labels (list[str] | None) – draw only these labels (
None= all). Filtering affects drawing only —results()yields the full result — and a fully-filtered result still publishes an empty detection list, clearing stale boxes.polygons (list[dict] | None) – static zone polygons pushed once at start and cleared at stop (normalized [0,1] points; see
OverlayClient.annotate()).on_result (Callable[[InferenceResult], InferenceResult | None] | None) – hook called per result on the worker thread. Return a (possibly replaced) result to flow onward, or
Noneto drop it (not drawn, not queued) — put tracking/filtering logic here. A raised exception drops that result, is recorded instatus().last_errorand is non-fatal.result_queue_size (int) – bound for the app-facing queue behind
results(); a slow app drops its oldest queued results (counted instatus().result_queue_drops).inference (InferenceClient | None) – inject an
InferenceClient(tests / client reuse). Injected clients are NOT closed bystop().overlay (OverlayClient | None) – inject an
OverlayClient. Not closed on stop.
- __init__(stream, model, *, fps=10, session_id='', raw_output_only=False, max_consecutive_failures=10, queue_size=100, draw=True, overlay_config=None, ttl_ms=None, min_score=0.0, labels=None, polygons=None, on_result=None, result_queue_size=100, inference=None, overlay=None)[source]
- start()[source]
Subscribe, apply overlay config / static zones, start drawing.
- stop()[source]
Stop the worker, clear what was drawn, close owned clients.
Idempotent, and a no-op before
start(). The worker is joined first so the box-clearing annotate cannot race a final result. Safe to call fromon_result(i.e. from the worker thread itself): the self-join is skipped and the teardown below runs on the caller’s thread.
- results()[source]
Yield
(frame_sequence, InferenceResult)post-callback.Ends when the worker stops (
stop()or a fatal stream error). The queue is bounded byresult_queue_sizewith drop-oldest overflow — a slow consumer loses its oldest results, counted instatus().result_queue_drops. The pre-start check is eager: it raises at call time, not on the firstnext().
- status()[source]
Snapshot of pipeline health and pass-through subscribe stats.
StreamPipeline
- class neoruntime_ipc_sdk.StreamPipeline(stream, model, *, fps=10, session_id='', raw_output_only=False, max_consecutive_failures=10, queue_size=100, draw=True, overlay_config=None, ttl_ms=None, min_score=0.0, labels=None, polygons=None, on_result=None, result_queue_size=100, inference=None, overlay=None)[source]
Bases:
objectOne-call pipeline: subscribe, draw on the hardware overlay, observe.
- Parameters:
stream (str) – camera stream id the model runs on (e.g.
"main").model (str) – registered model id.
fps (int) – result-rate cap for the subscription.
session_id (str) – optional inference session id (observability/logging).
raw_output_only (bool) – skip post-processing, hand back raw tensors.
max_consecutive_failures (int | None) – consecutive failed frames tolerated before the worker gives up (0/None = unlimited); see
InferenceClient.subscribe().queue_size (int | None) – bound for the subscribe-side result queue.
draw (bool) – push results to the hardware overlay.
Falsealso suppresses staticpolygons— the encoded stream stays clean; results still flow toresults().overlay_config (OverlayConfig | None) – optional
OverlayConfigapplied at start (labels, colors, strict frame-lock, …).ttl_ms (int | None) – positive int, per-event validity override forwarded to every annotate call; omitted = the daemon derives it from the stream fps.
min_score (float) – draw only detections with
score >= min_score.labels (list[str] | None) – draw only these labels (
None= all). Filtering affects drawing only —results()yields the full result — and a fully-filtered result still publishes an empty detection list, clearing stale boxes.polygons (list[dict] | None) – static zone polygons pushed once at start and cleared at stop (normalized [0,1] points; see
OverlayClient.annotate()).on_result (Callable[[InferenceResult], InferenceResult | None] | None) – hook called per result on the worker thread. Return a (possibly replaced) result to flow onward, or
Noneto drop it (not drawn, not queued) — put tracking/filtering logic here. A raised exception drops that result, is recorded instatus().last_errorand is non-fatal.result_queue_size (int) – bound for the app-facing queue behind
results(); a slow app drops its oldest queued results (counted instatus().result_queue_drops).inference (InferenceClient | None) – inject an
InferenceClient(tests / client reuse). Injected clients are NOT closed bystop().overlay (OverlayClient | None) – inject an
OverlayClient. Not closed on stop.
- __init__(stream, model, *, fps=10, session_id='', raw_output_only=False, max_consecutive_failures=10, queue_size=100, draw=True, overlay_config=None, ttl_ms=None, min_score=0.0, labels=None, polygons=None, on_result=None, result_queue_size=100, inference=None, overlay=None)[source]
- start()[source]
Subscribe, apply overlay config / static zones, start drawing.
- stop()[source]
Stop the worker, clear what was drawn, close owned clients.
Idempotent, and a no-op before
start(). The worker is joined first so the box-clearing annotate cannot race a final result. Safe to call fromon_result(i.e. from the worker thread itself): the self-join is skipped and the teardown below runs on the caller’s thread.
- results()[source]
Yield
(frame_sequence, InferenceResult)post-callback.Ends when the worker stops (
stop()or a fatal stream error). The queue is bounded byresult_queue_sizewith drop-oldest overflow — a slow consumer loses its oldest results, counted instatus().result_queue_drops. The pre-start check is eager: it raises at call time, not on the firstnext().
- status()[source]
Snapshot of pipeline health and pass-through subscribe stats.
StreamPipelineStatus
- class neoruntime_ipc_sdk.StreamPipelineStatus(running, results_seen, results_annotated, annotate_errors, result_queue_drops, subscribe_dropped, last_error, last_latency_ms, avg_latency_ms, last_skew_us, avg_skew_us)[source]
Point-in-time health snapshot from
StreamPipeline.status().results_seencounts every result the worker accepted from the subscription, before theon_resulthook runs — results the hook dropped or replaced are included.last_latency_ms/avg_latency_mslikewise time every accepted result, including ones later dropped by the hook.- __init__(running, results_seen, results_annotated, annotate_errors, result_queue_drops, subscribe_dropped, last_error, last_latency_ms, avg_latency_ms, last_skew_us, avg_skew_us)
Examples
One call: subscribe + hardware overlay + app-side results
from neoruntime_ipc_sdk import StreamPipeline
zones = [{"points": [[0, 0], [1, 0], [1, 1]], "label": "yard"}]
pipe = StreamPipeline(
"main", "yolov5n", fps=10,
min_score=0.5, labels=["person"], polygons=zones,
on_result=lambda r: r if r.objects else None, # skip empty results
).start()
# Optional: consume results app-side too (alerts, forwarding, extra
# processing). The annotated video never passes through here — boxes
# are baked into the encoded stream by the hardware overlay.
for seq, result in pipe.results():
print(seq, [(o.label, round(o.score, 2)) for o in result.objects])
if seq >= 100:
break
st = pipe.status() # results_seen / annotated / drops / skew ...
pipe.stop() # clears boxes + static zones; stream is clean
Semantics
Positioning vs InferencePipeline (pipeline.py): StreamPipeline is the platform-scheduled pipeline — the camera daemon feeds frames to the model (
subscribe), no pixels enter the app process, results go to the hardware overlay viaOverlayClientand are baked into the encoded stream. InferencePipeline is the client-side convenience — frames are pulled into the app and pre→infer→post runs synchronously on the app thread; reach for it when the app needs the pixels (custom preprocessing, multi-model chains on one frame, its own rasterizer). Choose by where the pixels should live.Single-shot lifecycle:
start()once,stop()once; build a new instance for another run.stop()is idempotent (no-op beforestart()), joins the worker before clearing boxes/static polygons, and closes only the clients it created (injectedinference=/overlay=are left open).Filtering affects drawing only:
min_score/labelsfilter what reaches the overlay;results()still yields the full result. A fully-filtered result publishes an empty detection list so stale boxes clear.on_result hook runs per result on the worker thread: return a (possibly replaced) result to keep it flowing, or
Noneto drop it; a raised exception drops only that result and is recorded instatus().last_error— never fatal.Two bounded queues, drop-oldest: the subscribe side (
queue_size, surfaced assubscribe_dropped) and the app side (result_queue_size, counted inresult_queue_drops); annotate failures incrementannotate_errorswithout stopping the pipeline.draw=False never touches the overlay (static polygons included) — the encoded stream stays clean; results still flow to
results().Session tag (P2-13):
session_id=spans inference and drawing — the subscription and every result drawing carry the same tag; when the process dies without cleanup (SIGKILL included), the inference session-end event makes camera-daemon sweep every polygon carrying that tag. The static zones at start and the clear writes atstop()carry no tag — operator writes that take effect unconditionally.status() passes the subscribe observability through: last/avg latency_ms and last/avg skew_us (result-timestamp minus frame-timestamp — the acceptance metric shared by the preview and strict frame-lock modes).