Files
ComfyUI-Dataset-Gates/docs/plans/2026-06-21-image-gate-design.md
Ethanfel 7e8878bade Add Image Gate (Manual Router) design + implementation plan
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>
2026-06-21 17:09:26 +02:00

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:

  1. 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.
  2. Block the executor thread on our own GateBus.wait(unique_id) (a MessageHolder- style singleton in a sleep(0.1) loop; separate namespace from cg-image-picker).
  3. 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/mask keyed by unique_id and picked up on resume.
    • ■ Stop → cancel the prompt cleanly via comfy.model_management.InterruptProcessingException (confirm exact symbol in plan).

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.pyImageGate node: INPUT_TYPES, IS_CHANGED=nan, run() (push preview → block → route via ExecutionBlocker). Pure helper route_tuple(chosen, image, blocker, max_routes) for unit testing.
  • gates/gate_server.pyGateBus (start/put/wait/cancel) + mask stash; aiohttp routes /datasete_gate/choice and /datasete_gate/mask; send_preview() helper.
  • web/image_gate.js — dynamic labeled outputs (show routes of 10), preview render, route/stop/mask buttons, posts the choice; reuses the pool's MaskEditor helper.

6. Edge cases

  • routes changed between runs → JS re-syncs visible slots; Python clamps chosen to routes.
  • 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 → GateBus honors the cancel flag and raises.

7. Testing

  • pytest: route_tuple (image at chosen, blocker elsewhere, correct length); GateBus (pre-seeded message returns; cancel raises; start resets); 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; changing routes adds/removes slots.