Files
laksjdjf-cgem156-ComfyUI/scripts/lora_xy/node.py
T
2024-04-03 23:53:43 +09:00

203 lines
7.7 KiB
Python

import comfy
from comfy_extras.nodes_custom_sampler import SamplerCustom
import folder_paths
from nodes import LoraLoader, PreviewImage, KSampler, KSamplerAdvanced
from ... import ROOT_NAME
import torch
from PIL import Image, ImageFont, ImageDraw
import numpy as np
import os
CATEGORY_NAME = ROOT_NAME + "lora_xy"
class LoraLoaderModelOnlyXY(LoraLoader):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"lora_name": (folder_paths.get_filename_list("loras"), ),
"strength_list": ("STRING", {"multiline": True}),
}
}
RETURN_TYPES = ("XY_MODEL","XY_LIST", )
FUNCTION = "load_lora_model_only_xy"
CATEGORY = CATEGORY_NAME
def load_lora_model_only_xy(self, model, lora_name, strength_list):
models = []
xy_list = []
weights = [float(x.strip()) for x in strength_list.strip().strip(",").split(",")]
for value in weights:
models.append(self.load_lora(model, None, lora_name, value, 0)[0])
xy_list.append(f"{lora_name.split('.')[0]}:{value}")
return (models, xy_list)
class SamplerCustomXY(SamplerCustom):
@classmethod
def INPUT_TYPES(s):
return {"required":
{"model_xy": ("XY_MODEL",),
"add_noise": ("BOOLEAN", {"default": True}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
"positive": ("CONDITIONING", ),
"negative": ("CONDITIONING", ),
"sampler": ("SAMPLER", ),
"sigmas": ("SIGMAS", ),
"latent_image": ("LATENT", ),
}
}
FUNCTION = "sample_xy"
CATEGORY = CATEGORY_NAME
def sample_xy(self, model_xy, **kwargs):
outputs = []
denoised_outputs = []
for model in model_xy:
output, denoised_output = self.sample(model, **kwargs)
outputs.append(output["samples"])
denoised_outputs.append(denoised_output["samples"])
return ({"samples":torch.cat(outputs)}, {"samples":torch.cat(denoised_outputs)})
class KSamplerXY(KSampler):
@classmethod
def INPUT_TYPES(s):
return {"required":
{"model_xy": ("XY_MODEL",),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
"positive": ("CONDITIONING", ),
"negative": ("CONDITIONING", ),
"latent_image": ("LATENT", ),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
}
}
FUNCTION = "sample_xy"
CATEGORY = CATEGORY_NAME
def sample_xy(self, model_xy, **kwargs):
outputs = []
for model in model_xy:
output = self.sample(model, **kwargs)[0]
outputs.append(output["samples"])
return ({"samples":torch.cat(outputs)},)
class KSamplerAdvancedXY(KSamplerAdvanced):
@classmethod
def INPUT_TYPES(s):
return {"required":
{"model_xy": ("XY_MODEL",),
"add_noise": (["enable", "disable"], ),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
"positive": ("CONDITIONING", ),
"negative": ("CONDITIONING", ),
"latent_image": ("LATENT", ),
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
"return_with_leftover_noise": (["disable", "enable"], ),
}
}
FUNCTION = "sample_xy"
CATEGORY = CATEGORY_NAME
def sample_xy(self, model_xy, **kwargs):
outputs = []
for model in model_xy:
output = self.sample(model, **kwargs)[0]
outputs.append(output["samples"])
return ({"samples":torch.cat(outputs)},)
class PreviewXY(PreviewImage):
@classmethod
def INPUT_TYPES(s):
return {
"required":{"images": ("IMAGE", ), "xy_list": ("XY_LIST", )},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
CATEGORY_NAME = ROOT_NAME + "preview_xy"
def save_images(self, images, xy_list, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
results = list()
pil_images = []
for (batch_number, image) in enumerate(images):
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
pil_images.append(img)
img = self.xy_plot(pil_images, xy_list)
file = "lora_xy_.png"
img.save(os.path.join(full_output_folder, file), compress_level=self.compress_level)
results.append({
"filename": file,
"subfolder": subfolder,
"type": self.type
})
counter += 1
return { "ui": { "images": results } }
def xy_plot(self, images, xy_list, text_height=100):
n = len(xy_list)
m = len(images) // n
image_width, image_height = images[0].width, images[0].height
# キャンバスのサイズを再計算(全画像が同じサイズの場合)
canvas_width = image_width * n
canvas_height = (image_height * m) + text_height # 文字列の高さ分を追加
# キャンバスを再作成
canvas = Image.new('RGB', (canvas_width, canvas_height), 'white')
# 画像と文字列の画像をキャンバスに配置(全画像が同じサイズの場合の最適化)
for i, img in enumerate(images):
# 画像を配置する位置を計算
x_offset = (i // m) * image_width
y_offset = (i % m) * (image_height) + text_height # 文字列の高さ分をオフセットして再計算
canvas.paste(img, (x_offset, y_offset))
text_images = [self.text_to_image(title).resize((image_width, text_height)) for title in xy_list]
# 文字列の画像をキャンバスに配置(各列の上部に)
for i, text_img in enumerate(text_images):
canvas.paste(text_img, (i * image_width, 0))
return canvas
def text_to_image(self, text):
font = ImageFont.load_default()
img = Image.new('RGB', (256, 20), 'white')
draw = ImageDraw.Draw(img)
text_width, text_height = draw.textbbox((0,0), text, font=font)[2:]
text_x = (256 - text_width) / 2
text_y = (20 - text_height) / 2
draw.text((text_x, text_y), text, font=font, fill='black')
return img