Interactive chooser/router: pauses execution, shows the image with up to 10 labeled route buttons + edit-mask + stop. Chosen route gets the image, others ExecutionBlocker-ed; gate-painted mask on a fixed output; stop raises InterruptProcessingException. TDD plan with a pure torch-free gate_bus; lazy comfy imports keep node logic unit-testable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.3 KiB
Image Gate (Manual Router) — Design
Date: 2026-06-21 Status: Approved (brainstorming complete, ready for implementation plan)
1. Purpose
An interactive "image chooser on steroids": during a prompt run the node pauses,
shows the incoming image with a row of labeled route buttons, and waits for a human
click. Clicking route K sends the image down output K (all other route branches are
silently skipped). A Stop button cancels the whole run. Optionally, an Edit mask
button opens ComfyUI's MaskEditor on the image and the painted mask is emitted on a
single mask output. Built for manual dataset sorting/gating in the "Dataset Gates" suite.
Third node in the ComfyUI-Datasete-Gates package.
2. IO
| dir | name | type | notes |
|---|---|---|---|
| in | image |
IMAGE | the image (or batch, routed as one unit) |
| widget | routes |
INT, default 2, 1..10 | number of visible route buttons/outputs |
| widget | per-route labels | (frontend) | editable, default 1..N; rename the visible output slots |
| hidden | unique_id |
UNIQUE_ID | node id, used to key the pause/choice |
| out | mask |
MASK | fixed slot 0; painted at the gate, zeros (sized to image) if none |
| out | route_1 … route_10 |
IMAGE | dynamic; JS shows only routes of them, labeled |
RETURN_TYPES = ("MASK",) + ("IMAGE",)*10. The node always returns all 11 outputs; the
chosen route carries the image, every other route returns ExecutionBlocker(None). JS
hides the unused slots (>routes); their ExecutionBlocker returns are harmless.
3. Behavior (the pause)
On execute:
- Push the image to the UI (
PromptServer.send_sync, base64 or temp file) so the node body shows the preview + the N labeled route buttons + 🖌 Edit mask + ■ Stop. - Block the executor thread on our own
GateBus.wait(unique_id)(aMessageHolder- style singleton in asleep(0.1)loop; separate namespace from cg-image-picker). - Resolve:
- route K → image to output
K,ExecutionBlocker(None)to the other routes;mask= the painted mask (or zeros). - 🖌 Edit mask → opens MaskEditor (reuse the pool node's clipspace flow); the mask
is POSTed to
/datasete_gate/maskkeyed byunique_idand picked up on resume. - ■ Stop → cancel the prompt cleanly via
comfy.model_management.InterruptProcessingException(confirm exact symbol in plan).
- route K → image to output
IS_CHANGED returns nan → the gate pauses on every run (never cached).
4. Why the global mask is safe
Verified in execution.py:257-266 + 305-306: if any input of a node is an
ExecutionBlocker, the node is skipped and the blocker propagates to all its outputs.
So a non-chosen route's downstream (which consumes the blocked routed image) never runs,
regardless of the live mask value. Caveat: a node wired to mask only (no routed
image) would run unconditionally — not the intended wiring.
5. Code shape (same package)
gates/gate.py—ImageGatenode:INPUT_TYPES,IS_CHANGED=nan,run()(push preview → block → route viaExecutionBlocker). Pure helperroute_tuple(chosen, image, blocker, max_routes)for unit testing.gates/gate_server.py—GateBus(start/put/wait/cancel) + mask stash; aiohttp routes/datasete_gate/choiceand/datasete_gate/mask;send_preview()helper.web/image_gate.js— dynamic labeled outputs (showroutesof 10), preview render, route/stop/mask buttons, posts the choice; reuses the pool's MaskEditor helper.
6. Edge cases
routeschanged between runs → JS re-syncs visible slots; Python clampschosentoroutes.- Stop while no mask painted → clean interrupt, no output.
- Multiple gates in one graph → execute sequentially (single executor thread), so only one
blocks at a time; still keyed by
unique_id. - Batch input → previewed as the first image / small grid; routed as one unit.
- External queue-cancel →
GateBushonors the cancel flag and raises.
7. Testing
- pytest:
route_tuple(image at chosen, blocker elsewhere, correct length);GateBus(pre-seeded message returns; cancel raises;startresets); mask zero-fallback sizing. - Manual (live): pause appears, buttons labeled, click routes image to the right branch
and only that branch runs; Edit mask round-trips and feeds
mask; Stop cancels cleanly; changingroutesadds/removes slots.