added XYZ Ksamplers
This commit is contained in:
@@ -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"
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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),)
|
||||
@@ -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),)
|
||||
Reference in New Issue
Block a user