Add batch image chooser gate

This commit is contained in:
2026-07-12 16:24:02 +02:00
parent 690278b592
commit dc2247d7b0
8 changed files with 920 additions and 13 deletions
+117
View File
@@ -0,0 +1,117 @@
import base64
import io
import math
import sys
import types
import pytest
import torch
from PIL import Image
import gates
from gates import image_chooser
def test_normalize_selection_deduplicates_and_preserves_batch_order():
assert image_chooser.normalize_selection("[3, 1, 3]", 4) == (1, 3)
@pytest.mark.parametrize(
"selection",
[
[],
"[]",
"not json",
"1",
[True],
[1.0],
["1"],
[-1],
[3],
],
)
def test_normalize_selection_rejects_invalid_choices(selection):
with pytest.raises(ValueError):
image_chooser.normalize_selection(selection, 3)
def test_select_batch_keeps_batch_dimension_and_original_pixels():
images = torch.arange(4 * 2 * 3 * 3, dtype=torch.float32).reshape(4, 2, 3, 3)
one = image_chooser.select_batch(images, [2])
many = image_chooser.select_batch(images, [3, 0, 3])
assert one.shape == (1, 2, 3, 3)
assert torch.equal(one[0], images[2])
assert many.shape == (2, 2, 3, 3)
assert torch.equal(many, images[[0, 3]])
assert many.dtype == images.dtype
def test_encode_previews_returns_small_jpegs_and_source_dimensions():
images = torch.zeros((2, 80, 40, 3), dtype=torch.float32)
images[1] = 1.0
previews = image_chooser.encode_previews(images, max_side=32)
assert [preview["index"] for preview in previews] == [0, 1]
assert [(preview["width"], preview["height"]) for preview in previews] == [
(40, 80),
(40, 80),
]
decoded = [
Image.open(io.BytesIO(base64.b64decode(preview["image"])))
for preview in previews
]
assert all(image.format == "JPEG" and image.mode == "RGB" for image in decoded)
assert [image.size for image in decoded] == [(16, 32), (16, 32)]
def test_encode_previews_accepts_bfloat16_images():
images = torch.zeros((1, 8, 8, 3), dtype=torch.bfloat16)
assert len(image_chooser.encode_previews(images)) == 1
def test_run_waits_for_token_scoped_selection(monkeypatch):
fake_server = types.ModuleType("gates.gate_server")
def send_image_choices(node_id, token, images):
assert images.shape[0] == 3
assert image_chooser.gate_bus.GateBus.put_token_payload(
node_id, token, [2, 0]
)
fake_server.send_image_choices = send_image_choices
monkeypatch.setitem(sys.modules, "gates.gate_server", fake_server)
monkeypatch.setattr(gates, "gate_server", fake_server, raising=False)
class InterruptProcessingException(Exception):
pass
fake_mm = types.ModuleType("comfy.model_management")
fake_mm.processing_interrupted = lambda: False
fake_mm.InterruptProcessingException = InterruptProcessingException
fake_comfy = types.ModuleType("comfy")
fake_comfy.model_management = fake_mm
monkeypatch.setitem(sys.modules, "comfy", fake_comfy)
monkeypatch.setitem(sys.modules, "comfy.model_management", fake_mm)
images = torch.arange(3, dtype=torch.float32).reshape(3, 1, 1, 1)
selected, = image_chooser.ImageChooserGate().run(images, unique_id="12")
assert torch.equal(selected.flatten(), torch.tensor([0.0, 2.0]))
assert "12" not in image_chooser.gate_bus.GateBus.active_tokens
def test_image_chooser_node_contract():
inputs = image_chooser.ImageChooserGate.INPUT_TYPES()
assert inputs["required"] == {"images": ("IMAGE",)}
assert inputs["hidden"] == {"unique_id": "UNIQUE_ID"}
assert image_chooser.ImageChooserGate.RETURN_TYPES == ("IMAGE",)
assert image_chooser.ImageChooserGate.RETURN_NAMES == ("images",)
assert image_chooser.ImageChooserGate.FUNCTION == "run"
assert image_chooser.ImageChooserGate.CATEGORY == "Dataset Gates"
assert math.isnan(image_chooser.ImageChooserGate.IS_CHANGED(images=None))
assert image_chooser.NODE_CLASS_MAPPINGS["ImageChooserGate"] \
is image_chooser.ImageChooserGate