add a depth map panel
The node showed a colour image only, so debugging a depth pipeline meant either trusting the numbers or writing a throwaway viewer. It now keeps one panel per frame stream -- image and depth -- and tiles them side by side, scaled to a common height. Wiring one is a plain viewer; wiring both shows them together. bbox and text stay overlays rather than panels. The contract follows what dora-zed already publishes: float32 [H, W] in millimetres with 0 for missing. It is sent there as a raw byte slice, so the payload arrives as UInt8 despite the Float32 contract and is reinterpreted rather than converted. Auto-ranging the colormap over the valid pixels' full extent, rather than over a percentile of them, is deliberate and was not the first attempt: trimming the tails collapsed on the scene the node exists for. A small object in front of a large wall is a few percent of the pixels, so both tails landed on the wall and the object clipped away. Rendering a frame and looking at it found that; every test passed either way. Verified with dora-cli 1.0.1: validate passes with both contracts injected, the dataflow runs both nodes to a clean exit, and a composed frame was rendered and inspected.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# dora-image-view
|
||||
|
||||
Dora node that overlays bounding boxes, labels and text on an image input and displays
|
||||
it in an OpenCV window. A display sink for debugging a vision pipeline: it produces no
|
||||
outputs.
|
||||
Dora node that displays a colour image and a depth map side by side in one OpenCV
|
||||
window, with bounding box and text overlays. A display sink for debugging a
|
||||
vision pipeline: it produces no outputs.
|
||||
|
||||
Based on [`opencv-plot`](https://github.com/dora-rs/dora-hub/tree/main/node-hub/opencv-plot)
|
||||
from dora-hub, brought up to the dora 1.0 node contract and packaged with `uv`.
|
||||
@@ -19,7 +19,8 @@ nodes:
|
||||
build: pip install .
|
||||
path: dora-image-view
|
||||
inputs:
|
||||
image: camera/image
|
||||
image: camera/image_bgr
|
||||
depth: camera/depth
|
||||
bbox: detector/bbox
|
||||
text: detector/text
|
||||
```
|
||||
@@ -27,9 +28,11 @@ nodes:
|
||||
Run the dataflow with `--uv` (`dora run dataflow.yml --uv`) so the node gets its own
|
||||
managed environment.
|
||||
|
||||
Wire `image` from any node publishing one of the encodings below, and `bbox` from any
|
||||
detector publishing the struct below. Both inputs are optional: an `image` alone is a
|
||||
plain viewer.
|
||||
Every input is optional, and each of `image` and `depth` fills one panel of the window,
|
||||
tiled left to right in that order. Wiring one of them is a plain viewer;
|
||||
wiring several shows them side by side, scaled to a common height. `bbox` and `text`
|
||||
are overlays rather than panels — boxes are drawn on the `image` panel, in its own
|
||||
pixels, and the caption on the composite.
|
||||
|
||||
### Runnable example
|
||||
|
||||
@@ -72,10 +75,11 @@ $ dora validate dataflow.yml
|
||||
All type annotations OK.
|
||||
```
|
||||
|
||||
`image` is `std/core/v1/UInt8` and `text` is `std/core/v1/String`; the payloads are raw
|
||||
buffers described by their metadata, so the URN names the element type rather than a
|
||||
struct. `bbox` is deliberately left untyped — senders disagree on whether its numeric
|
||||
fields are 32- or 64-bit, and the node reads either.
|
||||
`image` is `std/core/v1/UInt8`, `depth` is `std/core/v1/Float32` and `text` is
|
||||
`std/core/v1/String`; the payloads are raw buffers described by their
|
||||
metadata, so the URN names the element type rather than a struct. `bbox` is deliberately
|
||||
left untyped — senders disagree on whether its numeric fields are 32- or 64-bit, and the
|
||||
node reads either.
|
||||
|
||||
## Behavior
|
||||
|
||||
@@ -86,8 +90,9 @@ of that state:
|
||||
bbox (green rectangle plus `label, conf` text), draws the stored text at the top left,
|
||||
resizes to `VIEW_WIDTH`x`VIEW_HEIGHT` if both are set, and shows it in a window titled
|
||||
`Dora Node: dora-image-view`. Press `q` in the window to quit.
|
||||
- A **`depth`** event colour-maps the distances onto the depth panel and redraws.
|
||||
- A **`bbox`** or **`text`** event only updates stored state; it is drawn on the next
|
||||
`image` redraw.
|
||||
redraw.
|
||||
|
||||
Setting `CI=true` suppresses the display: the node still decodes every input but opens
|
||||
no window. That is what makes it safe to run in a pipeline test.
|
||||
@@ -97,6 +102,7 @@ no window. That is what makes it safe to run in a pipeline test.
|
||||
| Input | Payload | Metadata |
|
||||
|---|---|---|
|
||||
| `image` | Flat `UInt8` array | `width`, `height`, `encoding` |
|
||||
| `depth` | Flat `Float32` array, `[H, W]` | `shape`, `units` |
|
||||
| `bbox` | Arrow struct array, element `[0]` with fields `bbox`, `conf`, `labels` | `format` |
|
||||
| `text` | Arrow array of size 1 | — |
|
||||
|
||||
@@ -106,6 +112,11 @@ encodings — the rest carry their own dimensions. An unsupported encoding raise
|
||||
|
||||
Supported `format` values: `xyxy` and `xywh` (centre-based, converted to `xyxy`).
|
||||
|
||||
`depth` is in millimetres unless `units` is `m`. In a depth map, `0`
|
||||
and any non-finite value mean missing depth and are drawn black. A payload sent as a
|
||||
raw byte slice — which is what a C++ node does — arrives as `UInt8` despite the
|
||||
`Float32` contract, and is reinterpreted rather than converted.
|
||||
|
||||
```python
|
||||
node.send_output("image", pa.array(frame.ravel()),
|
||||
{"width": 640, "height": 480, "encoding": "bgr8"})
|
||||
@@ -127,11 +138,21 @@ None — it is a display sink.
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `VIEW_WIDTH` | image width | Resize the displayed frame to this width, in pixels. |
|
||||
| `VIEW_HEIGHT` | image height | Resize the displayed frame to this height, in pixels. |
|
||||
| `VIEW_WIDTH` | composite width | Resize the displayed composite to this width, in pixels. |
|
||||
| `VIEW_HEIGHT` | composite height | Resize the displayed composite to this height, in pixels. |
|
||||
| `VIEW_DEPTH_MIN_MM` | auto | Pin the near end of the depth colour range, in millimetres. |
|
||||
| `VIEW_DEPTH_MAX_MM` | auto | Pin the far end of the depth colour range, in millimetres. |
|
||||
|
||||
Resizing only happens when both are set. Both are also available as `--view-width` and
|
||||
`--view-height`; the environment variable wins when both are given.
|
||||
Resizing only happens when both `VIEW_WIDTH` and `VIEW_HEIGHT` are set. Both are also
|
||||
available as `--view-width` and `--view-height`; the environment variable wins when both
|
||||
are given.
|
||||
|
||||
Each end of the depth range is auto-ranged over the valid pixels of the current frame
|
||||
unless it is pinned, so pinning one end leaves the other automatic. Auto-ranging makes
|
||||
an unconfigured sensor legible at the cost of colours that shift from frame to frame;
|
||||
pin both ends to compare frames against each other. The range covers the full extent of
|
||||
the valid pixels rather than a percentile of them, so a small near object against a
|
||||
large wall stays visible.
|
||||
|
||||
`CI=true` suppresses the display window; it is not a configuration knob.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user