CV Reduce Points By Label
One representative per cluster, and the clock-hand trick
- points
- labels
- points
- distances
- count
Clustering nodes hand you a big pile of labelled points. This one answers "but where is each cluster?" - either at its centre of mass, or at its extreme member relative to an origin you nominate. That second option is the interesting one: it's how you turn a blob of pixels into a single meaningful point, like the tip of a clock hand or the innermost hit of a group.
Inputs
points is Nx1x2 and labels is (N,) integers - the pairing CV K-Means Points produces, same order. reduction picks the representative: the cluster centroid, or the member farthest from / closest to the origin.
origin_x / origin_y (both 0 by default) are the reference point for those two distance-based modes. This is the input people forget and then wonder why "farthest" chose a point at the bottom-right. For a clock dial, set it to the dial centre - then "farthest from origin" is the tip of each hand and "closest to origin" is where each hand meets the hub, which is the entire trick behind the pack's clock-reading exercise.
Outputs
points comes back Kx1x2 float32 - one point per label, rows in label order 0, 1, 2… distances is (K,) float32, the distance of each representative to the origin. That's less a measurement than a sort key: CV Pick Value takes it directly, so "the longest hand", "the outermost match", "the nearest landmark" are all one reduce plus one pick. count is K.
Label order is worth a sentence because it's the design decision that makes this composable: because rows follow label value rather than some internal iteration order, two different reductions over the same labels stay aligned with each other and with anything else indexed by label - including CV Region Properties, whose label map uses the same convention (row i-1 describes label i).
Where it fits in a real graph
The centre of mass per cluster is the straightforward use: k-means on colour gives you cluster means, k-means on point positions gives you the objects' centres, and you've compressed thousands of rows to a handful. Then draw them with CV Draw Points and you've got a labelled summary overlay.
The extreme-member mode is where it stops being a reducer and starts being a feature. Angle of each cluster, distance from an anchor, which of a set of matched keypoints is nearest the frame centre - all of those want one point per group, chosen by geometry rather than averaged. Averages are also a trap here: the centroid of a crescent or a ring of pixels can land outside the object entirely, which is precisely when you want the extreme member instead.
It's designed for per-point work. If you've got a full image and a label map covering it, CV Reduce Array By Label is the node you want - same "one row per label" idea, multi-channel, image scale, and it knows about negative labels.
Install
Part of ComfyUI CV (bmad4ever/comfyui_cv), GPL-3.0 fork of opencv-comfyui:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
# restart ComfyUI
Manager: search the pack title. Python ≥ 3.12, V3-node-API ComfyUI. The pack is ~470 auto-generated cv2.* wrappers plus ~317 hand-written nodes, and this is one of the hand-written ones - cv2 provides nothing for it, so the reduction is implemented in the pack.
Traps
Labels without points, or points without labels. The node assumes they're row-aligned. Mixing a filtered point set with an unfiltered label array gives you a confident, wrong answer - if you filtered points upstream with CV Filter Points By Mask, filter the labels with the same mask.
A cluster with no members doesn't exist here, so an empty cluster doesn't produce a garbage row - but it also means the output row indices are whatever labels are actually present, not a fixed 0..K-1 stencil. If you need fixed indices, produce the labels from a node that guarantees them.
Distance is Euclidean in pixels, not in whatever your feature space was. That's fine for the geometric uses above and misleading if the points came from a scaled or normalised space - remember CV Fit Points To Box and friends change coordinates.
The pack's workflows/exercise_clock_reading.json is the honest demo of the extreme-member mode: skeletonise the hands, cluster the pixels, reduce to one point per hand, then reduce the angles of those points to a number. It's a genuinely good exercise, and it's also why the two "By Label" reducers ship as a pair.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| points | NPARRAY | Nx1x2 labeled points (same order as 'labels'). | |
| labels | NPARRAY | (N,) integer label per point. | |
| reduction | COMBO | centroid | Representative per cluster: its centroid (mean), or the member farthest from / closest to the origin. |
| 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). |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| points | NPARRAY | Kx1x2 float32, one point per label. |
| distances | NPARRAY | (K,) float32 distance of each representative to the origin. |
| count | INT | K, the number of labels. |