From 5dbbeee053b0083404904555624cb80a7d1eee77 Mon Sep 17 00:00:00 2001 From: skycoder182 <212504411+skycoder182@users.noreply.github.com> Date: Tue, 27 May 2025 20:33:36 +0200 Subject: [PATCH] Several furhter helper nodes --- README.md | 10 ++- __init__.py | 19 +++++- .../Aspect_Ratio_and_Tile_size_calculator.py | 64 +++++++++++++++++++ nodes/BooleanToggleNode.py | 23 +++++++ nodes/blipimagecaptioning.py | 50 +++++++++++++++ nodes/concatenate_and_test_if_empty.py | 31 +++++++++ pyproject.toml | 14 ++-- 7 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 nodes/Aspect_Ratio_and_Tile_size_calculator.py create mode 100644 nodes/BooleanToggleNode.py create mode 100644 nodes/blipimagecaptioning.py create mode 100644 nodes/concatenate_and_test_if_empty.py diff --git a/README.md b/README.md index ae4aaeb..5000657 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file +# Restart ComfyUI +### 🧠 Image Analysis & Utilities + diff --git a/__init__.py b/__init__.py index 489b88b..7b7f22b 100644 --- a/__init__.py +++ b/__init__.py @@ -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'] diff --git a/nodes/Aspect_Ratio_and_Tile_size_calculator.py b/nodes/Aspect_Ratio_and_Tile_size_calculator.py new file mode 100644 index 0000000..930de0b --- /dev/null +++ b/nodes/Aspect_Ratio_and_Tile_size_calculator.py @@ -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", +} diff --git a/nodes/BooleanToggleNode.py b/nodes/BooleanToggleNode.py new file mode 100644 index 0000000..462c990 --- /dev/null +++ b/nodes/BooleanToggleNode.py @@ -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", +} \ No newline at end of file diff --git a/nodes/blipimagecaptioning.py b/nodes/blipimagecaptioning.py new file mode 100644 index 0000000..a8a2eb3 --- /dev/null +++ b/nodes/blipimagecaptioning.py @@ -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" +} + diff --git a/nodes/concatenate_and_test_if_empty.py b/nodes/concatenate_and_test_if_empty.py new file mode 100644 index 0000000..e1c83ed --- /dev/null +++ b/nodes/concatenate_and_test_if_empty.py @@ -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", +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7ef8053..3bd869c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" \ No newline at end of file +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"