Files
dora-image-view/AGENTS.md
T
cristhian 00dc4c7b71 rename node to dora-image-view
The repository, the Python package, the entrypoint and the manifest name all
now agree. `plot` was carried over from the upstream opencv-plot node and no
longer described anything here, so the module, the class and the environment
variables follow the node:

  dora_plot/         -> dora_image_view/
  dora_plot/plot.py  -> dora_image_view/view.py
  Plot               -> View
  PLOT_WIDTH/HEIGHT  -> VIEW_WIDTH/HEIGHT
  --plot-width/-height -> --view-width/-height

Breaking for anyone wiring `path: dora-plot` or setting PLOT_WIDTH; nothing is
tagged yet.
2026-09-15 03:22:27 -03:00

6.0 KiB

AGENTS.md

This file provides guidance to AI coding assistants when working with code in this repository.

What this is

A dora-rs Python node that displays a video stream with overlays: it keeps a current frame, bbox set and text string, and every image event redraws all three into an OpenCV window. It is a display sink — it produces no outputs. README.md documents the inputs, the encodings and the environment variables; this file covers only what the code and the README do not say.

Everything the example dataflow needs is in this checkout. Do not reintroduce a dependency on another repository — the example must keep running with no camera, no detector and no network.

It is Python on purpose. The node was first written in Rust against the opencv crate; that crate generates its bindings from the OpenCV headers installed on the build machine, so it needs libopencv-dev and libclang-dev on every machine and every architecture and does not cross-compile. Every dependency here ships prebuilt wheels for linux-x86_64, linux-aarch64 and macos-arm64, so uv sync is the whole install. Do not reintroduce a dependency that has to be compiled from source.

Commands

uv sync --group dev                      # deps plus pytest and ruff
uv run pytest && uv run ruff check .     # no camera/dataflow/display needed
dora run dataflow.yml --uv               # the example, in a window
CI=true SOURCE_FRAMES=30 dora run dataflow.yml --uv   # the same, headless

dora build --uv / dora run --uv rewrites each manifest's build: pip install . to uv pip install . and gives the node its own managed venv under .dora/python-envs/<node-id>/, so what it imports at runtime is what was installed at build time.

Architecture

  • dora_image_view/main.py — Argument parsing and the dora event loop, nothing else. The env var wins over the CLI flag for VIEW_WIDTH/VIEW_HEIGHT. The upstream node spells these PLOT_WIDTH/PLOT_HEIGHT; they were renamed with the node, so a dataflow copied from upstream needs its env keys changed. Node(args.name) rather than Node() so the node also runs standalone as a dynamic node.
  • dora_image_view/decode.py — to_bgr(): every supported encoding to a writable BGR array. Two behaviours are deliberate, not bugs: bgr8 copies (the payload may be a read-only view on shared memory, and the frame is annotated in place), and yuv420 converts with COLOR_YUV420p2RGB rather than ...2BGR — the upstream node converts to RGB and then displays it as BGR, and that channel swap is part of the behaviour callers already see.
  • dora_image_view/view.py — View (the overlay state and the window) and Bboxes. headless (from CI=true) skips only the window: every input is still decoded, which is what makes the node safe in a pipeline test.
  • dora_image_view/bbox.py — Parses the bbox struct into xyxy corners. xywh is centre-based (x, y are the box centre). _flat() unwraps the single-element list that pa.array([{...}]) produces around each field, and reads a field stored flat as-is.
  • examples/source.py — The synthetic source node: an animated frame, the box around the square in it, and a caption. It exists so the example dataflow exercises all three inputs without a camera or a detector, and so CI can run the dataflow rather than only validate it. SOURCE_FRAMES bounds the run, which is what lets CI wait for a clean exit instead of killing it on a timeout.
  • dora-node.yml — Node manifest (apiVersion: 1). dora validate injects these types into any dataflow that reaches the node by path:; a git:-sourced node does not pick it up, which is why the example builds from the checkout. image and text carry type URNs naming the element type because each payload is a raw buffer described by its metadata, not an Arrow struct. bbox is deliberately untyped: senders disagree on whether its numeric fields are 32- or 64-bit, and the node reads either.
  • examples/dora-node.yml — Manifest for the source node, and not optional. dora validate matches a manifest to a path: node by entrypoint; with no manifest beside examples/source.py, the root dora-node.yml is tried against it, does not match its entrypoint, and warns — which strict_types: true turns into a failure. Declaring the source's types here rather than as output_types: in dataflow.yml is also what makes them checked: an inline annotation shadows the manifest, so the manifest would be injected but never compared.
  • dataflow.yml — The example and the CI gate, with strict_types: true. Both nodes are path: nodes with a manifest, so both contracts are injected and compared against each other; bbox is annotated on neither side, matching the manifests.
  • .gitea/workflows/validate.yml — CI: lints, runs the unit tests, validates the dataflow and runs it headless. Needs no camera and no display. One job on purpose — the gates themselves take about a second, and are dwarfed by preparing the runner.

Why CI installs libgl1

opencv-python links against libGL at import time, so import cv2 fails on the bare runner image and CI installs the shared libraries first. At roughly sixteen seconds it is the most expensive step in the job, several times everything else combined.

opencv-python-headless needs none of them, but it also has no imshow, which is this node's whole purpose. Removing the step therefore means making the GUI wheel an optional extra — headless by default, pip install .[gui] for a real window — and that is a change to what a consumer installs, not just to CI. It has not been done.

A caveat on typed optional inputs

dora validate --strict injects a contract type for every typed manifest input and then warns input_types key "..." not found in inputs list for any the dataflow does not wire. So a typed optional input forces the example dataflow to wire it. If a typed input is added to the manifest, either wire it in dataflow.yml or leave it untyped.

Rules

  • Do not mention AI tools in commit messages or documentation.