diff --git a/README.md b/README.md index cca55cc..e5449a1 100644 --- a/README.md +++ b/README.md @@ -1,198 +1,197 @@ -# Endless-Nodes -Some basic custom nodes for the ComfyUI user interface for Stable Diffusion. Features: - -+ An image saver for images and JSON files to base folder, custom folders for one, or custom folders for both. Also allows for Python timestamping -+ Two aesthetic scoring models, one based on the same as AUTO1111, the other based on Image Reward -+ Converters for various numerical functions to the other (int, float, number) and to strings. Still in beta -+ Switches for text and numbers -+ Parameter collection nodes -+ MORE TO COME - -When using the [ComfyUI](https://github.com/comfyanonymous/ComfyUI) interface for [Stable Diffusion](https://github.com/Stability-AI/stablediffusion), I sometimes find that the standard nodes and the many, many, many custom nodes out there don't work the way I want them to, or how I think they do. - -Rightly or wrongly, I am pretending to teach myself a bit of Python to get some nodes up and running to do what I'd like. There are no promises that these nodes will work for you or that I will maintain them. Feel free to do with them as you wish, according to the license model. - -**UPDATE: Oct 3, 2023** - -+ Added an Image Saver that can place JSON files ***in separate folders*** -+ Added nodes to convert from one numeric type to another, and to string. **Still in beta** - -**UPDATE: Sep 24, 2023** - -+ Took the node from https://github.com/ZaneA/ComfyUI-ImageReward that uses Image Reward and repurposed it -+ Took the node from https://github.com/strimmlarn that does aesthetic scoring and repurposed it - -**UPDATE: Sep 20, 2023** - -+ Added an eight-input number switch because I needed it - -**UPDATE: Sep 18, 2023** - -+ Added the Endless Nodes Parameterizer with Text_G and Text_L prompt box -+ Added the Parameterizer with a_score for both pos/neg -+ Added the Parameterizer with a_score for both pos/neg and Text_G and Text_L prompt box -+ Fixed some typos - - -**UPDATE: Sep 17, 2023** - -+ Added the Endless Nodes Parameterizer - - -## Install and Requirements - -Navigate to your /ComfyUI/custom_nodes/ folder - -In Windows, you can then right-click to start a command prompt and type: - -`git clone https://github.com/tusharbhutt/Endless-Nodes` - -You can also get the nodes via the [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) - -**NOTE: Requires CLIP and Pytorch-Lightning for the Aesthetic Scorer and ImageReward for my take on the Image Reward node scorer. I've added them in the requirement file but if it doesn't work, you will need to download manually** - -## Node List - -## Endless Image Saver - -This is why I tried my hand ay Python in the first place! There are many, many, many, good image saver nodes out there, so why one more? Well: - -+ The default saver does not save to UNC in Windows -+ Some savers will allow you to save to UNC but have built-in folder formats -+ You can cobble some savers to save an image together with a text file, but the timestamp on the text file tends to be 2-3 seconds off from the image -+ No saver I know of lets you save the JSON file to a completely different folder - -So… this node will allow you to save your image file wherever you want, with full support for standard [Python date and time conventions]( https://docs.python.org/2/library/time.html#time.sleep), and you can save the JSON file somewhere else. - -I have more plans for this, but it’s ready for release now. - -![imagesaver](./img/imagesaverone.png) - - -Does it work... ? - - -![imagesaverfile](./img/imagesaverfile.png) - -See, it works! - -## Aesthetic Scorer - -This node will output a predicted aesthetic score as a number and display it with the appropriate node (e.g., rgthree's ["Any"](https://github.com/rgthree/rgthree-comfy#display-any) node). I took the node from https://github.com/strimmlarn that does aesthetic scoring and repurposed it so that it is simpler and outputs the score as a number. I combined the model loader and score calculator into one, and removed the Aesthetic Score Sorter. - -![aestheticone](./img/aestheticone.png) - -You can load a number of scoring models, I use the "chadscorer" model found here: - -https://github.com/grexzen/SD-Chad/blob/main/chadscorer.pth - -As for the original node from strimmlarn, please refer to this GitHub if you would like to examine it: - -https://github.com/strimmlarn/ComfyUI-Strimmlarns-Aesthetic-Score - -The scorer adds about 7-10 seconds to a workflow on my Nvidia 3060 12 GB card, your mileage may vary - -## Image Reward - -This node will output a predicted aesthetic score as a number and display it with the appropriate node (e.g., rgthree's ["Any"](https://github.com/rgthree/rgthree-comfy#display-any) node). I took the node from https://github.com/ZaneA/ComfyUI-ImageReward that in turn scores images using [ImageReward](https://github.com/THUDM/ImageReward). I combined the model loader and score calculator into one and added output nodes for both the standard deviation calculation (which is what Zane's node does) and the score on a scale of one to ten based on some simple statistic calculations. - -The difference between this node and the Aesthetics Scorer is that the underlying ImageReward is based on Reward Feedback Learning (ReFL) and uses 137K input samples that were scored by humans. It often scores much lower than the Aesthetics Scorer, but not always! - -![imagereward](./img/imagereward.png) - -As with the Aesthetics Scorer, the Image Reward node adds about 7-10 seconds to a workflow on my Nvidia 3060 12 GB card, your mileage may vary. - -For added GPU cycle time consumption, put them both in and watch how often they vehemently disagree with the scoring :) - -![disagree](./img/disagree.png) - - -### Endless Node Parameterizer - -This node has a collection of inputs for the CLIP text Encoder and Refiners for SDXL based workflows - -Inputs include: - -- base width: set width for the base encoder -- base height: set height for the base encoder -- base cropped width: crop width for the base encoder -- base cropped height: crop height for the base encoder -- base target width: target width for the base encoder -- base target height: target height for the base encoder -- refiner width: crop width for the refiner if you have included one -- refiner height: crop height for the refiner if you have included one -- refiner aesthetic score: set the score value for the refiner - -You can set up two of these one for the positive and one for the negative prompt - -![parameterizer](./img/parameterizer.png) - -## Parameterizer with prompt - -As above, but with TEXT_G and TEXT_L outputs - -![parameterizerprompt](./img/parameterizerprompt.png) - -## COMBO Parameterizer with and without prompt - -After making the Parameterizer, I realized having two separate ones for both the positive and negative CLIP encoders is not optimal, because almost everyone will use the same resolution for both the positive and negative base and refiners. However, you may (well, you *should…*) want separate aesthetic scorers for the positive and negative CLIPs, so I came up with one that does this for you. Also comes in a variant that has the prompt boxes for you. - -![comboparameterizer](./img/comboparameterizer.png) - -![comboparameterizerprompt](./img/comboparameterizerprompt.png) - -### Six Text Input Switch -Allows the user to select between six text inputs and uses a slider to make the selection. Useful for multiple inputs for prompt creation - - -![sixtext](./img/sixtext.png) - -**NOT SHOWN: There is an eight input variant now too, as of Sep 20, 2023** - -### Six Integer Input to Six Integer Output -I've seen a fair number of 3-, 4-, or more X-way text input and outputs, I wanted to do something for numbers as well. Use it as you wish. - - -![sixintconnect](./img/sixintconnect.png) - - -### Six Integer Widget -As above, but with widgets for entry instead of connectors - -![sixintwidget](./img/sixintwidget.png) - -### Various converters - -You've seen them elsewhere too, but there are few that do X to float or vice versa, so I threw them in. *Still in beta*, sometimes they work, other times the downstream node complains. - -![converters](./img/converters.png) - - -## Usage License and Restrictions - -See GPL Licensing V3 for usage. You may modify this code as long as you keep the credits for this repository and for those noted in the credit section below. **YOU ARE EXPRESSLY FORBIDDEN FROM USING THIS NODE TO CREATE ANY IMAGES OR ARTWORK THAT VIOLATES THE STABLE DIFFUSION USAGE NOTES [HERE](https://huggingface.co/stabilityai/stable-diffusion-2#misuse-malicious-use-and-out-of-scope-use) AND [HERE](https://huggingface.co/stabilityai/stable-diffusion-2#misuse-and-malicious-use).** - -For example, don't be a mouth-breather who creates fake celebrity nudes or sexual content of **anyone, even if you have their consent**. JUST. DON’T. BE. A. DICK/BITCH. - -The author expressly disclaims any liability for any images you create using these nodes. - -## Disclaimer - -These nodes may or may not be maintained. They work on my system but may not on yours. - -## Credits - -+[Comfyroll Custom Nodes](https://github.com/RockOfFire/ComfyUI_Comfyroll_CustomNode) for the overall node code layout, coding snippets, and inspiration for the text input and number switches. - -+[WLSH Nodes](https://github.com/wallish77/wlsh_nodes) for some coding for the Integer Widget. - -+[ComfyUI](https://github.com/comfyanonymous/ComfyUI) Interface for the basic ideas of what nodes I wanted. - -+[ComfyUI-Strimmlarns-Aesthetic-Score](https://github.com/strimmlarn/ComfyUI-Strimmlarns-Aesthetic-Score) for the original coding for the Aesthetic Scorer. The original scorer, and therefore my derivative too, use the [MLP class code](https://github.com/christophschuhmann/improved-aesthetic-predictor) from Christoph Schuhmann - -+[Zane A's ComfyUI-ImageReward](https://github.com/ZaneA/ComfyUI-ImageReward) for the original coding for the Image Reward node. Zane's node in turn uses [ImageReward](https://github.com/THUDM/ImageReward) - -+[Mikey nodes](https://github.com/bash-j/mikey_nodes )to grab code snippet to pass scoring metadata to image - -# Took some base code from the [WAS save image node](https://github.com/WASasquatch/was-node-suite-comfyui) to repurpose it - +# Endless-Nodes +Some basic custom nodes for the ComfyUI user interface for Stable Diffusion. Features: + ++ An image saver for images and JSON files to base folder, custom folders for one, or custom folders for both. Also allows for Python timestamping ++ Two aesthetic scoring models, one based on the same as AUTO1111, the other based on Image Reward ++ Converters for various numerical functions to the other (int, float, number) and to strings. Still in beta ++ Switches for text and numbers ++ Parameter collection nodes ++ MORE TO COME + +When using the [ComfyUI](https://github.com/comfyanonymous/ComfyUI) interface for [Stable Diffusion](https://github.com/Stability-AI/stablediffusion), I sometimes find that the standard nodes and the many, many, many custom nodes out there don't work the way I want them to, or how I think they do. + +Rightly or wrongly, I am pretending to teach myself a bit of Python to get some nodes up and running to do what I'd like. There are no promises that these nodes will work for you or that I will maintain them. Feel free to do with them as you wish, according to the license model. + +**UPDATE: Oct 3, 2023** + ++ Added an Image Saver that can place JSON files ***in separate folders*** ++ Added nodes to convert from one numeric type to another, and to string. **Still in beta** + +**UPDATE: Sep 24, 2023** + ++ Took the node from https://github.com/ZaneA/ComfyUI-ImageReward that uses Image Reward and repurposed it ++ Took the node from https://github.com/strimmlarn that does aesthetic scoring and repurposed it + +**UPDATE: Sep 20, 2023** + ++ Added an eight-input number switch because I needed it + +**UPDATE: Sep 18, 2023** + ++ Added the Endless Nodes Parameterizer with Text_G and Text_L prompt box ++ Added the Parameterizer with a_score for both pos/neg ++ Added the Parameterizer with a_score for both pos/neg and Text_G and Text_L prompt box ++ Fixed some typos + + +**UPDATE: Sep 17, 2023** + ++ Added the Endless Nodes Parameterizer + + +## Install and Requirements + +Navigate to your /ComfyUI/custom_nodes/ folder + +In Windows, you can then right-click to start a command prompt and type: + +`git clone https://github.com/tusharbhutt/Endless-Nodes` + +You can also get the nodes via the [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) + +**NOTE: Requires CLIP and Pytorch-Lightning for the Aesthetic Scorer and ImageReward for my take on the Image Reward node scorer. I've added them in the requirement file but if it doesn't work, you will need to download manually** + +## Node List + +## Endless Image Saver + +This is why I tried my hand ay Python in the first place! There are many, many, many, good image saver nodes out there, so why one more? Well: + ++ The default saver does not save to UNC in Windows ++ Some savers will allow you to save to UNC but have built-in folder formats ++ You can cobble some savers to save an image together with a text file, but the timestamp on the text file tends to be 2-3 seconds off from the image ++ No saver I know of lets you save the JSON file to a completely different folder + +So… this node will allow you to save your image file wherever you want, with full support for standard [Python date and time conventions]( https://docs.python.org/2/library/time.html#time.sleep), and you can save the JSON file somewhere else. + +I have more plans for this, but it’s ready for release now. + +![imagesaver](./img/imagesaverone.png) + + +Does it work... ? + + +![imagesaverfile](./img/imagesaverfile.png) + +See, it works! + +## Aesthetic Scorer + +This node will output a predicted aesthetic score as a number and display it with the appropriate node (e.g., rgthree's ["Any"](https://github.com/rgthree/rgthree-comfy#display-any) node). I took the node from https://github.com/strimmlarn that does aesthetic scoring and repurposed it so that it is simpler and outputs the score as a number. I combined the model loader and score calculator into one, and removed the Aesthetic Score Sorter. + +![aestheticone](./img/aestheticone.png) + +You can load a number of scoring models, I use the "chadscorer" model found here: + +https://github.com/grexzen/SD-Chad/blob/main/chadscorer.pth + +As for the original node from strimmlarn, please refer to this GitHub if you would like to examine it: + +https://github.com/strimmlarn/ComfyUI-Strimmlarns-Aesthetic-Score + +The scorer adds about 7-10 seconds to a workflow on my Nvidia 3060 12 GB card, your mileage may vary + +## Image Reward + +This node will output a predicted aesthetic score as a number and display it with the appropriate node (e.g., rgthree's ["Any"](https://github.com/rgthree/rgthree-comfy#display-any) node). I took the node from https://github.com/ZaneA/ComfyUI-ImageReward that in turn scores images using [ImageReward](https://github.com/THUDM/ImageReward). I combined the model loader and score calculator into one and added output nodes for both the standard deviation calculation (which is what Zane's node does) and the score on a scale of one to ten based on some simple statistic calculations. + +The difference between this node and the Aesthetics Scorer is that the underlying ImageReward is based on Reward Feedback Learning (ReFL) and uses 137K input samples that were scored by humans. It often scores much lower than the Aesthetics Scorer, but not always! + +![imagereward](./img/imagereward.png) + +As with the Aesthetics Scorer, the Image Reward node adds about 7-10 seconds to a workflow on my Nvidia 3060 12 GB card, your mileage may vary. + +For added GPU cycle time consumption, put them both in and watch how often they vehemently disagree with the scoring :) + +![disagree](./img/disagree.png) + + +### Endless Node Parameterizer + +This node has a collection of inputs for the CLIP text Encoder and Refiners for SDXL based workflows + +Inputs include: + +- base width: set width for the base encoder +- base height: set height for the base encoder +- base cropped width: crop width for the base encoder +- base cropped height: crop height for the base encoder +- base target width: target width for the base encoder +- base target height: target height for the base encoder +- refiner width: crop width for the refiner if you have included one +- refiner height: crop height for the refiner if you have included one +- refiner aesthetic score: set the score value for the refiner + +You can set up two of these one for the positive and one for the negative prompt + +![parameterizer](./img/parameterizer.png) + +## Parameterizer with prompt + +As above, but with TEXT_G and TEXT_L outputs + +![parameterizerprompt](./img/parameterizerprompt.png) + +## COMBO Parameterizer with and without prompt + +After making the Parameterizer, I realized having two separate ones for both the positive and negative CLIP encoders is not optimal, because almost everyone will use the same resolution for both the positive and negative base and refiners. However, you may (well, you *should…*) want separate aesthetic scorers for the positive and negative CLIPs, so I came up with one that does this for you. Also comes in a variant that has the prompt boxes for you. + +![comboparameterizer](./img/comboparameterizer.png) + +![comboparameterizerprompt](./img/comboparameterizerprompt.png) + +### Six Text Input Switch +Allows the user to select between six text inputs and uses a slider to make the selection. Useful for multiple inputs for prompt creation + + +![sixtext](./img/sixtext.png) + +**NOT SHOWN: There is an eight input variant now too, as of Sep 20, 2023** + +### Six Integer Input to Six Integer Output +I've seen a fair number of 3-, 4-, or more X-way text input and outputs, I wanted to do something for numbers as well. Use it as you wish. + + +![sixintconnect](./img/sixintconnect.png) + + +### Six Integer Widget +As above, but with widgets for entry instead of connectors + +![sixintwidget](./img/sixintwidget.png) + +### Various converters + +You've seen them elsewhere too, but there are few that do X to float or vice versa, so I threw them in. *Still in beta*, sometimes they work, other times the downstream node complains. + +![converters](./img/converters.png) + + +## Usage License and Restrictions + +See GPL Licensing V3 for usage. You may modify this code as long as you keep the credits for this repository and for those noted in the credit section below. **YOU ARE EXPRESSLY FORBIDDEN FROM USING THIS NODE TO CREATE ANY IMAGES OR ARTWORK THAT VIOLATES THE STABLE DIFFUSION USAGE NOTES [HERE](https://huggingface.co/stabilityai/stable-diffusion-2#misuse-malicious-use-and-out-of-scope-use) AND [HERE](https://huggingface.co/stabilityai/stable-diffusion-2#misuse-and-malicious-use).** + +For example, don't be a mouth-breather who creates fake celebrity nudes or sexual content of **anyone, even if you have their consent**. JUST. DON’T. BE. A. DICK/BITCH. + +The author expressly disclaims any liability for any images you create using these nodes. + +## Disclaimer + +These nodes may or may not be maintained. They work on my system but may not on yours. + +## Credits + ++[Comfyroll Custom Nodes](https://github.com/RockOfFire/ComfyUI_Comfyroll_CustomNode) for the overall node code layout, coding snippets, and inspiration for the text input and number switches. + ++[WLSH Nodes](https://github.com/wallish77/wlsh_nodes) for some coding for the Integer Widget. + ++[ComfyUI](https://github.com/comfyanonymous/ComfyUI) Interface for the basic ideas of what nodes I wanted. + ++[ComfyUI-Strimmlarns-Aesthetic-Score](https://github.com/strimmlarn/ComfyUI-Strimmlarns-Aesthetic-Score) for the original coding for the Aesthetic Scorer. The original scorer, and therefore my derivative too, use the [MLP class code](https://github.com/christophschuhmann/improved-aesthetic-predictor) from Christoph Schuhmann + ++[Zane A's ComfyUI-ImageReward](https://github.com/ZaneA/ComfyUI-ImageReward) for the original coding for the Image Reward node. Zane's node in turn uses [ImageReward](https://github.com/THUDM/ImageReward) + ++[Mikey nodes](https://github.com/bash-j/mikey_nodes )to grab code snippet to pass scoring metadata to image + +# Took some base code from the [WAS save image node](https://github.com/WASasquatch/was-node-suite-comfyui) to repurpose it \ No newline at end of file diff --git a/__init__.py b/__init__.py index 5af9b9c..25ff051 100644 --- a/__init__.py +++ b/__init__.py @@ -1,8 +1,8 @@ """ @author: BiffMunky -@title: 🌌 An Endless Sea of Stars Nodes 🌌 -@nickname: 🌌 Endless Nodes 🌌 -@description: A small set of nodes I created for various numerical and text inputs. Features switches for text and numbers, parameter collection nodes, and two aesthetic scoring modwls. +@title: Endless ️🌊🌠 Node 🌌 +@nickname: ♾️🌊🌠 +@description: A small set of nodes I created for various numerical and text inputs. Features switches for text and numbers, parameter collection nodes, and two aesthetic scoring models. """ from .endless_nodes import * @@ -15,16 +15,27 @@ NODE_CLASS_MAPPINGS = { "Endless Nodes Parameterizer & Prompts": EndlessNode_XLParameterizerPrompt, "Endless Nodes Combo Parameterizer": EndlessNode_ComboXLParameterizer, "Endless Nodes Combo Parameterizer & Prompts": EndlessNode_ComboXLParameterizerPrompt, - #"Endless Nodes Image Save with Text File": EndlessNode_ImageSaver, - # "Endless Nodes Display String": EndlessNode_DisplayString, - # "Endless Nodes Display Number": EndlessNode_DisplayNumber, - # "Endless Nodes Display Integer": EndlessNode_DisplayInt, - # "Endless Nodes Display Float": EndlessNode_DisplayFloat, + "Endless Nodes Image Save with Text File": EndlessNode_ImageSaver, "Endless Nodes Aesthetic Scoring": EndlessNode_Scoring, - "Endless Nodes Image Reward": EndlessNode_ImageReward, - + "Endless Nodes Aesthetic Scoring Auto": EndlessNode_ScoringAutoScore, + "Endless Nodes Image Reward": EndlessNode_ImageReward, + "Endless Nodes Image Reward Auto": EndlessNode_ImageRewardAutoScore, + "Endless Nodes Float to Integer": EndlessNode_FloattoInt, + "Endless Nodes Float to Number": EndlessNode_FloattoNum, + "Endless Nodes Float to String": EndlessNode_FloattoString, + "Endless Nodes Number to Float": EndlessNode_NumtoFloat, + "Endless Nodes Number to Integer": EndlessNode_NumtoInt, + "Endless Nodes Number to String": EndlessNode_NumtoString, + "Endless Nodes Integer to Float": EndlessNode_InttoFloat, + "Endless Nodes Integer to Number": EndlessNode_InttoNum, + "Endless Nodes Integer to String": EndlessNode_InttoString, + "Endless Nodes Float to X": EndlessNode_FloattoX, + "Endless Nodes Integer to X": EndlessNode_InttoX, + "Endless Nodes Number to X": EndlessNode_NumtoX, + + } __all__ = ['NODE_CLASS_MAPPINGS'] -print("\033[36m 🌌 An Endless Sea of Stars Custom Nodes 🌌 V0.24 \033[34m: \033[92mLoaded\033[0m") \ No newline at end of file +print("\033[36m 🌌 An Endless Sea of Stars Custom Nodes 🌌 \033[33mreV0.28 \033[34m: \033[92mLoaded\033[0m") \ No newline at end of file diff --git a/changlelog.md b/changlelog.md index cb2a03b..9535db1 100644 --- a/changlelog.md +++ b/changlelog.md @@ -1,3 +1,8 @@ +Oct 3/23: 0.29- Save Image module added, saves images and JSON to separate folder if requested +Sep 28/23: 0.28 - Unreleased - Added Variable types to X +Sep 28/23: 0.27 - Unreleased - Corrected scoring nodes to actually add the value of the score into the image metadata .... still goobered! +Sep 24/23: 0.26 - Unreleased - starting to correct scoring to get to image metadata +Sep 24/23: 0.25 - Added various X to String Nodes Sep 24/23: 0.24 - Added In Image Reward scoring model with a single node to load model and output standard deviation and scoring via number or string nodes Sep 24/23: 0.23 - Rework Aesthetic Score model and integrate it into single node to display score, added a requirements file Sep 23/23: 0.22 - Unreleased, convert ImageReward output to base ten score diff --git a/endless_nodes.py b/endless_nodes.py index 67c5a6e..6b4a6bd 100644 --- a/endless_nodes.py +++ b/endless_nodes.py @@ -1,30 +1,40 @@ """ @author: BiffMunky -@title: 🌌 An Endless Sea of Stars Nodes 🌌 +@title: 🌌 An Endless Sea of Stars Node 🌌 @nickname: 🌌 Endless Nodes 🌌 -@description: A small set of nodes I created for various numerical and text inputs. Features switches for text and numbers, parameter collection nodes, and two aesthetic scoring modwls. +@description: A small set of nodes I created for various numerical and text inputs. Features switches for text and numbers, parameter collection nodes, and two aesthetic scoring models. """ -# Version 0.24 - Imagr Rearwd nodeaddeded -#0.23 - Aesthetic Scorer addeded -#0.22 Unreleased - intro'd asestheic score +#0.29 - Save Image module added, saves images and JSON to separate folder if requested +#0.28 - Unreleased - Added Variable types to X +#0.27 - Unreleased - Corrected scoring nodes to actually add the value of the score into the image metadata .... still goobered! +#0.26 - Unreleased - starting to correct scoring to get to image metadata +#0.25 - Added various X to String Nodes +#0.24 - Image reward node added +#0.23 - Aesthetic Scorer added +#0.22 Unreleased - intro'd aestheticscore #0.21 unreleased -- trying for display nodes #0.20 sorted categories of nodes -#-------------------------------------- +#---------------------------------------------- # Endless Sea of Stars Custom Node Collection #https://github.com/tusharbhutt/Endless-Nodes +#---------------------------------------------- # -# +#______________________________________________________________________________________________________________________________________________________________ +# IMPORT MODULES BLOCK # -#import torch from PIL import Image from PIL.PngImagePlugin import PngInfo +from colorama import init, Fore, Back, Style from os.path import join from warnings import filterwarnings +import ImageReward as RM import clip +import colorama import datetime +import folder_paths as endless_paths import io import json import math @@ -32,24 +42,30 @@ import numpy as np import os import pytorch_lightning as pl import re -import sys +import socket import statistics +import sys +import time import torch import torch.nn as nn -import ImageReward as RM sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy")) import comfy.sd import comfy.utils - -import folder_paths +#import folder_paths import typing as tg -#-------------------------------------- -#Six Text Input Node for selection +# Initialize colorama for colored text +colorama.init(autoreset=True) + +#______________________________________________________________________________________________________________________________________________________________ +# "SWITCHES" BLOCK # + +#---------------------------------------------- +# Six Text Input Node for selection class EndlessNode_SixTextInputSwitch: def __init__(self): @@ -60,21 +76,21 @@ class EndlessNode_SixTextInputSwitch: return { "required": { "Input": ("INT", {"default": 1, "min": 1, "max": 6, "step": 1, "display": "slider"}), -#I like the slider idea, it's better for a touch screen - "text1": ("STRING", {"forceInput": True}), +#I like the slider idea, it's better for a touch screen + "text1": ("STRING", {"forceInput": True}), }, "optional": { "text2": ("STRING", {"forceInput": True}), "text3": ("STRING", {"forceInput": True}), "text4": ("STRING", {"forceInput": True}), "text5": ("STRING", {"forceInput": True}), - "text6": ("STRING", {"forceInput": True}), + "text6": ("STRING", {"forceInput": True}), } } RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("Output",) - + RETURN_NAMES = ("Output",) + FUNCTION = "six_text_switch" CATEGORY = "Endless 🌌/Switches" @@ -89,11 +105,12 @@ class EndlessNode_SixTextInputSwitch: elif Input == 4: return (text4,) elif Input == 5: - return (text5,) + return (text5,) else: - return (text6,) + return (text6,) -#Eight Text Input Node for selection (needed more slots, what can I say) +#---------------------------------------------- +# Eight Text Input Node for selection (needed more slots, what can I say) class EndlessNode_EightTextInputSwitch: @@ -105,23 +122,23 @@ class EndlessNode_EightTextInputSwitch: return { "required": { "Input": ("INT", {"default": 1, "min": 1, "max": 8, "step": 1, "display": "slider"}), -#I like the slider idea, it's better for a touch screen - "text1": ("STRING", {"forceInput": True}), +#I like the slider idea, it's better for a touch screen + "text1": ("STRING", {"forceInput": True}), }, "optional": { "text2": ("STRING", {"forceInput": True}), "text3": ("STRING", {"forceInput": True}), "text4": ("STRING", {"forceInput": True}), "text5": ("STRING", {"forceInput": True}), - "text6": ("STRING", {"forceInput": True}), + "text6": ("STRING", {"forceInput": True}), "text7": ("STRING", {"forceInput": True}), - "text8": ("STRING", {"forceInput": True}), + "text8": ("STRING", {"forceInput": True}), } } RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("Output",) - + RETURN_NAMES = ("Output",) + FUNCTION = "eight_text_switch" CATEGORY = "Endless 🌌/Switches" @@ -136,16 +153,16 @@ class EndlessNode_EightTextInputSwitch: elif Input == 4: return (text4,) elif Input == 5: - return (text5,) + return (text5,) elif Input == 6: return (text6,) elif Input == 7: - return (text7,) + return (text7,) else: - return (text8,) + return (text8,) -#-------------------------------------- -##Six Integer Input and Output via connectors +#---------------------------------------------- +# Six Integer Input and Output via connectors class EndlessNode_SixIntIOSwitch: @@ -155,41 +172,41 @@ class EndlessNode_SixIntIOSwitch: @classmethod def INPUT_TYPES(cls): return { - "required": { - "INT1": ("INT", {"forceInput": True}), + "required": { + "INT1": ("INT", {"forceInput": True}), }, "optional": { "INT2": ("INT", {"forceInput": True}), "INT3": ("INT", {"forceInput": True}), "INT4": ("INT", {"forceInput": True}), "INT5": ("INT", {"forceInput": True}), - "INT6": ("INT", {"forceInput": True}), + "INT6": ("INT", {"forceInput": True}), } } RETURN_TYPES = ("INT","INT","INT","INT","INT","INT",) - RETURN_NAMES = ("INT1","INT2","INT3","INT4","INT5","INT6",) - + RETURN_NAMES = ("INT1","INT2","INT3","INT4","INT5","INT6",) + FUNCTION = "six_intIO_switch" CATEGORY = "Endless 🌌/Switches" def six_intIO_switch(self, Input, INT1=0, INT2=0, INT3=0, INT4=0, INT5=0, INT6=0): if Input == 1: - return (INT1, ) + return (INT1,) elif Input == 2: - return (INT2, ) + return (INT2,) elif Input == 3: - return (INT3, ) + return (INT3,) elif Input == 4: - return (INT4, ) + return (INT4,) elif Input == 5: - return (INT5, ) + return (INT5,) else: - return (INT6, ) - -#-------------------------------------- -##Six Integer Input and Output by Widget + return (INT6,) + +#---------------------------------------------- +# Six Integer Input and Output by Widget class EndlessNode_SixIntIOWidget: def __init__(self): @@ -200,17 +217,17 @@ class EndlessNode_SixIntIOWidget: return { "required": { "int1": ("INT", {"default": 0,}), - }, - "optional": { + }, + "optional": { "int2": ("INT", {"default": 0,}), "int3": ("INT", {"default": 0,}), "int4": ("INT", {"default": 0,}), "int5": ("INT", {"default": 0,}), - "int6": ("INT", {"default": 0,}), + "int6": ("INT", {"default": 0,}), } } RETURN_TYPES = ("INT","INT","INT","INT","INT","INT",) - RETURN_NAMES = ("INT1","INT2","INT3","INT4","INT5","INT6",) + RETURN_NAMES = ("INT1","INT2","INT3","INT4","INT5","INT6",) FUNCTION = "six_int_widget" CATEGORY = "Endless 🌌/Switches" @@ -218,8 +235,13 @@ class EndlessNode_SixIntIOWidget: def six_int_widget(self,int1,int2,int3,int4,int5,int6): return(int1,int2,int3,int4,int5,int6) - -#Text Encode Combo Box with prompt + + +#______________________________________________________________________________________________________________________________________________________________ +# PARAMETERS BLOCK # + +#---------------------------------------------- +# Text Encode Combo Box with prompt class EndlessNode_XLParameterizerPrompt: def __init__(self): @@ -238,16 +260,16 @@ class EndlessNode_XLParameterizerPrompt: "refiner_width": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16}), "refiner_height": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16}), "refiner_ascore": ("FLOAT", {"default": 6, "min": 0, "max": 0xffffffffffffffff}), - }, - "optional": { + }, + "optional": { "endlessG": ("STRING", {"default": "TEXT_G,acts as main prompt and connects to refiner text input", "multiline": True}), - "endlessL": ("STRING", {"default": "TEXT_L, acts as supporting prompt", "multiline": True}), + "endlessL": ("STRING", {"default": "TEXT_L, acts as supporting prompt", "multiline": True}), } } RETURN_TYPES = ("INT","INT","INT","INT","INT","INT","INT","INT","FLOAT","STRING","STRING",) - RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Refiner Aesthetic Score","Text_G/Refiner Prompt","Text_L Prompt",) - + RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Refiner Aesthetic Score","Text_G/Refiner Prompt","Text_L Prompt",) + FUNCTION = "ParameterizerPrompt" CATEGORY = "Endless 🌌/Parameters" @@ -255,7 +277,8 @@ class EndlessNode_XLParameterizerPrompt: def ParameterizerPrompt(self,base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_ascore,endlessG,endlessL): return(base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_ascore,endlessG,endlessL) -# CLIP tect encodee box without prompt +#---------------------------------------------- +# CLIP text encode box without prompt class EndlessNode_XLParameterizer: def __init__(self): @@ -277,7 +300,7 @@ class EndlessNode_XLParameterizer: } } RETURN_TYPES = ("INT","INT","INT","INT","INT","INT","INT","INT","FLOAT",) - RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Refiner Aesthetic Score",) + RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Refiner Aesthetic Score",) FUNCTION = "Parameterizer" CATEGORY = "Endless 🌌/Parameters" @@ -286,8 +309,8 @@ class EndlessNode_XLParameterizer: def Parameterizer(self,base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_ascore): return(base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_ascore) - -#Text Encode Combo Box with prompt +#---------------------------------------------- +# Text Encode Combo Box with prompt class EndlessNode_ComboXLParameterizerPrompt: def __init__(self): @@ -306,19 +329,19 @@ class EndlessNode_ComboXLParameterizerPrompt: "refiner_width": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16}), "refiner_height": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16}), "refiner_pascore": ("FLOAT", {"default": 6.5, "min": 0, "max": 0xffffffffffffffff}), - "refiner_nascore": ("FLOAT", {"default": 2.5, "min": 0, "max": 0xffffffffffffffff}), - }, - "optional": { + "refiner_nascore": ("FLOAT", {"default": 2.5, "min": 0, "max": 0xffffffffffffffff}), + }, + "optional": { "PendlessG": ("STRING", {"default": "Positive TEXT_G,acts as main prompt and connects to refiner text input", "multiline": True}), "PendlessL": ("STRING", {"default": "Positive TEXT_L, acts as supporting prompt", "multiline": True}), "NendlessG": ("STRING", {"default": "Negative TEXT_G, acts as main prompt and connects to refiner text input", "multiline": True}), - "NendlessL": ("STRING", {"default": "Negative TEXT_L, acts as supporting prompt", "multiline": True}), + "NendlessL": ("STRING", {"default": "Negative TEXT_L, acts as supporting prompt", "multiline": True}), } } RETURN_TYPES = ("INT","INT","INT","INT","INT","INT","INT","INT","FLOAT","FLOAT","STRING","STRING", "STRING","STRING",) - RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Positive Refiner Aesthetic Score","Negative Refiner Aesthetic Score","Positive Text_G and Refiner Text Prompt","Postive Text_L Prompt","Negative Text_G and Refiner Text Prompt","Negative Text_L Prompt",) - + RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Positive Refiner Aesthetic Score","Negative Refiner Aesthetic Score","Positive Text_G and Refiner Text Prompt","Postive Text_L Prompt","Negative Text_G and Refiner Text Prompt","Negative Text_L Prompt",) + FUNCTION = "ComboParameterizerPrompt" CATEGORY = "Endless 🌌/Parameters" @@ -326,7 +349,8 @@ class EndlessNode_ComboXLParameterizerPrompt: def ComboParameterizerPrompt(self,base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_pascore,refiner_nascore,PendlessG,PendlessL,NendlessG,NendlessL): return(base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_pascore,refiner_nascore,PendlessG,PendlessL,NendlessG,NendlessL) -# CLIP text encode box without prompt, COMBO that allows one box for both pos/neg parameters to be fed to CLIP text, with separate POS/NEG Aestheticscore +#---------------------------------------------- +# CLIP text encode box without prompt, COMBO that allows one box for both pos/neg parameters to be fed to CLIP text, with separate POS/NEG Aesthetic score class EndlessNode_ComboXLParameterizer: def __init__(self): @@ -349,7 +373,7 @@ class EndlessNode_ComboXLParameterizer: } } RETURN_TYPES = ("INT","INT","INT","INT","INT","INT","INT","INT","FLOAT","FLOAT") - RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Positive Refiner Aesthetic Score","Negative Refiner Aesthetic Score",) + RETURN_NAMES = ("Base Width","Base Height","Base Cropped Width","Base Cropped Height","Base Target Width","Base Target Height","Refiner Width","Refiner Height","Positive Refiner Aesthetic Score","Negative Refiner Aesthetic Score",) FUNCTION = "ComboParameterizer" CATEGORY = "Endless 🌌/Parameters" @@ -358,9 +382,11 @@ class EndlessNode_ComboXLParameterizer: def ComboParameterizer(self,base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_pascore, refiner_nascore): return(base_width,base_height,base_crop_w,base_crop_h,base_target_w,base_target_h,refiner_width,refiner_height,refiner_pascore, refiner_nascore) +#______________________________________________________________________________________________________________________________________________________________ +# IMAGE SCORING BLOCK # -#-------------------------------------- -## Aesthetic Scoring Type One +#---------------------------------------------- +# Aesthetic Scoring Node folder_paths.folder_names_and_paths["aesthetic"] = ([os.path.join(folder_paths.models_dir,"aesthetic")], folder_paths.supported_pt_extensions) @@ -422,7 +448,7 @@ class EndlessNode_Scoring: } } - RETURN_TYPES = ("NUM",) + RETURN_TYPES = ("NUMBER","IMAGE") FUNCTION = "calc_score" CATEGORY = "Endless 🌌/Scoring" @@ -442,13 +468,59 @@ class EndlessNode_Scoring: image2 = preprocess(pil_image).unsqueeze(0).to(device) with torch.no_grad(): image_features = model2.encode_image(image2) - im_emb_arr = normalized(image_features.cpu().detach().numpy() ) + im_emb_arr = normalized(image_features.cpu().detach().numpy()) prediction = model(torch.from_numpy(im_emb_arr).to(device).type(torch.cuda.FloatTensor)) final_prediction = round(float(prediction[0]), 2) del model return (final_prediction,) +# #---------------------------------------------- NOT WORKING, NEED TO LOOK AT IT +# # Aesthetic Scoring Node with Scoring passed to image +# class EndlessNode_ScoringAutoScore: + # def __init__(self): + # pass + + # @classmethod + # def INPUT_TYPES(cls): + # return { + # "required": { + # "model_name": (folder_paths.get_filename_list("aesthetic"), {"multiline": False, "default": "chadscorer.pth"}), + # "image": ("IMAGE",), + # } + # } + + # RETURN_TYPES = ("NUMBER","IMAGE") + # FUNCTION = "calc_score" + # OUTPUT_NODE = True + # CATEGORY = "Endless 🌌/Scoring" + + # def calc_score(self, model_name, image): + # m_path = folder_paths.folder_names_and_paths["aesthetic"][0] + # m_path2 = os.path.join(m_path[0], model_name) + # model = MLP(768) # CLIP embedding dim is 768 for CLIP ViT L 14 + # s = torch.load(m_path2) + # model.load_state_dict(s) + # model.to("cuda") + # model.eval() + # device = "cuda" + # model2, preprocess = clip.load("ViT-L/14", device=device) # RN50x64 + # tensor_image = image[0] + # img = (tensor_image * 255).to(torch.uint8).numpy() + # pil_image = Image.fromarray(img, mode='RGB') + # image2 = preprocess(pil_image).unsqueeze(0).to(device) + # with torch.no_grad(): + # image_features = model2.encode_image(image2) + # im_emb_arr = normalized(image_features.cpu().detach().numpy()) + # prediction = model(torch.from_numpy(im_emb_arr).to(device).type(torch.cuda.FloatTensor)) + # final_prediction = round(float(prediction[0]), 2) + # del model + # # Metadata part + # extra_pnginfo = {"SCORE": str(final_prediction)} + # return (final_prediction, image) + +#---------------------------------------------- +# Image Reward Scoring class EndlessNode_ImageReward: def __init__(self): @@ -461,13 +533,11 @@ class EndlessNode_ImageReward: "model": ("STRING", {"multiline": False, "default": "ImageReward-v1.0"}), "prompt": ("STRING", {"multiline": True, "forceInput": True}), "images": ("IMAGE",), -# "rounded": ("BOOL", {"default": False}) # Add a boolean input }, } RETURN_TYPES = ("FLOAT", "STRING", "FLOAT", "STRING") RETURN_NAMES = ("SCORE_FLOAT", "SCORE_STRING", "VALUE_FLOAT", "VALUE_STRING") - OUTPUT_NODE = False CATEGORY = "Endless 🌌/Scoring" @@ -484,120 +554,510 @@ class EndlessNode_ImageReward: img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) score += self.model.score(prompt, [img]) score /= len(images) - - # if rounded: - # # Round the score to two decimal places - # score = round(score, 2) - # assume std dev follows normal distribution curve valuescale = 0.5 * (1 + math.erf(score / math.sqrt(2))) * 10 # *10 to get a value between -10 return (score, str(score), valuescale, str(valuescale)) + +# #---------------------------------------------- NOT WORKING, NEED TO LOOK AT +# # Image Reward Scoring with score passed to image - # ##test of image saver ## - - -# class EndlessNode_ImageSaver: +# class EndlessNode_ImageRewardAutoScore: # def __init__(self): - # self.output_dir = folder_paths.get_output_directory() - # self.type = "output" + # self.model = None # @classmethod # def INPUT_TYPES(cls): # return { # "required": { + # "model": ("STRING", {"multiline": False, "default": "ImageReward-v1.0"}), + # "prompt": ("STRING", {"multiline": True, "forceInput": True}), # "images": ("IMAGE",), - # "filename_prefix": ("STRING", {"default": "ComfyUI"}), - # "subfolder": ("STRING", {"default": None}), # Add subfolder input - # }, - # "hidden": { - # "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO" # }, # } - # RETURN_TYPES = () - # FUNCTION = "save_images" - + # RETURN_TYPES = ("FLOAT", "STRING", "FLOAT", "STRING", "IMAGE") + # RETURN_NAMES = ("SCORE_FLOAT", "SCORE_STRING", "VALUE_FLOAT", "VALUE_STRING", "TO_IMAGE") # OUTPUT_NODE = True - # CATEGORY = "Endless 🌌/IO" + # CATEGORY = "Endless 🌌/Scoring" - # def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None, subfolder=None): + # FUNCTION = "score_meta" - # # Replace illegal characters in the filename prefix with dashes - # filename_prefix = re.sub(r'[<>:"\/\\|?*]', '-', filename_prefix) + # def score_meta(self, model, prompt, images): + # if self.model is None: + # self.model = RM.load(model) - # # Get the current date in Y-m-d format - # today = datetime.datetime.now().strftime("%Y-%m-%d") - - # # If a custom subfolder is provided, use it; otherwise, use the date - # if subfolder is not None: - # full_output_folder = os.path.join(self.output_dir, subfolder) - # else: - # full_output_folder = os.path.join(self.output_dir, today) - - # # Create the subfolder if it doesn't exist - # os.makedirs(full_output_folder, exist_ok=True) - - # counter = self.get_next_number(full_output_folder) - - # results = list() + # # Scoring part + # score = 0.0 # for image in images: - # i = 255. * image.cpu().numpy() + # i = 255.0 * image.cpu().numpy() # img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + # score += self.model.score(prompt, [img]) + # score /= len(images) + # valuescale = 0.5 * (1 + math.erf(score / math.sqrt(2))) * 10 + + # # Metadata part + # extra_pnginfo = {"SCORE": str(score)} + + # # Returning both the score and the modified image + # return (score, str(score), valuescale, str(valuescale), images) + +# ______________________________________________________________________________________________________________________________________________________________ + # IMAGE SAVERS BLOCK # + +# ---------------------------------------------- +# Saver type one: saves IMAGE and JSON files, can specify separate folders for each, or one, or none, and use Python timestamps + + +class EndlessNode_ImageSaver: + def __init__(self): + self.output_dir = endless_paths.get_output_directory() + self.type = "output" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "filename_prefix": ("STRING", {"default": "ComfyUI"}), + "delimiter": ("STRING", {"default": "_"}), + "filename_number_padding": ("INT", {"default": 4, "min": 1, "max": 9, "step": 1}), + "filename_number_start": (["false", "true"],), + "img_folder": ("STRING", {"default": None}), + "json_folder": ("STRING", {"default": None}), + }, + "hidden": { + "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO" + }, + } + RETURN_TYPES = () + FUNCTION = "save_images" + OUTPUT_NODE = True + CATEGORY = "Endless 🌌/IO" + + def save_images(self, images, filename_prefix="ComfyUI", delimiter="_", + filename_number_padding=4, filename_number_start='false', + img_folder=None, json_folder=None, prompt=None, extra_pnginfo=None): + + # Replace illegal characters in the filename prefix with dashes + filename_prefix = re.sub(r'[<>:"\/\\|?*]', '-', filename_prefix) + + # Set IMG Extension + img_extension = '.png' + + counter = 1 + + results = list() + + for image in images: + i = 255. * image.cpu().numpy() + img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + + metadata = PngInfo() + if prompt is not None: + metadata.add_text("prompt", json.dumps(prompt)) + if extra_pnginfo is not None: + for x in extra_pnginfo: + metadata.add_text(x, json.dumps(extra_pnginfo[x])) + + img_file, json_file = self.generate_filenames(filename_prefix, delimiter, counter,filename_number_padding, filename_number_start,img_extension, img_folder, json_folder) + + try: + if img_extension == '.png': + img.save(img_file, pnginfo=metadata, compress_level=4) + elif img_extension == '.jpeg': + img.save(img_file, quality=100, optimize=True) + + with open(json_file, 'w', encoding='utf-8', newline='\n') as f: + if prompt is not None: + f.write(json.dumps(prompt, indent=4)) + + print(Fore.GREEN + f"+ File(s) saved to: {img_file}") + + results.append({ + "image_filename": os.path.basename(img_file), + "image_path": img_file, + "json_filename": os.path.basename(json_file), + "text_path": json_file, + "type": self.type + }) + + except OSError as e: + print(Fore.RED + " + Unable to save file: ", end='') + print({img_file}) + print(e) + except Exception as e: + print(Fore.RED + " + Unable to save file due to the following error: ", end='') + print(e) + + counter += 1 + + return {"ui": {"results": results}} + + def generate_filenames(self, filename_prefix, delimiter, counter, filename_number_padding, filename_number_start, img_extension, img_folder, json_folder): + if filename_number_start == 'true': + img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" + json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" + else: + img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" + json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" + + # Construct full paths for image and text files based on folders provided + + if img_folder: + img_folder = self.replace_date_time_placeholders(img_folder) + os.makedirs(img_folder, exist_ok=True) # Create the image folder if it doesn't exist + img_file = os.path.join(img_folder, img_file) + else: + img_file = os.path.join(self.output_dir, img_file) + + if json_folder: + json_folder = self.replace_date_time_placeholders(json_folder) + os.makedirs(json_folder, exist_ok=True) # Create the image folder if it doesn't exist + json_file = os.path.join(json_folder, json_file) + else: + json_file = os.path.join(os.path.dirname(img_file), json_file) - # metadata = PngInfo() - # if prompt is not None: - # metadata.add_text("prompt", json.dumps(prompt)) - # if extra_pnginfo is not None: - # for x in extra_pnginfo: - # metadata.add_text(x, json.dumps(extra_pnginfo[x])) - - # file = f"{counter:05}-c-{filename_prefix}.png" - # img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=4) - # results.append({ - # "filename": file, - # "subfolder": full_output_folder, - # "type": self.type - # }) - - # # Check if a user-specified folder for TEXT files is provided - # if subfolder is not None: - # # Create the full path for the TEXT file using the same name as the PNG - # text_file = os.path.join(subfolder, f"{counter:05}-c-{filename_prefix}.txt") - # else: - # # Use the same folder as the image if no custom subfolder is provided - # text_file = os.path.join(full_output_folder, f"{counter:05}-c-{filename_prefix}.txt") - - # # Save some example text content to the TEXT file (you can modify this) - # with open(text_file, 'w') as text: - # text.write("This is an example text file.") - - # counter += 1 - - # return {"ui": {"images": results}} - - # def get_next_number(self, directory): - # files = os.listdir(directory) - # highest_number = 0 - # for file in files: - # parts = file.split('-') - # try: - # num = int(parts[0]) - # if num > highest_number: - # highest_number = num - # except ValueError: - # # If it's not a number, skip this file - # continue - - # # Return the next number - # return highest_number + 1 + # Apply placeholders for date and time in filenames and folder + img_file = self.replace_date_time_placeholders(img_file) + json_file = self.replace_date_time_placeholders(json_file) -#-------------------------------------- -# CREDITS + + # Check if files with the same name exist, increment counter if necessary + while os.path.exists(img_file) or os.path.exists(json_file): + counter += 1 + if filename_number_start == 'true': + img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" + json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" + else: + img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" + json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" + + # Construct full paths for image and text files based on folders provided + + if img_folder: + img_folder = self.replace_date_time_placeholders(img_folder) + os.makedirs(img_folder, exist_ok=True) # Create the image folder if it doesn't exist + img_file = os.path.join(img_folder, img_file) + else: + img_file = os.path.join(self.output_dir, img_file) + + if json_folder: + json_folder = self.replace_date_time_placeholders(json_folder) + os.makedirs(json_folder, exist_ok=True) # Create the image folder if it doesn't exist + json_file = os.path.join(json_folder, json_file) + else: + json_file = os.path.join(os.path.dirname(img_file), json_file) + + # Apply placeholders for date and time in filenames and folder + img_file = self.replace_date_time_placeholders(img_file) + json_file = self.replace_date_time_placeholders(json_file) + + return img_file, json_file + + def replace_date_time_placeholders(self, filename): + # Replace date and time placeholders with actual date and time strings + now = datetime.datetime.now() + placeholders = { + '%Y': now.strftime('%Y'), # Year with century as a decimal number + '%y': now.strftime('%y'), # Year without century as a zero-padded decimal number + '%m': now.strftime('%m'), # Month as a zero-padded decimal number + '%d': now.strftime('%d'), # Day of the month as a zero-padded decimal number + '%H': now.strftime('%H'), # Hour (24-hour clock) as a zero-padded decimal number + '%M': now.strftime('%M'), # Minute as a zero-padded decimal number + '%S': now.strftime('%S'), # Second as a zero-padded decimal number + } + + for placeholder, replacement in placeholders.items(): + filename = filename.replace(placeholder, replacement) + + return filename +# ______________________________________________________________________________________________________________________________________________________________ + # CONVERTER NODES BLOCK # + +# ---------------------------------------------- +# Float value to Integer + +class EndlessNode_FloattoInt: + CATEGORY = "Endless 🌌/Converters" + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"FloatValue": ("FLOAT", {"default": 0.0},)} + } + + RETURN_TYPES = ("INT",) + FUNCTION = "inputfloat" + + def inputfloat(self, FloatValue): + return int(FloatValue,) + + +# ---------------------------------------------- +# Float value to Number, passes minimum one decimal +# There is no real "Number" function in Python, this is here so that nodes that need a NUMBER can take the FLOAT value + +class EndlessNode_FloattoNum: + CATEGORY = "Endless 🌌/Converters" + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"FloatValue": ("FLOAT", {"default": 0.0,}),} + } + + RETURN_TYPES = ("NUMBER",) + FUNCTION = "inputfloat" + + def inputfloat(self, FloatValue): + return float(FloatValue,) + + +# ---------------------------------------------- +# Float value to String, passes one to eight decimals + +class EndlessNode_FloattoString: + CATEGORY = "Endless 🌌/Converters" + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"FloatValue": ("FLOAT", {"default": 0.0,},)} + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "inputfloat" + + def inputfloat(self, FloatValue): + if isinstance(FloatValue, float): + formatted_value = f'{FloatValue:.1f}' if FloatValue.is_integer() else f'{FloatValue:.8f}' + return str((formatted_value),) + elif isinstance(FloatValue, int): + #Convert integer to float and then format + formatted_value = f'{float(FloatValue):.1f}' + return str((formatted_value),) + else: + try: + #Try to convert to float, and then format + float_value = float(FloatValue) + formatted_value = f'{float_value:.1f}' if float_value.is_integer() else f'{float_value:.8f}' + return str((formatted_value),) + except ValueError: + return ("Not a valid float",) + + +# # ---------------------------------------------- NEED TO FIX +# # Float value to X + +# class EndlessNode_FloattoX: + # CATEGORY = "Endless 🌌/Converters" + + # def __init__(self): + # pass + + # @classmethod + # def INPUT_TYPES(cls): + # return { + # "required": { + # "FloatValue": ("FLOAT", {"default": 0, "min": -8675309362436420, "max": 8675309362436420}), + # }, + # } + + # RETURN_TYPES = ("INT", "NUMBER", "STR") + # FUNCTION = "return_constant_number" + + # def return_constant_number(self, FloatValue): + + # # Return number + # return (int(FloatValue), (FloatValue), str(FloatValue)) + + +# ---------------------------------------------- +# Integer to Float + +class EndlessNode_InttoFloat: + CATEGORY = "Endless 🌌/Converters" + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"IntegerValue": ("INT",),} + } + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "inputint" + + def inputint(self, IntegerValue): + return int((IntegerValue),) + + +# ---------------------------------------------- +# Integer to Number + +class EndlessNode_InttoNum: + CATEGORY = "Endless 🌌/Converters" + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"IntegerValue": ("INT",),} + } + + RETURN_TYPES = ("NUMBER",) + FUNCTION = "inputint" + + def inputint(self, IntegerValue): + return int((IntegerValue),) + + +# ---------------------------------------------- +# Integer to String + +class EndlessNode_InttoString: + CATEGORY = "Endless 🌌/Converters" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"IntegerValue": ("INT",{"default": 0,},)} + } + RETURN_TYPES = ("STRING",) + FUNCTION = "inputint" + + def inputint(self, IntegerValue): + return str((IntegerValue),) + +# # ---------------------------------------------- NOT ORKING, NEED TO FIX +# # Integer value to X + +# class EndlessNode_InttoX: + # CATEGORY = "Endless 🌌/Converters" + + # def __init__(self): + # pass + + # @classmethod + # def INPUT_TYPES(cls): + # return { + # "required": { + # "number": ("INT", {"default": 0, "min": -8675309, "max": 8675309}), + # }, + # } + + # RETURN_TYPES = ("FLOAT", "NUMBER", "STR") + # FUNCTION = "return_constant_number" + + # def return_constant_number(self, number): + + # # Return number + # return (float(number), float(number), str(number)) + + +# ---------------------------------------------- +# Number to Float + +class EndlessNode_NumtoFloat: + CATEGORY = "Endless 🌌/Converters" + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"NumberValue": ("NUMBER",),} + } + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "inputnum" + + def inputnum(self, NumberValue): + return float((NumberValue),) + + +# ---------------------------------------------- +# Number to Integer + +class EndlessNode_NumtoInt: + CATEGORY = "Endless 🌌/Converters" + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"NumberValue": ("NUMBER",),} + } + RETURN_TYPES = ("INT",) + FUNCTION = "inputnum" + + def inputnum(self, NumberValue): + return int((NumberValue),) + +# ---------------------------------------------- +# Number value to String + +class EndlessNode_NumtoString: + def __init__(self): + pass + CATEGORY = "Endless 🌌/Converters" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"NumberValue": ("NUMBER",),} + } + RETURN_TYPES = ("STRING",) + FUNCTION = "inputnum" + + def inputnum(self, NumberValue): + return str((NumberValue),) + +# NEED TO FIX STILL +# class EndlessNode_NumtoX: + # CATEGORY = "Endless 🌌/Converters" + + # def __init__(self): + # pass + + # @classmethod + # def INPUT_TYPES(cls): + # return { + # "required": { + # "number": ("FLOAT", {"default": 0, "min": -8675309362436420, "max": 8675309362436420}), + # }, + # } + + # RETURN_TYPES = ("FLOAT", "INT", "STR") + # FUNCTION = "return_constant_number" + + # def return_constant_number(self, number): + + # # Return number + # return (float(number), int(number), str(number)) + + +#______________________________________________________________________________________________________________________________________________________________ +# CREDITS # + # -# Comfyroll Custom Nodes for the overall node code layout, coding snippets, and inspiration for the text input and number switches +# Comfyroll Custom Nodes for the initial node code layout, coding snippets, and inspiration for the text input and number switches # # https://github.com/RockOfFire/ComfyUI_Comfyroll_CustomNode # @@ -608,14 +1068,28 @@ class EndlessNode_ImageReward: # # https://github.com/comfyanonymous/ComfyUI # -# ComfyUI-Strimmlarns-Aesthetic-Score for the original coding for Aesthetic Scoring Type One +# ComfyUI-Strimmlarns-Aesthetic-Score for the original coding for Aesthetic Scoring # # https://github.com/strimmlarn/ComfyUI-Strimmlarns-Aesthetic-Score # # The scorer uses the MLP class code from Christoph Schuhmann # #https://github.com/christophschuhmann/improved-aesthetic-predictor -#[Zane A's ComfyUI-ImageReward](https://github.com/ZaneA/ComfyUI-ImageReward) for the original coding for the Umagr Reward nodee +#[Zane A's ComfyUI-ImageReward](https://github.com/ZaneA/ComfyUI-ImageReward) for the original coding for the Image Reward node # #Zane's node in turn uses [ImageReward](https://github.com/THUDM/ImageReward) -#-------------------------------------- \ No newline at end of file +# +# +#Mikey nodes to grab code snippet to pass scoring metadata to image +# +#https://github.com/bash-j/mikey_nodes + +# Took some base code from the WAS save image node to repurpose it +# +#https://github.com/WASasquatch/was-node-suite-comfyui +#-------------------------------------- + + +######################################################################################## CELLAR DWELLERS + + diff --git a/img/converters.png b/img/converters.png new file mode 100644 index 0000000..e859a67 Binary files /dev/null and b/img/converters.png differ