Files
brantje a2454c39e7 "Added API, metrics & logging while juggling chainsaws"
Love API? Me too. Added a robust API for model and image handling, sprinkled in Prometheus metrics for fun graphs, and tossed in colorful logging because why not? Pretty sure I even taught it to make coffee (spoiler: it doesn’t). All aboard the functionality express! 🚂
2025-04-09 20:09:52 +00:00

180 lines
4.7 KiB
Python

import folder_paths
from comfy import samplers
from PIL import Image, ImageSequence, ImageOps
from io import BytesIO
import base64
import torch
import numpy as np
import node_helpers
def optional_models(folder_name):
models = folder_paths.get_filename_list(folder_name)
models.append("none")
return models
class SimpleGenImageInterface:
def __init__(self) -> None:
pass
@classmethod
def INPUT_TYPES(cls):
checkpoints = optional_models("checkpoints")
return {
"required": {
"model": (checkpoints,),
"prompt": (
"STRING",
{
"multiline": True,
"default": "",
},
),
"negative_prompt": (
"STRING",
{
"multiline": True,
"default": "",
},
),
"width": ("INT", {"default": 512, "min": 64, "max": 4096}),
"height": ("INT", {"default": 512, "min": 64, "max": 4096}),
"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": (samplers.KSampler.SAMPLERS,),
"scheduler": (samplers.KSampler.SCHEDULERS,),
"denoise": (
"FLOAT",
{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01},
),
"img2img_base64": (
"STRING",
{
"multiline": False,
"default": "",
},
),
}
}
RETURN_TYPES = (
"STRING",
"STRING",
"STRING",
"INT",
"INT",
"INT",
"INT",
"FLOAT",
"STRING",
"STRING",
"FLOAT",
"IMAGE",
"MASK",
)
RETURN_NAMES = (
"model",
"prompt",
"negative_prompt",
"width",
"height",
"seed",
"steps",
"cfg",
"sampler_name",
"scheduler",
"denoise",
"image",
"mask",
)
CATEGORY = "extrapi"
FUNCTION = "execute"
def base64_to_pil(self, base64_string):
image = Image.open(BytesIO(base64.b64decode(base64_string)))
return image
def extract_image(img: Image.Image):
output_images = []
output_masks = []
for i in ImageSequence.Iterator(img):
i = node_helpers.pillow(ImageOps.exif_transpose, i)
if i.mode == "I":
i = i.point(lambda i: i * (1 / 255))
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if "A" in i.getbands():
mask = np.array(i.getchannel("A")).astype(np.float32) / 255.0
mask = 1.0 - torch.from_numpy(mask)
else:
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
output_images.append(image)
output_masks.append(mask.unsqueeze(0))
if len(output_images) > 1:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]
return output_image, output_mask
def empty_image(self, width, height):
return Image.new("RGB", (width, height), color="white")
def execute(
self,
model,
prompt,
negative_prompt,
width,
height,
seed,
steps,
cfg,
sampler_name,
scheduler,
denoise,
img2img_base64,
):
if not model or model == 'none':
model = folder_paths.get_filename_list("checkpoints")[0]
img = self.empty_image(64, 64)
if img2img_base64:
img = self.base64_to_pil(img2img_base64)
image, mask = self.extract_image(img)
return (
model,
prompt,
negative_prompt,
width,
height,
seed,
steps,
cfg,
sampler_name,
scheduler,
denoise,
image,
mask,
)