Nodes/ComfyUI CV/CV Reduce Points By Label
ComfyUI Node

CV Reduce Points By Label

One representative per cluster, and the clock-hand trick

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
CV Reduce Points By Label
  • points
  • labels
  • points
  • distances
  • count
◄reductioncentroid►
◄origin_x0.00►
◄origin_y0.00►

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.

Categoryimage/CV/points

Inputs (5)

NameTypeDefaultDescription
pointsNPARRAYNx1x2 labeled points (same order as 'labels').
labelsNPARRAY(N,) integer label per point.
reductionCOMBOcentroidRepresentative per cluster: its centroid (mean), or the member farthest from / closest to the origin.
origin_xFLOAT0.00-1000000–1000000X pixel coordinate of the reference origin (e.g. the dial center, anchor, or ray start).
origin_yFLOAT0.00-1000000–1000000Y pixel coordinate of the reference origin (e.g. the dial center, anchor, or ray start).

Outputs (3)

NameTypeDescription
pointsNPARRAYKx1x2 float32, one point per label.
distancesNPARRAY(K,) float32 distance of each representative to the origin.
countINTK, the number of labels.