From 172f191b45d2ea46de3d62a843cbea0aa8cb8170 Mon Sep 17 00:00:00 2001 From: RyanOnTheInside <7623207+ryanontheinside@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:29:03 -0500 Subject: [PATCH] load image from path, branding --- __init__.py | 46 +++++++++++++++++++++------------------------- misc_nodes.py | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/__init__.py b/__init__.py index b8560cb..d7c660e 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,9 @@ from .controls.value_controls import FloatControl, IntControl, StringControl from .controls.sequence_controls import FloatSequence, IntSequence, StringSequence from .controls.utility_controls import FPSMonitor, SimilarityFilter, LazyCondition from .controls.motion_controls import MotionController, ROINode, IntegerMotionController -from .misc_nodes import DTypeConverter, FastWebcamCapture, YOLOSimilarityCompare, TextRenderer, QuickShapeMask, MultilineText +from .misc_nodes import DTypeConverter, FastWebcamCapture, YOLOSimilarityCompare, TextRenderer, QuickShapeMask, MultilineText, LoadImageFromPath_ + +import re NODE_CLASS_MAPPINGS = { "FloatControl": FloatControl, @@ -25,32 +27,26 @@ NODE_CLASS_MAPPINGS = { "DTypeConverter": DTypeConverter, "FastWebcamCapture": FastWebcamCapture, "MultilineText": MultilineText, + "LoadImageFromPath_": LoadImageFromPath_, } -NODE_DISPLAY_NAME_MAPPINGS = { - "FloatControl": "Float Control (RyanOnTheInside)", - "IntControl": "Int Control (RyanOnTheInside)", - "StringControl": "String Control (RyanOnTheInside)", - "FloatSequence": "Float Sequence (RyanOnTheInside)", - "IntSequence": "Int Sequence (RyanOnTheInside)", - "StringSequence": "String Sequence (RyanOnTheInside)", - "FPSMonitor": "FPS Monitor (RyanOnTheInside)", - "MotionController": "Float Motion Controller (RyanOnTheInside)", - "ROINode": "ROI Node (RyanOnTheInside)", - "IntegerMotionController": "Integer Motion Controller (RyanOnTheInside)", - #"IntervalControl": "Interval Control (RyanOnTheInside)", - #"DeltaControl": "Delta Control (RyanOnTheInside)", - "QuickShapeMask": "Quick Shape Mask (RyanOnTheInside)", - "TAESDVaeEncode": "TAESD VAE Encode (RyanOnTheInside)", - "TAESDVaeDecode": "TAESD VAE Decode (RyanOnTheInside)", - "DTypeConverter": "DType Converter (RyanOnTheInside)", - "FastWebcamCapture": "Fast Webcam Capture (RyanOnTheInside)", - "SimilarityFilter": "Similarity Filter (RyanOnTheInside)", - "LazyCondition": "Lazy Condition (RyanOnTheInside)", - "YOLOSimilarityCompare": "YOLO Similarity Compare (RyanOnTheInside)", - "TextRenderer": "Text Renderer (RyanOnTheInside)", - "MultilineText": "Multiline Text (RyanOnTheInside)", -} +NODE_DISPLAY_NAME_MAPPINGS = {} + +suffix = " 🕒🅡🅣🅝" + +for node_name in NODE_CLASS_MAPPINGS.keys(): + # Convert camelCase or snake_case to Title Case + if node_name not in NODE_DISPLAY_NAME_MAPPINGS: + display_name = ' '.join(word.capitalize() for word in re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\d|\W|$)|\d+', node_name)) + else: + display_name = NODE_DISPLAY_NAME_MAPPINGS[node_name] + + # Add the suffix if it's not already present + if not display_name.endswith(suffix): + display_name += suffix + + # Assign the final display name to the mappings + NODE_DISPLAY_NAME_MAPPINGS[node_name] = display_name WEB_DIRECTORY = "./web/js" diff --git a/misc_nodes.py b/misc_nodes.py index 8add29b..c7aa5c6 100644 --- a/misc_nodes.py +++ b/misc_nodes.py @@ -4,7 +4,7 @@ import numpy as np import base64 import re from io import BytesIO -from PIL import Image, ImageFont, ImageDraw +from PIL import Image, ImageFont, ImageDraw, ImageOps import nodes import random from torchvision import transforms @@ -408,7 +408,7 @@ class TextRenderer: def INPUT_TYPES(s): return { "required": { - "any": (AlwaysEqualProxy(), {"multiline": True}), # Accept any input type and convert to string + "any": (AlwaysEqualProxy("*"),), # Accept any input type and convert to string "width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), "height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), "font_size": ("INT", {"default": 48, "min": 1, "max": 512, "step": 1}), @@ -424,7 +424,7 @@ class TextRenderer: RETURN_TYPES = ("IMAGE", "MASK") FUNCTION = "render_text" - CATEGORY = "image" + CATEGORY = "real-time" def render_text(self, any, width, height, font_size, font_color, background_color, x_offset, y_offset, align, wrap_width): @@ -530,4 +530,46 @@ class MultilineText: result = '\n'.join(lines) return (result,) + +class LoadImageFromPath_: + """A simple node that loads an image from a given file path""" + + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image_path": ("STRING", { + "default": "", + "multiline": False, + "tooltip": "Full path to the image file to load" + }) + }} + + RETURN_TYPES = ("IMAGE", "MASK") + FUNCTION = "load_image" + CATEGORY = "image" + + def load_image(self, image_path): + # Open and process the image + img = Image.open(image_path) + img = ImageOps.exif_transpose(img) + + # Convert to RGB if needed + if img.mode == 'I': + img = img.point(lambda i: i * (1 / 255)) + image = img.convert("RGB") + + # Convert to tensor + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + + # Create mask from alpha channel if it exists + if 'A' in img.getbands(): + mask = np.array(img.getchannel('A')).astype(np.float32) / 255.0 + mask = 1. - torch.from_numpy(mask) + else: + mask = torch.zeros((64,64), dtype=torch.float32, device="cpu") + + mask = mask.unsqueeze(0) + + return (image, mask)