diff --git a/tk_comfyui_batch_image/examples/workflows/m1_basic_vertical_stack.json b/tk_comfyui_batch_image/examples/workflows/m1_basic_vertical_stack.json new file mode 100644 index 0000000..9373bfb --- /dev/null +++ b/tk_comfyui_batch_image/examples/workflows/m1_basic_vertical_stack.json @@ -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 +} diff --git a/tk_comfyui_batch_image/tests/test_integration_e2e.py b/tk_comfyui_batch_image/tests/test_integration_e2e.py new file mode 100644 index 0000000..37aef46 --- /dev/null +++ b/tk_comfyui_batch_image/tests/test_integration_e2e.py @@ -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)