kubakub regions from matrix / masks
Turn a facade into a list of named regions
- matrix
- scope
- masks
- regions_json
- preview
- scope
- regions
- report
What it's for
Everything else in this pack runs on regions. A region is one window, one pier, one wall, one cornice - a named piece of the building you can give its own prompt, its own mask behaviour, its own animation. Before you can diffuse region by region, or clip a layer to "only the floor-1 windows", something has to produce that partition.
That's this node. You give it the facade matrix - the full projection image at delivery size, e.g. a 3840×2160 PNG, the thing a VJ would call the pixelmap - and it hands back a partition: every pixel belongs to exactly one region, regions are named, repeated elements share a group, and larger masks tag the regions they cover.
Nobody in the corpus is solving this with AI. Projection mapping is a tiny niche on Reddit (a couple of dozen threads a year, mostly unanswered questions from working VJs), and the professional workflow is After Effects masks plus a pixelmap - so the node meets people there and reads an AE mask export as readily as it reads colours off a flat illustration.
How it works
Three modes, and they're genuinely different tools:
color_regions- flat colour fills in the matrix. Each colour becomes a region; withsplit_disconnectedon, each separate patch of that colour is its own region, so twenty window panes in one blue are twenty regions, not one. Anti-aliased rims are handled: pixels further thancolor_tolerancefrom every fill go to the spatially nearest region instead of becoming specks of their own.line_drawing- the closed cells between lines.line_polarityauto takes whichever of dark/light is the minority as the lines;line_gap_close_pxthickens lines slightly before filling so a one-pixel break doesn't leak one cell into its neighbour.mask_folder- one region per PNG, named after the file.windows_03.pngbecomes regionwindows_03in groupwindows. Alpha is used when it varies, otherwise brightness > 50%.recursivereads subfolders,group_bytakes the group from the file stem or the subfolder name, and overlapping masks resolve smaller-wins, so windows cut out of a full wall mask works the way you'd hope.
The mask_folder mode is where the depth is. scope_masks names masks that aren't regions but a limit - nothing outside their union can ever be a region, which is how you keep the projection out of the parts of the image that will never be lit. split_masks names masks holding several shapes, each becoming its own region in reading order. tag_only_masks names masks that only label what they cover. The result is the hierarchy your AE export already encoded: region tagged with the floor and the facade.
Small stuff gets sorted too: min_region_area (400 px) with merge_small_regions on merges a speck into the neighbour it shares the longest border with, or drops it so its pixels stay unassigned and never get diffused. group_tolerance decides when two regions count as repeats of the same thing - relative bbox size and shape IoU - so 20 identical windows share a group_id and one prompt covers them.
Inputs and outputs
Required: matrix (only the first image of a batch is used), mode, min_region_area, merge_small_regions. Optional and important: mask_folder - and note that an empty file field is the sample: leave it blank and the node runs on the built-in sample facade's masks, which is why the starter workflows need no files from you. scope is a plain mask input usable in any mode.
Outputs, in the order I'd wire them:
regions- a label map plus the region table. This is the one you feed to region plan, mask field, brightness compensation. It's tiny (33 MB at 4K) compared to the mask batch.masks- a MASK batch, one per region, index = region_id. Convenient and expensive: 100 regions at 4K is about 3.3 GB, and the node warns above 2 GB. Turnoutput_masksoff when you only needregions.regions_json- the table as text: ids, groups, tags, boxes. Save it; it's the audit trail of what the partition actually did.preview- matrix in grey with one colour per group, borders and ids drawn in. This is how you debug a partition without diffusing anything.scopeandreport- where regions may be, and how many were found, how many pixels went unassigned, which regions were merged or dropped.
Install
Manager → search kubakub nodes → install → restart. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/kubakubkub/ComfyUI_KubaNodes
python -m pip install -r ComfyUI_KubaNodes/requirements.txt
ComfyUI 0.37.0 or newer (this pack uses the newer node API and advanced inputs). The only added package is opencv-python-headless; keep your own OpenCV build if you have one. No models, no Blender, no ffmpeg here - this node is array work plus your masks.
Common issues
- Hundreds of tiny regions. Your matrix isn't flat-colour - a rendered facade with gradients and shading won't split cleanly. Raise
color_toleranceandmin_region_area, or just use the mask folder, which is the honest answer for anything photographic. - Everything is one region.
split_disconnectedoff in colour mode does exactly this. Also checkcolor_toleranceisn't so high that your window blue and your wall blue are the same colour. - Out of memory on the
masksoutput. 4K × many regions is real RAM. Useregionsdownstream and setoutput_masksoff. - Line mode leaks between cells. Bump
line_gap_close_px- small gaps in a scanned drawing are normal, and the node closes them rather than asking you to fix the drawing. - Region ids look random. They follow reading order - rows top to bottom, left to right - and the mask batch index equals region_id. Once you know that,
masks[7]andregion 7stop being mysterious.
Inputs (19)
| Name | Type | Default | Description |
|---|---|---|---|
| matrix | IMAGE | The facade matrix: the full projection image at delivery size (e.g. a 3840x2160 PNG). Only the first image of a batch is used. | |
| mode | COMBO | color_regions | Where the regions come from. color_regions: flat colour fills in the matrix. line_drawing: closed cells of a line drawing. mask_folder: a folder of PNG masks (e.g. exported from After Effects). |
| min_region_area | INT | 4000–100000000 | Regions smaller than this (in pixels) are merged or dropped. |
| merge_small_regions | BOOLEAN | true | On: merge small regions into the neighbour with the longest shared border. Off: drop them (they stay unassigned). |
| color_tolerance | INT | 240–442 | color_regions: colours closer than this (RGB distance, 0-255 units) count as the same fill. |
| split_disconnected | BOOLEAN | true | color_regions: on = every separate patch of a colour is its own region (each window), off = one region per colour. |
| group_tolerance | FLOAT | 0.080–0.5 | How different two regions may be (relative bbox size, 1 - shape IoU) and still share a group_id. 0 = identical only. |
| recursive | BOOLEAN | true | mask_folder: also read PNGs in subfolders (Groups/, Windows/ ...). |
| group_by | COMBO | stem | mask_folder: group_id from the file name without its trailing number (stem) or from the subfolder name (folder). |
| output_masks | BOOLEAN | true | Off: the masks output is a single empty mask, saving RAM when only the regions output is used. |
| line_threshold | FLOAT | 0.500–1 | line_drawing: brightness that separates lines from paper. |
| line_polarity | COMBO | auto | line_drawing: auto takes whichever of dark/light is the minority as the lines. |
| line_gap_close_px | INT | 10–32 | line_drawing: thicken lines by this much first, so small gaps don't leak one cell into the next. |
| color_namesopt | STRING | color_regions: name the groups by matrix colour. Every region of that colour gets the name as group_id. | |
| mask_folderopt | STRING | mask_folder: folder of PNG masks (alpha or white = inside). 'windows_03.png' becomes region 'windows_03' in group 'windows'. Empty: the sample facade's masks at the matrix size, so the node runs as it is. | |
| scope_masksopt | STRING | mask_folder: masks (names or wildcards, comma or one per line) that are not regions but the area regions may use. Nothing outside their union is ever a region. | |
| split_masksopt | STRING | mask_folder: masks holding several separate shapes. Each shape becomes a region <name>_01, _02 ... in reading order, all in group <name>. | |
| tag_only_masksopt | STRING | mask_folder: masks that only tag the regions they cover and are no region themselves. | |
| scopeopt | MASK | Any mode: pixels outside this mask are never part of a region. |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| masks | MASK | One mask per region; batch index = region_id. |
| regions_json | STRING | The region table as text: ids, groups, tags, boxes and notes. |
| preview | IMAGE | The matrix in grey with one colour per group, region borders and ids. |
| scope | MASK | 1 where regions may be (all 1 without a scope). |
| regions | KUBA_REGIONS | Label map + region table for the kubakub region nodes. |
| report | STRING | How many regions and groups were found, pixels left without a region, and the notes (merged or dropped regions, the sample used when no folder is given). |