CV Draw Rays
Bearings out of a shared origin, drawn over the evidence
- source
- points
- labels
- image
Why you'd reach for this
Anything that measures angle-from-a-point produces bearings, and a bearing is one number that means nothing until it's drawn from its centre: a clock hand, a direction of arrival, a disk's radial tick marks, a laser scan line. You compute the endpoints from angles, then join each endpoint to a shared origin, and the picture is instantly assessable - you can see at a glance whether the hands point at the numerals or between them.
That's the whole node, and its intended use is the one the author states: render the final detected bearings on top of the raw evidence. The angle computation happens upstream; this node draws.
How it works
For each row in points it draws one anti-aliased line from (origin_x, origin_y) to that point. The origin is a pair of floats, not a socket, and it's shared by every ray in one call - which is what makes it a ray drawing rather than a segment drawing. (Segments are CV Draw Segments; arrows between two matched sets are CV Draw Flow Vectors.)
Building the endpoints from angles is the upstream half, and it's CV Polar To Points: cartesian in, (r, theta) out and back. So the canonical chain for a dial reader is: pixels → find the centre → CV Polar To Points to convert, or its inverse to turn a detected angle into an endpoint → this node to draw it over the original photo → compare.
The labels input gives one colour per ray cluster from the same deterministic, colour-vision-deficiency-safe palette as CV Draw Points, and connecting it overrides the flat colour exactly as it does in the sibling nodes. That's how you group rays - by detected class, by confidence band, by which stage of the pipeline found them - and it keeps the colour a ray gets consistent with the colour its point gets elsewhere in the graph.
None or an empty point set passes the image through unchanged. Unlike the siblings that draw a marker per point, this one iterates every row and hands the coordinates straight to cv2, so don't feed it NaN entries to mean "no ray here" - filter the array first.
Inputs and outputs that matter
- points -
Nx1x2ray endpoints, not angles. The angles were converted upstream. - origin_x / origin_y - the shared origin as floats. Pixel coordinates, same frame as the image you're drawing on.
- thickness - default 2. Thin rays over a busier overlay read better than thick ones.
- color - a bare number or a BGR tuple; ignored when
labelsis connected, and on a MASK only the blue component survives, so use a single value there. - labels (optional) - per-cluster colours.
- image - the only output. There's no count on this node, so if you need "how many rays", count the array upstream.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Python ≥ 3.12, a current ComfyUI (this pack uses the V3 node API), or install ComfyUI CV from ComfyUI Manager. CPU-only cv2 drawing.
Common issues
- Rays converge somewhere other than the feature. The origin is in a different coordinate frame - usually because the image was resized or cropped and the origin wasn't transformed with it.
- Rays point in mirrored directions. Pixel rows grow downward, so a bearing measured with y-up comes out vertically flipped. Negate y (or draw on a canvas that already went through the same axis convention) - the same trap
CV Draw Plot Frame'sy_axiscontrol exists to prevent. - Nothing drawn. Empty point array, or
pointsgiven as angles instead ofx,yendpoints. This node wants endpoints. - Invisible on a MASK. BGR tuple, one channel, blue only. Use
'255'. - Contrib wheels. This pack depends on
opencv-contrib-python-headless; installing a plainopencv-pythonover it empties the shared contrib submodules and contrib-backed nodes disappear.tools/repair_opencv_contrib.py --check, then--apply.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| source | COMFY_MATCHTYPE_V3 | Image or mask to draw on (a copy is made). Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| points | NPARRAY | Nx1x2 ray endpoints. | |
| origin_x | FLOAT | 0.00-1000000–1000000 | X pixel coordinate of the reference origin (e.g. the dial center, anchor, or ray start). |
| origin_y | FLOAT | 0.00-1000000–1000000 | Y pixel coordinate of the reference origin (e.g. the dial center, anchor, or ray start). |
| thickness | INT | 21–64 | Ray line width in pixels. |
| color | STRING | (0, 255, 255) | Color as a single value (broadcast to all channels) or BGR tuple, e.g. '255' or '(0, 255, 255)'. Shorter tuples are zero-padded; longer tuples are truncated. Ignored when labels are connected. |
| labelsopt | NPARRAY | (N,) integer labels: one color per cluster. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | COMFY_MATCHTYPE_V3 | Same format as the image input. |