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

CV Reduce Array By Label

One row per region — the flat-poster trick, and much more

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Reduce Array By Label
  • values
  • labels
  • mask
  • table
  • counts
  • count
◄reductionmean►
◄num_labels0►
◄empty_value0.00►

You have an image and you have a label map: superpixels, watershed, connected components, HFS segmentation, k-means. What you don't have is a way to say "what colour is region 47". This node collapses everything sharing a label into one row - mean, median, sum, min or max - and the row order is the label value. That last detail is what makes the obvious trick work: paint the table back through the label map and the image goes flat, region by region, like a poster print.

The round trip, concretely

values is your array - (N,), (N,C), [H,W] or [H,W,C], and its leading axes have to match labels. labels is [H,W] int (or (N,) for per-element values); negative labels are excluded, which is exactly what you want because cv2.watershed writes -1 along the boundaries between regions. reduction is mean for the palette case - the average colour of each region. median if a region straddles an edge and you want the robust version.

Out comes table, (K, C) float32, row i = reduction over label i. Feed that into CV Take By Index with the same label map and you've replaced every pixel with its region's average. Now nudge the table - brighten every row, quantise it, sort the regions - and the image follows. That's non-photorealistic rendering without a model, a stable diffusion pass, or a seed lottery.

counts is (K,) int32 telling you how many elements carried each label; a 0 there marks a row that's filler rather than measurement. count is just K.

The optional inputs earn their keep

num_labels at 0 derives the row count from the largest label present, which is fine until a region comes out empty - then your table is short and everything downstream is misaligned. Wire the count output of CV Superpixels (or whatever produced the labels) here to pin the height, and labels at or above K get ignored. That alignment matters enormously if you're feeding the table into k-means or a classifier later.

empty_value (0 by default) fills rows with no members. Zero paints such a region black - sometimes exactly right, sometimes a bold flag that a region vanished.

mask is an inclusion mask at the label resolution: nonzero elements take part, zeros don't. Drop the sky before averaging a landscape, or drop a shadow before measuring a surface.

Where it fits

Three uses, in ascending order of how much they surprise people:

  • Region palette / flat render. As above, with CV Superpixels (workflows/24_kmeans_clusters.json clusters the portrait's own pixel colours and paints the centres back, which is this pattern one step removed).
  • Region statistics into ML. Average colour, average Fourier magnitude, average whatever per label becomes a feature row per region - then cluster or classify the regions themselves. CV Region Properties is the more complete version of this idea for shape features; this node is for anything array-shaped.
  • Counting and measuring. sum over an all-ones input counts members, and the counts output already does that for you. mean over a depth map, sampled by a segmentation mask, gives you the mean depth per object - no learning involved.

Install

Ships in ComfyUI CV (bmad4ever/comfyui_cv), GPL-3.0, a 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 users: search the pack title. Needs Python ≥ 3.12 and a ComfyUI on the V3 node API.

Gotchas

Leading axes, not shapes. The tooltip says it and it still bites people: values can be [H,W,3] against an [H,W] label map (reduce colour per pixel), or (N,) against (N,) labels. What it can't be is N mismatched - that raises, and that's a wiring bug rather than bad data.

Negative labels silently vanish, which is correct for watershed boundaries and a surprise if you generated labels with a −1 sentinel of your own meaning. Fix the labels upstream.

table is always 2-D. Single-channel input gives you (K,1), not (K,). Harmless, and worth knowing before you reshape it.

The pack's docs are unambiguous that this is one of the nodes implementing its own logic rather than calling a cv2 function - the reduction is numpy. Fine for how it's used; just don't expect the OpenCV docs to describe it.

Categoryimage/CV/low-level

Inputs (6)

NameTypeDefaultDescription
valuesNPARRAYArray to reduce: (N,), (N,C), [H,W] or [H,W,C]. Its leading axes must match 'labels' - the trailing axis is the channel axis and is reduced independently.
labelsNPARRAYInteger label per element: (N,) or [H,W], matching the leading axes of 'values'. NEGATIVE labels are excluded (watershed writes -1 on boundaries).
reductionCOMBOmeanHow the members of each label are collapsed. mean is the region average (the palette case); median is the robust version when a region straddles an edge; sum with an all-ones input counts members (the 'counts' output does that for you).
num_labelsoptINT00–2147483647Number of rows K to emit. 0 = derive it from the largest label present. Wire the 'count' output of 'CV Superpixels' here to pin the table height even when a region ends up empty; labels >= K are then ignored.
empty_valueoptFLOAT0.00-1000000000000–1000000000000Value written for labels with no members (and for every row when the input is empty). 0 paints such a region black.
maskoptNPARRAYOptional inclusion mask, same shape as 'labels': nonzero elements take part in the reduction, zeros are ignored (e.g. drop the sky before averaging).

Outputs (3)

NameTypeDescription
tableNPARRAY(K,C) float32, row i = the reduction over label i. Always 2-D, so a single-channel input gives (K,1). Feed it to 'CV Take By Index' with the same label map to paint the result back over the image.
countsNPARRAY(K,) int32: how many elements carried each label. 0 marks a row that is 'empty_value', not a measurement.
countINTK, the number of rows in the table.