build with uv, and preview locally instead of a second dataflow
validate / validate (push) Successful in 59m16s

The build command was `pip install .`, which is what upstream and the dora docs
use because `--uv` rewrites it. Spelling it `uv pip install .` means the
dataflow never installs through pip at all, whether or not `--uv` was passed.
The cost is that the node then needs a virtualenv to install into, so it must
be run with `--uv`; the README says so.

The second dataflow is gone. It existed to wire this node into dora-image-view
just to see the frames, which pulled another repository over the network and
needed a tag there. PREVIEW=true now opens a local window of the frames as they
are published, so checking a camera, an encoding or a flip needs this
repository alone. It shows the frame after the flip and the resize, so what is
on screen is what is sent, and it never opens under CI, where there is no
display.

That a source node can display its own output overlaps with what a viewer node
does. The overlap is the point: the alternative was a second repository in the
loop before you can tell whether a camera works.
This commit is contained in:
2026-09-15 06:09:12 -03:00
parent 3c0e0f327d
commit dc3960809a
6 changed files with 87 additions and 50 deletions
+13 -3
View File
@@ -1,8 +1,8 @@
"""A dora webcam source: captures frames and publishes them as std/media/v1/Image.
Each `tick` reads one frame, optionally flips and resizes it, encodes it, and
sends it. The node stops when the tick input closes, or after CAPTURE_FRAMES
frames if that is set.
sends it. The node stops when the tick input closes, after CAPTURE_FRAMES
frames if that is set, or when `q` is pressed in the preview window.
"""
import argparse
@@ -11,7 +11,7 @@ import os
import cv2
from dora import Node
from dora_webcam import camera, encode, image
from dora_webcam import camera, encode, image, preview
FLIPS = {
"VERTICAL": 0,
@@ -43,6 +43,8 @@ def main() -> None:
# 0 runs until the tick closes; a positive count is what lets CI run the
# dataflow to completion instead of killing it on a timeout.
limit = _int("CAPTURE_FRAMES") or 0
# Never under CI: there is no display there, and imshow would fail.
previewing = os.getenv("PREVIEW", "").lower() in ("1", "true") and not ci
node = Node(args.name)
sent = 0
@@ -66,6 +68,11 @@ def main() -> None:
if width and height and (frame.shape[1], frame.shape[0]) != (width, height):
frame = cv2.resize(frame, (width, height))
# After the flip and the resize, so what is on screen is what is
# published rather than what came off the sensor.
if previewing and preview.show(frame):
break
# Taken before encoding: a compressed payload's byte count is not
# its pixel dimensions.
frame_height, frame_width = frame.shape[:2]
@@ -87,6 +94,9 @@ def main() -> None:
finally:
capture.release()
if previewing:
preview.close()
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
+36
View File
@@ -0,0 +1,36 @@
"""An optional local preview window.
A source node showing its own frames overlaps with what a viewer node does, and
that is the point: it means checking that a camera works needs this repository
only, not a second node wired into a dataflow. It is off unless asked for, and
never opens under CI.
"""
import cv2
import numpy as np
WINDOW = "dora-webcam"
def show(frame: np.ndarray) -> bool:
"""Display one frame and report whether the viewer asked to quit.
Parameters
----------
frame : np.ndarray
The captured BGR frame, before encoding, which is what a person wants
to look at.
Returns
-------
bool
True when `q` was pressed, which stops the node.
"""
cv2.imshow(WINDOW, frame)
return cv2.waitKey(1) & 0xFF == ord("q")
def close() -> None:
"""Tear the window down, if one was ever opened."""
cv2.destroyAllWindows()