diff --git a/hordelib/horde.py b/hordelib/horde.py index f9884f6c..bd14f329 100644 --- a/hordelib/horde.py +++ b/hordelib/horde.py @@ -174,6 +174,7 @@ class HordeLib: "extra_source_images": {"datatype": list, "default": []}, # Stable Cascade Remix "extra_texts": {"datatype": list, "default": []}, # QR Codes (for now) "workflow": {"datatype": str, "default": "auto_detect"}, + "transparent": {"datatype": bool, "default": False}, } EXTRA_IMAGES_SCHEMA = { @@ -1029,6 +1030,22 @@ def _final_pipeline_adjustments(self, payload, pipeline_data) -> tuple[dict, lis pipeline_params["controlnet_qr_model_loader.control_net_name"] = ( "control_v1p_sd15_qrcode_monster_v2.safetensors" ) + if payload.get("transparent") is True: + if SharedModelManager.manager.compvis: + model_details = SharedModelManager.manager.compvis.get_model_reference_info(payload["model_name"]) + # SD2, Cascade and SD3 not supported + if model_details and model_details.get("baseline") in ["stable diffusion 1", "stable_diffusion_xl"]: + self.generator.reconnect_input(pipeline_data, "sampler.model", "layer_diffuse_apply") + self.generator.reconnect_input(pipeline_data, "layer_diffuse_apply.model", "model_loader") + self.generator.reconnect_input(pipeline_data, "output_image.images", "layer_diffuse_decode_rgba") + self.generator.reconnect_input(pipeline_data, "layer_diffuse_decode_rgba.images", "vae_decode") + if model_details.get("baseline") == "stable diffusion 1": + pipeline_params["layer_diffuse_apply.config"] = "SD15, Attention Injection, attn_sharing" + pipeline_params["layer_diffuse_decode_rgba.sd_version"] = "SD15" + else: + pipeline_params["layer_diffuse_apply.config"] = "SDXL, Conv Injection" + pipeline_params["layer_diffuse_decode_rgba.sd_version"] = "SDXL" + return pipeline_params, faults def _get_appropriate_pipeline(self, params): diff --git a/hordelib/nodes/comfyui-layerdiffuse b/hordelib/nodes/comfyui-layerdiffuse new file mode 160000 index 00000000..c5f1c0aa --- /dev/null +++ b/hordelib/nodes/comfyui-layerdiffuse @@ -0,0 +1 @@ +Subproject commit c5f1c0aa45592d2f48764472db3f7d2da622b6f1 diff --git a/hordelib/pipelines/pipeline_stable_diffusion.json b/hordelib/pipelines/pipeline_stable_diffusion.json index 983dd28d..f27780eb 100644 --- a/hordelib/pipelines/pipeline_stable_diffusion.json +++ b/hordelib/pipelines/pipeline_stable_diffusion.json @@ -1,7 +1,7 @@ { "3": { "inputs": { - "seed": 62706718437716, + "seed": 325847265780417, "steps": 20, "cfg": 8, "sampler_name": "euler", @@ -155,5 +155,37 @@ "_meta": { "title": "repeat_image_batch" } + }, + "16": { + "inputs": { + "config": "SD15, Attention Injection, attn_sharing", + "weight": 1, + "model": [ + "4", + 0 + ] + }, + "class_type": "LayeredDiffusionApply", + "_meta": { + "title": "layer_diffuse_apply" + } + }, + "17": { + "inputs": { + "sd_version": "SD15", + "sub_batch_size": 16, + "samples": [ + "3", + 0 + ], + "images": [ + "14", + 0 + ] + }, + "class_type": "LayeredDiffusionDecodeRGBA", + "_meta": { + "title": "layer_diffuse_decode_rgba" + } } } diff --git a/pyproject.toml b/pyproject.toml index 14809ae8..8239c211 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ exclude = ''' [tool.ruff] # XXX this isn't part of CI yet line-length=119 -exclude=["ComfyUI", "comfy_controlnet_preprocessors", "facerestore", "comfy_qr", "build"] +exclude=["ComfyUI", "comfy_controlnet_preprocessors", "facerestore", "comfy_qr", "comfyui-layerdiffuse", "build"] ignore=[ # "F401", # imported but unused "E402", # Module level import not at top of file diff --git a/requirements.txt b/requirements.txt index bbc3bedf..1366bb3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ horde_sdk>=0.9.1 horde_model_reference>=0.5.2 pydantic +numpy==1.26.4 torch>=2.1.0 # xformers>=0.0.19 torchvision diff --git a/tests/test_horde_inference_layerdiffusion.py b/tests/test_horde_inference_layerdiffusion.py new file mode 100644 index 00000000..c4deec27 --- /dev/null +++ b/tests/test_horde_inference_layerdiffusion.py @@ -0,0 +1,92 @@ +# test_horde.py + +from PIL import Image + +from hordelib.horde import HordeLib + + +class TestHordeInferenceTransparent: + def test_layerdiffuse_sd15( + self, + hordelib_instance: HordeLib, + stable_diffusion_model_name_for_testing: str, + ): + data = { + "sampler_name": "k_euler", + "cfg_scale": 8, + "denoising_strength": 1.0, + "seed": 1312, + "height": 512, + "width": 512, + "karras": False, + "tiling": False, + "hires_fix": False, + "transparent": True, + "clip_skip": 1, + "control_type": None, + "image_is_control": False, + "return_control_map": False, + "prompt": "an ancient digital AI hydra monster###watermark, text", + "ddim_steps": 25, + "n_iter": 2, + "model": stable_diffusion_model_name_for_testing, + } + image_results = hordelib_instance.basic_inference(data) + + assert len(image_results) == 2 + + img_pairs_to_check = [] + + img_filename_base = "layer_diffusion_sd15_n_iter_{0}.png" + + for i, image_result in enumerate(image_results): + assert image_result.image is not None + assert isinstance(image_result.image, Image.Image) + + img_filename = img_filename_base.format(i) + + image_result.image.save(f"images/{img_filename}", quality=100) + img_pairs_to_check.append((f"images_expected/{img_filename}", image_result.image)) + + def test_layerdiffuse_sdxl( + self, + hordelib_instance: HordeLib, + sdxl_refined_model_name: str, + ): + data = { + "sampler_name": "k_euler", + "cfg_scale": 8, + "denoising_strength": 1.0, + "seed": 1312, + "height": 1024, + "width": 1024, + "karras": False, + "tiling": False, + "hires_fix": False, + "transparent": True, + "clip_skip": 1, + "control_type": None, + "image_is_control": False, + "return_control_map": False, + "prompt": "an ancient digital AI hydra monster###watermark, text", + "ddim_steps": 25, + "n_iter": 2, + "model": sdxl_refined_model_name, + } + + image_results = hordelib_instance.basic_inference(data) + + assert len(image_results) == 2 + + img_pairs_to_check = [] + + img_filename_base = "layer_diffusion_sdxl_n_iter_{0}.png" + + for i, image_result in enumerate(image_results): + assert image_result.image is not None + assert isinstance(image_result.image, Image.Image) + + img_filename = img_filename_base.format(i) + + image_result.image.save(f"images/{img_filename}", quality=100) + img_pairs_to_check.append((f"images_expected/{img_filename}", image_result.image))