Unfilled Area Mask
Find the holes your outpaint pass gave up on
- image
- mask
- mask_preview
- unfilled_rows
Outpainting a panorama from a single photo has a signature failure: the model nails the horizon and the middle, then quietly gives up at the top and bottom. A wide-but-shallow source photo leaves the zenith and nadir with almost no context, and some models just paint them black rather than invent content. That black isn't a geometry mistake - it's a hole, and the fix is to run a second inpaint pass over exactly those regions. Unfilled Area Mask finds them for you.
How it works
It's deliberately conservative. The node computes luminance and marks everything below threshold (default 0.10) as unfilled - that catches canvas black without touching deep shadow. Then, crucially, it restricts the search to the poles: limit_to_poles (default 0.4) only considers the top and bottom 40% of the image height. That's the safety that keeps a genuinely dark scene - a night shot, a shadowed horizon - from being flagged as "unfilled" and repainted into something you didn't ask for. Set it to 0 only if you really want to scan the whole image and eat the risk of masking real dark content.
Inputs and outputs
image- the outpainted panorama to inspect.threshold- luminance below this counts as unfilled. 0.10 is the right default; raise it if your model leaves a slightly-off-black gray behind.limit_to_poles- 0.4 = top/bottom 40% each. 0 = whole image (risky).grow(optional, default 24) - expands the mask into the good content so the fill has context to blend with.blur(optional, default 12) - softens the mask edge, and it does it with a circular wrap because equirect images wrap horizontally.
Outputs: mask for your inpainting sampler, a mask_preview image so you can eyeball what's being masked, and unfilled_rows - the number of rows flagged, which is a quick "did the pass even bother?" check. Zero rows means the model filled everything and you can skip the second pass.
The workflow shape
Warp → outpaint → UnfilledMask → inpaint the mask (grow+blur already make it a good mask to feed) → optionally Harmonize Boundary after, if the fresh fill leaves a step against the old content. It's a loop-closer: the node that turns "my pano has black blobs at the top" into a one-node diagnostic plus a targeted second pass, instead of hand-painting masks on a 2048px-wide canvas.
Install
Part of Mickmumpitz-Nodes (MIT, deps: numpy, Pillow, opencv-python - no models). ComfyUI Manager, search "Mickmumpitz", or:
cd ComfyUI/custom_nodes && git clone https://github.com/mickmumpitz/ComfyUI-Mickmumpitz-Nodes.git
Restart, find it as "Unfilled Area Mask" under Mickmumpitz/Panorama. Reach for it any time a pano comes back with poles that look like a void - it's the fastest route from "that looks wrong" to a mask that fixes it.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | The outpainted panorama to inspect. | |
| threshold | FLOAT | 0.100–1 | Luminance below this counts as unfilled. 0.10 catches canvas black without touching deep shadow. |
| limit_to_poles | FLOAT | 0.400–0.5 | Only consider this fraction of the height at the top and at the bottom. 0 = whole image (risky: masks real dark content). |
| growopt | INT | 240–512 | Expand the mask into the good content so the fill has context to blend with. |
| bluropt | FLOAT | 120–200 | Soften the mask edge. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |
| mask_preview | IMAGE | — |
| unfilled_rows | INT | — |