Several furhter helper nodes

This commit is contained in:
skycoder182
2025-05-27 20:33:36 +02:00
parent f61b1055b4
commit 5dbbeee053
7 changed files with 202 additions and 9 deletions
+8 -2
View File
@@ -7,9 +7,13 @@ A comprehensive collection of useful custom nodes for ComfyUI workflows. Designe
## 🚀 Current Tools
### 📁 Directory Image Tools
### 📁 Tools
- **DirectoryImageLoader**: Load images from a directory by index
- **DirectoryImageInfo**: Get detailed information about images in a directory
- **Aspect Ratio and Tile size calculator**: Calculates the aspect ratio of an image and determines optimal tile dimensions based on a target megapixel value, upscale factor, and optional padding. Useful for tiled image processing workflows.
- **BLIP2 Image Caption**: Automatically generates a descriptive caption for an input image using Salesforce’s BLIP image captioning model. Ideal for semantic image analysis or annotation tasks.
- **Boolean Toggle**: Returns a boolean value from a toggle input. Useful for conditionally controlling workflow branches.
- **Concatenate and Test if Empty**: Concatenates up to four text inputs and checks whether the result is empty. Outputs both the combined string and a boolean indicating if it's empty.
*More tools coming soon! This is just the beginning of the Skycoder Tools collection.*
@@ -33,4 +37,6 @@ A comprehensive collection of useful custom nodes for ComfyUI workflows. Designe
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/skycoder/comfyui-skycoder-tools.git
# Restart ComfyUI
# Restart ComfyUI
### 🧠 Image Analysis & Utilities
+17 -2
View File
@@ -4,15 +4,30 @@ A collection of useful custom nodes for ComfyUI workflows
"""
from .nodes.directory_image_loader import NODE_CLASS_MAPPINGS as DIR_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as DIR_DISPLAY_MAPPINGS
from .Aspect_Ratio_and_Tile_size_calculator import NODE_CLASS_MAPPINGS as ASPECT_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as ASPECT_DISPLAY_MAPPINGS
from .blipimagecaptioning import NODE_CLASS_MAPPINGS as BLIP_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as BLIP_DISPLAY_MAPPINGS
from .BooleanToggleNode import NODE_CLASS_MAPPINGS as TOGGLE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as TOGGLE_DISPLAY_MAPPINGS
from .concatenate_and_test_if_empty import NODE_CLASS_MAPPINGS as CONCAT_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as CONCAT_DISPLAY_MAPPINGS
# Kombiniere alle Node-Mappings (für zukünftige Erweiterungen)
# Combine all Node Mappings
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
# Directory Image Loader Nodes
NODE_CLASS_MAPPINGS.update(DIR_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(DIR_DISPLAY_MAPPINGS)
NODE_CLASS_MAPPINGS.update(ASPECT_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(ASPECT_DISPLAY_MAPPINGS)
NODE_CLASS_MAPPINGS.update(BLIP_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(BLIP_DISPLAY_MAPPINGS)
NODE_CLASS_MAPPINGS.update(TOGGLE_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(TOGGLE_DISPLAY_MAPPINGS)
NODE_CLASS_MAPPINGS.update(CONCAT_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(CONCAT_DISPLAY_MAPPINGS)
# Exportiere alle Node-Mappings für ComfyUI
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
@@ -0,0 +1,64 @@
from PIL import Image
import numpy as np
from math import sqrt, floor
class Aspect_Ratio_and_Tile_size_calculator:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"tile_megapixel": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 100.0, "step": 0.01, "label": "Tile Megapixel"}),
"upscale_factor": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 100.0, "step": 0.01, "label": "Upscale Factor"}),
"tile_padding": ("INT", {"default": 0, "min": 0, "max": 4096, "step": 1, "label": "Tile Padding"}),
}
}
RETURN_TYPES = ("FLOAT", "INT", "INT", "FLOAT", "INT", "INT", "INT")
RETURN_NAMES = ("aspect_ratio", "width", "height", "upscale_factor", "tile_width", "tile_height", "tile_padding")
FUNCTION = "get_aspect_ratio"
CATEGORY = "Custom"
def get_aspect_ratio(self, image, tile_megapixel, upscale_factor, tile_padding):
# Tensor to PIL conversion
def tensor2pil(img):
if hasattr(img, "__len__") and not isinstance(img, (bytes, str)) and len(img) > 0:
img = img[0]
img = img.detach().cpu().numpy()
img = (img * 255).astype(np.uint8)
if img.shape[0] == 3:
img = np.transpose(img, (1, 2, 0))
return Image.fromarray(img)
pil_img = tensor2pil(image)
width, height = pil_img.size
aspect = width / height if height else 0.0
# Variablen
a = upscale_factor
b = tile_megapixel
c = width
d = height
p = tile_padding
# Berechnung tile_width und tile_height
if c > 0 and d > 0 and b > 0 and a > 0:
divisor = floor((c * a) / sqrt(b * 1000000 * c / d)) + 1
tile_width = (c * a) / divisor if divisor > 0 else c * a
tile_height = tile_width * d / c
else:
tile_width = 0.0
tile_height = 0.0
# Padding addieren
tile_width += p
tile_height += p
return (float(aspect), int(width), int(height), float(upscale_factor), int(tile_width), int(tile_height), int(tile_padding))
NODE_CLASS_MAPPINGS = {
"Aspect_Ratio_and_Tile_size_calculator": Aspect_Ratio_and_Tile_size_calculator,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Aspect_Ratio_and_Tile_size_calculator": "Aspect Ratio and Tile size calculator",
}
+23
View File
@@ -0,0 +1,23 @@
class BooleanToggle:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"toggle": ("BOOLEAN", {"default": False, "label": "Boolean Toggle"}),
}
}
RETURN_TYPES = ("BOOLEAN",)
RETURN_NAMES = ("toggle_value",)
FUNCTION = "toggle_func"
CATEGORY = "Custom"
def toggle_func(self, toggle):
return (toggle,)
NODE_CLASS_MAPPINGS = {
"BooleanToggle": BooleanToggle,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"BooleanToggle": "Boolean Toggle",
}
+50
View File
@@ -0,0 +1,50 @@
import torch
from transformers import BlipProcessor, BlipForConditionalGeneration
from PIL import Image
import folder_paths
import numpy as np
class BLIP2Captioning:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE", {"tooltip": "Input image to generate caption for."})
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("caption",)
FUNCTION = "generate_caption"
CATEGORY = "BLIP2"
def __init__(self):
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model_id = "Salesforce/blip-image-captioning-base"
# Laden von Processor & Modell
self.processor = BlipProcessor.from_pretrained(self.model_id)
self.model = BlipForConditionalGeneration.from_pretrained(self.model_id).to(self.device)
def generate_caption(self, image):
# Sicheres Konvertieren: ComfyUI-Tensor → PIL
pil_image = tensor2pil(image).convert("RGB")
# BLIP Processing + Inferenz
inputs = self.processor(images=pil_image, return_tensors="pt").to(self.device)
out = self.model.generate(**inputs)
caption = self.processor.decode(out[0], skip_special_tokens=True)
return (caption,)
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
NODE_CLASS_MAPPINGS = {
"BLIP2Captioning": BLIP2Captioning
}
NODE_DISPLAY_NAME_MAPPINGS = {
"BLIP2Captioning": "BLIP2 Image Caption"
}
+31
View File
@@ -0,0 +1,31 @@
class ConcatenateAndTestIfEmpty:
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"text1": ("STRING", {"forceInput": True}),
"text2": ("STRING", {"forceInput": True}),
"text3": ("STRING", {"forceInput": True}),
"text4": ("STRING", {"forceInput": True}),
}
}
RETURN_TYPES = ("BOOLEAN", "STRING")
RETURN_NAMES = ("is_empty", "result")
FUNCTION = "concat_and_test"
CATEGORY = "Custom"
def concat_and_test(self, text1=None, text2=None, text3=None, text4=None):
result = ""
for t in (text1, text2, text3, text4):
if t is not None:
result += t
is_empty = result == ""
return (is_empty, result)
NODE_CLASS_MAPPINGS = {
"ConcatenateAndTestIfEmpty": ConcatenateAndTestIfEmpty,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ConcatenateAndTestIfEmpty": "Concatenate and Test if Empty",
}
+9 -5
View File
@@ -7,7 +7,7 @@ name = "comfyui-skycoder-tools"
version = "1.0.0"
description = "A collection of useful custom nodes for ComfyUI workflows"
authors = [
{name = "Skycoder", email = "skycoder@example.com"}
{name = "Skycoder182", email = "skycoder@example.com"}
]
license = {text = "MIT"}
readme = "README.md"
@@ -31,7 +31,11 @@ dependencies = [
]
[project.urls]
Homepage = "https://github.com/skycoder/comfyui-skycoder-tools"
Repository = "https://github.com/skycoder/comfyui-skycoder-tools"
Issues = "https://github.com/skycoder/comfyui-skycoder-tools/issues"
Discussions = "https://github.com/skycoder/comfyui-skycoder-tools/discussions"
Homepage = "https://github.com/skycoder182/comfyui-skycoder-tools"
Repository = "https://github.com/skycoder182/comfyui-skycoder-tools"
Issues = "https://github.com/skycoder182/comfyui-skycoder-tools/issues"
Discussions = "https://github.com/skycoder182/comfyui-skycoder-tools/discussions"
[tool.comfy]
PublisherId = "skycoder"
DisplayName = "Skycoder Tools"