Gemini Spatial to Bboxes
Turn Gemini's boxes into real masks you can inpaint and detail with
- image
- mask
- masks
- bboxes_json
- match_count
This is the node that makes the pack useful. GeminiSpatialNode hands you a pile of JSON saying "the dog is at coordinates 120,80,340,290" - which is neat but doesn't do anything on its own. Feed that JSON here, along with the image, and you get actual ComfyUI masks and clean pixel-space bounding boxes you can wire into inpainting, detailing, or region conditioning.
Think of it as the missing adapter between a vision model's words and ComfyUI's mask machinery. The whole auto-detailing loop - detect a region, re-render it, paste it back - normally needs a local detector like GroundingDINO or a YOLO plus SAM to build masks. This node replaces the entire detection stage with one API call that's already happened upstream, then does the same box-to-mask conversion locally, for free, on your GPU or lack thereof.
How it works
The node parses the JSON (it tolerates markdown fences, so a ```json-wrapped response works), flattens it into a list of detections, and filters for whatever label you care about. It converts each box from Gemini's 0–1000 normalized space into pixel coordinates using your image's actual dimensions, then builds:
mask- a single mask that's the union of every matching box. One region, one mask.masks- a batched tensor of individual masks, one per box. For "process each of these five faces separately" workflows.bboxes_json- the boxes in pixel space as[{"x1","y1","x2","y2","width","height","label"}]. This is the exact formatGeminiSpatialBboxToCoordsNodeexpects next.match_count- an INT telling you how many boxes survived filtering. The single most useful output for debugging.
The inputs
json_output- paste or wire in thejson_outputfromGeminiSpatialNode. Default"[]", multiline.image- used only for its dimensions. The masks are full-image-size, so it needs to know how big the picture is.label- a comma-separated substring filter against thelabeleach detection carries. Default"head", which hints at the author's use case. If Gemini wrote"dog", you match withdog. Put multiple comma-separated labels in to catch variations.drop_size- a percentage (0–100) of total image area; any box smaller than that gets dropped. Default 0 = keep everything. This is your "ignore the speck in the corner" dial.
Wiring it in
The mask and masks outputs plug straight into anything that takes a MASK - a SetLatentNoiseMask for inpaint, a detailer that accepts a mask input, or a mask-to-SEGS converter if you want to feed the Impact Pack's DetailerForEach loop and get the full detect-crop-render-paste pipeline. Because you're driving it from Gemini instead of a trained detector, you can target things YOLO never learned - "the red umbrella", "the middle person" - the whole point of promptable masking.
Troubleshooting
match_countis 0 - yourlabeldoesn't substring-match anything Gemini wrote back. Look at the rawjson_outputfirst; the labels there are the truth. Case and spacing count.- Everything got dropped -
drop_sizetoo aggressive. Remember it's a percentage of image area, not pixels. - Boxes feel too tight - a known property of grounding boxes generally: they hug the object, so pad the mask a bit before a detail pass or the edges of your re-render will show. The same caveat applies whether the box came from GroundingDINO, Qwen-VL, or this pack.
One gotcha: this node can't fix a bad detection. If Gemini placed the box wrong, you're masking the wrong spot, and no amount of node fiddling changes that - that's what the annotated_image preview upstream is for.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| json_output | STRING | [] | — |
| label | STRING | head | — |
| drop_size | FLOAT | 0.00–100 | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |
| masks | MASK | — |
| bboxes_json | STRING | — |
| match_count | INT | — |