v1.3.4: 新增Lazy Switch KJ节点和重要功能修复
- 新增Lazy Switch KJ (UTK)节点:支持懒加载评估的条件流程控制 - 支持任意数据类型的条件切换,提供真正的懒加载机制 - 修复Crop By Mask (UTK)节点批处理逻辑:现在正确支持图像和mask批次对应 - 改进批处理算法:每个图像使用对应位置的mask进行独立裁剪 - 智能处理批次数量不匹配:自动重复或截断mask以匹配图像数量 - 增强日志输出:每个图像的裁剪信息单独记录,便于调试 - 保持向后兼容性:单图像+单mask的使用方式保持不变
This commit is contained in:
+24
-1
@@ -8,13 +8,24 @@ A comprehensive toolkit for ComfyUI that provides various utility nodes for imag
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
__version__ = "1.3.3"
|
||||
__version__ = "1.3.4"
|
||||
__author__ = "CyberDickLang"
|
||||
__email__ = "286878701@qq.com"
|
||||
__url__ = "https://github.com/whmc76"
|
||||
|
||||
# 更新日志
|
||||
CHANGELOG = {
|
||||
"1.3.4": [
|
||||
"新增Lazy Switch KJ节点和重要功能修复:",
|
||||
"- 新增Lazy Switch KJ (UTK)节点:支持懒加载评估的条件流程控制",
|
||||
"- 支持任意数据类型的条件切换,提供真正的懒加载机制",
|
||||
"- 修复Crop By Mask (UTK)节点批处理逻辑:现在正确支持图像和mask批次对应",
|
||||
"- 改进批处理算法:每个图像使用对应位置的mask进行独立裁剪",
|
||||
"- 智能处理批次数量不匹配:自动重复或截断mask以匹配图像数量",
|
||||
"- 增强日志输出:每个图像的裁剪信息单独记录,便于调试",
|
||||
"- 保持向后兼容性:单图像+单mask的使用方式保持不变",
|
||||
"- 优化性能:避免不必要的计算,特别适用于条件工作流",
|
||||
],
|
||||
"1.3.3": [
|
||||
"新增多个kjnodes节点移植和架构优化:",
|
||||
"- 新增Color Match (UTK)节点:支持6种颜色匹配算法,用于图像间色彩转移",
|
||||
@@ -596,6 +607,15 @@ except ImportError:
|
||||
COLOR_TO_MASK_MAPPINGS = {}
|
||||
COLOR_TO_MASK_DISPLAY = {}
|
||||
|
||||
try:
|
||||
from .nodes.tools.lazy_switch import \
|
||||
NODE_CLASS_MAPPINGS as LAZY_SWITCH_MAPPINGS
|
||||
from .nodes.tools.lazy_switch import \
|
||||
NODE_DISPLAY_NAME_MAPPINGS as LAZY_SWITCH_DISPLAY
|
||||
except ImportError:
|
||||
LAZY_SWITCH_MAPPINGS = {}
|
||||
LAZY_SWITCH_DISPLAY = {}
|
||||
|
||||
# 合并所有节点映射
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(EMPTY_UNIT_MAPPINGS)
|
||||
@@ -629,6 +649,7 @@ NODE_CLASS_MAPPINGS.update(LORA_INFO_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(KONTEXT_PRESETS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(PROMPT_HELPER_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(COLOR_TO_MASK_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(LAZY_SWITCH_MAPPINGS)
|
||||
|
||||
# 合并显示名称映射
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
@@ -663,6 +684,7 @@ NODE_DISPLAY_NAME_MAPPINGS.update(LORA_INFO_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(KONTEXT_PRESETS_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(PROMPT_HELPER_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(COLOR_TO_MASK_DISPLAY)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(LAZY_SWITCH_DISPLAY)
|
||||
|
||||
NODE_CATEGORIES = {
|
||||
"UniversalToolkit": [
|
||||
@@ -698,6 +720,7 @@ NODE_CATEGORIES = {
|
||||
"LoraInfo_UTK",
|
||||
"LoadKontextPresets_UTK",
|
||||
"ColorToMask_UTK",
|
||||
"LazySwitchKJ_UTK",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
+73
-20
@@ -132,26 +132,42 @@ class CropByMask_UTK:
|
||||
l_images = []
|
||||
l_masks = []
|
||||
|
||||
# 处理图像批次
|
||||
for l in image:
|
||||
l_images.append(torch.unsqueeze(l, 0))
|
||||
|
||||
# 处理mask批次
|
||||
if mask_for_crop.dim() == 2:
|
||||
mask_for_crop = torch.unsqueeze(mask_for_crop, 0)
|
||||
# 如果有多张mask输入,使用第一张
|
||||
if mask_for_crop.shape[0] > 1:
|
||||
log(
|
||||
f"Warning: Multiple mask inputs, using the first.",
|
||||
message_type="warning",
|
||||
)
|
||||
mask_for_crop = torch.unsqueeze(mask_for_crop[0], 0)
|
||||
|
||||
# 反转mask(如果需要)
|
||||
if invert_mask:
|
||||
mask_for_crop = 1 - mask_for_crop
|
||||
l_masks.append(tensor2pil(torch.unsqueeze(mask_for_crop, 0)).convert("L"))
|
||||
|
||||
# 将所有mask转换为PIL图像
|
||||
for i in range(mask_for_crop.shape[0]):
|
||||
l_masks.append(tensor2pil(torch.unsqueeze(mask_for_crop[i], 0)).convert("L"))
|
||||
|
||||
# 如果mask数量少于图像数量,重复使用最后一个mask
|
||||
while len(l_masks) < len(l_images):
|
||||
l_masks.append(l_masks[-1])
|
||||
|
||||
# 如果mask数量多于图像数量,截断到图像数量
|
||||
if len(l_masks) > len(l_images):
|
||||
l_masks = l_masks[:len(l_images)]
|
||||
log(f"Warning: More masks than images, using first {len(l_images)} masks.", message_type="warning")
|
||||
|
||||
_mask = mask2image(mask_for_crop)
|
||||
# 获取画布尺寸
|
||||
canvas_width, canvas_height = (
|
||||
tensor2pil(torch.unsqueeze(image[0], 0)).convert("RGB").size
|
||||
)
|
||||
|
||||
# 存储所有的裁剪框用于预览(使用第一个mask)
|
||||
first_mask = l_masks[0]
|
||||
try:
|
||||
bluredmask = gaussian_blur(_mask, 20).convert("L")
|
||||
bluredmask = gaussian_blur(first_mask, 20).convert("L")
|
||||
except ImportError:
|
||||
bluredmask = _mask.convert("L")
|
||||
bluredmask = first_mask.convert("L")
|
||||
|
||||
x = 0
|
||||
y = 0
|
||||
@@ -162,14 +178,11 @@ class CropByMask_UTK:
|
||||
elif detect == "max_inscribed_rect":
|
||||
(x, y, width, height) = max_inscribed_rect(bluredmask)
|
||||
else:
|
||||
(x, y, width, height) = mask_area(_mask)
|
||||
(x, y, width, height) = mask_area(first_mask)
|
||||
|
||||
width = num_round_up_to_multiple(width, 8)
|
||||
height = num_round_up_to_multiple(height, 8)
|
||||
log(f"CropByMask_UTK: Box detected. x={x},y={y},width={width},height={height}")
|
||||
canvas_width, canvas_height = (
|
||||
tensor2pil(torch.unsqueeze(image[0], 0)).convert("RGB").size
|
||||
)
|
||||
|
||||
x1 = x - left_reserve if x - left_reserve > 0 else 0
|
||||
y1 = y - top_reserve if y - top_reserve > 0 else 0
|
||||
x2 = (
|
||||
@@ -182,7 +195,9 @@ class CropByMask_UTK:
|
||||
if y + height + bottom_reserve < canvas_height
|
||||
else canvas_height
|
||||
)
|
||||
preview_image = tensor2pil(mask_for_crop).convert("RGB")
|
||||
|
||||
# 创建预览图像
|
||||
preview_image = first_mask.convert("RGB")
|
||||
preview_image = draw_rect(
|
||||
preview_image,
|
||||
x,
|
||||
@@ -201,12 +216,50 @@ class CropByMask_UTK:
|
||||
line_color="#00F000",
|
||||
line_width=(width + height) // 200,
|
||||
)
|
||||
|
||||
crop_box = (x1, y1, x2, y2)
|
||||
|
||||
# 处理每个图像和对应的mask
|
||||
for i in range(len(l_images)):
|
||||
_canvas = tensor2pil(l_images[i]).convert("RGB")
|
||||
_mask = l_masks[0]
|
||||
ret_images.append(pil2tensor(_canvas.crop(crop_box)))
|
||||
ret_masks.append(image2mask(_mask.crop(crop_box)))
|
||||
_mask = l_masks[i] # 使用对应的mask而不是第一个
|
||||
|
||||
# 对每个mask单独计算裁剪区域
|
||||
try:
|
||||
current_bluredmask = gaussian_blur(_mask, 20).convert("L")
|
||||
except ImportError:
|
||||
current_bluredmask = _mask.convert("L")
|
||||
|
||||
curr_x, curr_y, curr_width, curr_height = 0, 0, 0, 0
|
||||
if detect == "min_bounding_rect":
|
||||
(curr_x, curr_y, curr_width, curr_height) = min_bounding_rect(current_bluredmask)
|
||||
elif detect == "max_inscribed_rect":
|
||||
(curr_x, curr_y, curr_width, curr_height) = max_inscribed_rect(current_bluredmask)
|
||||
else:
|
||||
(curr_x, curr_y, curr_width, curr_height) = mask_area(_mask)
|
||||
|
||||
curr_width = num_round_up_to_multiple(curr_width, 8)
|
||||
curr_height = num_round_up_to_multiple(curr_height, 8)
|
||||
|
||||
curr_x1 = curr_x - left_reserve if curr_x - left_reserve > 0 else 0
|
||||
curr_y1 = curr_y - top_reserve if curr_y - top_reserve > 0 else 0
|
||||
curr_x2 = (
|
||||
curr_x + curr_width + right_reserve
|
||||
if curr_x + curr_width + right_reserve < canvas_width
|
||||
else canvas_width
|
||||
)
|
||||
curr_y2 = (
|
||||
curr_y + curr_height + bottom_reserve
|
||||
if curr_y + curr_height + bottom_reserve < canvas_height
|
||||
else canvas_height
|
||||
)
|
||||
|
||||
current_crop_box = (curr_x1, curr_y1, curr_x2, curr_y2)
|
||||
|
||||
ret_images.append(pil2tensor(_canvas.crop(current_crop_box)))
|
||||
ret_masks.append(image2mask(_mask.crop(current_crop_box)))
|
||||
|
||||
log(f"CropByMask_UTK: Image {i+1} - Box detected. x={curr_x},y={curr_y},width={curr_width},height={curr_height}")
|
||||
|
||||
log(
|
||||
f"CropByMask_UTK Processed {len(ret_images)} image(s).",
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Lazy Switch Node for ComfyUI Universal Toolkit
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lazy switch functionality adapted from kjnodes.
|
||||
Controls flow of execution based on a boolean switch with lazy evaluation.
|
||||
|
||||
:copyright: (c) 2024 by May
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
# Try to import IO.ANY from ComfyUI's typing system
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO
|
||||
ANY_TYPE = IO.ANY
|
||||
except ImportError:
|
||||
# Fallback for older ComfyUI versions or different typing systems
|
||||
try:
|
||||
from comfy_extras.nodes_custom_sampler import AnyType
|
||||
ANY_TYPE = AnyType("*")
|
||||
except ImportError:
|
||||
# Create a simple ANY type fallback
|
||||
class AnyType(str):
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
ANY_TYPE = AnyType("*")
|
||||
|
||||
|
||||
class LazySwitchKJ_UTK:
|
||||
"""
|
||||
Lazy Switch node that controls flow of execution based on a boolean switch.
|
||||
|
||||
This node implements lazy evaluation, meaning it only evaluates the branch
|
||||
that will actually be used based on the switch value. This can improve
|
||||
performance by avoiding unnecessary computations.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"switch": ("BOOLEAN", {"tooltip": "Boolean value to control which input is returned"}),
|
||||
"on_false": (ANY_TYPE, {
|
||||
"lazy": True,
|
||||
"tooltip": "Value returned when switch is False"
|
||||
}),
|
||||
"on_true": (ANY_TYPE, {
|
||||
"lazy": True,
|
||||
"tooltip": "Value returned when switch is True"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ANY_TYPE,)
|
||||
RETURN_NAMES = ("output",)
|
||||
FUNCTION = "switch"
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
|
||||
DESCRIPTION = """
|
||||
Controls flow of execution based on a boolean switch.
|
||||
|
||||
This node implements lazy evaluation - it only processes the input
|
||||
that will actually be used based on the switch value. This can
|
||||
significantly improve performance by avoiding unnecessary computations
|
||||
in complex workflows.
|
||||
|
||||
Features:
|
||||
- **Lazy Evaluation**: Only evaluates the selected branch
|
||||
- **Any Type Support**: Works with any data type (images, masks, strings, etc.)
|
||||
- **Flow Control**: Essential for conditional workflow execution
|
||||
- **Performance Optimization**: Reduces unnecessary processing
|
||||
|
||||
Usage:
|
||||
- Connect your boolean condition to the 'switch' input
|
||||
- Connect the value for False condition to 'on_false'
|
||||
- Connect the value for True condition to 'on_true'
|
||||
- The node will output the appropriate value based on the switch
|
||||
|
||||
Common use cases:
|
||||
- Conditional image processing pipelines
|
||||
- A/B testing different parameters
|
||||
- Workflow branching based on user input
|
||||
- Performance optimization in complex workflows
|
||||
"""
|
||||
|
||||
def check_lazy_status(self, switch, on_false=None, on_true=None):
|
||||
"""
|
||||
Check which inputs are needed for lazy evaluation.
|
||||
|
||||
This method tells ComfyUI which inputs it needs to evaluate
|
||||
based on the current switch value.
|
||||
"""
|
||||
if switch and on_true is None:
|
||||
return ["on_true"]
|
||||
if not switch and on_false is None:
|
||||
return ["on_false"]
|
||||
|
||||
def switch(self, switch, on_false=None, on_true=None):
|
||||
"""
|
||||
Switch between two values based on a boolean condition.
|
||||
|
||||
Args:
|
||||
switch: Boolean value determining which input to return
|
||||
on_false: Value to return when switch is False
|
||||
on_true: Value to return when switch is True
|
||||
|
||||
Returns:
|
||||
Tuple containing the selected value
|
||||
"""
|
||||
value = on_true if switch else on_false
|
||||
return (value,)
|
||||
|
||||
|
||||
# Node registration
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LazySwitchKJ_UTK": LazySwitchKJ_UTK,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LazySwitchKJ_UTK": "Lazy Switch KJ (UTK)",
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "universaltoolkit"
|
||||
description = "A comprehensive toolkit based on ComfyUI, providing image, mask, audio, and tools nodes, fully modular and v3 compatible."
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = [
|
||||
"torch",
|
||||
|
||||
Reference in New Issue
Block a user