commit LoadImagesFromPath and ImageTaggerSaveV2 nodes

This commit is contained in:
chflame163
2025-08-04 20:35:34 +08:00
parent 3bfe8e435d
commit 677468955a
7 changed files with 307 additions and 3 deletions
+24
View File
@@ -147,6 +147,8 @@ When this error has occurred, please check the network environment.
<font size="4">**If the dependency package error after updating, please double clicking ```repair_dependency.bat``` (for Official ComfyUI Protable) or ```repair_dependency_aki.bat``` (for ComfyUI-aki-v1.x) in the plugin folder to reinstall the dependency packages. </font><br />
* Commit [LoadImagesFromPath](#LoadImagesFromPath) and [ImageTaggerSaveV2](#ImageTaggerSaveV2) nodes, used to load a list of images from a folder and save images and tagger text file with corresponding file names.
* Commit [LoadImageFromPath](LoadImageFromPath) node, The images in a folder can be loaded and output as image list, also supporting output of a list corresponding to a file name.
* Commit [SegformerUltraV3](SegformerUltraV3), [LoadSegformerModel](LoadSegformerModel), [SegformerClothesSetting](SegformerClothesSetting) and [SegformerFashionSetting](SegformerFashionSetting) nodes, Separate the loading of models and settings to save resources when using multiple nodes.
* Add multiple languages and increase support for 5 languages: Chinese, French, Japanese, Korean and Russian. This feature producted by [ComfyUI-Globalization-Node-Translation](https://github.com/yamanacn/ComfyUI-Globalization-Node-Translation), thank you to the original author.
* Commit [HalfTone](#HalfTone) node, use for halftone processing of images.
@@ -1744,6 +1746,28 @@ Node Options:
<sup>*</sup> Enter```%date``` for the current date (YY-mm-dd) and ```%time``` for the current time (HH-MM-SS). You can enter ```/``` for subdirectories. For example, ```%date/name_%tiem``` will output the image to the ```YY-mm-dd``` folder, with ```name_HH-MM-SS``` as the file name prefix.
### <a id="table1">ImageTaggerSaveV2</a>
The upgraded version of [ImageTaggerSave](#ImageTaggerSave) node can be used in conjunction with [LoadImagesFromPath](#LoadImagesFromPath) node to save the text label files of corresponding images in the folder, while maintaining the original file name.
The following options have been added to the original node:
![image](image/image_tagger_save_v2_node.jpg)
* custom_filename: User defined file name. If there is input here, use it as the save file name; otherwise, use filename_prefix as the file name prefix.
* remove_custom_filename_ext: Whether to remove the extension from the original file name.
### <a id="table1">LoadImagesFromPath</a>
Load images from the specified folder.
Node Options:
![image](image/load_images_from_path_node.jpg)
* path: Folder path.
* image_load_cap: The number of output files. The default value of 0 means to read all image files in the folder.
* select_every_nth: Loads one image every ```select_every_nth``` images, skipping others.
Outputs:
* images: Output image list.
* masks: Output the mask list corresponding to the image.
* file_name: Output a list of file names corresponding to the images.
* frame_count: Output the total number of images.
# <a id="table1">LayerMask</a>
+24
View File
@@ -128,6 +128,7 @@ os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
## 更新说明
<font size="4">**如果本插件更新后出现依赖包错误,请双击运行插件目录下的```install_requirements.bat```(官方便携包),或 ```install_requirements_aki.bat```(秋叶整合包) 重新安装依赖包。
* 添加 [LoadImagesFromPath](#LoadImagesFromPath) 和 [ImageTaggerSaveV2](#ImageTaggerSaveV2) 节点,用于从文件夹加载图片列表并保存对应文件名的图片和标签。
* 添加 [SegformerUltraV3](SegformerUltraV3), [LoadSegformerModel](LoadSegformerModel), [SegformerClothesSetting](SegformerClothesSetting) 和 [SegformerFashionSetting](SegformerFashionSetting) 节点,将模型与设置分离加载,在使用多个节点时节省资源。
* 添加多语言,除英语外,增加支持5种语言:中文、法语、日语、韩语和俄语。该功能使用[ComfyUI-Globalization-Node-Translation](https://github.com/yamanacn/ComfyUI-Globalization-Node-Translation)制作,感谢原作者。
* 添加 [HalfTone](#HalfTone) 节点,用于对图像进行半色调处理。
@@ -159,6 +160,7 @@ LayerMask: SegmentAnythingUltra, LayerMask: SegmentAnythingUltra V2, LayerMask:
LayerUtility: UserPromptGeneratorTxt2ImgPrompt, LayerUtility: UserPromptGeneratorTxt2ImgPromptWithReference, LayerUtility: UserPromptGeneratorReplaceWord,
LayerUtility: AddBlindWaterMark, LayerUtility: ShowBlindWaterMark, LayerMask: YoloV8Detect
* 添加 [LoadImageFromPath](LoadImageFromPath) 节点,可将一个文件夹中的图片加载为图片列表输出,支持单个文件名对应列表输出。
* 合并[alexisrolland](https://github.com/alexisrolland) 提交的分支,添加Image Blend Advanced v3 和 Drop Shadow v3 节点,支持透明背景。
* 添加[BenUltra](#BenUltra) 和 [LoadBenModel](#LoadBenModel)节点。这两个节点是[PramaLLC/BEN](https://huggingface.co/PramaLLC/BEN) 项目在ComfyUI中的实现。
从 [huggingface](https://huggingface.co/PramaLLC/BEN/tree/main) 或 [百度网盘](https://pan.baidu.com/s/17mdBxfBl_R97mtNHuiHsxQ?pwd=2jn3)下载```BEN_Base.pth``` 和 ```config.json``` 两个文件并复制到 ```ComfyUI/models/BEN```文件夹。
@@ -1555,6 +1557,28 @@ BooleanOperator的升级版,增加了节点内数值输入,增加了大于
<sup>*</sup>输入```%date```表示当前日期(YY-mm-dd),```%time```表示当前时间(HH-MM-SS)。可以输入```/```表示子目录。例如```%date/name_%time``` 将输出图片到```YY-mm-dd```文件夹下,以```name_HH-MM-SS```为文件名前缀。
### <a id="table1">ImageTaggerSaveV2</a>
[ImageTaggerSave](#ImageTaggerSave) 节点的升级版,可配合[LoadImagesFromPath](#LoadImagesFromPath) 节点保存文件夹中对应图片的文本标签文件,并保持原文件名。
在原节点上增加了以下选项:
![image](image/image_tagger_save_v2_node.jpg)
* custom_filename: 用户自定义文件名。如果此处有输入则作为保存文件名,否则使用filename_prefix作为文件名前缀。
* remove_custom_filename_ext: 是否移除原文件名中的扩展名。
### <a id="table1">LoadImagesFromPath</a>
从指定的文件夹中加载图片。
节点选项说明:
![image](image/load_images_from_path_node.jpg)
* path: 文件夹路径。
* image_load_cap: 输出的文件数量。默认值0表示读取文件夹中的所有图片文件。
* select_every_nth: 每隔多少张图片加载一张图片。
输出:
* images: 输出的图片列表。
* masks: 输出图片对应的遮罩列表。
* file_name: 输出图片对应的文件名列表。
* frame_count: 输出图片总数。
# <a id="table1">LayerMask</a>
Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+148 -2
View File
@@ -1,4 +1,5 @@
import os.path
from pathlib import Path
import shutil
from PIL import Image
from PIL.PngImagePlugin import PngInfo
@@ -133,10 +134,155 @@ class LSImageTaggerSave:
return { "ui": { "images": results } }
class LSImageTaggerSave_V2:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
self.compress_level = 4
self.NODE_NAME = 'ImageTaggerSaveV2'
@classmethod
def INPUT_TYPES(s):
return {"required":
{"image": ("IMAGE", ),
"tag_text": ("STRING", {"default": "", "forceInput":True}),
"custom_path": ("STRING", {"default": ""}),
"custom_filename": ("STRING", {"default": ""}),
"remove_custom_filename_ext": ("BOOLEAN", {"default": True}),
"filename_prefix": ("STRING", {"default": "comfyui"}),
"timestamp": (["None", "second", "millisecond"],),
"format": (["png", "jpg"],),
"quality": ("INT", {"default": 80, "min": 10, "max": 100, "step": 1}),
"preview": ("BOOLEAN", {"default": True}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "image_tagger_save_v2"
OUTPUT_NODE = True
CATEGORY = '😺dzNodes/LayerUtility/SystemIO'
def image_tagger_save_v2(self, image, tag_text, custom_path, custom_filename, remove_custom_filename_ext,
filename_prefix, timestamp, format, quality,
preview,
prompt=None, extra_pnginfo=None):
now = datetime.datetime.now()
custom_path = custom_path.replace("%date", now.strftime("%Y-%m-%d"))
custom_path = custom_path.replace("%time", now.strftime("%H-%M-%S"))
filename_prefix = filename_prefix.replace("%date", now.strftime("%Y-%m-%d"))
filename_prefix = filename_prefix.replace("%time", now.strftime("%H-%M-%S"))
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, image[0].shape[1], image[0].shape[0])
if custom_filename != "":
if remove_custom_filename_ext:
file = Path(custom_filename).stem
else:
file = custom_filename
if timestamp == "millisecond":
file = f'{file}_{now.strftime("%Y-%m-%d_%H-%M-%S-%f")[:-3]}'
elif timestamp == "second":
file = f'{file}_{now.strftime("%Y-%m-%d_%H-%M-%S")}'
else:
if timestamp == "millisecond":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S-%f")[:-3]}'
elif timestamp == "second":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S")}'
else:
file = f'{filename}_{counter:08}'
results = list()
temp_sub_dir = generate_random_name('_savepreview_', '_temp', 16)
temp_dir = os.path.join(folder_paths.get_temp_directory(), temp_sub_dir)
metadata = None
i = 255. * image[0].cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
preview_filename = ""
if custom_path != "":
if not os.path.exists(custom_path):
try:
os.makedirs(custom_path)
except Exception as e:
log(f"Error: {self.NODE_NAME} skipped, because unable to create temporary folder.",
message_type='warning')
raise FileNotFoundError(f"cannot create custom_path {custom_path}, {e}")
else:
custom_path = folder_paths.get_output_directory()
full_output_folder = os.path.normpath(custom_path)
# save preview image to temp_dir
if os.path.isdir(temp_dir):
shutil.rmtree(temp_dir)
try:
os.makedirs(temp_dir)
except Exception as e:
print(e)
log(f"Error: {self.NODE_NAME} skipped, because unable to create temporary folder.",
message_type='warning')
try:
preview_filename = os.path.join(generate_random_name('saveimage_preview_', '_temp', 16) + '.png')
img.save(os.path.join(temp_dir, preview_filename))
except Exception as e:
print(e)
log(f"Error: {self.NODE_NAME} skipped, because unable to create temporary file.", message_type='warning')
if custom_filename == "":
# check if file exists, change filename
while os.path.isfile(os.path.join(full_output_folder, f"{file}.{format}")):
counter += 1
if timestamp == "millisecond":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S-%f")[:-3]}_{counter:08}'
elif timestamp == "second":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S")}_{counter:08}'
else:
file = f"{filename}_{counter:08}"
image_file_name = os.path.join(full_output_folder, f"{file}.{format}")
tag_file_name = os.path.join(full_output_folder, f"{file}.txt")
if format == "png":
img.save(image_file_name, pnginfo=metadata, compress_level= (100 - quality) // 10)
else:
if img.mode == "RGBA":
img = img.convert("RGB")
img.save(image_file_name, quality=quality)
with open(tag_file_name, "w", encoding="utf-8") as f:
f.write(remove_empty_lines(tag_text))
log(f"{self.NODE_NAME} -> Saving image to {image_file_name}")
if preview:
if custom_path == "":
results.append({
"filename": f"{file}.{format}",
"subfolder": subfolder,
"type": self.type
})
else:
results.append({
"filename": preview_filename,
"subfolder": temp_sub_dir,
"type": "temp"
})
counter += 1
return { "ui": { "images": results } }
NODE_CLASS_MAPPINGS = {
"LayerUtility: ImageTaggerSave": LSImageTaggerSave
"LayerUtility: ImageTaggerSave": LSImageTaggerSave,
"LayerUtility: ImageTaggerSaveV2": LSImageTaggerSave_V2
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LayerUtility: ImageTaggerSave": "LayerUtility: Image Tagger Save"
"LayerUtility: ImageTaggerSave": "LayerUtility: Image Tagger Save",
"LayerUtility: ImageTaggerSaveV2": "LayerUtility: Image Tagger Save V2",
}
+110
View File
@@ -0,0 +1,110 @@
import os
from PIL import Image, ImageSequence, ImageOps
import torch
import numpy as np
import folder_paths
import node_helpers
class LS_LoadImagesFromPath:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"path": ("STRING", {"placeholder": "c:/images", "images_path": []}),
},
"optional": {
"image_load_cap": ("INT", {"default": 0, "min": 0, "max": 999999, "step": 1}),
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1}),
},
}
RETURN_TYPES = ("IMAGE", "MASK", "STRING", "INT")
RETURN_NAMES = ("images", "masks", "file_name", "frame_count")
FUNCTION = "ls_load_images"
CATEGORY = '😺dzNodes/LayerUtility/SystemIO'
OUTPUT_IS_LIST = (True, True, True, False)
def ls_load_images(self, path: str, image_load_cap: int, select_every_nth: int):
load_images = []
load_masks = []
load_file_names = []
load_frame_count = 0
if os.path.isdir(path):
input_dir = os.path.normpath(path)
files = [
os.path.join(input_dir, f)
for f in os.listdir(input_dir)
if os.path.isfile(os.path.join(input_dir, f))
]
for i in range(len(files)):
if i % select_every_nth != 0:
continue
image_file = files[i]
image_path = folder_paths.get_annotated_filepath(image_file)
img = node_helpers.pillow(Image.open, image_path)
output_images = []
output_masks = []
w, h = None, None
excluded_formats = ['MPO']
for i in ImageSequence.Iterator(img):
i = node_helpers.pillow(ImageOps.exif_transpose, i)
if i.mode == 'I':
i = i.point(lambda i: i * (1 / 255))
image = i.convert("RGB")
if len(output_images) == 0:
w = image.size[0]
h = image.size[1]
if image.size[0] != w or image.size[1] != h:
continue
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
elif i.mode == 'P' and 'transparency' in i.info:
mask = np.array(i.convert('RGBA').getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
else:
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
output_images.append(image)
output_masks.append(mask.unsqueeze(0))
if len(output_images) > 1 and img.format not in excluded_formats:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]
load_images.append(output_image)
load_masks.append(output_mask)
load_file_names.append(os.path.basename(image_file))
load_frame_count += 1
if image_load_cap > 0 and load_frame_count >= image_load_cap:
break
return (load_images, load_masks, load_file_names, load_frame_count)
else:
raise Exception("directory is not valid: " + directory)
NODE_CLASS_MAPPINGS = {
"LayerUtility: LoadImagesFromPath": LS_LoadImagesFromPath,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LayerUtility: LoadImagesFromPath": "LayerUtility: Load Images From Path",
}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_layerstyle"
description = "A set of nodes for ComfyUI it generate image like Adobe Photoshop's Layer Style. the Drop Shadow is first completed node, and follow-up work is in progress."
version = "2.0.21"
version = "2.0.22"
license = {text = "MIT License"}
dependencies = ["numpy", "pillow", "torch", "matplotlib", "Scipy", "scikit_image", "scikit_learn", "opencv-contrib-python", "pymatting", "timm", "colour-science", "transformers", "blend_modes", "huggingface_hub", "loguru"]