add:easy controlnetLoaderADV node

This commit is contained in:
yolain
2023-12-14 18:27:29 +08:00
parent 22ce1f9f36
commit 1b7af8ea45
9 changed files with 224 additions and 47 deletions
+3
View File
@@ -14,6 +14,9 @@
"easy controlnetLoader": {
"title": "简易Controlnet"
},
"easy controlnetLoaderADV": {
"title": "简易Controlnet(高级)"
},
"easy LLLite": {
"title": "简易LLLite"
},
+14 -3
View File
@@ -19,15 +19,26 @@ EasyUse is simplified on the basis of [tinyterraNodes](https://github.com/TinyTe
### Updated
**[Updated at 12/13/2023]**
**2023-12-14**
- `easy a1111Loader` and `easy comfyLoader` added `batch_size` of required input parameters
- Added the `easy controlnetLoaderADV` node
- `easy controlnetLoaderADV` and `easy controlnetLoader` added `control_net ` of optional input parameters
- `easy preSampling` and `easy preSamplingAdvanced` added 'image_to_latent' optional input parameters
- Added the `easy imageSizeBySide` node, which can be output as a long side or a short side
<details>
<summary><b>Updated at 12/13/2023</b></summary>
- Added the `easy LLLiteLoader` node, if you have pre-installed the kohya-ss/ControlNet-LLLite-ComfyUI package, please move the model files in the models to `ComfyUI\models\controlnet\` (i.e. in the default controlnet path of comfy, please do not change the file name of the model, otherwise it will not be read).
- Modify `easy controlnetLoader` to the bottom of the loader category.
- Added size display for `easy imageSize` and `easy imageSizeByLongerSize` outputs.
</details>
**[Updated at 12/11/2023]**
<details>
<summary><b>Updated at 12/11/2023</b></summary>
- Added the `showSpentTime` node to display the time spent on image diffusion and the time spent on VAE decoding images
</details>
### Major optimizations
+15 -3
View File
@@ -15,19 +15,31 @@
<img src="./docs/workflow_node_compare.png">
EasyUse 在 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的基础上做了简化,在简化的节点中去除了过多的传入和传出参数,建议您配合 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 中的 **pipeIn**、**pipeOut**、**pipeEdit** 使用,可参考下方示例里 [图生图的工作流](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#%E5%9B%BE%E7%94%9F%E5%9B%BEcontrolnet)。
EasyUse 在 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的基础上做了简化,在简化的节点中去除了过多的传入和传出参数,建议您配合 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 中的 **pipeIn**、**pipeOut**、**pipeEdit** 使用,可参考下方示例里 [工作流](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#sdturbo%E9%AB%98%E6%B8%85%E4%BF%AE%E5%A4%8Dsvd)。
### 更新
**2023-12-13**
**2023-12-14**
- `easy a1111Loader` 和 `easy comfyLoader` 新增 `batch_size` 传入参数
- 新增 `easy controlnetLoaderADV` 节点
- `easy controlnetLoaderADV` 和 `easy controlnetLoader` 新增 `control_net` 可选传入参数
- `easy preSampling` 和 `easy preSamplingAdvanced` 新增 `image_to_latent` 可选传入参数
- 新增 `easy imageSizeBySide` 节点,可选输出为长边或短边
<details>
<summary><b>2023-12-13</b></summary>
- 新增 `easy LLLiteLoader` 节点,如果您预先安装过 kohya-ss/ControlNet-LLLite-ComfyUI 包,请将 models 里的模型文件移动至 ComfyUI\models\controlnet\ (即comfy默认的controlnet路径里,请勿修改模型的文件名,不然会读取不到)。
- 修改 `easy controlnetLoader` 到 loader 分类底下。
- 新增 `easy imageSize` 和 `easy imageSizeByLongerSize` 输出的尺寸显示。
</details>
**2023-12-11**
<details>
<summary><b>2023-12-11</b></summary>
- 新增 `easy showSpentTime` 节点用于展示图片推理花费时间与VAE解码花费时间。
</details>
### 主要的优化
Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 324 KiB

+152 -34
View File
@@ -28,7 +28,7 @@ from comfy_extras.chainner_models import model_loading
from typing import Dict, List, Optional, Tuple, Union, Any
from .adv_encode import advanced_encode, advanced_encode_XL
from nodes import MAX_RESOLUTION, VAEEncode, VAEEncodeTiled, VAEDecode, VAEDecodeTiled
from nodes import MAX_RESOLUTION, RepeatLatentBatch
from .config import BASE_RESOLUTIONS
from server import PromptServer
@@ -781,6 +781,7 @@ class a1111Loader:
"positive": ("STRING", {"default": "Positive", "multiline": True}),
"negative": ("STRING", {"default": "Negative", "multiline": True}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
},
"optional": {"optional_lora_stack": ("LORA_STACK",)},
"hidden": {"prompt": "PROMPT", "positive_weight_interpretation": "A1111", "negative_weight_interpretation": "A1111"}, "my_unique_id": "UNIQUE_ID"}
@@ -794,7 +795,7 @@ class a1111Loader:
def adv_pipeloader(self, ckpt_name, vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, negative, optional_lora_stack=None, prompt=None,
positive, negative, batch_size, optional_lora_stack=None, prompt=None,
positive_weight_interpretation='A1111', negative_weight_interpretation='A1111',
my_unique_id=None
):
@@ -813,7 +814,7 @@ class a1111Loader:
raise ValueError("Invalid base_resolution format.")
# Create Empty Latent
latent = torch.zeros([1, 4, empty_latent_height // 8, empty_latent_width // 8]).cpu()
latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]).cpu()
samples = {"samples": latent}
# Clean models from loaded_objects
@@ -894,7 +895,7 @@ class a1111Loader:
"negative_balance": None,
"empty_latent_width": empty_latent_width,
"empty_latent_height": empty_latent_height,
"batch_size": 1,
"batch_size": batch_size,
"seed": 0,
"empty_samples": samples, }
}
@@ -921,6 +922,8 @@ class comfyLoader:
"positive": ("STRING", {"default": "Positive", "multiline": True}),
"negative": ("STRING", {"default": "Negative", "multiline": True}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
},
"optional": {"optional_lora_stack": ("LORA_STACK",)},
"hidden": {"prompt": "PROMPT", "positive_weight_interpretation": "comfy", "negative_weight_interpretation": "comfy"}, "my_unique_id": "UNIQUE_ID"}
@@ -934,7 +937,7 @@ class comfyLoader:
def adv_pipeloader(self, ckpt_name, vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, negative, optional_lora_stack=None, prompt=None,
positive, negative, batch_size, optional_lora_stack=None, prompt=None,
positive_weight_interpretation='comfy', negative_weight_interpretation='comfy',
my_unique_id=None
):
@@ -943,7 +946,7 @@ class comfyLoader:
ckpt_name, vae_name, clip_skip,
lora_name, lora_model_strength, lora_clip_strength,
resolution, empty_latent_width, empty_latent_height,
positive, negative, optional_lora_stack, prompt,
positive, negative, batch_size, optional_lora_stack, prompt,
positive_weight_interpretation, negative_weight_interpretation,
my_unique_id
)
@@ -958,12 +961,11 @@ class controlnetSimple:
return {
"required": {
"pipe": ("PIPE_LINE",),
"control_net_name": (folder_paths.get_filename_list("controlnet"),),
"image": ("IMAGE",),
"control_net_name": (folder_paths.get_filename_list("controlnet"),),
},
"optional": {
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"control_net": ("CONTROL_NET",),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
}
}
@@ -975,16 +977,17 @@ class controlnetSimple:
FUNCTION = "controlnetApply"
CATEGORY = "EasyUse/Loader"
def controlnetApply(self, pipe, control_net_name, image, positive=None, negative=None, strength=1):
controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
control_net = comfy.controlnet.load_controlnet(controlnet_path)
def controlnetApply(self, pipe, image, control_net_name, control_net=None,strength=1):
if control_net is None:
controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
control_net = comfy.controlnet.load_controlnet(controlnet_path)
control_hint = image.movedim(-1, 1)
_positive = pipe["positive"] if positive is None else positive
_negative = pipe["negative"] if negative is None else negative
positive = pipe["positive"]
negative = pipe["negative"]
if strength != 0:
if _negative is None:
if negative is None:
p = []
for t in positive:
n = [t[0], t[1].copy()]
@@ -994,11 +997,11 @@ class controlnetSimple:
n[1]['control'] = c_net
n[1]['control_apply_to_uncond'] = True
p.append(n)
_positive = p
positive = p
else:
cnets = {}
out = []
for conditioning in [_positive, _negative]:
for conditioning in [positive, negative]:
c = []
for t in conditioning:
d = t[1].copy()
@@ -1016,12 +1019,94 @@ class controlnetSimple:
n = [t[0], d]
c.append(n)
out.append(c)
_positive = out[0]
_negative = out[1]
positive = out[0]
negative = out[1]
# 拼接条件
positive = _positive if positive is None else _positive + pipe['positive']
negative = _negative if negative is None else _negative + pipe['negative']
new_pipe = {
"model": pipe['model'],
"positive": positive,
"negative": negative,
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": pipe["samples"],
"images": pipe["images"],
"seed": 0,
"loader_settings": pipe["loader_settings"]
}
return (new_pipe,)
# controlnetADV
class controlnetAdvanced:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pipe": ("PIPE_LINE",),
"image": ("IMAGE",),
"control_net_name": (folder_paths.get_filename_list("controlnet"),),
},
"optional": {
"control_net": ("CONTROL_NET",),
"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})
}
}
RETURN_TYPES = ("PIPE_LINE",)
RETURN_NAMES = ("pipe",)
OUTPUT_NODE = True
FUNCTION = "controlnetApply"
CATEGORY = "EasyUse/Loader"
def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1):
if control_net is None:
controlnet_path = folder_paths.get_full_path("controlnet", control_net_name)
control_net = comfy.controlnet.load_controlnet(controlnet_path)
control_hint = image.movedim(-1, 1)
positive = pipe["positive"]
negative = pipe["negative"]
if strength != 0:
if negative is None:
p = []
for t in positive:
n = [t[0], t[1].copy()]
c_net = control_net.copy().set_cond_hint(control_hint, strength)
if 'control' in t[1]:
c_net.set_previous_controlnet(t[1]['control'])
n[1]['control'] = c_net
n[1]['control_apply_to_uncond'] = True
p.append(n)
positive = p
else:
cnets = {}
out = []
for conditioning in [positive, negative]:
c = []
for t in conditioning:
d = t[1].copy()
prev_cnet = d.get('control', None)
if prev_cnet in cnets:
c_net = cnets[prev_cnet]
else:
c_net = control_net.copy().set_cond_hint(control_hint, strength, (start_percent, end_percent))
c_net.set_previous_controlnet(prev_cnet)
cnets[prev_cnet] = c_net
d['control'] = c_net
d['control_apply_to_uncond'] = False
n = [t[0], d]
c.append(n)
out.append(c)
positive = out[0]
negative = out[1]
new_pipe = {
"model": pipe['model'],
@@ -1095,6 +1180,9 @@ class samplerSettings:
"seed_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
"control_before_generate": (["fixed", "increment", "decrement", "randomize"], {"default": "randomize"}),
},
"optional": {
"image_to_latent": ("IMAGE",),
},
"hidden":
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -1106,7 +1194,7 @@ class samplerSettings:
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, steps, cfg, sampler_name, scheduler, denoise, seed_num, control_before_generate, prompt=None, extra_pnginfo=None, my_unique_id=None):
def settings(self, pipe, steps, cfg, sampler_name, scheduler, denoise, seed_num, control_before_generate, image_to_latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
# seed生成
seed_num = control_seed(control_before_generate, seed_num)
@@ -1117,6 +1205,17 @@ class samplerSettings:
length = len(node["widgets_values"])
node["widgets_values"][length-2] = seed_num
vae = pipe["vae"]
# 图生图转换
if image_to_latent is not None:
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
samples = {"samples": vae.encode(image_to_latent)}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
else:
samples = pipe["samples"]
images = pipe["images"]
print(samples)
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
@@ -1124,8 +1223,8 @@ class samplerSettings:
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": pipe["samples"],
"images": pipe["images"],
"samples": samples,
"images": images,
"seed": seed_num,
"loader_settings": {
@@ -1163,6 +1262,9 @@ class samplerSettingsAdvanced:
"seed_num": ("INT", {"default": 0, "min": 0, "max": 1125899906842624}),
"control_before_generate": (["fixed", "increment", "decrement", "randomize"], {"default": "randomize"}),
},
"optional": {
"image_to_latent": ("Image",)
},
"hidden":
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -1174,7 +1276,7 @@ class samplerSettingsAdvanced:
FUNCTION = "settings"
CATEGORY = "EasyUse/PreSampling"
def settings(self, pipe, steps, cfg, sampler_name, scheduler, start_at_step, end_at_step, add_noise, seed_num, control_before_generate, prompt=None, extra_pnginfo=None, my_unique_id=None):
def settings(self, pipe, steps, cfg, sampler_name, scheduler, start_at_step, end_at_step, add_noise, seed_num, control_before_generate, image_to_latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
# seed生成
seed_num = control_seed(control_before_generate, seed_num)
@@ -1185,6 +1287,17 @@ class samplerSettingsAdvanced:
length = len(node["widgets_values"])
node["widgets_values"][length-2] = seed_num
# 图生图转换
vae = pipe["vae"]
if image_to_latent is not None:
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
samples = {"samples": vae.encode(image_to_latent)}
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
images = image_to_latent
else:
samples = pipe["samples"]
images = pipe["images"]
new_pipe = {
"model": pipe['model'],
"positive": pipe['positive'],
@@ -1192,8 +1305,8 @@ class samplerSettingsAdvanced:
"vae": pipe['vae'],
"clip": pipe['clip'],
"samples": pipe["samples"],
"images": pipe["images"],
"samples": samples,
"images": images,
"seed": seed_num,
"loader_settings": {
@@ -1516,9 +1629,6 @@ class samplerSimple:
if add_noise == "disable":
disable_noise = True
def vae_decode_latent(vae, samples, tile_size):
return VAEDecodeTiled().decode(vae, samples, tile_size)[0] if tile_size is not None else VAEDecode().decode(vae, samples)[0]
def process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive,
samp_negative,
steps, start_step, last_step, cfg, sampler_name, scheduler, denoise,
@@ -1533,7 +1643,13 @@ class samplerSimple:
# 推理结束时间
end_time = int(time.time() * 1000)
# 解码图片
samp_images = vae_decode_latent(samp_vae, samp_samples, tile_size)
latent = samp_samples["samples"]
# 解码图片
if tile_size is not None:
samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, )
else:
samp_images = samp_vae.decode(latent).cpu()
# 推理总耗时(包含解码)
end_decode_time = int(time.time() * 1000)
@@ -1686,8 +1802,8 @@ class samplerSDTurbo:
latent = samp_samples['samples']
# 解码图片
if tile_size:
samp_images = (samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, ),)
if tile_size is not None:
samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, )
else:
samp_images = samp_vae.decode(latent).cpu()
@@ -1770,6 +1886,7 @@ NODE_CLASS_MAPPINGS = {
"easy a1111Loader": a1111Loader,
"easy comfyLoader": comfyLoader,
"easy controlnetLoader": controlnetSimple,
"easy controlnetLoaderADV": controlnetAdvanced,
"easy globalSeed": globalSeed,
"easy preSampling": samplerSettings,
"easy preSamplingAdvanced": samplerSettingsAdvanced,
@@ -1785,6 +1902,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy a1111Loader": "EasyLoader (A1111)",
"easy comfyLoader": "EasyLoader (comfy)",
"easy controlnetLoader": "EasyControlnet",
"easy controlnetLoaderADV": "EasyControlnet (Advanced)",
"easy globalSeed": "GlobalSeed",
"easy preSampling": "PreSampling",
"easy preSamplingAdvanced": "PreSampling (Advanced)",
+34 -1
View File
@@ -119,7 +119,38 @@ class imageSize:
result = (0, 0)
return {"ui": {"text": "Width: "+str(result[0])+" , Height: "+str(result[1])}, "result": result}
# 图像尺寸
# 图像尺寸(最长边)
class imageSizeBySide:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"side": (["Longest", "Shortest"],)
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("resolution",)
FUNCTION = "image_side"
CATEGORY = "EasyUse/Image"
def image_side(self, image, side):
image = tensor2pil(image)
if image.size:
if side == "Longest":
result = (image.size[0],) if image.size[0] > image.size[1] else (image.size[1],)
elif side == 'Shortest':
result = (image.size[0],) if image.size[0] < image.size[1] else (image.size[1],)
else:
result = (0,)
return {"ui": {"text": str(result[0])}, "result": result}
# 图像尺寸(最长边)
class imageSizeByLongerSide:
def __init__(self):
pass
@@ -152,11 +183,13 @@ class imageSizeByLongerSide:
NODE_CLASS_MAPPINGS = {
"easy imageInsetCrop": imageInsetCrop,
"easy imageSize": imageSize,
"easy imageSizeBySide": imageSizeBySide,
"easy imageSizeByLongerSide": imageSizeByLongerSide
}
NODE_DISPLAY_NAME_MAPPINGS = {
"easy imageInsetCrop": "ImageInsetCrop",
"easy imageSize": "ImageSize",
"easy imageSizeBySide": "ImageSize (Side)",
"easy imageSizeByLongerSide": "ImageSize (LongerSide)"
}
+4 -4
View File
@@ -4,8 +4,8 @@ import os
import folder_paths
import comfy
def get_file_list(path):
return [file for file in os.listdir(path) if file != "put_models_here.txt" and "lllite" in file]
def get_file_list(filenames):
return [file for file in filenames if file != "put_models_here.txt" and "lllite" in file]
def extra_options_to_module_prefix(extra_options):
@@ -252,7 +252,7 @@ class LLLiteLoader:
return {
"required": {
"model": ("MODEL",),
"model_name": (get_file_list(folder_paths.get_folder_paths("controlnet")[0]),),
"model_name": (get_file_list(folder_paths.get_filename_list("controlnet")),),
"cond_image": ("IMAGE",),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
"steps": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1}),
@@ -268,7 +268,7 @@ class LLLiteLoader:
def load_lllite(self, model, model_name, cond_image, strength, steps, start_percent, end_percent):
# cond_image is b,h,w,3, 0-1
model_path = os.path.join(folder_paths.get_folder_paths("controlnet")[0], model_name)
model_path = os.path.join(folder_paths.get_full_path("controlnet", model_name))
model_lllite = model.clone()
patch = load_control_net_lllite_patch(model_path, cond_image, strength, steps, start_percent, end_percent)
+2 -2
View File
@@ -5,7 +5,7 @@ app.registerExtension({
name: "comfy.easyUse.imageWidgets",
nodeCreated(node) {
if (["easy imageSize","easy imageSizeByLongerSide"].includes(node.comfyClass)) {
if (["easy imageSize","easy imageSizeBySide","easy imageSizeByLongerSide"].includes(node.comfyClass)) {
const inputEl = document.createElement("textarea");
inputEl.className = "comfy-multiline-input";
@@ -29,7 +29,7 @@ app.registerExtension({
},
beforeRegisterNodeDef(nodeType, nodeData, app) {
if (["easy imageSize","easy imageSizeByLongerSide"].includes(nodeData.name)) {
if (["easy imageSize","easy imageSizeBySide","easy imageSizeByLongerSide"].includes(nodeData.name)) {
function populate(arr_text) {
var text = '';
for (let i = 0; i < arr_text.length; i++){