added XYZ Ksamplers

This commit is contained in:
Fillip
2024-08-19 19:08:44 -07:00
parent 81c8781b29
commit 9119ccecb2
4 changed files with 279 additions and 0 deletions
+9
View File
@@ -51,6 +51,9 @@ from .nodes.FL_KsamplerBasic import FL_KsamplerBasic
from .nodes.FL_KsamplerFractals import FL_FractalKSampler
from .nodes.FL_UpscaleModel import FL_UpscaleModel
from .nodes.FL_SaveCSV import FL_SaveCSV
from. nodes.FL_KSamplerXYZPlot import FL_KSamplerXYZPlot
from .nodes.FL_SamplerStrings import FL_SamplerStrings
from .nodes.FL_SchedulerStrings import FL_SchedulerStrings
@@ -109,6 +112,9 @@ NODE_CLASS_MAPPINGS = {
"FL_FractalKSampler": FL_FractalKSampler,
"FL_UpscaleModel": FL_UpscaleModel,
"FL_SaveCSV": FL_SaveCSV,
"FL_KSamplerXYZPlot": FL_KSamplerXYZPlot,
"FL_SamplerStrings": FL_SamplerStrings,
"FL_SchedulerStrings": FL_SchedulerStrings,
}
@@ -166,6 +172,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"FL_FractalKSampler": "FL Fractal KSampler",
"FL_UpscaleModel": "FL Upscale Model",
"FL_SaveCSV": "FL Save CSV",
"FL_KSamplerXYZPlot": "FL KSampler XYZ Plot",
"FL_SamplerStrings": "FL Sampler String XYZ",
"FL_SchedulerStrings": "FL Scheduler String XYZ"
}
+206
View File
@@ -0,0 +1,206 @@
import torch
from nodes import common_ksampler, VAEDecode, VAEEncode
import comfy.samplers
import comfy.utils
import logging
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from .utils import tensor_to_pil, pil_to_tensor
class FL_KSamplerXYZPlot:
# Positioning and style parameters
CELL_MARGIN = 70
AXIS_LABEL_MARGIN = 150
FONT_SIZE = 40
AXIS_LABEL_OFFSET_X = 70
AXIS_LABEL_OFFSET_Y = 70
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"input_type": (["latent", "image"],),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"x_axis": (["steps", "cfg", "denoise", "sampler_name", "scheduler"],),
"x_values": ("STRING", {"default": "20,30,40"}),
"y_axis": (["steps", "cfg", "denoise", "sampler_name", "scheduler"],),
"y_values": ("STRING", {"default": "7,8,9"}),
"z_axis": (["none", "steps", "cfg", "denoise", "sampler_name", "scheduler"],),
"z_values": ("STRING", {"default": ""}),
"z_stack_mode": (["vertical", "horizontal"],),
},
"optional": {
"latent_image": ("LATENT",),
"vae": ("VAE",),
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "sample_xyz_plot"
CATEGORY = "🏵️Fill Nodes/Ksamplers"
def parse_range(self, value_string, param_type):
if param_type in ["sampler_name", "scheduler"]:
return [x.strip() for x in value_string.split(',')]
return [float(x.strip()) for x in value_string.split(',')]
def generate_grid(self, x_values, y_values, z_values):
if z_values:
return [(x, y, z) for z in z_values for y in y_values for x in x_values]
return [(x, y, None) for y in y_values for x in x_values]
def create_image_grid(self, images, rows, cols, x_values, y_values, x_axis, y_axis):
if not images:
raise ValueError("No images provided to create grid")
cell_width, cell_height = images[0].size
grid_w = cols * (cell_width + self.CELL_MARGIN) + self.CELL_MARGIN + self.AXIS_LABEL_MARGIN
grid_h = rows * (cell_height + self.CELL_MARGIN) + self.CELL_MARGIN + self.AXIS_LABEL_MARGIN
grid = Image.new('RGB', size=(grid_w, grid_h), color='white')
draw = ImageDraw.Draw(grid)
try:
font = ImageFont.truetype("arial.ttf", self.FONT_SIZE)
except IOError:
font = ImageFont.load_default()
# Draw red axes
draw.line([(self.AXIS_LABEL_MARGIN, grid_h - self.AXIS_LABEL_MARGIN), (grid_w, grid_h - self.AXIS_LABEL_MARGIN)], fill='red', width=2) # X-axis
draw.line([(self.AXIS_LABEL_MARGIN, 0), (self.AXIS_LABEL_MARGIN, grid_h - self.AXIS_LABEL_MARGIN)], fill='red', width=2) # Y-axis
for i, img in enumerate(images):
col = i % cols
row = rows - 1 - (i // cols) # Flip the row order
x = col * (cell_width + self.CELL_MARGIN) + self.CELL_MARGIN + self.AXIS_LABEL_MARGIN
y = row * (cell_height + self.CELL_MARGIN) + self.CELL_MARGIN
grid.paste(img, (x, y))
# Add x-axis labels
for i, label in enumerate(x_values):
x = i * (cell_width + self.CELL_MARGIN) + self.CELL_MARGIN + self.AXIS_LABEL_MARGIN + cell_width // 2
y = grid_h - self.AXIS_LABEL_MARGIN + 5
draw.text((x, y), str(label), fill='black', font=font, anchor='mt')
# Add y-axis labels
for i, label in enumerate(y_values):
x = self.AXIS_LABEL_MARGIN - 5
y = (rows - 1 - i) * (cell_height + self.CELL_MARGIN) + self.CELL_MARGIN + cell_height // 2
draw.text((x, y), str(label), fill='black', font=font, anchor='rm')
# Add axis titles with adjustable positioning
draw.text((grid_w // 2, grid_h - self.AXIS_LABEL_OFFSET_Y), x_axis, fill='black', font=font, anchor='ms')
draw.text((self.AXIS_LABEL_OFFSET_X, grid_h // 2), y_axis, fill='black', font=font, anchor='ms', rotation=90)
return grid
def sample_xyz_plot(self, model, positive, negative, seed, steps, cfg, sampler_name, scheduler, denoise,
x_axis, x_values, y_axis, y_values, z_axis, z_values, input_type, z_stack_mode,
latent_image=None, image=None, vae=None):
try:
device = comfy.model_management.get_torch_device()
x_values = self.parse_range(x_values, x_axis)
y_values = self.parse_range(y_values, y_axis)
z_values = self.parse_range(z_values, z_axis) if z_axis != "none" else [None]
param_grid = self.generate_grid(x_values, y_values, z_values)
# Input selection and error handling
if input_type == "latent":
if latent_image is None:
raise ValueError("Latent input is selected, but no latent image is provided.")
input_list = [{"samples": latent_image["samples"][i:i+1]} for i in range(latent_image["samples"].shape[0])]
elif input_type == "image":
if image is None:
raise ValueError("Image input is selected, but no image is provided.")
if vae is None:
raise ValueError("Image input is selected, but no VAE is provided for encoding.")
input_list = [image[i:i+1] for i in range(image.shape[0])]
else:
raise ValueError(f"Invalid input type: {input_type}")
final_grids = []
for idx, item in enumerate(input_list):
results = []
for x_val, y_val, z_val in param_grid:
current_params = {
"steps": steps,
"cfg": cfg,
"denoise": denoise,
"sampler_name": sampler_name,
"scheduler": scheduler
}
current_params[x_axis] = x_val
current_params[y_axis] = y_val
if z_axis != "none":
current_params[z_axis] = z_val
if input_type == "image":
latent = VAEEncode().encode(vae, item)[0]
else:
latent = item
samples = common_ksampler(model, seed + idx, int(current_params["steps"]), current_params["cfg"],
current_params["sampler_name"], current_params["scheduler"],
positive, negative, latent,
denoise=current_params["denoise"])[0]
if vae is not None:
vae_decoder = VAEDecode()
output_image = vae_decoder.decode(vae, samples)[0]
results.append(tensor_to_pil(output_image))
if z_axis != "none":
grids = []
for i, z_val in enumerate(z_values):
start = i * len(x_values) * len(y_values)
end = start + len(x_values) * len(y_values)
grid = self.create_image_grid(
results[start:end], len(y_values), len(x_values),
x_values, y_values, x_axis, y_axis
)
draw = ImageDraw.Draw(grid)
font = ImageFont.truetype("arial.ttf", self.FONT_SIZE) if self.FONT_SIZE else ImageFont.load_default()
draw.text((200, 10), f"{z_axis}: {z_val}", fill='black', font=font)
grids.append(grid)
if z_stack_mode == "vertical":
total_width = max(grid.width for grid in grids)
total_height = sum(grid.height for grid in grids)
final_grid = Image.new('RGB', (total_width, total_height), color='white')
y_offset = 0
for grid in grids:
final_grid.paste(grid, (0, y_offset))
y_offset += grid.height
else: # horizontal
total_width = sum(grid.width for grid in grids)
total_height = max(grid.height for grid in grids)
final_grid = Image.new('RGB', (total_width, total_height), color='white')
x_offset = 0
for grid in grids:
final_grid.paste(grid, (x_offset, 0))
x_offset += grid.width
else:
final_grid = self.create_image_grid(
results, len(y_values), len(x_values),
x_values, y_values, x_axis, y_axis
)
final_grids.append(pil_to_tensor(final_grid))
return (torch.cat(final_grids, dim=0),)
except Exception as e:
logging.error(f"Error in FL_KsamplerXYZPlot: {str(e)}")
raise
+42
View File
@@ -0,0 +1,42 @@
class FL_SamplerStrings:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"euler": ("BOOLEAN", {"default": False}),
"euler_cfg_pp": ("BOOLEAN", {"default": False}),
"euler_ancestral": ("BOOLEAN", {"default": False}),
"euler_ancestral_cfg_pp": ("BOOLEAN", {"default": False}),
"heun": ("BOOLEAN", {"default": False}),
"heunpp2": ("BOOLEAN", {"default": False}),
"dpm_2": ("BOOLEAN", {"default": False}),
"dpm_2_ancestral": ("BOOLEAN", {"default": False}),
"lms": ("BOOLEAN", {"default": False}),
"dpm_fast": ("BOOLEAN", {"default": False}),
"dpm_adaptive": ("BOOLEAN", {"default": False}),
"dpmpp_2s_ancestral": ("BOOLEAN", {"default": False}),
"dpmpp_sde": ("BOOLEAN", {"default": False}),
"dpmpp_sde_gpu": ("BOOLEAN", {"default": False}),
"dpmpp_2m": ("BOOLEAN", {"default": False}),
"dpmpp_2m_sde": ("BOOLEAN", {"default": False}),
"dpmpp_2m_sde_gpu": ("BOOLEAN", {"default": False}),
"dpmpp_3m_sde": ("BOOLEAN", {"default": False}),
"dpmpp_3m_sde_gpu": ("BOOLEAN", {"default": False}),
"ddpm": ("BOOLEAN", {"default": False}),
"lcm": ("BOOLEAN", {"default": False}),
"ipndm": ("BOOLEAN", {"default": False}),
"ipndm_v": ("BOOLEAN", {"default": False}),
"deis": ("BOOLEAN", {"default": False}),
"ddim": ("BOOLEAN", {"default": False}),
"uni_pc": ("BOOLEAN", {"default": False}),
"uni_pc_bh2": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_sampler_string"
CATEGORY = "🏵️Fill Nodes/Ksamplers"
def generate_sampler_string(self, **kwargs):
selected_samplers = [key for key, value in kwargs.items() if value]
return (",".join(selected_samplers),)
+22
View File
@@ -0,0 +1,22 @@
class FL_SchedulerStrings:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"normal": ("BOOLEAN", {"default": False}),
"karras": ("BOOLEAN", {"default": False}),
"exponential": ("BOOLEAN", {"default": False}),
"sgm_uniform": ("BOOLEAN", {"default": False}),
"simple": ("BOOLEAN", {"default": False}),
"ddim_uniform": ("BOOLEAN", {"default": False}),
"beta": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_scheduler_string"
CATEGORY = "🏵️Fill Nodes/Ksamplers"
def generate_scheduler_string(self, **kwargs):
selected_schedulers = [key for key, value in kwargs.items() if value]
return (",".join(selected_schedulers),)