test: add E2E integration test + example ComfyUI workflow
Add test_integration_e2e.py that exercises the full loader → generator → composer pipeline with GreyBackend, asserting 2 pages, 4 panels, and cache-hit behaviour on second run (PNG round-trip tolerance atol=1/255). Add examples/workflows/m1_basic_vertical_stack.json as a reference ComfyUI workflow JSON wiring CheckpointLoaderSimple → ComicScriptLoader → ComicBatchGenerator → ComicPageComposer → SaveImage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9d3a587cf6
commit
1e4ccca032
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"last_node_id": 7,
|
||||
"last_link_id": 8,
|
||||
"nodes": [
|
||||
{ "id": 1, "type": "CheckpointLoaderSimple", "pos": [40, 100],
|
||||
"inputs": [], "outputs": [{"name":"MODEL","type":"MODEL","links":[1]},
|
||||
{"name":"CLIP","type":"CLIP","links":[2]},
|
||||
{"name":"VAE","type":"VAE","links":[3]}],
|
||||
"widgets_values": ["realistic_vision.safetensors"] },
|
||||
{ "id": 2, "type": "ComicScriptLoader", "pos": [40, 300],
|
||||
"inputs": [],
|
||||
"outputs": [{"name":"comic_script","type":"COMIC_SCRIPT","links":[4]},
|
||||
{"name":"summary","type":"STRING","links":null}],
|
||||
"widgets_values": ["file", "comics/basic.json", "", ""] },
|
||||
{ "id": 3, "type": "ComicBatchGenerator", "pos": [400, 150],
|
||||
"inputs": [
|
||||
{"name":"comic_script","type":"COMIC_SCRIPT","link":4},
|
||||
{"name":"model","type":"MODEL","link":1},
|
||||
{"name":"clip","type":"CLIP","link":2},
|
||||
{"name":"vae","type":"VAE","link":3}
|
||||
],
|
||||
"outputs": [
|
||||
{"name":"generated_panels","type":"IMAGE","links":[5]},
|
||||
{"name":"comic_script","type":"COMIC_SCRIPT","links":[6]},
|
||||
{"name":"generation_log","type":"STRING","links":null}
|
||||
],
|
||||
"widgets_values": ["euler","normal",25,7.0,1.0,"retry_then_skip",2,"",""] },
|
||||
{ "id": 4, "type": "ComicPageComposer", "pos": [800, 150],
|
||||
"inputs": [
|
||||
{"name":"generated_panels","type":"IMAGE","link":5},
|
||||
{"name":"comic_script","type":"COMIC_SCRIPT","link":6}
|
||||
],
|
||||
"outputs": [
|
||||
{"name":"composed_pages","type":"IMAGE","links":[7]},
|
||||
{"name":"panel_bboxes_json","type":"STRING","links":null},
|
||||
{"name":"individual_panels","type":"IMAGE","links":null}
|
||||
],
|
||||
"widgets_values": [false] },
|
||||
{ "id": 5, "type": "SaveImage", "pos": [1200, 150],
|
||||
"inputs": [{"name":"images","type":"IMAGE","link":7}],
|
||||
"widgets_values": ["comic/page"] }
|
||||
],
|
||||
"links": [
|
||||
[1,1,0,3,1,"MODEL"],
|
||||
[2,1,1,3,2,"CLIP"],
|
||||
[3,1,2,3,3,"VAE"],
|
||||
[4,2,0,3,0,"COMIC_SCRIPT"],
|
||||
[5,3,0,4,0,"IMAGE"],
|
||||
[6,3,1,4,1,"COMIC_SCRIPT"],
|
||||
[7,4,0,5,0,"IMAGE"]
|
||||
],
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
"""End-to-end: loader → generator → composer with the basic.json fixture."""
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from tk_comfyui_batch_image.core.sampler_runner import SamplerBackend
|
||||
from tk_comfyui_batch_image.nodes.batch_generator import ComicBatchGenerator
|
||||
from tk_comfyui_batch_image.nodes.page_composer import ComicPageComposer
|
||||
from tk_comfyui_batch_image.nodes.script_loader import ComicScriptLoader
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures" / "scripts"
|
||||
|
||||
|
||||
class GreyBackend(SamplerBackend):
|
||||
def encode(self, clip, text):
|
||||
return ("c", text)
|
||||
|
||||
def empty_latent(self, w, h, batch_size=1):
|
||||
return {"w": w, "h": h}
|
||||
|
||||
def sample(self, model, seed, steps, cfg, sampler_name, scheduler,
|
||||
cond_pos, cond_neg, latent, denoise):
|
||||
return latent
|
||||
|
||||
def vae_decode(self, vae, samples):
|
||||
h, w = samples["h"], samples["w"]
|
||||
return np.full((h, w, 3), 0.5, dtype=np.float32)
|
||||
|
||||
|
||||
def test_full_pipeline_basic_script(tmp_path: Path):
|
||||
loader = ComicScriptLoader()
|
||||
text = (FIXTURES / "basic.json").read_text(encoding="utf-8")
|
||||
script, summary = loader.load(mode="inline", json_text=text,
|
||||
json_file="", json_path="")
|
||||
|
||||
gen = ComicBatchGenerator()
|
||||
panels, pass_script, log = gen.generate(
|
||||
comic_script=script, model="M", clip="C", vae="V",
|
||||
sampler_name="euler", scheduler="normal", steps=10, cfg=7.0, denoise=1.0,
|
||||
on_failure="halt", retries=0,
|
||||
positive_suffix="", negative_suffix="",
|
||||
_backend=GreyBackend(), _out_dir=tmp_path,
|
||||
)
|
||||
|
||||
composer = ComicPageComposer()
|
||||
pages, bboxes_json, pass_panels = composer.compose(
|
||||
generated_panels=panels, comic_script=pass_script, debug_overlay=False,
|
||||
)
|
||||
|
||||
# 2 pages of same size
|
||||
assert pages.shape[0] == 2
|
||||
# all panels generated
|
||||
assert panels.shape[0] == 4
|
||||
# rerun without changes → everything cached
|
||||
gen2 = ComicBatchGenerator()
|
||||
panels2, _, log2 = gen2.generate(
|
||||
comic_script=script, model="M", clip="C", vae="V",
|
||||
sampler_name="euler", scheduler="normal", steps=10, cfg=7.0, denoise=1.0,
|
||||
on_failure="halt", retries=0,
|
||||
positive_suffix="", negative_suffix="",
|
||||
_backend=GreyBackend(), _out_dir=tmp_path,
|
||||
)
|
||||
assert "cached" in log2
|
||||
# PNG round-trip introduces ≤1/255 quantisation; use atol accordingly
|
||||
assert torch.allclose(panels, panels2, atol=1.0 / 255)
|
||||
Reference in New Issue
Block a user