CV Reduce Values By Label
Mean and median both lie — use the mode
- values
- labels
- values
- support
- count
Take any per-point measurement - the angle of a pixel relative to a dial centre, a depth, a hue, a score - and give each point a cluster label, and this node gives you one number per cluster. Mean is easy. The reason the node is interesting is the third option: mode, the centre of the densest window in the cluster's value distribution.
Why mode
Here's the motivating case, straight from the pack. You're reading an analog clock. You skeletonise the hands, cluster the pixels per hand, and convert each pixel to an angle about the dial centre. Now: what angle is that hand? The mean and the median both get dragged by a contaminating minority - a numeral fused to the hand, a bit of the rim caught by the threshold, the hub blob's worth of pixels. The hand's own pixels are collinear, so they pile up in a narrow spike around the true angle. The densest window finds that spike. That's the entire argument, and it generalises to any "mostly-consistent group with junk attached" measurement.
bandwidth (default 2) is the mode's only real knob: the half-width of the density window. The value with the most neighbours within ±bandwidth wins, and the output is the mean of that window - so you get the spike's centre, not its noisiest member.
circular_period (default 0) is the one people miss. Set it to 360 and the values are treated as circular, so 359° and 1° are two degrees apart instead of 358. Leave it at 0 and a hand pointing just past twelve will average out to something pointing at six. If your values are angles in degrees, set it.
Inputs and outputs
values is a flat (N,) numeric array, one per point. labels is (N,) ints, one per value, same order - typically straight from CV K-Means Points. reduction is mean, median or mode.
values out is (K,) float32, one per label, in label order. support is (K,) int32: for mode, how many members fell inside the winning window; for mean/median, just the cluster size. That support count is how you tell a real spike from a coin flip - low support relative to cluster size means there is no clear mode and the number you got is an artefact of the bandwidth. count is K.
Empty input gives empty outputs, no error, so a graph that filters everything out degrades rather than dies.
The pairing that makes it click
This is the partner of CV Reduce Points By Label, and they're designed to be used together over the same labels: one node gives you where each cluster is, the other gives you what each cluster measures. Rows line up, so points[k] and values[k] describe the same cluster and you can zip them back into a coordinate space (there's a CV Polar To Points node for going the other way, which is how the clock pipeline turns an angle back into a ray).
Two nodes, one label set, and suddenly a pile of pixels is a small table of interpretable features. That's the pattern this whole corner of the pack is built around.
When to use the other one
CV Reduce Values By Label is a per-point reducer: one value per point, one label per point, one number out per label. If you're working at image scale - a full [H,W] label map and a multi-channel array, rows indexed by label value, so you can paint the result back - that's CV Reduce Array By Label. Similar names, genuinely different jobs, and picking the wrong one gets you shape errors rather than wrong numbers.
Also worth knowing before you lean on it: the mode path is quadratic in the cluster size, because it's effectively comparing each value against its neighbours. Clusters of a few hundred points are nothing; clusters of fifty thousand will make you wait. Mean and median don't have that cost.
Install
Ships in ComfyUI CV (bmad4ever/comfyui_cv), GPL-3.0, forked from 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 users: search the pack title. Needs Python ≥ 3.12 and a ComfyUI on the V3 node API - if the pack doesn't appear at all, that's usually a ComfyUI that predates V3 rather than a broken install.
The pack's docs classify this as NC: the statistics are implemented in the pack, not delegated to cv2. That's a feature for the circular-period handling, because no OpenCV function does what you need here anyway. The best demo is workflows/exercise_clock_reading.json, which uses both reducers end to end, and reads far more clearly than trying to reason about the mode in the abstract.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| values | NPARRAY | (N,) numeric array, one value per point. | |
| labels | NPARRAY | (N,) integer label per value. | |
| reduction | COMBO | mean | Per-cluster summary. mode (densest window) is the robust choice when a minority of contaminating values skews the mean and median. |
| bandwidth | FLOAT | 2.000.000001–1000000 | Mode only: half-width of the density window. The value with the most neighbors within +/-bandwidth wins; the result is the mean of that window. |
| circular_period | FLOAT | 0.000–1000000 | Treat values as circular with this period (360 for angles in degrees, so 359 and 1 are 2 apart); 0 = plain numbers. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| values | NPARRAY | (K,) float32, one value per label. |
| support | NPARRAY | (K,) int32: members inside the winning mode window (= cluster size for mean/median). Low support relative to the cluster size means no clear spike. |
| count | INT | K, the number of labels. |