2618 lines
133 KiB
Python
2618 lines
133 KiB
Python
# Efficiency Nodes ExtendeD - Expansion of Efficiency Nodes for ComfyUI. Significant UX improvements.
|
|
# by NyaamZ - 2023 - 2025
|
|
# https://github.com/NyaamZ/efficiency-nodes-ED
|
|
|
|
# 00b4e6d4-922e-4a3b-943e-33a467587fe1
|
|
|
|
import os
|
|
import sys
|
|
import re
|
|
import nodes
|
|
|
|
from pathlib import Path #For embedding stacker ED
|
|
from functools import reduce # For regional ED
|
|
|
|
# Get the absolute path of various directories
|
|
my_dir = os.path.dirname(os.path.abspath(__file__))
|
|
custom_nodes_dir = os.path.abspath(os.path.join(my_dir, '..'))
|
|
efficiency_nodes_dir = os.path.abspath(os.path.join(custom_nodes_dir, 'efficiency-nodes-comfyui'))
|
|
comfy_dir = os.path.abspath(os.path.join(my_dir, '..', '..'))
|
|
|
|
# Append comfy_dir to sys.path & import files
|
|
sys.path.append(comfy_dir)
|
|
from comfy_extras.nodes_upscale_model import UpscaleModelLoader, ImageUpscaleWithModel
|
|
from comfy_extras.nodes_rebatch import LatentRebatch
|
|
from server import PromptServer
|
|
|
|
import comfy.sd
|
|
import comfy.utils
|
|
|
|
sys.path.remove(comfy_dir)
|
|
|
|
sys.path.append(efficiency_nodes_dir)
|
|
from tsc_utils import *
|
|
sys.path.remove(efficiency_nodes_dir)
|
|
|
|
# Append custom_nodes_dir to sys.path
|
|
sys.path.append(custom_nodes_dir)
|
|
|
|
NODES = nodes.NODE_CLASS_MAPPINGS
|
|
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ["AYS SD1", "AYS SDXL", "AYS SVD", "GITS"]
|
|
|
|
##############################################################################################################
|
|
##############################################################################################################
|
|
# ED #
|
|
##############################################################################################################
|
|
##############################################################################################################
|
|
|
|
|
|
##############################################################################################################
|
|
# ED rgthree Context
|
|
_all_ed_context_input_output_data = {
|
|
"base_ctx": ("base_ctx", "RGTHREE_CONTEXT", "CONTEXT"),
|
|
"model": ("model", "MODEL", "MODEL"),
|
|
"clip": ("clip", "CLIP", "CLIP"),
|
|
"vae": ("vae", "VAE", "VAE"),
|
|
"positive": ("positive", "CONDITIONING", "POSITIVE"),
|
|
"negative": ("negative", "CONDITIONING", "NEGATIVE"),
|
|
"latent": ("latent", "LATENT", "LATENT"),
|
|
"images": ("images", "IMAGE", "IMAGE"),
|
|
"seed": ("seed", "INT", "SEED"),
|
|
"steps": ("steps", "INT", "STEPS"),
|
|
"step_refiner": ("step_refiner", "INT", "STEP_REFINER"),
|
|
"cfg": ("cfg", "FLOAT", "CFG"),
|
|
"ckpt_name": ("ckpt_name", folder_paths.get_filename_list("checkpoints"), "CKPT_NAME"),
|
|
"sampler": ("sampler", comfy.samplers.KSampler.SAMPLERS, "SAMPLER"),
|
|
"scheduler": ("scheduler", SCHEDULERS, "SCHEDULER"),
|
|
"clip_width": ("clip_width", "INT", "CLIP_WIDTH"),
|
|
"clip_height": ("clip_height", "INT", "CLIP_HEIGHT"),
|
|
"text_pos_g": ("text_pos_g", "STRING", "TEXT_POS_G"),
|
|
"text_pos_l": ("text_pos_l", "STRING", "TEXT_POS_L"),
|
|
"text_neg_g": ("text_neg_g", "STRING", "TEXT_NEG_G"),
|
|
"text_neg_l": ("text_neg_l", "STRING", "TEXT_NEG_L"),
|
|
"mask": ("mask", "MASK", "MASK"),
|
|
"control_net": ("control_net", "CONTROL_NET", "CONTROL_NET"),
|
|
"lora_stack": ("lora_stack", "LORA_STACK", "LORA_STACK"),
|
|
"clip_encoder": ("clip_encoder", "CLIP_ENCODER", "CLIP_ENCODER"),
|
|
}
|
|
|
|
def new_context_ed(base_ctx, **kwargs):
|
|
"""Creates a new context from the provided data, with an optional base ctx to start."""
|
|
context = base_ctx if base_ctx is not None else None
|
|
new_ctx = {}
|
|
for key in _all_ed_context_input_output_data:
|
|
if key == "base_ctx":
|
|
continue
|
|
v = kwargs[key] if key in kwargs else None
|
|
new_ctx[key] = v if v is not None else context[key] if context is not None and key in context else None
|
|
return new_ctx
|
|
|
|
def context_2_tuple_ed(ctx, inputs_list=None):
|
|
"""Returns a tuple for returning in the order of the inputs list."""
|
|
if inputs_list is None:
|
|
inputs_list = _all_ed_context_input_output_data.keys()
|
|
tup_list = [ctx,]
|
|
for key in inputs_list:
|
|
if key == "base_ctx":
|
|
continue
|
|
tup_list.append(ctx[key] if ctx is not None and key in ctx else None)
|
|
return tuple(tup_list)
|
|
|
|
##############################################################################################################
|
|
# CASHE
|
|
cashe_ed = {
|
|
"ultra_bbox_detector": [],
|
|
"ultra_segm_detector": [],
|
|
"sam_model": [],
|
|
"upscaler": []
|
|
}
|
|
MAX_CASHE_UPSCALER = 2
|
|
MAX_CASHE_ULTRALYTICS = 2
|
|
|
|
class ED_Cashe:
|
|
@staticmethod
|
|
def cashload(cashe_type, model_name):
|
|
global cashe_ed
|
|
for entry in cashe_ed[cashe_type]:
|
|
if entry[0] == model_name:
|
|
print(f"\r{message('Efficiency Nodes ED:')} using saved {cashe_type} cashe - {entry[0]}")
|
|
return entry[1]
|
|
return None
|
|
|
|
@staticmethod
|
|
def cashsave(cashe_type, model_name, model, max_cashe):
|
|
global cashe_ed
|
|
if len(cashe_ed[cashe_type])>= max_cashe:
|
|
cashe_ed[cashe_type].pop(0)
|
|
cashe_ed[cashe_type].append([model_name, model])
|
|
print(f"\r{message('Efficiency Nodes ED:')} save {cashe_type} cashe - {model_name}")
|
|
return
|
|
|
|
############################################################################################################
|
|
# UTILS
|
|
class AnyType(str):
|
|
def __ne__(self, __value: object) -> bool:
|
|
return False
|
|
|
|
class ED_Util:
|
|
any_typ = AnyType("*")
|
|
|
|
@staticmethod
|
|
def try_install_custom_node(extension_name, custom_node_url):
|
|
msg = f"To use this node, '{extension_name}' extension is required."
|
|
|
|
try:
|
|
import cm_global
|
|
cm_global.try_call(api='cm.try-install-custom-node',
|
|
sender="Efficiency Nodes 💬ED", custom_node_url=custom_node_url, msg=msg)
|
|
except Exception as e:
|
|
print(msg)
|
|
print(f"Efficiency Nodes ED - ComfyUI-Manager is outdated. The custom node installation feature is not available.")
|
|
|
|
raise Exception(f"[ERROR] To use this node, you need to install '{extension_name}'")
|
|
|
|
@staticmethod
|
|
def get_image_size(image):
|
|
samples = image.movedim(-1, 1)
|
|
size = samples.shape[3], samples.shape[2]
|
|
return size
|
|
|
|
@staticmethod
|
|
def get_latent_size(latent, ORIGINAL_VALUES=False):
|
|
lc = latent.copy()
|
|
size = lc["samples"].shape[3], lc["samples"].shape[2]
|
|
if ORIGINAL_VALUES == False:
|
|
size = size[0] * 8, size[1] * 8
|
|
return size
|
|
|
|
@staticmethod
|
|
def apply_load_lora(lora_stack, ckpt, clip, show_node=""):
|
|
lora_params = []
|
|
lora_params.extend(lora_stack)
|
|
|
|
def recursive_load_lora(lora_params, ckpt, clip, folder_paths, lora_count):
|
|
if len(lora_params) == 0:
|
|
return ckpt, clip
|
|
|
|
lora_name, strength_model, strength_clip = lora_params[0]
|
|
|
|
if os.path.isabs(lora_name):
|
|
lora_path = lora_name
|
|
else:
|
|
lora_path = folder_paths.get_full_path("loras", lora_name)
|
|
|
|
lora_count += 1
|
|
lora_model_info = f"{os.path.splitext(os.path.basename(lora_name))[0]}({round(strength_model, 2)},{round(strength_clip, 2)})"
|
|
print(f" [{lora_count}] lora(mod,clip): {lora_model_info}")
|
|
lora_model, lora_clip = comfy.sd.load_lora_for_models(ckpt, clip, comfy.utils.load_torch_file(lora_path), strength_model, strength_clip)
|
|
|
|
# Call the function again with the new lora_model and lora_clip and the remaining tuples
|
|
return recursive_load_lora(lora_params[1:], lora_model, lora_clip, folder_paths, lora_count)
|
|
|
|
if show_node:
|
|
print(f"\033[36m{show_node} - Lora load(Not use Cashe):\033[0m")
|
|
print(f"Lora:")
|
|
lora_count = 0
|
|
|
|
# Unpack lora parameters from the first element of the list for now
|
|
lora_name, strength_model, strength_clip = lora_params[0]
|
|
lora_model, lora_clip = recursive_load_lora(lora_params, ckpt, clip, folder_paths, lora_count)
|
|
|
|
return lora_model, lora_clip
|
|
|
|
@staticmethod
|
|
def strip_comments(text):
|
|
def replacer(match):
|
|
s = match.group(0)
|
|
if s.startswith('/'):
|
|
return " " # note: a space and not an empty string
|
|
else:
|
|
return s
|
|
pattern = re.compile(
|
|
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
|
|
re.DOTALL | re.MULTILINE
|
|
)
|
|
return re.sub(pattern, replacer, text)
|
|
|
|
@staticmethod
|
|
def vae_encode(vae, pixels, is_tiled = False, tile_size = 512):
|
|
if is_tiled:
|
|
latent = nodes.VAEEncodeTiled().encode(vae, pixels, tile_size=tile_size, overlap=64, temporal_size=64, temporal_overlap=8)[0]
|
|
print(f"\r{message('Efficient Nodes ED:')} use tiled VAE encode (tile size:{tile_size})")
|
|
else:
|
|
latent = nodes.VAEEncode().encode(vae, pixels)[0]
|
|
return latent
|
|
|
|
@staticmethod
|
|
def vae_decode(vae, latent, is_tiled = False, tile_size = 512):
|
|
if is_tiled:
|
|
images = nodes.VAEDecodeTiled().decode(vae, latent, tile_size)[0]
|
|
print(f"\r{message('Efficient Nodes ED:')} use tiled VAE decode (tile size:{tile_size})")
|
|
else:
|
|
images = nodes.VAEDecode().decode(vae, latent)[0]
|
|
return images
|
|
|
|
@staticmethod
|
|
def ultra_detector_model(model_name, type):
|
|
detector_type = "ultra_" + type + "_detector"
|
|
if model_name is None or model_name == "None":
|
|
return None
|
|
cash = ED_Cashe.cashload(detector_type, model_name)
|
|
if cash is not None:
|
|
return cash
|
|
|
|
if 'UltralyticsDetectorProvider' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI Impact Subpack', 'https://github.com/ltdrdata/ComfyUI-Impact-Subpack')
|
|
|
|
if type == "bbox":
|
|
model = NODES['UltralyticsDetectorProvider']().doit(model_name)[0]
|
|
else:
|
|
model = NODES['UltralyticsDetectorProvider']().doit(model_name)[1]
|
|
ED_Cashe.cashsave(detector_type, model_name, model, MAX_CASHE_ULTRALYTICS)
|
|
return model
|
|
|
|
@staticmethod
|
|
def load_sam_model(model_name, device_mode):
|
|
if model_name == "None" or model_name is None:
|
|
return None
|
|
cash = ED_Cashe.cashload("sam_model", model_name)
|
|
if cash is not None:
|
|
return cash
|
|
|
|
if 'SAMLoader' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI Impact Subpack', 'https://github.com/ltdrdata/ComfyUI-Impact-Subpack')
|
|
|
|
sam = NODES['SAMLoader']().load_model(model_name, device_mode)[0]
|
|
ED_Cashe.cashsave("sam_model", model_name, sam, MAX_CASHE_ULTRALYTICS)
|
|
return sam
|
|
|
|
@staticmethod
|
|
def load_upscale_model(model_name):
|
|
cash = ED_Cashe.cashload("upscaler", model_name)
|
|
if cash is not None:
|
|
return cash
|
|
model = UpscaleModelLoader().load_model(model_name)[0]
|
|
ED_Cashe.cashsave("upscaler", model_name, model, MAX_CASHE_UPSCALER)
|
|
return model
|
|
|
|
@staticmethod
|
|
def get_widget_value(prompt, node, widget_name):
|
|
# workflow = extra_pnginfo["workflow"] if "workflow" in extra_pnginfo else { "nodes": [] }
|
|
# node_id = None
|
|
# for node in workflow["nodes"]:
|
|
# name = node["type"]
|
|
# if "properties" in node:
|
|
# if "Node name for S&R" in node["properties"]:
|
|
# name = node["properties"]["Node name for S&R"]
|
|
# if name == node_name:
|
|
# node_id = node["id"]
|
|
# break
|
|
# if "title" in node:
|
|
# name = node["title"]
|
|
# if name == node_name:
|
|
# node_id = node["id"]
|
|
# break
|
|
|
|
if node is not None and node["mode"] == 0:
|
|
values = prompt[str(node["id"])]
|
|
if "inputs" in values:
|
|
if widget_name in values["inputs"]:
|
|
value = values["inputs"][widget_name]
|
|
if isinstance(value, list):
|
|
raise ValueError("Converted widgets are not supported via named reference, use the inputs instead.")
|
|
return value
|
|
|
|
return None
|
|
|
|
#@staticmethod
|
|
# def workflow_to_map(workflow):
|
|
# nodes = {}
|
|
# links = {}
|
|
# for link in workflow['links']:
|
|
# links[link[0]] = link[1:]
|
|
# for node in workflow['nodes']:
|
|
# nodes[str(node['id'])] = node
|
|
# return nodes, links
|
|
# for link in nodes[my_unique_id]['outputs'][0]['links']:
|
|
# link_node_id = links[link][2]
|
|
|
|
# for node in workflow["nodes"]:
|
|
# node_id = node["id"]
|
|
# if node["type"] == "Apply LoRA 💬ED" and node["id"] == link_node_id:
|
|
# if node["properties"]["Turn on Applry Lora"] == True:
|
|
# print(f"\033[36mEfficient Loader ED:Apply LoRA ED is linked, Lora loading is pending.\033[0m")
|
|
# use_apply_lora = True
|
|
# break
|
|
|
|
class wildcard_ED:
|
|
wildcards_dir = os.path.abspath(os.path.join(custom_nodes_dir, "ComfyUI-Impact-Pack/wildcards"))
|
|
|
|
@staticmethod
|
|
def sq_wildcard(text, iterate_count):
|
|
def read_wildcard(match, card_name, operation, counter, iterate_count):
|
|
file_path = os.path.join(wildcard_ED.wildcards_dir, f"{card_name}.txt")
|
|
|
|
if not os.path.isfile(file_path):
|
|
raise Exception(f"Efficient nodes ED: wildcard file ({card_name}.txt) is not found.")
|
|
|
|
with open(file_path, "r") as file:
|
|
lines = file.readlines()
|
|
|
|
counter = max(0, min(counter, len(lines) - 1))
|
|
|
|
if operation == "ASC":
|
|
counter = min(counter + iterate_count, len(lines) - 1)
|
|
elif operation == "DSC":
|
|
counter = max(counter - iterate_count, 0)
|
|
else:
|
|
operation = "FIX"
|
|
|
|
result = lines[counter].strip()
|
|
match_after = f"__{card_name}__#{operation}{counter}"
|
|
|
|
print(f" {match} >> {message('counter:')}: {warning('#' + operation + str(counter))}, {message('replaced text:')} {warning(result)}")
|
|
return result, match_after
|
|
|
|
global get_booru_tag_id, get_booru_tag_text_b
|
|
text_b = get_booru_tag_text_b
|
|
|
|
card_files, _ = folder_paths.recursive_search(wildcard_ED.wildcards_dir)
|
|
|
|
pattern = r"(__[\w.\-+/*\\]+?__#[A-Z]{3}[0-9]*)"
|
|
matches = re.findall(pattern, text)
|
|
|
|
if matches:
|
|
print(f"\r{message('Efficient nodes ED:')} sequential wildcards processing -")
|
|
|
|
for match in matches:
|
|
card_name, operation_counter = match.split('__#')
|
|
card_name = card_name[2:]
|
|
operation, counter = operation_counter[:3], int(operation_counter[3:])
|
|
|
|
if card_name.replace('/', '\\') + ".txt" in card_files:
|
|
wc_text, match_after = read_wildcard(match, card_name, operation, counter, iterate_count)
|
|
text_b = text_b.replace(match, match_after)
|
|
else:
|
|
raise Exception(f"Efficient nodes ED: wildcard file ({match}) is not found.")
|
|
|
|
text = text.replace(match, wc_text)
|
|
|
|
PromptServer.instance.send_sync("ed-node-feedback", {
|
|
"node_id": get_booru_tag_id,
|
|
"widget_name": "text_b",
|
|
"type": "text",
|
|
"data": text_b
|
|
})
|
|
|
|
return text
|
|
|
|
@staticmethod
|
|
def process(text, seed, iterate_count=0):
|
|
text = wildcard_ED.sq_wildcard(text, iterate_count)
|
|
|
|
if 'ImpactWildcardProcessor' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI Impact Pack', 'https://github.com/ltdrdata/ComfyUI-Impact-Pack')
|
|
|
|
return NODES['ImpactWildcardProcessor'].process(text=text, seed=seed)
|
|
|
|
class BNK_EncoderWrapper:
|
|
|
|
def __init__(self, token_normalization, weight_interpretation):
|
|
self.token_normalization = token_normalization
|
|
self.weight_interpretation = weight_interpretation
|
|
|
|
def encode(self, clip, text):
|
|
if 'BNK_CLIPTextEncodeAdvanced' not in NODES:
|
|
ED_Util.try_install_custom_node('Advanced CLIP Text Encode', 'https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb')
|
|
|
|
return NODES['BNK_CLIPTextEncodeAdvanced']().encode(clip, text, self.token_normalization, self.weight_interpretation)
|
|
|
|
@staticmethod
|
|
def imp_encode(text, clip, clip_encoder=None):
|
|
if clip_encoder is None:
|
|
return nodes.CLIPTextEncode().encode(clip, text)[0]
|
|
else:
|
|
return clip_encoder.encode(clip, text)[0]
|
|
|
|
##############################################################################################################
|
|
# LoRA Stacker ED
|
|
class LoRA_Stacker_ED:
|
|
MODES = ["simple", "advanced"]
|
|
MAX_LORA_COUNT = 9
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
inputs = {
|
|
"required": {
|
|
"input_mode": (cls.MODES,),
|
|
"lora_count": ("INT", {"default": 3, "min": 0, "max": cls.MAX_LORA_COUNT, "step": 1}),
|
|
}
|
|
}
|
|
|
|
for i in range(1, cls.MAX_LORA_COUNT +1):
|
|
inputs["required"][f"lora_name_{i}"] = (["None"] + folder_paths.get_filename_list("loras"),)
|
|
inputs["required"][f"lora_wt_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
|
|
inputs["required"][f"model_str_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
|
|
inputs["required"][f"clip_str_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
|
|
|
|
inputs["optional"] = {
|
|
"lora_stack": ("LORA_STACK",)
|
|
}
|
|
return inputs
|
|
|
|
RETURN_TYPES = ("LORA_STACK",)
|
|
RETURN_NAMES = ("LORA_STACK",)
|
|
FUNCTION = "lora_stacker_ed"
|
|
CATEGORY = "Efficiency Nodes/Stackers"
|
|
|
|
def lora_stacker_ed(self, input_mode, lora_count, lora_stack=None, **kwargs):
|
|
if lora_count > 0:
|
|
# Extract values from kwargs
|
|
loras = [kwargs.get(f"lora_name_{i}") for i in range(1, lora_count + 1)]
|
|
|
|
# Create a list of tuples using provided parameters, exclude tuples with lora_name as "None"
|
|
if input_mode == "simple":
|
|
weights = [kwargs.get(f"lora_wt_{i}") for i in range(1, lora_count + 1)]
|
|
loras = [(lora_name, lora_weight, lora_weight) for lora_name, lora_weight in zip(loras, weights) if lora_name != "None"]
|
|
else:
|
|
model_strs = [kwargs.get(f"model_str_{i}") for i in range(1, lora_count + 1)]
|
|
clip_strs = [kwargs.get(f"clip_str_{i}") for i in range(1, lora_count + 1)]
|
|
loras = [(lora_name, model_str, clip_str) for lora_name, model_str, clip_str in zip(loras, model_strs, clip_strs) if lora_name != "None"]
|
|
else:
|
|
loras = []
|
|
|
|
# If lora_stack is not None, extend the loras list with lora_stack
|
|
if lora_stack is not None:
|
|
loras.extend([l for l in lora_stack if l[0] != "None"])
|
|
#print(f"\033[36mloras:{(loras,)}\033[0m")
|
|
return (loras,)
|
|
|
|
@classmethod
|
|
def VALIDATE_INPUTS(cls, **kwargs):
|
|
return_value = True
|
|
names = ["None"] + folder_paths.get_filename_list("loras")
|
|
|
|
if kwargs['lora_count'] > 0:
|
|
for i in range(1, cls.MAX_LORA_COUNT +1):
|
|
name = kwargs[f"lora_name_{i}"]
|
|
if not name in names:
|
|
return_value = f"Lora not found: {name}"
|
|
raise Exception(f"\033[30m \033[101mLoRA Stacker ED: LoRA '{name}' is not found\033[0m")
|
|
break
|
|
|
|
return return_value
|
|
|
|
##############################################################################################################
|
|
# Embedding Stacker ED
|
|
class Embedding_Stacker_ED:
|
|
MAX_EMBEDDING_COUNT = 9
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
inputs = {
|
|
"required": {
|
|
"positive_embeddings_count": ("INT", {"default": 1, "min": 0, "max": cls.MAX_EMBEDDING_COUNT, "step": 1}),
|
|
"negative_embeddings_count": ("INT", {"default": 3, "min": 0, "max": cls.MAX_EMBEDDING_COUNT, "step": 1}),
|
|
}
|
|
}
|
|
|
|
for i in range(1, cls.MAX_EMBEDDING_COUNT +1):
|
|
inputs["required"][f"positive_embedding_{i}"] = (["None"] + folder_paths.get_filename_list("embeddings"),)
|
|
inputs["required"][f"positive_emphasis_{i}"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 3.0, "step": 0.05})
|
|
for i in range(1, cls.MAX_EMBEDDING_COUNT +1):
|
|
inputs["required"][f"negative_embedding_{i}"] = (["None"] + folder_paths.get_filename_list("embeddings"),)
|
|
inputs["required"][f"negative_emphasis_{i}"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 3.0, "step": 0.05})
|
|
|
|
inputs["optional"] = {
|
|
"lora_stack": ("LORA_STACK",)
|
|
}
|
|
return inputs
|
|
|
|
RETURN_TYPES = ("LORA_STACK",)
|
|
RETURN_NAMES = ("LORA_STACK",)
|
|
FUNCTION = "embedding_stacker"
|
|
CATEGORY = "Efficiency Nodes/Stackers"
|
|
|
|
def embedding_stacker(self, positive_embeddings_count, negative_embeddings_count, lora_stack=None, **kwargs):
|
|
# POSITIVE EMBEDDING
|
|
if positive_embeddings_count > 0:
|
|
# Extract values from kwargs
|
|
pos_embs = [kwargs.get(f"positive_embedding_{i}") for i in range(1, positive_embeddings_count + 1)]
|
|
# Create a list of tuples using provided parameters, exclude tuples with lora_name as "None"
|
|
pos_emps = [kwargs.get(f"positive_emphasis_{i}") for i in range(1, positive_embeddings_count + 1)]
|
|
pos_embs = [("POS_EMBEDDING", pos_emb, round(pos_emp, 2)) for pos_emb, pos_emp in zip(pos_embs, pos_emps) if pos_emb != "None"]
|
|
else:
|
|
pos_embs = []
|
|
|
|
# NEGTIVE EMBEDDING
|
|
if negative_embeddings_count > 0:
|
|
# Extract values from kwargs
|
|
neg_embs = [kwargs.get(f"negative_embedding_{i}") for i in range(1, negative_embeddings_count + 1)]
|
|
# Create a list of tuples using provided parameters, exclude tuples with lora_name as "None"
|
|
neg_emps = [kwargs.get(f"negative_emphasis_{i}") for i in range(1, negative_embeddings_count + 1)]
|
|
neg_embs = [("NEG_EMBEDDING", neg_emb, round(neg_emp, 2)) for neg_emb, neg_emp in zip(neg_embs, neg_emps) if neg_emb != "None"]
|
|
else:
|
|
neg_embs = []
|
|
|
|
loras = pos_embs + neg_embs
|
|
# If lora_stack is not None, extend the loras list with lora_stack
|
|
if lora_stack is not None:
|
|
loras.extend([l for l in lora_stack if l[0] != "None"])
|
|
#print(f"\033[36mlorasEmbedding:{(loras,)}\033[0m")
|
|
return (loras,)
|
|
|
|
@classmethod
|
|
def VALIDATE_INPUTS(cls, **kwargs):
|
|
return_value = True
|
|
names = ["None"] + folder_paths.get_filename_list("embeddings")
|
|
|
|
if kwargs['positive_embeddings_count'] > 0:
|
|
for i in range(1, cls.MAX_EMBEDDING_COUNT +1):
|
|
name = kwargs[f"positive_embedding_{i}"]
|
|
if not name in names:
|
|
return_value = f"Embedding not found: {name}"
|
|
raise Exception(f"\033[30m \033[101mEmbedding Stacker ED: embedding '{name}' is not found\033[0m")
|
|
break
|
|
|
|
if kwargs['negative_embeddings_count'] > 0:
|
|
for i in range(1, cls.MAX_EMBEDDING_COUNT +1):
|
|
name = kwargs[f"negative_embedding_{i}"]
|
|
if not name in names:
|
|
return_value = f"Embedding not found: {name}"
|
|
raise Exception(f"\033[30m \033[101mEmbedding Stacker ED: embedding '{name}' is not found\033[0m")
|
|
break
|
|
|
|
return return_value
|
|
|
|
@staticmethod
|
|
def embedding_process(lora_stack, positive, negative, positive_refiner, negative_refiner):
|
|
if lora_stack is None:
|
|
return (lora_stack, positive, negative, positive_refiner, negative_refiner)
|
|
|
|
new_lora_stack = []
|
|
pos = positive
|
|
neg = negative
|
|
pos_refiner = positive_refiner
|
|
neg_refiner = negative_refiner
|
|
|
|
for entry in lora_stack:
|
|
if entry[0] == "POS_EMBEDDING":
|
|
emb = "embedding:" + os.path.splitext(entry[1])[0]
|
|
if entry[2] != 1:
|
|
emb = f"({emb}:{entry[2]})"
|
|
pos = f"{positive.rstrip(' ,')}, {emb},"
|
|
positive = pos
|
|
if positive_refiner is not None:
|
|
pos_refiner = f"{positive_refiner.rstrip(' ,')}, {emb},"
|
|
positive_refiner = pos_refiner
|
|
elif entry[0] == "NEG_EMBEDDING":
|
|
emb = "embedding:" + os.path.splitext(entry[1])[0]
|
|
if entry[2] != 1:
|
|
emb = f"({emb}:{entry[2]})"
|
|
neg = f"{negative.rstrip(' ,')}, {emb},"
|
|
negative = neg
|
|
if negative_refiner is not None:
|
|
neg_refiner = f"{negative_refiner.rstrip(' ,')}, {emb},"
|
|
negative_refiner = neg_refiner
|
|
else:
|
|
new_lora_stack.append(entry)
|
|
|
|
if len(new_lora_stack) == 0:
|
|
new_lora_stack = None
|
|
#print(f"\033[36mpos:{pos}\033[0m")
|
|
#print(f"\033[36mneg:{neg}\033[0m")
|
|
return (new_lora_stack, pos, neg, pos_refiner, neg_refiner)
|
|
|
|
##############################################################################################################
|
|
# Wildcard Encode ED
|
|
list_counter_map = {}
|
|
class WildcardEncode_ED:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"context": ("RGTHREE_CONTEXT",), },
|
|
"optional": {
|
|
"model": ("MODEL",),
|
|
"clip": ("CLIP",),
|
|
"vae": ("VAE",),
|
|
"positive": ("CONDITIONING",),
|
|
"negative": ("CONDITIONING",),
|
|
"latent": ("LATENT",),
|
|
"images": ("IMAGE",),
|
|
"signal": (ED_Util.any_typ,),
|
|
},
|
|
"hidden": {"unique_id": "UNIQUE_ID"},
|
|
}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "MODEL", "CLIP", "VAE", "CONDITIONING", "CONDITIONING", "LATENT", "IMAGE", "STRING",)
|
|
RETURN_NAMES = ("CONTEXT", "MODEL", "CLIP", "VAE", "POSITIVE" ,"NEGATIVE", "LATENT", "IMAGE", "TEXT_POS",)
|
|
FUNCTION = "wildcard_encode"
|
|
CATEGORY = "Efficiency Nodes/Prompt"
|
|
|
|
def wildcard_encode(self, context=None, signal=None, unique_id=None, **kwargs):
|
|
ctx = new_context_ed(context, **kwargs)
|
|
|
|
_, model, clip, vae, positive, negative, latent, images, seed, batch_size, pos_prompt, lora_stack, clip_encoder = context_2_tuple_ed(ctx,["model", "clip", "vae", "positive", "negative", "latent", "images", "seed", "step_refiner", "text_pos_g", "lora_stack", "clip_encoder"])
|
|
|
|
if pos_prompt:
|
|
#Process wildcard
|
|
if unique_id not in list_counter_map:
|
|
count = 0
|
|
else:
|
|
count = list_counter_map[unique_id]
|
|
list_counter_map[unique_id] = count + 1
|
|
|
|
if batch_size > 1:
|
|
seed += count
|
|
|
|
if not positive:
|
|
pos_prompt = wildcard_ED.process(pos_prompt, seed, count)
|
|
positive = BNK_EncoderWrapper.imp_encode(pos_prompt, clip, clip_encoder)
|
|
|
|
if lora_stack:
|
|
model, clip = ED_Util.apply_load_lora(lora_stack, model, clip, "Wildcard Encode ED")
|
|
lora_stack = None
|
|
|
|
ctx = new_context_ed(ctx, model=model, clip=clip, vae=vae, positive=positive, negative=negative, latent=latent, images=images, seed=seed, text_pos_g=pos_prompt)
|
|
return (ctx, model, clip, vae, positive, negative, latent, images, pos_prompt,)
|
|
|
|
###############################################################################################################
|
|
# Efficient Loader ED
|
|
class EfficientLoader_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
paint_mode = {
|
|
"✍️ Txt2Img": 1,
|
|
"🦱 Img2Img": 2,
|
|
"🎨 Inpaint(Ksampler)": 3,
|
|
"🎨 Inpaint(MaskDetailer)": 4,
|
|
}
|
|
inputs = {"required": { "ckpt_name": (["🔌 model_opt input"] + folder_paths.get_filename_list("checkpoints"),),
|
|
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
|
|
"clip_skip": ("INT", {"default": -2, "min": -24, "max": 0, "step": 1}),
|
|
"paint_mode": ( list(paint_mode.keys()), {"default": "✍️ Txt2Img"}),
|
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 262144}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (SCHEDULERS,),
|
|
#"positive": ("STRING", {"default": "","multiline": True, "dynamicPrompts": True}),
|
|
#"negative": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": True}),
|
|
"image_width": ("INT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
"image_height": ("INT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"lora_stack": ("LORA_STACK",),
|
|
"cnet_stack": ("CONTROL_NET_STACK",),
|
|
"pixels": ("IMAGE",),
|
|
"mask": ("MASK",),
|
|
"model_opt": ("MODEL",),
|
|
"clip_opt": ("CLIP",),
|
|
"positive": ("STRING", {"forceInput": True}),
|
|
"negative": ("STRING", {"forceInput": True}),},
|
|
"hidden": { "prompt": "PROMPT",
|
|
"my_unique_id": "UNIQUE_ID",
|
|
"extra_pnginfo": "EXTRA_PNGINFO",}
|
|
}
|
|
return inputs
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "DEPENDENCIES",)
|
|
RETURN_NAMES = ("CONTEXT", "MODEL", "POSITIVE", "NEGATIVE", "LATENT", "VAE", "CLIP", "DEPENDENCIES",)
|
|
OUTPUT_IS_LIST = (False, False, False, False, True, False, False, False,)
|
|
FUNCTION = "efficientloader_ed"
|
|
CATEGORY = "Efficiency Nodes/Loaders"
|
|
|
|
def efficientloader_ed(self, ckpt_name, vae_name, clip_skip, paint_mode, batch_size,
|
|
seed, cfg, sampler_name, scheduler, image_width, image_height, lora_stack=None, cnet_stack=None,
|
|
pixels=None, mask=None, model_opt=None, clip_opt=None, positive="", negative="",
|
|
refiner_name="None", positive_refiner=None, negative_refiner=None, ascore=None, prompt=None,
|
|
my_unique_id=None, extra_pnginfo=None, loader_type="regular"):
|
|
|
|
global loaded_objects
|
|
|
|
# Clean globally stored objects
|
|
globals_cleanup(prompt)
|
|
list_counter_map.clear()
|
|
|
|
# Strip comments
|
|
positive_prompt = ED_Util.strip_comments(positive)
|
|
negative_prompt = ED_Util.strip_comments(negative)
|
|
|
|
# Embedding stacker process
|
|
lora_stack, positive_prompt, negative_prompt, positive_refiner, negative_refiner = Embedding_Stacker_ED.embedding_process(lora_stack, positive_prompt, negative_prompt, positive_refiner, negative_refiner)
|
|
|
|
# GET PROPERTIES #
|
|
properties = self.get_properties(extra_pnginfo, my_unique_id, prompt)
|
|
|
|
# Model, clip, vae, lora_params
|
|
model, clip, vae, lora_stack, lora_params, is_flux_model = \
|
|
self.load_model_clip_vae(properties, ckpt_name, vae_name, clip_skip,lora_stack, model_opt, clip_opt, prompt, my_unique_id)
|
|
|
|
#################### PROMPT ENCODING #####################
|
|
#Encode prompt
|
|
if not is_flux_model and (properties['token_normalization'] != "none" or properties['weight_interpretation'] != "comfy"):
|
|
print(f"\033[34mEfficient Loader ED : Use CLIP Text Encode Advanced - token normalization : {properties['token_normalization']}, weight interpretation : {properties['weight_interpretation']}\033[0m")
|
|
clip_encoder = BNK_EncoderWrapper(properties['token_normalization'], properties['weight_interpretation'])
|
|
else:
|
|
clip_encoder = None
|
|
|
|
negative_encoded = BNK_EncoderWrapper.imp_encode(negative_prompt, clip, clip_encoder)
|
|
|
|
if is_flux_model or not properties['wildcard_node_exist'] or cnet_stack or not properties['use_latent_rebatch'] or batch_size == 1 or properties['use_apply_lora']:
|
|
positive_prompt = wildcard_ED.process(positive_prompt, seed)
|
|
positive_encoded = BNK_EncoderWrapper.imp_encode(positive_prompt, clip, clip_encoder)
|
|
else:
|
|
positive_encoded = None
|
|
|
|
#################### LATENT PROCESSING ####################
|
|
#✍️ Txt2Img
|
|
if paint_mode == "✍️ Txt2Img":
|
|
# Create Empty Latent
|
|
samples_latent = nodes.EmptyLatentImage().generate(image_width, image_height, batch_size)[0]
|
|
else:
|
|
if pixels is None:
|
|
raise Exception("Efficient Loader ED: Img2Img or inpaint mode requires an image.\n\n\n\n\n\n")
|
|
image_width, image_height = ED_Util.get_image_size(pixels)
|
|
|
|
#VAE Encode
|
|
latent_t = ED_Util.vae_encode(vae, pixels, properties['tiled_vae_encode'])
|
|
|
|
# 🎨 Inpaint
|
|
if paint_mode == "🎨 Inpaint(Ksampler)":
|
|
if mask is None:
|
|
raise Exception("Efficient Loader ED: inpaint mode requires an mask.\n\n\n\n\n\n")
|
|
if not positive_encoded:
|
|
positive_prompt = wildcard_ED.process(positive_prompt, seed)
|
|
positive_encoded = BNK_EncoderWrapper.imp_encode(positive_prompt, clip, clip_encoder)
|
|
|
|
positive_encoded, negative_encoded, latent_t = nodes.InpaintModelConditioning().encode(positive_encoded, negative_encoded, pixels, vae, mask)
|
|
|
|
elif paint_mode == "🎨 Inpaint(MaskDetailer)":
|
|
if mask is None:
|
|
raise Exception("Efficient Loader ED: inpaint mode requires an mask.\n\n\n\n\n\n")
|
|
|
|
#RepeatLatentBatch
|
|
samples_latent = nodes.RepeatLatentBatch().repeat(latent_t, batch_size)[0]
|
|
|
|
# change image_width and image_height widget from image size
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "image_width", "type": "text", "data": image_width})
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "image_height", "type": "text", "data": image_height})
|
|
|
|
# LatentRebatch -> List
|
|
if properties['use_latent_rebatch'] and paint_mode != "🎨 Inpaint(MaskDetailer)" and not properties['use_apply_lora']:
|
|
latent_list = LatentRebatch().rebatch((samples_latent,), (1,))[0]
|
|
else:
|
|
latent_list = []
|
|
latent_list.append(samples_latent)
|
|
|
|
if positive_encoded:
|
|
# Apply ControlNet and Regional Stack if given
|
|
model, positive_encoded, negative_encoded = ED_Reg.apply_controlnet_region(cnet_stack, positive_encoded, negative_encoded, model, clip, seed, clip_encoder)
|
|
|
|
# Data for XY Plot
|
|
refiner_clip = refiner_clip_skip = None
|
|
dependencies = (vae_name, ckpt_name, clip, clip_skip, refiner_name, refiner_clip, refiner_clip_skip,
|
|
positive_prompt, negative_prompt, properties['token_normalization'], properties['weight_interpretation'], ascore,
|
|
image_width, image_height, lora_params, cnet_stack)
|
|
|
|
context = new_context_ed(None, model=model, clip=clip, vae=vae, positive=positive_encoded, negative=negative_encoded,
|
|
latent=samples_latent, images=pixels, seed=seed, step_refiner=batch_size, cfg=cfg, ckpt_name=ckpt_name, sampler=sampler_name, scheduler=scheduler, clip_width=image_width, clip_height=image_height, text_pos_g=positive_prompt, text_neg_g=negative_prompt, mask=mask, lora_stack=lora_stack, clip_encoder=clip_encoder)
|
|
|
|
return (context, model, positive_encoded, negative_encoded, latent_list, vae, clip, dependencies,)
|
|
|
|
@classmethod
|
|
def VALIDATE_INPUTS(cls, **kwargs):
|
|
return_value = True
|
|
names = ["🔌 model_opt input"] + folder_paths.get_filename_list("checkpoints")
|
|
|
|
name = kwargs["ckpt_name"]
|
|
|
|
if not name in names:
|
|
return_value = f"Checkpoint not found: {name}"
|
|
raise Exception(f"\033[30m \033[101mEfficient Loader ED : checkpoint '{name}' is not found\033[0m")
|
|
|
|
return return_value
|
|
|
|
@staticmethod
|
|
def get_properties(extra_pnginfo, my_unique_id, prompt):
|
|
properties = {
|
|
"this_sync": True,
|
|
"tiled_vae_encode": False,
|
|
"use_latent_rebatch": True,
|
|
"use_apply_lora": False,
|
|
"token_normalization": "none",
|
|
"weight_interpretation": "comfy",
|
|
"wildcard_node_exist": False,
|
|
}
|
|
|
|
if extra_pnginfo and "workflow" in extra_pnginfo:
|
|
workflow = extra_pnginfo["workflow"]
|
|
# nodes, links = workflow_to_map(workflow)
|
|
for node in workflow["nodes"]:
|
|
if node["id"] == int(my_unique_id):
|
|
properties['tiled_vae_encode'] = node["properties"]["Use tiled VAE encode"]
|
|
if properties['use_latent_rebatch']:
|
|
properties['use_latent_rebatch'] = node["properties"]["Use Latent Rebatch"]
|
|
# properties['this_sync'] = node["properties"]["Synchronize widget with image size"]
|
|
properties['token_normalization'] = node["properties"]["Token normalization"]
|
|
properties['weight_interpretation'] = node["properties"]["Weight interpretation"]
|
|
if node["type"] == "Wildcard Encode 💬ED" and node["mode"] == 0:
|
|
properties['wildcard_node_exist'] = True
|
|
if not properties['use_apply_lora']:
|
|
if node["properties"]["Turn on Apply Lora"] == True:
|
|
print(f"\033[36mWildcard Encode ED: Turn on Apply Lora, loading Lora is pending.\033[0m")
|
|
properties['use_apply_lora'] = True
|
|
if node["type"] == "KSampler (Efficient) 💬ED":
|
|
if node["mode"] != 0:
|
|
properties['use_latent_rebatch'] = False
|
|
if node["type"] == "Refiner Script 💬ED" and node["mode"] == 0:
|
|
if ED_Util.get_widget_value(prompt, node, "ignore_batch_size") == True:
|
|
properties['use_latent_rebatch'] = False
|
|
|
|
return properties
|
|
|
|
@staticmethod
|
|
def load_model_clip_vae(properties, ckpt_name, vae_name, clip_skip, lora_stack, model_opt, clip_opt, prompt, my_unique_id):
|
|
# Retrieve cache numbers
|
|
vae_cache, ckpt_cache, lora_cache, refn_cache = get_cache_numbers("Efficient Loader")
|
|
|
|
global loaded_objects
|
|
lora_params = None
|
|
|
|
if lora_stack:
|
|
lora_params = []
|
|
lora_params.extend(lora_stack)
|
|
|
|
if ckpt_name == "🔌 model_opt input":
|
|
if model_opt is None:
|
|
raise Exception("Efficient Loader ED: ckpt_name or model_opt is required.\n\n\n\n\n\n")
|
|
if clip_opt is None:
|
|
raise Exception("Efficient Loader ED: clip_opt is required when using model_opt.\n\n\n\n\n\n")
|
|
|
|
loaded_objects["ckpt"] = []
|
|
loaded_objects["lora"] = []
|
|
model = model_opt
|
|
clip =clip_opt
|
|
loaded_objects["ckpt"].append(("using model_opt", None, None, None, [my_unique_id]))
|
|
|
|
if vae_name != "Baked VAE":
|
|
vae = load_vae(vae_name, my_unique_id, cache=vae_cache, cache_overwrite=True)
|
|
else:
|
|
Exception("Efficient Loader ED: Baked VAE is None \n\n\n\n\n\n")
|
|
|
|
###print_loaded_objects_entries()
|
|
print_loaded_objects_entries(my_unique_id, prompt)
|
|
|
|
### lora apply when using model_opt !!After print_loaded_objects_entries()!!
|
|
if lora_stack and not properties['use_apply_lora']:
|
|
model, clip = ED_Util.apply_load_lora(lora_stack, model, clip)
|
|
lora_stack = None
|
|
|
|
else:
|
|
if lora_stack and not properties['use_apply_lora']:
|
|
# Load LoRa(s)
|
|
model, clip = load_lora(lora_params, ckpt_name, my_unique_id, cache=lora_cache, ckpt_cache=ckpt_cache, cache_overwrite=True)
|
|
lora_stack = None
|
|
|
|
if vae_name == "Baked VAE":
|
|
vae = get_bvae_by_ckpt_name(ckpt_name)
|
|
|
|
else:
|
|
loaded_objects["lora"] = []
|
|
model, clip, vae = load_checkpoint(ckpt_name, my_unique_id, cache=ckpt_cache, cache_overwrite=True)
|
|
|
|
# Check for custom VAE
|
|
if vae_name != "Baked VAE":
|
|
vae = load_vae(vae_name, my_unique_id, cache=vae_cache, cache_overwrite=True)
|
|
|
|
###print_loaded_objects_entries()
|
|
print_loaded_objects_entries(my_unique_id, prompt)
|
|
|
|
modle_type = None
|
|
if hasattr(model, "model"):
|
|
modle_type = model.model.__class__.__name__
|
|
|
|
if modle_type == "Flux":
|
|
print(f"\r{message('Efficient Loader ED :')}\033[38;5;173m model type is {modle_type}, ignore clip skip\033[0m")
|
|
is_flux_model = True
|
|
elif clip_skip == 0:
|
|
print(f"\r{message('Efficient Loader ED :')}\033[38;5;173m clip skip is 0, ignore clip skip\033[0m")
|
|
is_flux_model = False
|
|
else:
|
|
clip = nodes.CLIPSetLastLayer().set_last_layer(clip, clip_skip)[0]
|
|
print(f"Clip skip: {clip_skip}")
|
|
is_flux_model = False
|
|
|
|
return model, clip, vae, lora_stack, lora_params, is_flux_model
|
|
|
|
#####################################################################################################
|
|
# Load Image ED
|
|
prompt_blacklist_ed = set([
|
|
'filename_prefix', 'file'
|
|
])
|
|
|
|
class LoadImage_ED(nodes.LoadImage):
|
|
UPSCALE_METHODS = ["🚫 Do not upscale", "nearest-exact", "bilinear", "area", "bicubic", "lanczos"] + ["upscale_models/"+x for x in folder_paths.get_filename_list("upscale_models")]
|
|
PROPORTION_METHODS = ["disabled", "1.5x", "2x", "3x", "4x", "based on width", "based on height", "disabled & crop center"]
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
input_dir = folder_paths.get_input_directory()
|
|
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
|
return {"required": {
|
|
"image": (sorted(files), {"image_upload": True}),
|
|
"upscale_method": (s.UPSCALE_METHODS,),
|
|
"width": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
"height": ("INT", {"default": 1024, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
"keep_proportions": (s.PROPORTION_METHODS,),},
|
|
"hidden": { "my_unique_id": "UNIQUE_ID",}
|
|
}
|
|
CATEGORY = "Efficiency Nodes/Image"
|
|
RETURN_TYPES = ("IMAGE", "MASK", "STRING",)
|
|
RETURN_NAMES = ("IMAGE", "MASK", "PROMPT_TEXT",)
|
|
FUNCTION = "load_image"
|
|
|
|
def load_image(self, image, upscale_method, width, height, keep_proportions, my_unique_id):
|
|
output_image, output_mask = super().load_image(image)
|
|
image_width, image_height = ED_Util.get_image_size(output_image)
|
|
text = self.get_prompt(image)
|
|
|
|
if "not upscale" not in upscale_method:
|
|
if "upscale_models" in upscale_method:
|
|
upscaler = ED_Util.load_upscale_model(upscale_method.split("/", 1)[-1])
|
|
output_image = ImageUpscaleWithModel().upscale(upscaler, output_image)[0]
|
|
upscale_method = "lanczos"
|
|
|
|
width, height = self.calculate_dimensions(width, height, keep_proportions, image_width, image_height)
|
|
crop = "center" if keep_proportions == "disabled & crop center" else "disabled"
|
|
|
|
self.send_feedback(my_unique_id, width, height)
|
|
|
|
output_image = nodes.ImageScale().upscale(output_image, upscale_method, width, height, crop)[0]
|
|
if output_mask is not None:
|
|
output_mask = self.upscale_mask(output_mask, upscale_method, width, height, crop)
|
|
|
|
return output_image, output_mask, text
|
|
|
|
@staticmethod
|
|
def calculate_dimensions(width, height, keep_proportions, original_width, original_height):
|
|
scale_factors = {"1.5x": 1.5, "2x": 2, "3x": 3, "4x": 4}
|
|
|
|
if keep_proportions in {"based on width", "based on height"}:
|
|
width = original_width if width == 0 else width
|
|
height = original_height if height == 0 else height
|
|
ratio = (width / original_width) if keep_proportions == "based on width" else (height / original_height)
|
|
return round(original_width * ratio), round(original_height * ratio)
|
|
|
|
elif keep_proportions in scale_factors:
|
|
factor = scale_factors[keep_proportions]
|
|
return int(original_width * factor), int(original_height * factor)
|
|
|
|
return width, height
|
|
|
|
@staticmethod
|
|
def send_feedback(unique_id, width, height):
|
|
feedback = PromptServer.instance.send_sync
|
|
feedback("ed-node-feedback", {"node_id": unique_id, "widget_name": "width", "type": "text", "data": width})
|
|
feedback("ed-node-feedback", {"node_id": unique_id, "widget_name": "height", "type": "text", "data": height})
|
|
|
|
@staticmethod
|
|
def upscale_mask(mask, method, width, height, crop):
|
|
method = "bicubic" if method == "lanczos" else method
|
|
mask = mask.unsqueeze(1)
|
|
mask = comfy.utils.common_upscale(mask, width, height, method, crop)
|
|
return mask.squeeze(1)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(s, image, **kwargs):
|
|
image_path = folder_paths.get_annotated_filepath(image)
|
|
m = hashlib.sha256()
|
|
with open(image_path, 'rb') as f:
|
|
m.update(f.read())
|
|
return m.digest().hex()
|
|
|
|
@classmethod
|
|
def VALIDATE_INPUTS(s, image, **kwargs):
|
|
if not folder_paths.exists_annotated_filepath(image):
|
|
return "Invalid image file: {}".format(image)
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def get_prompt(image):
|
|
image_path = folder_paths.get_annotated_filepath(image)
|
|
img = Image.open(image_path)
|
|
info = img.info
|
|
|
|
positive = ""
|
|
negative = ""
|
|
text = ""
|
|
prompt_dicts = {}
|
|
node_inputs = {}
|
|
|
|
def get_node_inputs(x):
|
|
if x in node_inputs:
|
|
return node_inputs[x]
|
|
else:
|
|
node_inputs[x] = None
|
|
|
|
obj = NODES.get(x, None)
|
|
if obj is not None:
|
|
input_types = obj.INPUT_TYPES()
|
|
node_inputs[x] = input_types
|
|
return input_types
|
|
else:
|
|
return None
|
|
|
|
if not isinstance(info, dict):
|
|
text = "There is no prompt information within the image."
|
|
|
|
elif 'prompt' in info:
|
|
prompt = json.loads(info['prompt'])
|
|
for k, v in prompt.items():
|
|
input_types = get_node_inputs(v['class_type'])
|
|
if input_types is not None and 'required' in input_types:
|
|
inputs = input_types['required'].copy()
|
|
if 'optional' in input_types:
|
|
inputs.update(input_types['optional'])
|
|
|
|
for name, value in inputs.items():
|
|
if name in prompt_blacklist_ed:
|
|
continue
|
|
|
|
if value[0] == 'STRING' and name in v['inputs'] and not isinstance(v['inputs'][name], list):
|
|
prompt_dicts[f"{k}.{name.strip()}"] = (v['class_type'], v['inputs'][name])
|
|
if value[0] == 'INT' and name in v['inputs'] and name.lower() == 'seed':
|
|
prompt_dicts[f"{k}.{name.strip()}"] = (v['class_type'], v['inputs'][name])
|
|
|
|
for k, v in prompt_dicts.items():
|
|
if type(v[1]) is str and ',' in v[1]:
|
|
text += f"{k} [{v[0]}] ==> {v[1]}\n"
|
|
elif type(v[1]) is int:
|
|
text += f"{k} [{v[0]}] ==> {v[1]}\n"
|
|
#positive = prompt_dicts.get(positive_id.strip(), "")
|
|
#negative = prompt_dicts.get(negative_id.strip(), "")
|
|
|
|
elif 'exif' in info:
|
|
text = info['exif'].decode("utf-16-be")
|
|
|
|
# 영어 알파벳이 처음 등장하는 위치 찾기
|
|
match = re.search(r'[a-zA-Z]', text)
|
|
if match:
|
|
text = text[match.start():] # 첫 번째 영어 문자가 나온 위치부터 출력
|
|
text = text.replace("\n","\n\n") + ("\n")
|
|
else:
|
|
text = "There is no prompt information within the image."
|
|
|
|
else:
|
|
text = "There is no prompt information within the image."
|
|
|
|
return text
|
|
|
|
|
|
# Save Image ED
|
|
class SaveImage_ED(nodes.SaveImage):
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": { },
|
|
"optional": {
|
|
"context_opt": ("RGTHREE_CONTEXT",),
|
|
"image_opt": ("IMAGE",),
|
|
"filename_prefix": ("STRING", {"default": "%date:yyyy-MM-dd%/%date:MM-dd%"}),
|
|
},
|
|
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"},
|
|
}
|
|
|
|
RETURN_TYPES = ()
|
|
FUNCTION = "save_images"
|
|
OUTPUT_NODE = True
|
|
CATEGORY = "Efficiency Nodes/Image"
|
|
|
|
def save_images(self, context_opt=None, image_opt=None, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None, unique_id=None):
|
|
images = None
|
|
|
|
if context_opt is not None:
|
|
_, images = context_2_tuple_ed(context_opt,["images"])
|
|
if image_opt is not None:
|
|
images = image_opt
|
|
|
|
if images is not None:
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": unique_id, "widget_name": "play_sound", "type": "sound", "data": "play_sound"})
|
|
|
|
return super().save_images(images, filename_prefix=filename_prefix, prompt=prompt, extra_pnginfo=extra_pnginfo)
|
|
else:
|
|
return { "ui": { "images": list() } }
|
|
|
|
###############################################################################################################
|
|
# Control Net Script ED
|
|
# class Control_Net_Script_ED:
|
|
# @classmethod
|
|
# def INPUT_TYPES(cls):
|
|
# return {"required": {"control_net": ("CONTROL_NET",),
|
|
# "image": ("IMAGE",),
|
|
# "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
|
# "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
|
# "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001})},
|
|
# "optional": {"cnet_stack": ("CONTROL_NET_STACK",),
|
|
# "script": ("SCRIPT",)},
|
|
# }
|
|
|
|
# RETURN_TYPES = ("SCRIPT",)
|
|
# RETURN_NAMES = ("SCRIPT",)
|
|
# FUNCTION = "control_net_script_ed"
|
|
# CATEGORY = "Efficiency Nodes/Scripts"
|
|
|
|
# def control_net_script_ed(self, control_net, image, strength, start_percent, end_percent, cnet_stack=None, script=None):
|
|
# script = script or {}
|
|
# # If control_net_stack is None, initialize as an empty list
|
|
# cnet_stack = [] if cnet_stack is None else cnet_stack
|
|
|
|
# # Extend the control_net_stack with the new tuple
|
|
# cnet_stack.extend([(control_net, image, strength, start_percent, end_percent)])
|
|
# script["control_net"] = (cnet_stack)
|
|
# return (script,)
|
|
|
|
##############################################################################################################
|
|
# Refiner Script ED
|
|
class Refiner_Script_ED:
|
|
SET_SEED_CFG_SAMPLER = {
|
|
"from context": 1,
|
|
"from node only": 2,
|
|
}
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required": {
|
|
"set_seed_cfg_sampler": (list(cls.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
"add_noise": (["enable", "disable"], ),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"steps": ("INT", {"default": 30, "min": 1, "max": 10000}),
|
|
"cfg": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 100.0}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "dpmpp_sde"}),
|
|
"scheduler": (SCHEDULERS, {"default": "karras"}),
|
|
"denoise": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"start_at_step": ("INT", {"default": 17, "min": 0, "max": 10000}),
|
|
"end_at_step": ("INT", {"default": 100, "min": 0, "max": 10000}),
|
|
"ignore_batch_size": ("BOOLEAN", {"default": True}),
|
|
"do_refine_only": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {"refiner_model_opt": ("MODEL",),
|
|
"refiner_clip_opt": ("CLIP",),
|
|
"refiner_vae_opt": ("VAE",),
|
|
#"script": ("SCRIPT",),
|
|
},
|
|
"hidden": {"my_unique_id": "UNIQUE_ID",},}
|
|
RETURN_TYPES = ("SCRIPT",)
|
|
RETURN_NAMES = ("SCRIPT",)
|
|
FUNCTION = "refiner_script_ed"
|
|
CATEGORY = "Efficiency Nodes/Scripts"
|
|
|
|
def refiner_script_ed(self, set_seed_cfg_sampler, add_noise, seed, steps, cfg, sampler_name, scheduler, denoise, start_at_step, end_at_step, ignore_batch_size, do_refine_only, refiner_model_opt=None, refiner_clip_opt=None, refiner_vae_opt=None, script=None, my_unique_id=None):
|
|
script = script or {}
|
|
|
|
if refiner_model_opt:
|
|
if refiner_clip_opt is None or refiner_vae_opt is None:
|
|
raise Exception("Refiner Script ED: refiner_clip and refiner_vae are required.\n\n\n\n\n\n")
|
|
|
|
if set_seed_cfg_sampler == "from context":
|
|
seed = None
|
|
|
|
refiner_script = (refiner_model_opt, refiner_clip_opt, refiner_vae_opt, add_noise, seed, steps, cfg, sampler_name, scheduler, denoise, start_at_step, end_at_step, ignore_batch_size, do_refine_only)
|
|
#print(f"\033[38;5;173mRefiner Script ED: Refiner script loading. steps:{steps}, start step:{start_at_step}, end step:{end_at_step}\033[0m")
|
|
script["refiner_script"] = refiner_script
|
|
|
|
return (script,)
|
|
|
|
##############################################################################################################
|
|
#Reginal scripts process
|
|
class ED_Reg:
|
|
@staticmethod
|
|
def apply_controlnet_region(cnet_stack, positive_encoded, negative_encoded, model=None, clip=None, seed=0, clip_encoder=None):
|
|
if cnet_stack:
|
|
for control_net_tuple in cnet_stack:
|
|
if str(control_net_tuple[-1]) == "Region_Stack":
|
|
model, positive_encoded = ED_Reg.process_region_stack(control_net_tuple, positive_encoded, model, clip, seed, clip_encoder)
|
|
|
|
for control_net_tuple in cnet_stack:
|
|
if str(control_net_tuple[-1]) != "Region_Stack":
|
|
print(f"\r{message('Efficiency Nodes ED:')}\033[38;5;173m apply control net stack.\033[0m")
|
|
|
|
control_net, image, strength, start_percent, end_percent = control_net_tuple
|
|
positive_encoded, negative_encoded = nodes.ControlNetApplyAdvanced().apply_controlnet(positive_encoded, negative_encoded, control_net, image, strength, start_percent, end_percent)
|
|
|
|
return (model, positive_encoded, negative_encoded)
|
|
|
|
@staticmethod
|
|
def process_region_stack(control_net_tuple, base_positive, model, clip, seed, clip_encoder):
|
|
region_script, global_prompt_weight, width, height, _ = control_net_tuple
|
|
print(f"\r{message('Efficiency Nodes ED:')}\033[38;5;173m apply regional stack - script count : {len(region_script)}\033[0m")
|
|
|
|
processd_positive, regions = ED_Reg.process_region_script(region_script, base_positive, model, clip, seed, clip_encoder)
|
|
|
|
flattened_regions = ED_Reg.attention_couple_regions(regions)
|
|
|
|
if 'AttentionCouple' not in NODES:
|
|
ED_Util.try_install_custom_node('A8R8 ComfyUI Nodes', 'https://github.com/ramyma/A8R8_ComfyUI_nodes')
|
|
|
|
model = NODES['AttentionCouple']().attention_couple(model, global_prompt_weight,
|
|
base_positive, height, width, flattened_regions)[0]
|
|
|
|
return (model, processd_positive)
|
|
|
|
@staticmethod
|
|
def process_region_script(region_script, base_positive, model, clip, seed, clip_encoder):
|
|
regions = []
|
|
for region_tuple in region_script:
|
|
mask, prompt, condition_strength, set_conditon_area, region_weight, lora_stack = region_tuple
|
|
|
|
prompt = wildcard_ED.process(prompt, seed)
|
|
reg_positive = BNK_EncoderWrapper.imp_encode(prompt, clip, clip_encoder)
|
|
hooks = ED_Reg.process_script_lora_stack(lora_stack)
|
|
base_positive = NODES['ConditioningSetPropertiesAndCombine']().set_properties(base_positive, reg_positive, condition_strength, set_conditon_area, mask, hooks, None)[0]
|
|
regions.extend([{"cond": reg_positive, "mask": mask, "weight": region_weight}])
|
|
|
|
return (base_positive, regions)
|
|
|
|
@staticmethod
|
|
def process_script_lora_stack (lora_stack):
|
|
hooks = None
|
|
if lora_stack:
|
|
lora_params = []
|
|
lora_count = 0
|
|
lora_params.extend(lora_stack)
|
|
print(f"\r{message('Regional Script ED:')} create LoRA hook -")
|
|
|
|
for lora in lora_params:
|
|
lora_name, strength_model, strength_clip = lora
|
|
lora_count += 1
|
|
lora_model_info = f"{os.path.splitext(os.path.basename(lora_name))[0]}({round(strength_model, 2)},{round(strength_clip, 2)})"
|
|
print(f" [{lora_count}] lora(mod,clip): {lora_model_info}")
|
|
hooks = NODES['CreateHookLora']().create_hook(lora_name, strength_model, strength_clip, hooks)[0]
|
|
|
|
return hooks
|
|
|
|
@staticmethod
|
|
def attention_couple_regions (regions):
|
|
flattened_regions = reduce(
|
|
lambda acc, region: acc + region
|
|
if isinstance(region, list)
|
|
else acc + [region]
|
|
if region
|
|
else acc,
|
|
regions,
|
|
[],
|
|
)
|
|
return flattened_regions
|
|
|
|
##############################################################################################################
|
|
# REGIONAL NODES
|
|
##############################################################################################################
|
|
# Regional Stacker - Main
|
|
class Regional_Stacker_ED:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required": {
|
|
"region_script": ("REGION_SCRIPT",),
|
|
"global_prompt_weight": ("FLOAT",{"default": 1.0, "min": 0.01, "max": 1.0, "step": 0.1, "tooltip": "Base prompt strength.", }, ),
|
|
"width": ("INT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
"height": ("INT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
"empty image": (["Create empty image"],),},
|
|
}
|
|
|
|
RETURN_TYPES = ("CONTROL_NET_STACK",)
|
|
RETURN_NAMES = ("CNET_STACK",)
|
|
FUNCTION = "regional_stacker"
|
|
CATEGORY = "Efficiency Nodes/Stackers"
|
|
|
|
def regional_stacker(self, region_script, global_prompt_weight, width, height, **kwargs):
|
|
# Extend the control_net_stack with the new tuple
|
|
cnet_stack = []
|
|
cnet_stack.extend([(region_script, global_prompt_weight, width, height, "Region_Stack")])
|
|
return (cnet_stack,)
|
|
|
|
# Regional Processor - Main
|
|
class Regional_Processor_ED:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required": {
|
|
"model": ("MODEL",),
|
|
"clip": ("CLIP",),
|
|
"base_positive": ("CONDITIONING",),
|
|
"region_script": ("REGION_SCRIPT",),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"global_prompt_weight": ("FLOAT",{"default": 1.0, "min": 0.01, "max": 1.0, "step": 0.1, "tooltip": "Base prompt strength.", }, ),
|
|
"width": ("INT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
"height": ("INT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 1}),
|
|
"empty image": (["Creates empty image"], ), },
|
|
}
|
|
|
|
RETURN_TYPES = ("MODEL", "CONDITIONING",)
|
|
RETURN_NAMES = ("MODEL", "POSITIVE",)
|
|
FUNCTION = "regional_processor"
|
|
CATEGORY = "Efficiency Nodes/Stackers"
|
|
|
|
def regional_processor(self, model, clip, base_positive, region_script, seed, global_prompt_weight, width, height, **kwargs):
|
|
# Extend the control_net_stack with the new tuple
|
|
cnet_stack = []
|
|
cnet_stack.extend([(region_script, global_prompt_weight, width, height, "Region_Stack")])
|
|
model, base_positive, _ = ED_Reg.apply_controlnet_region(cnet_stack, base_positive, None, model, clip, seed, None)
|
|
|
|
return (model, base_positive)
|
|
|
|
# Regional Script -Sub
|
|
class Regional_Script_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"url": ("STRING", {"default": "None", "multiline": False}),
|
|
"use_blur_mask": ("BOOLEAN", {"default": False, "label_on": "Use Gaussian Blur Mask", "label_off": "Don't use Blur Mask"}),
|
|
"blur_mask_kernel_size": ("INT", {"default": 10, "min": 0, "max": 100, "step": 1}),
|
|
"blur_mask_sigma": ("FLOAT", {"default": 10.0, "min": 0.1, "max": 100.0, "step": 0.1}),
|
|
"prompt": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
"Select to add Wildcard": (["Select the Wildcard to add to the text"],),
|
|
"condition_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
|
"set_conditon_area": (["default", "mask bounds"],),
|
|
"region_weight": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 1.0, "step": 0.01},),},
|
|
"optional": {
|
|
"region_script": ("REGION_SCRIPT", ),
|
|
"image": ("IMAGE",),
|
|
"mask": ("MASK",),
|
|
"lora_stack": ("LORA_STACK",),},
|
|
}
|
|
|
|
RETURN_TYPES = ("REGION_SCRIPT", "IMAGE",)
|
|
RETURN_NAMES = ("REGION_SCRIPT", "MASK_IMAGE",)
|
|
FUNCTION = "regional_script"
|
|
CATEGORY = "Efficiency Nodes/Scripts"
|
|
|
|
def regional_script(self, use_blur_mask, blur_mask_kernel_size, blur_mask_sigma, prompt, condition_strength, set_conditon_area, region_weight, region_script=None, image=None, mask=None, lora_stack=None, **kwargs):
|
|
region_script = [] if region_script is None else region_script
|
|
mask_image = None
|
|
prompt = ED_Util.strip_comments(prompt)
|
|
|
|
if mask is not None:
|
|
if use_blur_mask:
|
|
if 'ImpactGaussianBlurMask' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI Impact Pack', 'https://github.com/ltdrdata/ComfyUI-Impact-Pack')
|
|
|
|
mask = NODES['ImpactGaussianBlurMask']().doit(mask, blur_mask_kernel_size, blur_mask_sigma)[0]
|
|
|
|
mask_image = NODES['MaskToImage']().mask_to_image(mask)[0]
|
|
region_script.extend([(mask, prompt, condition_strength, set_conditon_area, region_weight, lora_stack)])
|
|
|
|
return (region_script, mask_image)
|
|
|
|
##############################################################################################################
|
|
# Int Holder ED
|
|
class Int_Holder_ED:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": { },
|
|
"optional": {
|
|
"int_opt": ("INT", {"forceInput": True}),
|
|
"output_int": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
|
},
|
|
"hidden": {"my_unique_id": "UNIQUE_ID",},
|
|
}
|
|
|
|
FUNCTION = "store_value"
|
|
RETURN_TYPES = ("INT", )
|
|
RETURN_NAMES = ("INT",)
|
|
OUTPUT_NODE = True
|
|
CATEGORY = "Efficiency Nodes/Simple Eval"
|
|
|
|
def store_value(self, int_opt=None, output_int=0, my_unique_id=None):
|
|
if int_opt:
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "output_int", "type": "text", "data": int_opt})
|
|
output_int = int_opt
|
|
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "", "type": "title", "data": output_int})
|
|
return (output_int,)
|
|
|
|
##############################################################################################################
|
|
# Context To BasicPipe
|
|
class ContextToBasicPipe:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"context": ("RGTHREE_CONTEXT",),},
|
|
}
|
|
|
|
RETURN_TYPES = ("BASIC_PIPE", )
|
|
RETURN_NAMES = ("BASIC_PIPE", )
|
|
FUNCTION = "context_to_basic_pipe"
|
|
|
|
CATEGORY = "Efficiency Nodes/Misc"
|
|
|
|
def context_to_basic_pipe(self, context):
|
|
_, model, clip, vae, positive, negative = context_2_tuple_ed(context,["model", "clip", "vae", "positive", "negative"])
|
|
pipe = (model, clip, vae, positive, negative)
|
|
return (pipe, )
|
|
|
|
# Context To DetailerPipe
|
|
class ContextToDetailerPipe:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"context": ("RGTHREE_CONTEXT",),
|
|
"bbox_detector": ("BBOX_DETECTOR", ),
|
|
"wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
|
|
"Select to add Wildcard": (["Select the Wildcard to add to the text"],),
|
|
},
|
|
"optional": {
|
|
"sam_model_opt": ("SAM_MODEL", ),
|
|
"segm_detector_opt": ("SEGM_DETECTOR",),
|
|
"detailer_hook": ("DETAILER_HOOK",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("DETAILER_PIPE",)
|
|
RETURN_NAMES = ("DETAILER_PIPE",)
|
|
FUNCTION = "context_to_detailer_pipe"
|
|
|
|
CATEGORY = "Efficiency Nodes/Misc"
|
|
|
|
def context_to_detailer_pipe(self, *args, **kwargs):
|
|
ctx = kwargs['context']
|
|
bbox_detector = kwargs['bbox_detector']
|
|
wildcard = kwargs['wildcard']
|
|
sam_model_opt = kwargs.get('sam_model_opt', None)
|
|
segm_detector_opt = kwargs.get('segm_detector_opt', None)
|
|
detailer_hook = kwargs.get('detailer_hook', None)
|
|
|
|
_, model, clip, vae, positive, negative = context_2_tuple_ed(ctx,["model", "clip", "vae", "positive", "negative"])
|
|
pipe = model, clip, vae, positive, negative, wildcard, bbox_detector, segm_detector_opt, sam_model_opt, detailer_hook, None, None, None, None
|
|
return (pipe,)
|
|
|
|
##############################################################################################################
|
|
# Get Booru Tag
|
|
get_booru_tag_id = 0
|
|
get_booru_tag_text_b = ""
|
|
|
|
class GetBooruTag_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"url": ("STRING", {"default": "None", "multiline": False}),
|
|
},
|
|
"optional": {
|
|
#"text_a": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
"text_a": ("STRING", {"forceInput": True}),
|
|
"text_b": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
#"text_c": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
"text_c": ("STRING", {"forceInput": True}),
|
|
"Select to add Wildcard": (["Select the Wildcard to add to the text"], ),
|
|
},
|
|
"hidden": {"my_unique_id": "UNIQUE_ID",},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("STRING",)
|
|
FUNCTION = 'get_prompt'
|
|
CATEGORY = 'Efficiency Nodes/Prompt'
|
|
|
|
def get_prompt(self, url, text_a="", text_b="", text_c="", my_unique_id=None, **kwargs):
|
|
global get_booru_tag_id, get_booru_tag_text_b
|
|
get_booru_tag_id = my_unique_id
|
|
get_booru_tag_text_b = text_b
|
|
|
|
#strip commet
|
|
text_a = ED_Util.strip_comments(text_a)
|
|
text_b = ED_Util.strip_comments(text_b)
|
|
text_c = ED_Util.strip_comments(text_c)
|
|
|
|
strip = " ,\n"
|
|
out_text = ""
|
|
if text_a != "":
|
|
out_text = f"{text_a.lstrip(strip).rstrip(strip)},\n\n"
|
|
if text_b != "":
|
|
out_text += f"{text_b.lstrip(strip).rstrip(strip)},\n\n"
|
|
if text_c != "":
|
|
out_text += f"{text_c.lstrip(strip).rstrip(strip)}"
|
|
|
|
out_text = f"{out_text.rstrip(strip)}"
|
|
|
|
return (out_text,)
|
|
|
|
##############################################################################################################
|
|
# Simple text
|
|
class SimpleText_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"text": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("STRING",)
|
|
FUNCTION = 'get_text'
|
|
CATEGORY = 'Efficiency Nodes/Prompt'
|
|
|
|
def get_text(self, text):
|
|
#strip commet
|
|
text = ED_Util.strip_comments(text)
|
|
|
|
strip = " ,\n"
|
|
text = f"{text.lstrip(strip).rstrip(strip)}"
|
|
|
|
return (text,)
|
|
|
|
##############################################################################################################
|
|
# TIPO Script ED
|
|
|
|
class TIPOScript_ED:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
if 'TIPO' in NODES:
|
|
import kgen.models as tipo_models
|
|
MODEL_NAME_LIST = [
|
|
f"{model_name} | {file}".strip("_")
|
|
for model_name, ggufs in tipo_models.tipo_model_list
|
|
for file in ggufs
|
|
] + [i[0] for i in tipo_models.tipo_model_list]
|
|
else:
|
|
MODEL_NAME_LIST = ['KBlueLeaf/TIPO-500M-ft | TIPO-500M-ft-F16.gguf']
|
|
|
|
return {"required": {
|
|
"context": ("RGTHREE_CONTEXT",),
|
|
#"tags": ("STRING", {"default": "", "multiline": True}),
|
|
"tipo_model": (MODEL_NAME_LIST, {"default": MODEL_NAME_LIST[0]}),
|
|
"format": ("STRING", {"default": """<|special|>,
|
|
<|characters|>, <|copyrights|>,
|
|
<|artist|>,
|
|
|
|
<|general|>,
|
|
|
|
<|extended|>.
|
|
|
|
<|quality|>, <|meta|>, <|rating|>""", "multiline": True, },),
|
|
"temperature": ("FLOAT", {"default": 0.5, "step": 0.01}),
|
|
"top_p": ("FLOAT", {"default": 0.95, "step": 0.01}),
|
|
"min_p": ("FLOAT", {"default": 0.05, "step": 0.01}),
|
|
"top_k": ("INT", {"default": 80}),
|
|
"tag_length": (["very_short", "short", "long", "very_long"], {"default": "long"},),
|
|
"nl_length": (["very_short", "short", "long", "very_long"], {"default": "long"},),
|
|
"device": (["cpu", "cuda"], {"default": "cuda"}),
|
|
},
|
|
"optional": {
|
|
"positive_opt": ("CONDITIONING",),
|
|
"nl_prompt": ("STRING", {"default": "", "multiline": True}),
|
|
"ban_tags": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "CONDITIONING", "STRING", "STRING", "STRING", "STRING",)
|
|
RETURN_NAMES = ("CONTEXT", "POSITIVE", "PROMPT", "USER_PROMPT", "UNFORMATTED_PROMPT", "UNFORMATTED_USER_PROMPT",)
|
|
FUNCTION = "tipo_process"
|
|
CATEGORY = 'Efficiency Nodes/Prompt'
|
|
|
|
def tipo_process(self, context, tipo_model, tag_length, nl_length, format, temperature, top_p, min_p, top_k, device, positive_opt=None, nl_prompt = "", ban_tags = ""):
|
|
# Unpack context
|
|
_, clip, seed, width, height, positive_prompt, clip_encoder = context_2_tuple_ed(context, ["clip", "seed", "clip_width", "clip_height", "text_pos_g", "clip_encoder"])
|
|
|
|
if 'TIPO' not in NODES:
|
|
ED_Util.try_install_custom_node('z-tipo-extension', 'https://github.com/KohakuBlueleaf/z-tipo-extension')
|
|
|
|
out_string = NODES['TIPO']().execute(tipo_model, positive_prompt, nl_prompt, width, height, seed, tag_length, nl_length, ban_tags, format, temperature, top_p, min_p, top_k, device)
|
|
|
|
positive_encoded = BNK_EncoderWrapper.imp_encode(out_string[0], clip, clip_encoder)
|
|
context = new_context_ed(context, positive=positive_encoded, text_pos_g=out_string[0])
|
|
|
|
return context, positive_encoded, out_string[0], out_string[1], out_string[2], out_string[3]
|
|
|
|
##############################################################################################################
|
|
# SAMPLER
|
|
##############################################################################################################
|
|
# KSampler (Efficient) ED
|
|
class KSampler_ED():
|
|
SET_SEED_CFG_SAMPLER = {
|
|
"from node to ctx": 1,
|
|
"from context": 2,
|
|
"from node only": 3,
|
|
}
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required":
|
|
{"context": ("RGTHREE_CONTEXT",),
|
|
"set_seed_cfg_sampler": (list(cls.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"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": (SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"preview_method": (["auto", "latent2rgb", "taesd", "vae_decoded_only", "none"],),
|
|
},
|
|
"optional": {
|
|
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "mask bbox", "label_off": "crop region"}),
|
|
"max_size": ("FLOAT", {"default": 1216, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"feather": ("INT", {"default": 15, "min": 0, "max": 100, "step": 1}),
|
|
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}),
|
|
#"cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
|
|
"positive_opt": ("CONDITIONING",),
|
|
"script": ("SCRIPT",),
|
|
#"detailer_hook": ("DETAILER_HOOK",),
|
|
},
|
|
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",},}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "IMAGE", "INT",)
|
|
RETURN_NAMES = ("CONTEXT", "OUTPUT_IMAGE", "STEPS_INT",)
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "sample_ed"
|
|
CATEGORY = "Efficiency Nodes/Sampling"
|
|
|
|
def sample_ed(self, context, set_seed_cfg_sampler, seed, steps, cfg, sampler_name, scheduler, denoise, preview_method,
|
|
guide_size=512, guide_size_for=True, max_size=1216, feather=15, crop_factor=3,
|
|
positive_opt=None, script=None,
|
|
prompt=None, extra_pnginfo=None, my_unique_id=None):
|
|
|
|
# Unpack context
|
|
_, model, clip, vae, positive, negative, latent_image, optional_image, batch_size, mask = \
|
|
context_2_tuple_ed(context, ["model", "clip", "vae", "positive", "negative", "latent", "images", "step_refiner", "mask"])
|
|
|
|
# positive에 guidance 가 있는지 확인하고 있으면 스킵이 안되게 함.
|
|
positive, c_index = self.is_positive_changed(positive, positive_opt)
|
|
|
|
if model is None or latent_image is None:
|
|
raise Exception("KSampler (Efficient) ED: model and latent are required.")
|
|
|
|
previous_preview_method = global_preview_method()
|
|
properties, vae_decode = self.extract_mask_detailer_settings(extra_pnginfo, my_unique_id)
|
|
|
|
# Handle seed, cfg, sampler, scheduler from context or update context
|
|
context, seed, cfg, sampler_name, scheduler = self.handle_seed_cfg_sampler(context, set_seed_cfg_sampler, seed, cfg, sampler_name, scheduler, my_unique_id)
|
|
|
|
print(f"\r{message('KSampler (Efficient) ED:')} current seed - {warning(str(seed))}")
|
|
|
|
# Handle script if present
|
|
latent_list = None
|
|
script, refiner_script, do_refine_only = self.check_refiner_script(script)
|
|
|
|
if do_refine_only:
|
|
latent_list = ED_Util.vae_encode(vae, optional_image, properties['tiled_vae'])
|
|
|
|
###### Mask Detailer ######
|
|
elif properties['mask_detailer_mode']:
|
|
print(f"\r{message('KSampler (Efficient) ED:')}\033[38;5;173m use Mask Detailer(ImpactPack) for inpainting\033[0m")
|
|
set_preview_method(preview_method)
|
|
|
|
output_images, _, _ = MaskDetailer_ED.mask_sampling(optional_image, mask, model, clip, vae, positive, negative,
|
|
guide_size, guide_size_for, max_size, properties['mask_mode'],
|
|
seed, steps, cfg, sampler_name, scheduler, denoise,
|
|
feather, crop_factor, properties['drop_size'], properties['refiner_ratio'], batch_size, properties['cycle'],
|
|
None, properties['inpaint_model'], properties['noise_mask_feather'])
|
|
|
|
latent_list = ED_Util.vae_encode(vae, output_images, properties['tiled_vae']) if refiner_script else None
|
|
store_ksampler_results("image", my_unique_id, output_images)
|
|
result_ui = nodes.PreviewImage().save_images(output_images, prompt=prompt, extra_pnginfo=extra_pnginfo)["ui"]
|
|
|
|
###### KSampler (Efficient) ######
|
|
else:
|
|
return_dict = NODES['KSampler (Efficient)']().sample(model, seed, steps, cfg,
|
|
sampler_name, scheduler, positive, negative, latent_image, preview_method, vae_decode,
|
|
denoise=denoise, prompt=prompt, extra_pnginfo=extra_pnginfo, my_unique_id=my_unique_id,
|
|
optional_vae=vae, script=script, add_noise=None, start_at_step=None, end_at_step=c_index,
|
|
return_with_leftover_noise=None, sampler_type="regular")
|
|
|
|
_, _, _, latent_list, _, output_images = return_dict["result"]
|
|
result_ui = return_dict["ui"]
|
|
|
|
###### Refiner Script ######
|
|
if refiner_script:
|
|
output_images = self.apply_refiner_script(context, refiner_script, latent_list or latent_image, preview_method, properties['tiled_vae'])
|
|
|
|
store_ksampler_results("image", my_unique_id, output_images)
|
|
result_ui = nodes.PreviewImage().save_images(output_images, prompt=prompt, extra_pnginfo=extra_pnginfo)["ui"]
|
|
|
|
# RESULT
|
|
set_preview_method(previous_preview_method)
|
|
context = new_context_ed(context, latent=latent_list or latent_image, images=output_images)
|
|
|
|
return {"ui": result_ui, "result": (context, output_images, steps)}
|
|
|
|
@staticmethod
|
|
def is_positive_changed(positive, positive_opt):
|
|
c_index = 1000
|
|
if positive_opt:
|
|
positive = positive_opt
|
|
for t in positive:
|
|
if "guidance" in t[1]:
|
|
c_index += t[1]["guidance"]
|
|
break
|
|
return positive, c_index
|
|
|
|
@staticmethod
|
|
def extract_mask_detailer_settings(extra_pnginfo, my_unique_id):
|
|
properties = {
|
|
"mask_detailer_mode": False,
|
|
"drop_size": 5,
|
|
"cycle": 1,
|
|
"inpaint_model": False,
|
|
"noise_mask_feather": 20,
|
|
"mask_mode": True,
|
|
"refiner_ratio": 0.2,
|
|
"tiled_vae": False,
|
|
}
|
|
# if vae_decode != "true (tiled)":
|
|
# vae_decode = "true"
|
|
|
|
if extra_pnginfo and "workflow" in extra_pnginfo:
|
|
workflow = extra_pnginfo["workflow"]
|
|
for node in workflow["nodes"]:
|
|
if node["id"] == int(my_unique_id):
|
|
props = node["properties"]
|
|
properties['mask_detailer_mode'] = props.get("MaskDetailer mode", False)
|
|
properties['drop_size'] = int(props.get("(MaskDetailer) drop size", 5))
|
|
properties['cycle'] = int(props.get("(MaskDetailer) cycle", 1))
|
|
properties['inpaint_model'] = props.get("(MaskDetailer) inpaint model enable", False)
|
|
properties['noise_mask_feather'] = int(props.get("(MaskDetailer) noise mask feather", 20))
|
|
properties['tiled_vae'] = props.get("Use tiled VAE decode", False)
|
|
vae_decode = "true (tiled)" if properties['tiled_vae'] else "true"
|
|
return properties , vae_decode
|
|
|
|
return properties, vae_decode
|
|
|
|
@staticmethod
|
|
def handle_seed_cfg_sampler(context, mode, seed, cfg, sampler_name, scheduler, my_unique_id):
|
|
if mode == "from context":
|
|
_, c_seed, c_cfg, c_sampler, c_scheduler = context_2_tuple_ed(context, ["seed", "cfg", "sampler", "scheduler"])
|
|
if c_seed is None:
|
|
raise Exception("KSampler (Efficient) ED: no seed, cfg, sampler, scheduler in the context.")
|
|
|
|
seed, cfg, sampler_name, scheduler = c_seed, c_cfg, c_sampler, c_scheduler
|
|
|
|
for key, value in zip(["seed", "cfg", "sampler_name", "scheduler"], [seed, cfg, sampler_name, scheduler]):
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": key, "type": "text", "data": value})
|
|
|
|
elif mode == "from node to ctx":
|
|
context = new_context_ed(context, seed=seed, cfg=cfg, sampler=sampler_name, scheduler=scheduler)
|
|
|
|
return context, seed, cfg, sampler_name, scheduler
|
|
|
|
@staticmethod
|
|
def check_refiner_script(script):
|
|
refiner_script = None
|
|
do_refine_only = False
|
|
|
|
if script and "refiner_script" in script:
|
|
refiner_script = script["refiner_script"]
|
|
do_refine_only = refiner_script[-1]
|
|
script = script.copy()
|
|
del script['refiner_script']
|
|
|
|
return script, refiner_script, do_refine_only
|
|
|
|
@staticmethod
|
|
def apply_refiner_script(context, refiner_script, latent_image, preview_method, is_tiled, return_with_leftover_noise=None):
|
|
|
|
if refiner_script:
|
|
refiner_model, refiner_clip, refiner_vae, refiner_add_noise, refiner_seed, refiner_steps, refiner_cfg, refiner_sampler_name, refiner_scheduler, refiner_denoise, refiner_start_at_step, refiner_end_at_step, refiner_ignore_batch_size, _ = refiner_script
|
|
|
|
# Unpack context
|
|
_, model, clip, vae, positive, negative, seed, cfg, sampler, scheduler, positive_prompt, negative_prompt = \
|
|
context_2_tuple_ed(context, ["model", "clip", "vae", "positive", "negative", "seed", "cfg", "sampler", "scheduler", "text_pos_g", "text_neg_g"])
|
|
|
|
if not refiner_model:
|
|
refiner_model = model
|
|
refiner_clip = clip
|
|
refiner_vae = vae
|
|
refiner_positive = positive
|
|
refiner_negative = negative
|
|
else:
|
|
refiner_positive = nodes.CLIPTextEncode().encode(refiner_clip, positive_prompt)[0]
|
|
refiner_negative = nodes.CLIPTextEncode().encode(refiner_clip, negative_prompt)[0]
|
|
|
|
if not refiner_seed:
|
|
refiner_seed = seed
|
|
refiner_cfg =cfg
|
|
refiner_sampler_name = sampler
|
|
refiner_scheduler = scheduler
|
|
|
|
if refiner_ignore_batch_size:
|
|
latent_image = nodes.LatentFromBatch().frombatch(latent_image, 0, 1)[0]
|
|
|
|
print(f"\r{message('KSampler (Efficient) ED:')}\033[38;5;173m running refiner script. start step:{refiner_start_at_step}, steps:{refiner_steps}, cfg:{refiner_cfg}, seed:{refiner_seed}\033[0m")
|
|
|
|
set_preview_method(preview_method)
|
|
latent_image = nodes.KSamplerAdvanced().sample(refiner_model, refiner_add_noise, refiner_seed, refiner_steps,
|
|
refiner_cfg, refiner_sampler_name, refiner_scheduler, refiner_positive, refiner_negative, latent_image,
|
|
refiner_start_at_step, refiner_end_at_step, return_with_leftover_noise, refiner_denoise)[0]
|
|
|
|
refiner_images = ED_Util.vae_decode(refiner_vae, latent_image, is_tiled)
|
|
|
|
return refiner_images
|
|
|
|
##############################################################################################################
|
|
# KSamplerTEXT ED #for BackGround Make
|
|
class KSamplerTEXT_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required":
|
|
{"context": ("RGTHREE_CONTEXT",),
|
|
"set_seed_cfg_sampler": (list(KSampler_ED.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"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": (SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"preview_method": (["auto", "latent2rgb", "taesd", "vae_decoded_only", "none"],),
|
|
"vae_decode": (["true", "true (tiled)", "false"],),
|
|
"positive": ("STRING", {"default": "","multiline": True}),
|
|
"negative": ("STRING", {"default": "", "multiline": True}),
|
|
},
|
|
#"optional": {
|
|
#"script": ("SCRIPT",),},
|
|
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",},
|
|
}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "IMAGE",)
|
|
RETURN_NAMES = ("CONTEXT", "OUTPUT_IMAGE",)
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "backgroundmake_ed"
|
|
CATEGORY = "Efficiency Nodes/Sampling"
|
|
|
|
def backgroundmake_ed(self, context, set_seed_cfg_sampler, seed, steps, cfg, sampler_name, scheduler, preview_method,
|
|
vae_decode, positive=None, negative=None, denoise=1.0, refiner_denoise=1.0,
|
|
prompt=None, extra_pnginfo=None, my_unique_id=None,
|
|
script=None, add_noise=None, start_at_step=None, end_at_step=None,
|
|
return_with_leftover_noise=None, sampler_type="regular"):
|
|
|
|
_, model, clip, vae, optional_latent, optional_image, c_seed, c_cfg, c_sampler, c_scheduler = context_2_tuple_ed(context,["model", "clip", "vae", "latent", "images", "seed", "cfg", "sampler", "scheduler"])
|
|
|
|
if model is None:
|
|
raise Exception("KSampler TEXT ED: model is None. \n\n\n\n\n\n")
|
|
if optional_latent is None:
|
|
raise Exception("KSampler TEXT ED requires 'Latent' for sampling.\n\n\n\n\n\n")
|
|
|
|
if set_seed_cfg_sampler == "from context":
|
|
if c_seed is None:
|
|
raise Exception("KSampler TEXT (Eff.) 💬ED: no seed, cfg, sampler, scheduler in the context.\n\n\n\n\n\n")
|
|
else:
|
|
seed = c_seed
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "seed", "type": "text", "data": seed})
|
|
cfg = c_cfg
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "cfg", "type": "text", "data": cfg})
|
|
sampler_name = c_sampler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "sampler_name", "type": "text", "data": sampler_name})
|
|
scheduler = c_scheduler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "scheduler", "type": "text", "data": scheduler})
|
|
elif set_seed_cfg_sampler =="from node to ctx":
|
|
context = new_context_ed(context, seed=seed, cfg=cfg, sampler=sampler_name, scheduler=scheduler)
|
|
|
|
if optional_latent is not None:
|
|
image_width, image_height = ED_Util.get_latent_size(optional_latent)
|
|
print(f"KSampler TEXT ED: size get from latent (width {image_width} x height {image_height})")
|
|
elif optional_image is not None:
|
|
image_width, image_height = ED_Util.get_image_size(optional_image)
|
|
print(f"KSampler TEXT ED: size get from image (width {image_width} x height {image_height})")
|
|
else:
|
|
raise Exception("KSamplerTEXT ED: reference image or latent is required.\n\n\n\n\n\n")
|
|
|
|
nodes.EmptyLatentImage().generate(image_width, image_height, batch_size=1)[0]
|
|
|
|
positive_encoded = nodes.CLIPTextEncode().encode(clip, positive)[0]
|
|
negative_encoded = nodes.CLIPTextEncode().encode(clip, negative)[0]
|
|
|
|
return_dict = NODES['KSampler (Efficient)']().sample(model, seed, steps, cfg, sampler_name, scheduler,
|
|
positive_encoded, negative_encoded, latent_image, preview_method, vae_decode, denoise=denoise, prompt=prompt,
|
|
extra_pnginfo=extra_pnginfo, my_unique_id=my_unique_id,
|
|
optional_vae=vae, script=script, add_noise=add_noise, start_at_step=start_at_step, end_at_step=end_at_step,
|
|
return_with_leftover_noise=return_with_leftover_noise, sampler_type="regular")
|
|
|
|
_, _, _, latent_list, _, output_images = return_dict["result"]
|
|
|
|
context = new_context_ed(context, latent=latent_list, images=output_images)
|
|
return (context, output_images)
|
|
|
|
##############################################################################################################
|
|
# EXTENSIONS
|
|
##############################################################################################################
|
|
# Face Detailer ED
|
|
class FaceDetailer_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
if 'UltralyticsDetectorProvider' in NODES:
|
|
bboxs = ["bbox/"+x for x in folder_paths.get_filename_list("ultralytics_bbox")] + ["segm/"+x for x in folder_paths.get_filename_list("ultralytics_segm")]
|
|
segms = ["None"] + ["segm/"+x for x in folder_paths.get_filename_list("ultralytics_segm")]
|
|
sams = ["None"] + [x for x in folder_paths.get_filename_list("sams") if 'hq' not in x]
|
|
else:
|
|
bboxs = ['bbox/face_yolov8m.pt']
|
|
segms = ['None']
|
|
sams = ['None', 'sam_vit_b_01ec64.pth']
|
|
return {"required": {
|
|
"context": ("RGTHREE_CONTEXT",),
|
|
"set_seed_cfg_sampler": (list(KSampler_ED.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
"bbox_detector": (bboxs, ),
|
|
"segm_detector_opt": (segms, ),
|
|
"sam_model_opt": (sams, ),
|
|
"sam_mode": (["AUTO", "Prefer GPU", "CPU"],),
|
|
|
|
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}),
|
|
"max_size": ("FLOAT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"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}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
|
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
|
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
|
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
|
|
|
"bbox_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"bbox_dilation": ("INT", {"default": 10, "min": -512, "max": 512, "step": 1}),
|
|
"bbox_crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}),
|
|
|
|
"sam_detection_hint": (["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area", "mask-points", "mask-point-bbox", "none"],),
|
|
"sam_dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
|
|
"sam_threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"sam_bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}),
|
|
"sam_mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"sam_mask_hint_use_negative": (["False", "Small", "Outter"],),
|
|
"drop_size": ("INT", {"min": 1, "max": nodes.MAX_RESOLUTION, "step": 1, "default": 10}),
|
|
"cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"image_opt": ("IMAGE",),
|
|
"wildcard": ("STRING", {"forceInput": True}),
|
|
"detailer_hook": ("DETAILER_HOOK",),
|
|
#"wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
|
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
|
},
|
|
"hidden": {
|
|
"my_unique_id": "UNIQUE_ID",
|
|
"extra_pnginfo": "EXTRA_PNGINFO",}
|
|
}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "IMAGE", "IMAGE", "IMAGE", "MASK", "IMAGE",)
|
|
RETURN_NAMES = ("CONTEXT", "OUTPUT_IMAGE", "CROPPED_REFINED", "CROPPED_ALPHA", "MASK", "CNET_IMAGES",)
|
|
OUTPUT_IS_LIST = (False, False, True, True, False, True,)
|
|
FUNCTION = "doit_ed"
|
|
CATEGORY = "Efficiency Nodes/Image"
|
|
DESCRIPTION = "This node enhances details by automatically detecting specific objects in the input image using detection models (bbox, segm, sam) and regenerating the image by enlarging the detected area based on the guide size.\nAlthough this node is specialized to simplify the commonly used facial detail enhancement workflow, it can also be used for various automatic inpainting purposes depending on the detection model."
|
|
|
|
def doit_ed(self, context, set_seed_cfg_sampler, bbox_detector, segm_detector_opt, sam_model_opt, sam_mode,
|
|
guide_size, guide_size_for,
|
|
max_size, seed, steps, cfg, sampler_name, scheduler, denoise, feather, noise_mask, force_inpaint,
|
|
bbox_threshold, bbox_dilation, bbox_crop_factor,
|
|
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold,
|
|
sam_mask_hint_use_negative, drop_size, wildcard="", image_opt=None, cycle=1,
|
|
detailer_hook=None, inpaint_model=False, noise_mask_feather=0,
|
|
scheduler_func_opt=None, my_unique_id=None, extra_pnginfo=None):
|
|
|
|
_, model, clip, vae, positive, negative, image, c_seed, c_steps, c_cfg, c_sampler, c_scheduler = context_2_tuple_ed(context,["model", "clip", "vae", "positive", "negative", "images", "seed", "steps", "cfg", "sampler", "scheduler"])
|
|
|
|
# GET PROPERTIES #
|
|
properties = self.get_properties(extra_pnginfo, my_unique_id)
|
|
|
|
if image_opt is not None:
|
|
image = image_opt
|
|
print(f"FaceDetailer ED: Using image_opt instead of context image.")
|
|
|
|
if set_seed_cfg_sampler == "from context":
|
|
if c_seed is None:
|
|
raise Exception("FaceDetailer ED: no seed, cfg, sampler, scheduler in the context.\n\n\n\n\n\n")
|
|
else:
|
|
seed = c_seed
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "seed", "type": "text", "data": seed})
|
|
if c_steps is not None:
|
|
steps = c_steps
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "steps", "type": "text", "data": steps})
|
|
cfg = c_cfg
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "cfg", "type": "text", "data": cfg})
|
|
sampler_name = c_sampler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "sampler_name", "type": "text", "data": sampler_name})
|
|
scheduler = c_scheduler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "scheduler", "type": "text", "data": scheduler})
|
|
elif set_seed_cfg_sampler =="from node to ctx":
|
|
context = new_context_ed(context, seed=seed, cfg=cfg, sampler=sampler_name, scheduler=scheduler)
|
|
|
|
bbox_detector = ED_Util.ultra_detector_model(bbox_detector, "bbox")
|
|
segm_detector_opt = ED_Util.ultra_detector_model(segm_detector_opt, "segm")
|
|
sam_model_opt = ED_Util.load_sam_model(sam_model_opt, sam_mode)
|
|
|
|
if 'FaceDetailer' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI Impact Pack', 'https://github.com/ltdrdata/ComfyUI-Impact-Pack')
|
|
|
|
result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, _, result_cnet_images = \
|
|
NODES['FaceDetailer']().doit(image, model, clip, vae, guide_size, guide_size_for, max_size,
|
|
seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, feather, noise_mask, force_inpaint,
|
|
bbox_threshold, bbox_dilation, bbox_crop_factor, sam_detection_hint, sam_dilation,
|
|
sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold,
|
|
sam_mask_hint_use_negative, drop_size, bbox_detector, wildcard, cycle=cycle,
|
|
sam_model_opt=sam_model_opt, segm_detector_opt=segm_detector_opt, detailer_hook=detailer_hook,
|
|
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
|
scheduler_func_opt=scheduler_func_opt, tiled_encode=properties['tiled_vae_decode'], tiled_decode=properties['tiled_vae_decode'])
|
|
|
|
context = new_context_ed(context, images=result_img) #RE
|
|
return (context, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images,)
|
|
|
|
@staticmethod
|
|
def get_properties(extra_pnginfo, my_unique_id):
|
|
properties = {
|
|
"tiled_vae_decode": False,
|
|
}
|
|
|
|
if extra_pnginfo and "workflow" in extra_pnginfo:
|
|
workflow = extra_pnginfo["workflow"]
|
|
for node in workflow["nodes"]:
|
|
if node["id"] == int(my_unique_id):
|
|
properties['tiled_vae_decode'] = node["properties"]["Use tiled VAE decode"]
|
|
|
|
return properties
|
|
|
|
|
|
##############################################################################################################
|
|
# Mask Detailer ED
|
|
class MaskDetailer_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"context": ("RGTHREE_CONTEXT",),
|
|
"set_seed_cfg_sampler": (list(KSampler_ED.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
|
|
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "mask bbox", "label_off": "crop region"}),
|
|
"max_size": ("FLOAT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"mask_mode": ("BOOLEAN", {"default": True, "label_on": "masked only", "label_off": "whole"}),
|
|
|
|
"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}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
|
|
|
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
|
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}),
|
|
"drop_size": ("INT", {"min": 1, "max": nodes.MAX_RESOLUTION, "step": 1, "default": 10}),
|
|
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 100}),
|
|
|
|
"cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"detailer_hook": ("DETAILER_HOOK",),
|
|
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
|
"bbox_fill": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"contour_fill": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
|
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
|
},
|
|
"hidden": {"my_unique_id": "UNIQUE_ID",},
|
|
}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "IMAGE", "IMAGE", "IMAGE", )
|
|
RETURN_NAMES = ("CONTEXT", "OUTPUT_IMAGE", "CROPPED_REFINED", "CROPPED_ALPHA", )
|
|
OUTPUT_IS_LIST = (False, False, True, True,)
|
|
FUNCTION = "doit_ed"
|
|
|
|
CATEGORY = "Efficiency Nodes/Image"
|
|
|
|
@staticmethod
|
|
def mask_sampling(image, mask, model, clip, vae, positive, negative, guide_size, guide_size_for, max_size, mask_mode,
|
|
seed, steps, cfg, sampler_name, scheduler, denoise,
|
|
feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle,
|
|
detailer_hook, inpaint_model, noise_mask_feather,
|
|
bbox_fill=False, contour_fill=True, scheduler_func_opt=None):
|
|
|
|
basic_pipe = (model, clip, vae, positive, negative)
|
|
|
|
if 'MaskDetailerPipe' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI Impact Pack', 'https://github.com/ltdrdata/ComfyUI-Impact-Pack')
|
|
|
|
enhanced_img_batch, cropped_enhanced_list, cropped_enhanced_alpha_list, _, _ = \
|
|
NODES['MaskDetailerPipe']().doit(image, mask, basic_pipe,
|
|
guide_size, guide_size_for, max_size, mask_mode, seed, steps, cfg, sampler_name,
|
|
scheduler, denoise, feather, crop_factor, drop_size, refiner_ratio,
|
|
batch_size, cycle, None, detailer_hook, inpaint_model, noise_mask_feather,
|
|
bbox_fill, contour_fill, scheduler_func_opt)
|
|
return (enhanced_img_batch, cropped_enhanced_list, cropped_enhanced_alpha_list)
|
|
|
|
def doit_ed(self, context, set_seed_cfg_sampler, guide_size, guide_size_for, max_size, mask_mode,
|
|
seed, steps, cfg, sampler_name, scheduler, denoise,
|
|
feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1,
|
|
detailer_hook=None, inpaint_model=False, noise_mask_feather=0,
|
|
bbox_fill=False, contour_fill=True, scheduler_func_opt=None, my_unique_id=None):
|
|
|
|
_, model, clip, vae, positive, negative, image, c_batch, c_seed, c_steps, c_cfg, c_sampler, c_scheduler, mask = context_2_tuple_ed(context,["model", "clip", "vae", "positive", "negative", "images", "step_refiner", "seed", "steps", "cfg", "sampler", "scheduler", "mask"])
|
|
|
|
# if image_opt is not None:
|
|
# image = image_opt
|
|
# print(f"MaskDetailer ED: Using image_opt instead of context image.")
|
|
# if mask_opt is not None:
|
|
# mask = mask_opt
|
|
# print(f"MaskDetailer ED: Using mask_opt instead of context mask.")
|
|
|
|
if set_seed_cfg_sampler == "from context":
|
|
if c_seed is None:
|
|
raise Exception("MaskDetailer ED: no seed, cfg, sampler, scheduler in the context.\n\n\n\n\n\n")
|
|
else:
|
|
seed = c_seed
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "seed", "type": "text", "data": seed})
|
|
if c_steps is not None:
|
|
steps = c_steps
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "steps", "type": "text", "data": steps})
|
|
cfg = c_cfg
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "cfg", "type": "text", "data": cfg})
|
|
sampler_name = c_sampler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "sampler_name", "type": "text", "data": sampler_name})
|
|
scheduler = c_scheduler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "scheduler", "type": "text", "data": scheduler})
|
|
# batch_size = c_batch
|
|
# PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "batch_size", "type": "text", "data": batch_size})
|
|
elif set_seed_cfg_sampler =="from node to ctx":
|
|
context = new_context_ed(context, seed=seed, cfg=cfg, sampler=sampler_name, scheduler=scheduler)
|
|
|
|
enhanced_img_batch, cropped_enhanced_list, cropped_enhanced_alpha_list = \
|
|
MaskDetailer_ED.mask_sampling(image, mask, model, clip, vae, positive, negative,
|
|
guide_size, guide_size_for, max_size, mask_mode,
|
|
seed, steps, cfg, sampler_name, scheduler, denoise,
|
|
feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle,
|
|
detailer_hook, inpaint_model, noise_mask_feather,
|
|
bbox_fill, contour_fill, scheduler_func_opt)
|
|
|
|
context = new_context_ed(context, images=enhanced_img_batch) #RE
|
|
return (context, enhanced_img_batch, cropped_enhanced_list, cropped_enhanced_alpha_list,)
|
|
|
|
##############################################################################################################
|
|
# Detailer (SEGS) ED
|
|
class DetailerForEach_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"context": ("RGTHREE_CONTEXT",),
|
|
"segs": ("SEGS", ),
|
|
"set_seed_cfg_sampler": (list(KSampler_ED.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
|
|
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "mask bbox", "label_off": "crop region"}),
|
|
"max_size": ("FLOAT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"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}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
|
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
|
|
|
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
|
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
|
"wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
|
|
|
"cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"image_opt": ("IMAGE",),
|
|
"detailer_hook": ("DETAILER_HOOK",),
|
|
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
|
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
|
},
|
|
"hidden": {
|
|
"my_unique_id": "UNIQUE_ID",
|
|
"extra_pnginfo": "EXTRA_PNGINFO",}
|
|
}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "SEGS", "IMAGE", "IMAGE", "IMAGE", "IMAGE", )
|
|
RETURN_NAMES = ("CONTEXT", "SEGS", "OUTPUT_IMAGE", "CROPPED_REFINED", "CROPPED_REFINED_ALPHA", "CNET_IMAGES",)
|
|
OUTPUT_IS_LIST = (False, False, False, True, True, True)
|
|
|
|
FUNCTION = "doit_ed"
|
|
|
|
CATEGORY = "Efficiency Nodes/Image"
|
|
DESCRIPTION = "It enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size."
|
|
|
|
def doit_ed(self, context, set_seed_cfg_sampler, segs, guide_size, guide_size_for, max_size,
|
|
seed, steps, cfg, sampler_name, scheduler,
|
|
denoise, feather, noise_mask, force_inpaint, wildcard, cycle=1,
|
|
image_opt=None, detailer_hook=None, refiner_basic_pipe_opt=None,
|
|
inpaint_model=False, noise_mask_feather=0,
|
|
scheduler_func_opt=None, my_unique_id=None, extra_pnginfo=None):
|
|
|
|
_, model, clip, vae, positive, negative, image, c_batch, c_seed, c_steps, c_cfg, c_sampler, c_scheduler, mask = context_2_tuple_ed(context,["model", "clip", "vae", "positive", "negative", "images", "step_refiner", "seed", "steps", "cfg", "sampler", "scheduler", "mask"])
|
|
|
|
# GET PROPERTIES #
|
|
properties = self.get_properties(extra_pnginfo, my_unique_id)
|
|
|
|
if image_opt is not None:
|
|
image = image_opt
|
|
print(f"Detailer (SEGS) ED: Using image_opt instead of context image.")
|
|
|
|
if len(image) > 1:
|
|
raise Exception('[Impact Pack] ERROR: Detailer (SEGS) ED does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
|
|
|
if set_seed_cfg_sampler == "from context":
|
|
if c_seed is None:
|
|
raise Exception("Detailer (SEGS) ED: no seed, cfg, sampler, scheduler in the context.\n\n\n\n\n\n")
|
|
else:
|
|
seed = c_seed
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "seed", "type": "text", "data": seed})
|
|
if c_steps is not None:
|
|
steps = c_steps
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "steps", "type": "text", "data": steps})
|
|
cfg = c_cfg
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "cfg", "type": "text", "data": cfg})
|
|
sampler_name = c_sampler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "sampler_name", "type": "text", "data": sampler_name})
|
|
scheduler = c_scheduler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "scheduler", "type": "text", "data": scheduler})
|
|
elif set_seed_cfg_sampler =="from node to ctx":
|
|
context = new_context_ed(context, seed=seed, cfg=cfg, sampler=sampler_name, scheduler=scheduler)
|
|
|
|
if 'DetailerForEachDebug' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI Impact Pack', 'https://github.com/ltdrdata/ComfyUI-Impact-Pack')
|
|
|
|
enhanced_img, _, cropped_enhanced, cropped_enhanced_alpha, cnet_pil_list = \
|
|
NODES['DetailerForEachDebug']().doit(image, segs, model, clip, vae, guide_size,
|
|
guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise,
|
|
feather, noise_mask, force_inpaint, wildcard, detailer_hook=detailer_hook,
|
|
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
|
scheduler_func_opt=scheduler_func_opt, tiled_encode=properties['tiled_vae_decode'], tiled_decode=properties['tiled_vae_decode'])
|
|
|
|
context = new_context_ed(context, images=enhanced_img) #RE
|
|
return (context, segs, enhanced_img, cropped_enhanced, cropped_enhanced_alpha, cnet_pil_list,)
|
|
|
|
@staticmethod
|
|
def get_properties(extra_pnginfo, my_unique_id):
|
|
properties = {
|
|
"tiled_vae_decode": False,
|
|
}
|
|
|
|
if extra_pnginfo and "workflow" in extra_pnginfo:
|
|
workflow = extra_pnginfo["workflow"]
|
|
for node in workflow["nodes"]:
|
|
if node["id"] == int(my_unique_id):
|
|
properties['tiled_vae_decode'] = node["properties"]["Use tiled VAE decode"]
|
|
|
|
return properties
|
|
|
|
|
|
##############################################################################################################
|
|
# Ultimate SD Upscale ED
|
|
class UltimateSDUpscaleED():
|
|
SET_TILE_SIZE_FROM = {
|
|
"Image size": 1,
|
|
"Canvas size": 2,
|
|
"Node setting": 3,
|
|
}
|
|
# The modes available for Ultimate SD Upscale
|
|
MODES = {
|
|
"Linear": 1,
|
|
"Chess": 2,
|
|
"None": 3,
|
|
}
|
|
# The seam fix modes
|
|
SEAM_FIX_MODES = {
|
|
"None": 1,
|
|
"Band Pass": 2,
|
|
"Half Tile": 3,
|
|
"Half Tile + Intersections": 4,
|
|
}
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required":
|
|
{
|
|
"context": ("RGTHREE_CONTEXT",),
|
|
"set_seed_cfg_sampler": (list(KSampler_ED.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
"upscale_model": (folder_paths.get_filename_list("upscale_models"), ),
|
|
"upscale_by": ("FLOAT", {"default": 2, "min": 0.05, "max": 4, "step": 0.05}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"steps": ("INT", {"default": 20, "min": 1, "max": 10000, "step": 1}),
|
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
# Upscale Params
|
|
"mode_type": (list(s.MODES.keys()),),
|
|
#"mode_type": UltimateSDUpscale().INPUT_TYPES()["required"]["mode_type"],
|
|
"set_tile_size_from": (list(s.SET_TILE_SIZE_FROM.keys()), {"default": "Image size"}),
|
|
"tile_width": ("INT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"tile_height": ("INT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"mask_blur": ("INT", {"default": 8, "min": 0, "max": 64, "step": 1}),
|
|
"tile_padding": ("INT", {"default": 32, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
# Seam fix params
|
|
"seam_fix_mode": (list(s.SEAM_FIX_MODES.keys()),),
|
|
#"seam_fix_mode": UltimateSDUpscale().INPUT_TYPES()["required"]["seam_fix_mode"],
|
|
"seam_fix_denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"seam_fix_width": ("INT", {"default": 64, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
"seam_fix_mask_blur": ("INT", {"default": 8, "min": 0, "max": 64, "step": 1}),
|
|
"seam_fix_padding": ("INT", {"default": 16, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
|
# Misc
|
|
"force_uniform_tiles": ("BOOLEAN", {"default": True}),
|
|
"tiled_decode": ("BOOLEAN", {"default": False}),
|
|
},
|
|
"optional": {"image_opt": ("IMAGE",),},
|
|
"hidden": {"my_unique_id": "UNIQUE_ID",},}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "IMAGE", "IMAGE")
|
|
RETURN_NAMES = ("CONTEXT", "OUTPUT_IMAGE", "SOURCE_IMAGE")
|
|
FUNCTION = "upscale_ed"
|
|
CATEGORY = "Efficiency Nodes/Image"
|
|
|
|
def upscale_ed(self, context, set_seed_cfg_sampler, upscale_model, upscale_by,
|
|
seed, steps, cfg, sampler_name, scheduler, denoise,
|
|
mode_type, set_tile_size_from, tile_width, tile_height, mask_blur, tile_padding,
|
|
seam_fix_mode, seam_fix_denoise, seam_fix_mask_blur,
|
|
seam_fix_width, seam_fix_padding, force_uniform_tiles, tiled_decode, image_opt=None, my_unique_id=None):
|
|
|
|
_, model, clip, vae, positive, negative, image, c_seed, c_steps, c_cfg, c_sampler, c_scheduler = context_2_tuple_ed(context,["model", "clip", "vae", "positive", "negative", "images", "seed", "steps", "cfg", "sampler", "scheduler"])
|
|
|
|
if image_opt is not None:
|
|
image = image_opt
|
|
print(f"UltimateSDUpscale ED: Using image_opt instead of context image.")
|
|
if set_seed_cfg_sampler == "from context":
|
|
if c_seed is None:
|
|
raise Exception("UltimateSDUpscale ED: no seed, cfg, sampler, scheduler in the context.\n\n\n\n\n\n")
|
|
else:
|
|
seed = c_seed
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "seed", "type": "text", "data": seed})
|
|
if c_steps is not None:
|
|
steps = c_steps
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "steps", "type": "text", "data": steps})
|
|
cfg = c_cfg
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "cfg", "type": "text", "data": cfg})
|
|
sampler_name = c_sampler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "sampler_name", "type": "text", "data": sampler_name})
|
|
scheduler = c_scheduler
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "scheduler", "type": "text", "data": scheduler})
|
|
elif set_seed_cfg_sampler =="from node to ctx":
|
|
context = new_context_ed(context, seed=seed, cfg=cfg, sampler=sampler_name, scheduler=scheduler)
|
|
|
|
if set_tile_size_from == "Image size":
|
|
tile_width, tile_height = ED_Util.get_image_size(image)
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "tile_width", "type": "text", "data": tile_width})
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "tile_height", "type": "text", "data": tile_height})
|
|
elif set_tile_size_from == "Canvas size":
|
|
tile_width, tile_height = ED_Util.get_image_size(image)
|
|
tile_height = tile_height * upscale_by
|
|
tile_width = tile_width * upscale_by
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "tile_width", "type": "text", "data": tile_width})
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "tile_height", "type": "text", "data": tile_height})
|
|
|
|
upscaler = ED_Util.load_upscale_model(upscale_model)
|
|
|
|
if 'UltimateSDUpscale' not in NODES:
|
|
ED_Util.try_install_custom_node('UltimateSDUpscale', 'https://github.com/ssitu/ComfyUI_UltimateSDUpscale')
|
|
|
|
tensor = NODES['UltimateSDUpscale']().upscale(image, model,
|
|
positive, negative, vae, upscale_by, seed,
|
|
steps, cfg, sampler_name, scheduler, denoise, upscaler,
|
|
mode_type, tile_width, tile_height, mask_blur, tile_padding,
|
|
seam_fix_mode, seam_fix_denoise, seam_fix_mask_blur,
|
|
seam_fix_width, seam_fix_padding, force_uniform_tiles, tiled_decode)[0]
|
|
|
|
context = new_context_ed(context, images=tensor) #RE
|
|
return (context, tensor, image,)
|
|
|
|
#######################################################################################################
|
|
# SUPIR UPSCALER ED
|
|
#######################################################################################################
|
|
# SUPIR model loader ED
|
|
class SUPIR_Model_Loader_ED:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"context": ("RGTHREE_CONTEXT",),
|
|
"upscale_model": (["🚫 Do not upscale"] + folder_paths.get_filename_list("upscale_models"), ),
|
|
"upscale_by": ("FLOAT", {"default": 2, "min": 0.05, "max": 4, "step": 0.05}),
|
|
"rescale_method": (["nearest-exact", "bilinear", "area", "bicubic", "lanczos"],),
|
|
"supir_model": (folder_paths.get_filename_list("checkpoints"),),
|
|
"fp8_unet": ("BOOLEAN", {"default": False}),
|
|
"diffusion_dtype": (['fp16', 'bf16', 'fp32', 'auto'], {"default": 'auto'}),
|
|
},
|
|
"optional": {
|
|
"high_vram": ("BOOLEAN", {"default": False}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "SUPIRMODEL", "SUPIRVAE", "IMAGE")
|
|
RETURN_NAMES = ("CONTEXT", "SUPIR_MODEL","SUPIR_VAE", "UPSCALED_IMAGE")
|
|
FUNCTION = "process_ed"
|
|
CATEGORY = "Efficiency Nodes/Loaders"
|
|
DESCRIPTION = """
|
|
Loads the SUPIR model and merges it with the SDXL model.
|
|
|
|
Diffusion type should be kept on auto, unless you have issues loading the model.
|
|
fp8_unet casts the unet weights to torch.float8_e4m3fn, which saves a lot of VRAM but has slight quality impact.
|
|
high_vram: uses Accelerate to load weights to GPU, slightly faster model loading.
|
|
"""
|
|
|
|
def upscale(self, upscale_model, image, upscale_by, rescale_method):
|
|
samples = image.movedim(-1,1)
|
|
width = round(samples.shape[3])
|
|
height = round(samples.shape[2])
|
|
target_width = round(samples.shape[3] * upscale_by)
|
|
target_height = round(samples.shape[2] * upscale_by)
|
|
samples = ImageUpscaleWithModel().upscale(upscale_model, image)[0].movedim(-1,1)
|
|
|
|
upscaled_width = round(samples.shape[3])
|
|
upscaled_height = round(samples.shape[2])
|
|
|
|
if upscaled_width != target_width or upscaled_height != target_height:
|
|
samples = comfy.utils.common_upscale(samples, target_width, target_height, rescale_method, "disabled")
|
|
|
|
samples = samples.movedim(1,-1)
|
|
return (samples)
|
|
|
|
def process_ed(self, context, upscale_model, upscale_by, rescale_method, supir_model, diffusion_dtype, fp8_unet, high_vram=False):
|
|
|
|
_, model, clip, vae, image = context_2_tuple_ed(context,["model", "clip", "vae", "images"])
|
|
|
|
if upscale_model != "🚫 Do not upscale":
|
|
upscaler = ED_Util.load_upscale_model(upscale_model)
|
|
upscaled_image = self.upscale(upscaler, image, upscale_by, rescale_method)
|
|
else:
|
|
upscaled_image = image
|
|
|
|
if 'SUPIR_model_loader_v2' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI-SUPIR', 'https://github.com/kijai/ComfyUI-SUPIR')
|
|
|
|
sup_model, sup_vae = NODES['SUPIR_model_loader_v2']().process(supir_model, diffusion_dtype,
|
|
fp8_unet, model, clip, vae, high_vram=False)
|
|
return (context, sup_model, sup_vae, upscaled_image)
|
|
|
|
#####################################################################################################
|
|
# SUPIR Sampler ED
|
|
class SUPIR_Sampler_ED():
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required":
|
|
{
|
|
"context": ("RGTHREE_CONTEXT",),
|
|
"SUPIR_model": ("SUPIRMODEL",),
|
|
"SUPIR_vae": ("SUPIRVAE",),
|
|
"set_seed_cfg_sampler": (list(KSampler_ED.SET_SEED_CFG_SAMPLER.keys()), {"default": "from context"}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"steps": ("INT", {"default": 20, "min": 1, "max": 10000, "step": 1}),
|
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
|
"EDM_s_churn": ("INT", {"default": 5, "min": 0, "max": 40, "step": 1}),
|
|
"s_noise": ("FLOAT", {"default": 1.003, "min": 1.0, "max": 1.1, "step": 0.001}),
|
|
"DPMPP_eta": ("FLOAT", {"default": 1.0, "min": 0, "max": 10.0, "step": 0.01}),
|
|
"control_scale_start": ("FLOAT", {"default": 1.0, "min": 0, "max": 10.0, "step": 0.01}),
|
|
"control_scale_end": ("FLOAT", {"default": 1.0, "min": 0, "max": 10.0, "step": 0.01}),
|
|
"restore_cfg": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 20.0, "step": 0.01}),
|
|
"keep_model_loaded": ("BOOLEAN", {"default": False}),
|
|
"sampler": (['RestoreDPMPP2MSampler', 'RestoreEDMSampler', 'TiledRestoreDPMPP2MSampler',
|
|
'TiledRestoreEDMSampler',], {"default": 'RestoreEDMSampler' }),
|
|
"use_tiled_vae": ("BOOLEAN", {"default": True}),
|
|
"vae_tile_size": ("INT", {"default": 512, "min": 64, "max": 8192, "step": 64}),
|
|
#"decoder_tile_size": ("INT", {"default": 512, "min": 64, "max": 8192, "step": 64}),
|
|
"encoder_dtype": (['bf16', 'fp32', 'auto' ], {"default": 'auto'}),
|
|
},
|
|
"optional": {
|
|
"image_opt": ("IMAGE",),
|
|
"sampler_tile_size": ("INT", {"default": 1024, "min": 64, "max": 4096, "step": 32}),
|
|
"sampler_tile_stride": ("INT", {"default": 512, "min": 32, "max": 2048, "step": 32}),
|
|
},
|
|
"hidden": {"my_unique_id": "UNIQUE_ID",},}
|
|
|
|
RETURN_TYPES = ("RGTHREE_CONTEXT", "IMAGE", "IMAGE")
|
|
RETURN_NAMES = ("CONTEXT", "OUTPUT_IMAGE", "SOURCE_IMAGE")
|
|
FUNCTION = "process_ed"
|
|
CATEGORY = "Efficiency Nodes/Image"
|
|
DESCRIPTION = """
|
|
- **latent:**
|
|
Latent to sample from, when using SUPIR latent this is just for the noise shape,
|
|
it's actually not used otherwise here. Identical to feeding this comfy empty latent.
|
|
If fed anything else it's used as it is, no noise is added.
|
|
- **cfg:**
|
|
Linearly scaled CFG is always used, first step will use the cfg_scale_start value,
|
|
and that is interpolated to the cfg_scale_end value at last step.
|
|
To disable scaling set these values to be the same.
|
|
- **EDM_s_churn:**
|
|
controls the rate of adaptation of the diffusion process to changes in noise levels
|
|
over time. Has no effect with DPMPP samplers.
|
|
- **s_noise:**
|
|
This parameter directly controls the amount of noise added to the image at each
|
|
step of the diffusion process.
|
|
- **DPMPP_eta:**
|
|
Scaling factor that influences the diffusion process by adjusting how the denoising
|
|
process adapts to changes in noise levels over time.
|
|
No effect with EDM samplers.
|
|
- **control_scale:**
|
|
The strenght of the SUPIR control model, scales linearly from start to end.
|
|
Lower values allow more freedom from the input image.
|
|
- **restore_cfg:**
|
|
Controls the degree of restoration towards the original image during the diffusion
|
|
process. It allows for dome fine-tuning of the process.
|
|
- **samplers:**
|
|
EDM samplers need lots of steps but generally have better quality.
|
|
DPMPP samplers work well with lower steps, good for lightning models.
|
|
Tiled samplers enable tiled diffusion process, this is very slow but allows higher
|
|
resolutions to be used by saving VRAM. Tile size should be chosen so the image
|
|
is evenly tiled. Tile stride affects the overlap of the tiles. Check the
|
|
SUPIR Tiles -node for preview to understand how the image is tiled.
|
|
|
|
"""
|
|
|
|
def process_ed(self, context, SUPIR_model, SUPIR_vae, set_seed_cfg_sampler, seed, steps, cfg, EDM_s_churn, s_noise, DPMPP_eta, control_scale_start, control_scale_end, restore_cfg, keep_model_loaded, sampler,
|
|
use_tiled_vae, vae_tile_size, encoder_dtype, image_opt=None, sampler_tile_size=1024, sampler_tile_stride=512, my_unique_id=None):
|
|
|
|
_, positive_prompt, negative_prompt, source_image, c_seed, c_steps, c_cfg = context_2_tuple_ed(context,["text_pos_g", "text_neg_g", "images", "seed", "steps", "cfg"])
|
|
|
|
if image_opt is None:
|
|
image_opt = source_image
|
|
|
|
if set_seed_cfg_sampler == "from context":
|
|
if c_seed is None:
|
|
raise Exception("UltimateSDUpscale ED: no seed, cfg, sampler, scheduler in the context.\n\n\n\n\n\n")
|
|
else:
|
|
seed = c_seed
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "seed", "type": "text", "data": seed})
|
|
# if c_steps is not None:
|
|
# steps = c_steps
|
|
# PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "steps", "type": "text", "data": steps})
|
|
cfg = c_cfg
|
|
PromptServer.instance.send_sync("ed-node-feedback", {"node_id": my_unique_id, "widget_name": "cfg", "type": "text", "data": cfg})
|
|
elif set_seed_cfg_sampler =="from node to ctx":
|
|
context = new_context_ed(context, seed=seed, cfg=cfg)
|
|
|
|
if 'SUPIR_first_stage' not in NODES:
|
|
ED_Util.try_install_custom_node('ComfyUI-SUPIR', 'https://github.com/kijai/ComfyUI-SUPIR')
|
|
|
|
# SUPIR_first_stage
|
|
SUPIR_vae2, denoised_image, denoised_latent = NODES['SUPIR_first_stage']().process(SUPIR_vae, image_opt, encoder_dtype, use_tiled_vae, vae_tile_size, vae_tile_size)
|
|
|
|
# SUPIR_conditioner
|
|
positive_encoded, negative_encoded = NODES['SUPIR_conditioner']().condition(SUPIR_model, denoised_latent, positive_prompt, negative_prompt, captions="")
|
|
|
|
# SUPIR_encode
|
|
SUPIR_latent = NODES['SUPIR_encode']().encode(SUPIR_vae2, denoised_image, encoder_dtype, use_tiled_vae, vae_tile_size)[0]
|
|
|
|
# SUPIR_sample
|
|
SUPIR_latent = NODES['SUPIR_sample']().sample(SUPIR_model, SUPIR_latent, steps, seed,
|
|
cfg, EDM_s_churn, s_noise, positive_encoded, negative_encoded, cfg, control_scale_start, control_scale_end,
|
|
restore_cfg, keep_model_loaded, DPMPP_eta, sampler, sampler_tile_size=1024, sampler_tile_stride=512)[0]
|
|
|
|
# SUPIR_decode
|
|
output_image = NODES['SUPIR_decode']().decode(SUPIR_vae, SUPIR_latent, use_tiled_vae, vae_tile_size)[0]
|
|
context = new_context_ed(context, images=output_image)
|
|
|
|
return (context, output_image, source_image)
|
|
|
|
|
|
##############################################################################################################
|
|
# NODE MAPPING
|
|
##############################################################################################################
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
#ED
|
|
"Efficient Loader 💬ED": EfficientLoader_ED,
|
|
"KSampler (Efficient) 💬ED": KSampler_ED,
|
|
"KSampler Text 💬ED": KSamplerTEXT_ED,
|
|
"Load Image 💬ED": LoadImage_ED,
|
|
"Save Image 🔔ED": SaveImage_ED,
|
|
#"Control Net Script 💬ED": Control_Net_Script_ED,
|
|
"Refiner Script 💬ED": Refiner_Script_ED,
|
|
"Embedding Stacker 💬ED": Embedding_Stacker_ED,
|
|
"Wildcard Encode 💬ED": WildcardEncode_ED,
|
|
"LoRA Stacker 💬ED": LoRA_Stacker_ED,
|
|
"Int Holder 💬ED": Int_Holder_ED,
|
|
"Regional Stacker 💬ED": Regional_Stacker_ED,
|
|
"Regional Processor 💬ED": Regional_Processor_ED,
|
|
"Regional Script 💬ED": Regional_Script_ED,
|
|
"Context To BasicPipe": ContextToBasicPipe,
|
|
"Context To DetailerPipe": ContextToDetailerPipe,
|
|
"Get Booru Tag 💬ED": GetBooruTag_ED,
|
|
"Simple Text 💬ED": SimpleText_ED,
|
|
"TIPO Script 💬ED": TIPOScript_ED,
|
|
|
|
"FaceDetailer 💬ED": FaceDetailer_ED,
|
|
"MaskDetailer 💬ED": MaskDetailer_ED,
|
|
"Detailer (SEGS) 💬ED": DetailerForEach_ED,
|
|
"Ultimate SD Upscale 💬ED": UltimateSDUpscaleED,
|
|
|
|
"SUPIR Model Loader 💬ED": SUPIR_Model_Loader_ED,
|
|
"SUPIR Sampler 💬ED": SUPIR_Sampler_ED,
|
|
}
|
|
|
|
|
|
|