Initiliazing Runware ComfyUI Nodes Alpha 0.8
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
class apiManager:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"API Key": ("STRING", {
|
||||
"tooltip": "Set Your Runware API Key To Start Using Runware Image Inference Services.\n\n(eg: BcVr1JVQM8FGzrRwb3GsbCq5ww1QwabV)\n\nIf You Don't Have One, You Can Get It From: https://my.runware.ai/keys",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "API Managers is a Runware Utility That helps you set your API Key From The ComfyUI Interface Without having to adjust the env file locally."
|
||||
CATEGORY = "Runware"
|
||||
RETURN_TYPES = ()
|
||||
OUTPUT_NODE = True
|
||||
@@ -0,0 +1,86 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class bgremoval:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Image": ("IMAGE", {
|
||||
"tooltip": "Specifies the input image to be processed."
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"Post Process Mask": ("BOOLEAN", {
|
||||
"tooltip": "Controls whether the mask should undergo additional post-processing. This step can improve the accuracy and quality of the background removal mask.",
|
||||
"default": False,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"Return Only Mask": ("BOOLEAN", {
|
||||
"tooltip": "Whether to return only the mask. The mask is the opposite of the image background removal.",
|
||||
"default": False,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"Alpha Matting": ("BOOLEAN", {
|
||||
"tooltip": "Alpha matting is a post-processing technique that enhances the quality of the output by refining the edges of the foreground object.",
|
||||
"default": False,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"Alpha Matting Foreground Threshold": ("INT", {
|
||||
"tooltip": "Threshold value used in alpha matting to distinguish the foreground from the background. Adjusting this parameter affects the sharpness and accuracy of the foreground object edges.",
|
||||
"default": 240,
|
||||
"min": 1,
|
||||
"max": 255,
|
||||
}),
|
||||
"Alpha Matting Background Threshold": ("INT", {
|
||||
"tooltip": "Threshold value used in alpha matting to refine the background areas. It influences how aggressively the algorithm removes the background while preserving image details. The higher the value, the more computation is needed and therefore the more expensive the operation is.",
|
||||
"default": 10,
|
||||
"min": 1,
|
||||
"max": 255,
|
||||
}),
|
||||
"Alpha Matting Erode Size": ("INT", {
|
||||
"tooltip": "Specifies the size of the erosion operation used in alpha matting. Erosion helps in smoothing the edges of the foreground object for a cleaner removal of the background.",
|
||||
"default": 10,
|
||||
"min": 1,
|
||||
"max": 255,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
DESCRIPTION = "Remove backgrounds from images effortlessly using Runware's low-cost image editing Inference."
|
||||
FUNCTION = "rembg"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def rembg(self, **kwargs):
|
||||
image = kwargs.get("Image")
|
||||
postProcessMask = kwargs.get("Post Process Mask", False)
|
||||
returnOnlyMask = kwargs.get("Return Only Mask", False)
|
||||
alphaMatting = kwargs.get("Alpha Matting", False)
|
||||
alphaMattingForegroundThreshold = kwargs.get("Alpha Matting Foreground Threshold", 240)
|
||||
alphaMattingBackgroundThreshold = kwargs.get("Alpha Matting Background Threshold", 10)
|
||||
alphaMattingErodeSize = kwargs.get("Alpha Matting Erode Size", 10)
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageBackgroundRemoval",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"inputImage": rwUtils.convertTensor2IMG(image),
|
||||
"postProcessMask": postProcessMask,
|
||||
"returnOnlyMask": returnOnlyMask,
|
||||
"alphaMatting": alphaMatting,
|
||||
"outputFormat": "WEBP",
|
||||
"outputType": "base64Data",
|
||||
}
|
||||
]
|
||||
|
||||
if(alphaMatting):
|
||||
genConfig[0]["alphaMattingForegroundThreshold"] = alphaMattingForegroundThreshold
|
||||
genConfig[0]["alphaMattingBackgroundThreshold"] = alphaMattingBackgroundThreshold
|
||||
genConfig[0]["alphaMattingErodeSize"] = alphaMattingErodeSize
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return images
|
||||
@@ -0,0 +1,73 @@
|
||||
from ..utils import runwareUtils as rwUtils
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
@routes.post('/setAPIKey')
|
||||
async def setAPIKey(reqPayload):
|
||||
reqData = await reqPayload.json()
|
||||
apiKey = reqData.get('apiKey', None)
|
||||
if(apiKey is None or apiKey == "" or len(apiKey) < 30):
|
||||
return web.json_response({'success': False, 'error': 'API Key Not Provided!'})
|
||||
try:
|
||||
apiKey = apiKey.strip()
|
||||
rwUtils.setAPIKey(apiKey)
|
||||
except Exception as e:
|
||||
return web.json_response({'success': False, 'error': str(e)})
|
||||
return web.json_response({'success': True})
|
||||
|
||||
@routes.post('/promptEnhance')
|
||||
async def promptEnhance(reqPayload):
|
||||
reqData = await reqPayload.json()
|
||||
userPrompt = reqData.get('userPrompt')
|
||||
utilityConfig = [{
|
||||
"taskType": "promptEnhance",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"prompt": userPrompt,
|
||||
"promptMaxLength": 300,
|
||||
"promptVersions": 1,
|
||||
}]
|
||||
|
||||
try:
|
||||
utilityResults = rwUtils.inferenecRequest(utilityConfig)
|
||||
except Exception as e:
|
||||
return web.json_response({'success': False, 'error': str(e)})
|
||||
enhancedPrompt = utilityResults["data"][0]["text"]
|
||||
return web.json_response({'success': True, 'enhancedPrompt': enhancedPrompt})
|
||||
|
||||
@routes.post('/modelSearch')
|
||||
async def modelSearch(reqPayload):
|
||||
reqData = await reqPayload.json()
|
||||
modelQuery = reqData.get('modelQuery', "")
|
||||
modelArch = reqData.get('modelArch', "all")
|
||||
modelCategory = reqData.get('modelCat', "checkpoint")
|
||||
modelType = reqData.get('modelType', "base")
|
||||
controlNetConditioning = reqData.get('condtioning', "all")
|
||||
|
||||
utilityConfig = [{
|
||||
"taskType": "modelSearch",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"category": modelCategory,
|
||||
"limit": 15,
|
||||
}]
|
||||
|
||||
if(modelCategory != "controlnet" and modelCategory != "lora"):
|
||||
utilityConfig[0]["type"] = modelType
|
||||
elif(modelCategory == "controlnet" and controlNetConditioning != "all"):
|
||||
utilityConfig[0]["conditioning"] = controlNetConditioning
|
||||
|
||||
if(modelArch != "all"):
|
||||
utilityConfig[0]["architecture"] = modelArch
|
||||
if(modelQuery != ""):
|
||||
utilityConfig[0]["search"] = modelQuery
|
||||
|
||||
try:
|
||||
utilityResults = rwUtils.inferenecRequest(utilityConfig)
|
||||
except Exception as e:
|
||||
return web.json_response({'success': False, 'error': str(e)})
|
||||
totalResults = utilityResults["data"][0]["totalResults"]
|
||||
if(totalResults < 1):
|
||||
return web.json_response({'success': False, 'error': 'No Results Found!'})
|
||||
modelList = utilityResults["data"][0]["results"]
|
||||
return web.json_response({'success': True, 'modelList': modelList})
|
||||
@@ -0,0 +1,155 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class controlNet:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Guide Image": ("IMAGE", {
|
||||
"tooltip": "Specifies the preprocessed image to be used as guide to control the image generation process."
|
||||
}),
|
||||
"ControlNet Search": ("STRING", {
|
||||
"tooltip": "Searchg For Specific ControlNet By Name Or Civit AIR Code (eg: Canny).",
|
||||
}),
|
||||
"Model Architecture": ([
|
||||
"All",
|
||||
"FLUX.1-Schnell",
|
||||
"FLUX.1-Dev",
|
||||
"Pony",
|
||||
"SD 1.5",
|
||||
"SD 1.5 Hyper",
|
||||
"SD 1.5 LCM",
|
||||
"SD 3",
|
||||
"SDXL 1.0",
|
||||
"SDXL 1.0 LCM",
|
||||
"SDXL Distilled",
|
||||
"SDXL Hyper",
|
||||
"SDXL Lightning",
|
||||
"SDXL Turbo",
|
||||
], {
|
||||
"tooltip": "Choose ControlNet Model Architecture To Filter Results.",
|
||||
"default": "All",
|
||||
}),
|
||||
"ControlNetType": ([
|
||||
"All",
|
||||
"Canny",
|
||||
"Depth",
|
||||
"MLSD",
|
||||
"Normal BAE",
|
||||
"Open Pose",
|
||||
"Tile",
|
||||
"Seg",
|
||||
"Line Art",
|
||||
"Line Art Anime",
|
||||
"Shuffle",
|
||||
"Scribble",
|
||||
"Soft Edge",
|
||||
], {
|
||||
"tooltip": "Choose ControlNet Type To Filter Results.",
|
||||
"default": "All",
|
||||
}),
|
||||
"ControlNetList": ([
|
||||
"civitai:38784@44716 (SD1.5 Canny)",
|
||||
"civitai:38784@44876 (SD1.5 Inpaint)",
|
||||
"civitai:38784@44877 (SD1.5 Lineart)",
|
||||
"civitai:38784@44795 (SD1.5 MLSD)",
|
||||
"civitai:38784@44774 (SD1.5 NormalBAE)",
|
||||
"runware:20@1 (SDXL Canny)",
|
||||
"runware:25@1 (Flux Dev Canny)",
|
||||
"runware:26@1 (Flux Dev Tile)",
|
||||
"runware:28@1 (Flux Dev Blur)",
|
||||
"runware:29@1 (Flux Dev OpenPose)",
|
||||
"runware:30@1 (Flux Dev Gray)",
|
||||
"runware:31@1 (Flux Dev Low Quality)",
|
||||
], {
|
||||
"tooltip": "ControlNet Results Will Show UP Here So You Could Choose From.",
|
||||
"default": "civitai:38784@44716 (SD1.5 Canny)",
|
||||
}),
|
||||
"startStep": ("INT", {
|
||||
"tooltip": "Represents the step number at which the ControlNet model starts to control the inference process. (Enter -1 To Disable)",
|
||||
"min": -1,
|
||||
"max": 99,
|
||||
"default": -1,
|
||||
}),
|
||||
"startStepPercentage": ("INT", {
|
||||
"tooltip": "Represents the percentage of steps at which the ControlNet model starts to control the inference process. (Enter -1 To Disable)",
|
||||
"min": -1,
|
||||
"max": 99,
|
||||
"default": 0,
|
||||
}),
|
||||
"endStep": ("INT", {
|
||||
"tooltip": "Represents the step number at which the ControlNet preprocessor ends to control the inference process. (Enter -1 To Disable)",
|
||||
"min": -1,
|
||||
"max": 100,
|
||||
"default": -1,
|
||||
}),
|
||||
"endStepPercentage": ("INT", {
|
||||
"tooltip": "Represents the percentage of steps at which the ControlNet model ends to control the inference process. (Enter -1 To Disable)",
|
||||
"min": -1,
|
||||
"max": 100,
|
||||
"default": 80,
|
||||
}),
|
||||
"Control Mode": (["prompt", "controlnet", "balanced"], {
|
||||
"tooltip": "Choose Control Mode To Control The Inference Process.",
|
||||
"default": "balanced",
|
||||
}),
|
||||
"weight": ("FLOAT", {
|
||||
"tooltip": "Represents the weight (strength) of the ControlNet model in the image.",
|
||||
"default": 1.0,
|
||||
"min": 0,
|
||||
"max": 1.0,
|
||||
"step": 0.1,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Directly Search and Configure ControlNet Guidance to Connect It With Runware Image Inference Nodes In ComfyUI."
|
||||
FUNCTION = "controlNet"
|
||||
RETURN_TYPES = ("RUNWARECONTROLNET",)
|
||||
RETURN_NAMES = ("Runware ControlNet",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, ControlNetList, startStep, startStepPercentage, endStep, endStepPercentage):
|
||||
if((startStep == -1 and startStepPercentage == -1) or (startStep != -1 and startStepPercentage != -1)):
|
||||
raise Exception("Error: Please Provide Either Start Step or Start Step Percentage!")
|
||||
if((endStep == -1 and endStepPercentage == -1) or (endStep != -1 and endStepPercentage != -1)):
|
||||
raise Exception("Error: Please Provide Either End Step or End Step Percentage!")
|
||||
if((startStep != -1 and endStep != -1) and (startStep > endStep)):
|
||||
raise Exception("Error: Start Step Cannot Be Greater Than End Step!")
|
||||
elif((startStep != -1 and endStep != -1) and (startStep == endStep)):
|
||||
raise Exception("Error: Start Step Cannot Be Equal To End Step!")
|
||||
if((startStepPercentage != -1 and endStepPercentage != -1) and (startStepPercentage > endStepPercentage)):
|
||||
raise Exception("Error: Start Step Percentage Cannot Be Greater Than End Step Percentage!")
|
||||
elif((startStepPercentage != -1 and endStepPercentage != -1) and (startStepPercentage == endStepPercentage)):
|
||||
raise Exception("Error: Start Step Percentage Cannot Be Equal To End Step Percentage!")
|
||||
return True
|
||||
|
||||
def controlNet(self, **kwargs):
|
||||
guideImage = kwargs.get("Guide Image")
|
||||
currentModel = kwargs.get("ControlNetList")
|
||||
controlNetAIRCode = currentModel.split(" ")[0]
|
||||
startStep = kwargs.get("startStep")
|
||||
startStepPercentage = kwargs.get("startStepPercentage")
|
||||
endStep = kwargs.get("endStep")
|
||||
endStepPercentage = kwargs.get("endStepPercentage")
|
||||
controlMode = kwargs.get("Control Mode")
|
||||
weight = kwargs.get("weight")
|
||||
|
||||
controlNetGuideOBJ = [{
|
||||
"model": controlNetAIRCode,
|
||||
"guideImage": rwUtils.convertTensor2IMG(guideImage),
|
||||
"weight": round(weight, 2),
|
||||
"controlMode": controlMode,
|
||||
}]
|
||||
|
||||
if(startStep != -1):
|
||||
controlNetGuideOBJ[0]["startStep"] = startStep
|
||||
elif(startStepPercentage != -1):
|
||||
controlNetGuideOBJ[0]["startStepPercentage"] = startStepPercentage
|
||||
if(endStep != -1):
|
||||
controlNetGuideOBJ[0]["endStep"] = endStep
|
||||
elif(endStepPercentage != -1):
|
||||
controlNetGuideOBJ[0]["endStepPercentage"] = endStepPercentage
|
||||
|
||||
return (controlNetGuideOBJ,)
|
||||
@@ -0,0 +1,37 @@
|
||||
class controlNetCombine:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"ControlNet 1": ("RUNWARECONTROLNET", {
|
||||
"tooltip": "Connect a Runware ControlNet Node.",
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"ControlNet 2": ("RUNWARECONTROLNET", {
|
||||
"tooltip": "Connect a Runware ControlNet Node.",
|
||||
}),
|
||||
"ControlNet 3": ("RUNWARECONTROLNET", {
|
||||
"tooltip": "Connect a Runware ControlNet Node.",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Combine One or More ControlNet's To Guide Image Generation Process in Runware Image Inference."
|
||||
FUNCTION = "controlNetCombine"
|
||||
RETURN_TYPES = ("RUNWARECONTROLNET",)
|
||||
RETURN_NAMES = ("Runware ControlNet's",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def controlNetCombine(self, **kwargs):
|
||||
controlNet1 = kwargs.get("ControlNet 1")
|
||||
controlNet2 = kwargs.get("ControlNet 2", None)
|
||||
controlNet3 = kwargs.get("ControlNet 3", None)
|
||||
|
||||
controlNetObjectArray = controlNet1
|
||||
if(controlNet2 is not None):
|
||||
controlNetObjectArray = controlNetObjectArray + controlNet2
|
||||
if(controlNet3 is not None):
|
||||
controlNetObjectArray = controlNetObjectArray + controlNet3
|
||||
print(controlNetObjectArray)
|
||||
return (controlNetObjectArray,)
|
||||
@@ -0,0 +1,118 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class controlNetPreprocessor:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Image": ("IMAGE", {
|
||||
"tooltip": "Specifies the input image to be preprocessed to generate a guide image. This guide image will be used as a reference for image generation processes, guiding the AI to generate images that are more aligned with the input image."
|
||||
}),
|
||||
"PreProcessor Type": ([
|
||||
"Canny",
|
||||
"Depth",
|
||||
"MLSD",
|
||||
"NormalBAE",
|
||||
"OpenPose",
|
||||
"Tile",
|
||||
"Seg",
|
||||
"LineArt",
|
||||
"LineArtAnime",
|
||||
"Shuffle",
|
||||
"Scribble",
|
||||
"SoftEdge",
|
||||
], {
|
||||
"tooltip": "Choose Preprocessor Type To Generate The Guide Image.",
|
||||
"default": "Canny",
|
||||
}),
|
||||
"Include Hands And Faces [ OpenPose ]": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Include the hands and face in the pose outline when using the OpenPose preprocessor.",
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"Canny Low Threshold": ("INT", {
|
||||
"tooltip": "Defines the lower threshold when using the Canny edge detection preprocessor.",
|
||||
"default": 100,
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
}),
|
||||
"Canny High Threshold": ("INT", {
|
||||
"tooltip": "Defines the high threshold when using the Canny edge detection preprocessor.",
|
||||
"default": 200,
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
}),
|
||||
"Image Resizing": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "It Allows Image Resizing if the dimension of the image is higher than a specific value it will be resized.",
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"dimensions": ([
|
||||
"Square (512x512)", "Square HD (1024x1024)", "Portrait 3:4 (768x1024)",
|
||||
"Portrait 9:16 (576x1024)", "Landscape 4:3 (1024x768)",
|
||||
"Landscape 16:9 (1024x576)", "Custom"
|
||||
], {
|
||||
"default": "Square HD (1024x1024)",
|
||||
"tooltip": "Resize the dimensions of the generated Guide image by specifying its width and height in pixels, or select from the predefined options. Image dimensions must be multiples of 64 (e.g., 512x512, 1024x768).",
|
||||
}),
|
||||
"width": ("INT", {
|
||||
"tooltip": "If the Image height dimension is larger than this value, the output image will be resized to the specified height.",
|
||||
"default": 1024,
|
||||
"min": 512,
|
||||
"max": 2048,
|
||||
"step": 64,
|
||||
}),
|
||||
"height": ("INT", {
|
||||
"tooltip": "If the Image width dimension is larger than this value, the output image will be resized to the specified width.",
|
||||
"default": 1024,
|
||||
"min": 512,
|
||||
"max": 2048,
|
||||
"step": 64,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "ControlNet offers advanced capabilities for precise image processing through the use of guide images in specific formats, known as preprocessed images. This powerful tool enhances the control and customization of image generation, enabling users to achieve desired artistic styles and detailed adjustments effectively."
|
||||
FUNCTION = "controlNetPreProcess"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("GUIDE IMAGE",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def controlNetPreProcess(self, **kwargs):
|
||||
image = kwargs.get("Image")
|
||||
preProcessorType = kwargs.get("PreProcessor Type")
|
||||
includeHandsAndFaces = kwargs.get("Include Hands And Faces [ OpenPose ]")
|
||||
cannyLowThreshold = kwargs.get("Canny Low Threshold")
|
||||
cannyHighThreshold = kwargs.get("Canny High Threshold")
|
||||
imageResizing = kwargs.get("Image Resizing")
|
||||
width = kwargs.get("width")
|
||||
height = kwargs.get("height")
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageControlNetPreProcess",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"inputImage": rwUtils.convertTensor2IMG(image),
|
||||
"preProcessorType": preProcessorType,
|
||||
"outputFormat": "WEBP",
|
||||
"outputType": "base64Data",
|
||||
}
|
||||
]
|
||||
|
||||
# For Debugging Purposes Only
|
||||
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
|
||||
|
||||
if(imageResizing):
|
||||
genConfig[0]["width"] = width
|
||||
genConfig[0]["height"] = height
|
||||
if(preProcessorType == "OpenPose" and includeHandsAndFaces):
|
||||
genConfig[0]["includeHandsAndFaceOpenPose"] = includeHandsAndFaces
|
||||
elif(preProcessorType == "Canny"):
|
||||
genConfig[0]["lowThresholdCanny"] = cannyLowThreshold
|
||||
genConfig[0]["highThresholdCanny"] = cannyHighThreshold
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return images
|
||||
@@ -0,0 +1,42 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class imageCaptioning:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Image": ("IMAGE", {
|
||||
"tooltip": "Specifies the input image to be processed."
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"Image Description": ("STRING", {
|
||||
"multiline": True,
|
||||
"placeholder": "Don't Write Anything Here, The Image Description Will Be Generated Automatically.",
|
||||
}),
|
||||
},
|
||||
"hidden": { "node_id": "UNIQUE_ID" }
|
||||
}
|
||||
|
||||
DESCRIPTION = "Image to text, also known as image captioning, allows you to obtain descriptive text prompts based on uploaded or previously generated images. This process is instrumental in generating textual descriptions that can be used to create additional images or provide detailed insights into visual content."
|
||||
FUNCTION = "imageCaptioning"
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("IMAGE PROMPT",)
|
||||
CATEGORY = "Runware"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def imageCaptioning(self, **kwargs):
|
||||
print(kwargs)
|
||||
image = kwargs.get("Image")
|
||||
kwargs.set("Image Description", "Don't Write Anything Here, The Image Description Will Be Generated Automatically.")
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageCaption",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"inputImage": rwUtils.convertTensor2IMG(image)
|
||||
}
|
||||
]
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
genText = genResult["data"][0]["text"]
|
||||
return (genText, )
|
||||
@@ -0,0 +1,211 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class txt2img:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Model": ("RUNWAREMODEL", {
|
||||
"tooltip": "Connect a Runware Model From Runware Model Node.",
|
||||
}),
|
||||
"positivePrompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"placeholder": "Positive Prompt: a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides positive guidance for the task. This parameter is essential to shape the desired results.\n\nYou Can Press (Ctrl + Alt + E) To Enhance The Prompt!",
|
||||
"tooltip": "Positive Prompt: a text instruction to guide the model on generating the image. You Can Also Press (Ctrl + Alt + E) To Enhance The Prompt!"
|
||||
}),
|
||||
"negativePrompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"placeholder": "Negative Prompt: a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides negative guidance for the task. This parameter helps to avoid certain undesired results.",
|
||||
"tooltip": "Negative Prompt: a text instruction to guide the model on generating the image."
|
||||
}),
|
||||
"Prompt Weighting": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Prompt weighting allows you to adjust how strongly different parts of your prompt influence the generated image.",
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"dimensions": ([
|
||||
"Square (512x512)", "Square HD (1024x1024)", "Portrait 3:4 (768x1024)",
|
||||
"Portrait 9:16 (576x1024)", "Landscape 4:3 (1024x768)",
|
||||
"Landscape 16:9 (1024x576)", "Custom"
|
||||
], {
|
||||
"default": "Square (512x512)",
|
||||
"tooltip": "Adjust the dimensions of the generated image by specifying its width and height in pixels, or select from the predefined options. Image dimensions must be multiples of 64 (e.g., 512x512, 1024x768).",
|
||||
}),
|
||||
"width": ("INT", {
|
||||
"tooltip": "The Width of the image in pixels.",
|
||||
"default": 512,
|
||||
"min": 512,
|
||||
"max": 2048,
|
||||
"step": 64,
|
||||
}),
|
||||
"height": ("INT", {
|
||||
"tooltip": "The Height of the image in pixels.",
|
||||
"default": 512,
|
||||
"min": 512,
|
||||
"max": 2048,
|
||||
"step": 64,
|
||||
}),
|
||||
"steps": ("INT", {
|
||||
"tooltip": "Guidance scale represents how closely the images will resemble the prompt or how much freedom the AI model has. Higher values are closer to the prompt. Low values may reduce the quality of the results.",
|
||||
"default": 4,
|
||||
"min": 1,
|
||||
"max": 100,
|
||||
}),
|
||||
"scheduler": ([
|
||||
"Default", "DDIMScheduler", "DDIMInverseScheduler", "DDPMScheduler", "DEISMultistepScheduler",
|
||||
"DPMSolverSinglestepScheduler", "DPMSolverMultistepScheduler", "DPMSolverMultistepInverse", "DPMSolverSDEScheduler", "HeunDiscreteScheduler", "KDPM2DiscreteScheduler", "KDPM2AncestralDiscreteScheduler", "KarrasVeScheduler", "LMSDiscreteScheduler", "PNDMScheduler", "ScoreSdeVeScheduler", "ScoreSdeVpScheduler", "IPNDMScheduler", "EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "EDMEulerScheduler", "FlowMatchEulerDiscreteScheduler", "VQDiffusionScheduler", "UniPCMultistepScheduler", "RePaintScheduler", "DPM++ 2M Karras", "DPM++ 2M SDE Karras", "DPM++ 2M SDE", "DPM++ SDE Karras", "DPM++ SDE", "LCMScheduler", "EDMDPMSolverMultistepScheduler", "TCDScheduler"
|
||||
], {
|
||||
"tooltip": "An scheduler is a component that manages the inference process. Different schedulers can be used to achieve different results like more detailed images, faster inference, or more accurate results.",
|
||||
"default": "Default",
|
||||
}),
|
||||
"cfgScale": ("FLOAT", {
|
||||
"tooltip": "The scale of the image generation. The higher the scale, the more detailed the image will be.",
|
||||
"default": 6.5,
|
||||
"min": 1.0,
|
||||
"max": 30.0,
|
||||
"step": 0.5,
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"tooltip": "A value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value.",
|
||||
"default": rwUtils.genRandSeed(),
|
||||
"min": 1,
|
||||
"max": 9223372036854776000,
|
||||
}),
|
||||
"clipSkip": ("INT", {
|
||||
"tooltip": "Enables skipping layers of the CLIP embedding process, leading to quicker and more varied image generation.",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 2,
|
||||
}),
|
||||
"strength": ("FLOAT", {
|
||||
"tooltip": "When doing Image-to-Image or Inpainting, this parameter is used to determine the influence of the seedImage image in the generated output. A lower value results in more influence from the original image, while a higher value allows more creative deviation.",
|
||||
"default": 0.80,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.05,
|
||||
}),
|
||||
"Mask Margin": ("BOOLEAN", {
|
||||
"tooltip": "Enables Or Disables Mask Margin Feature.",
|
||||
"default": False,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"maskMargin": ("INT", {
|
||||
"tooltip": "Adds extra context pixels around the masked region during inpainting. When this parameter is present, the model will zoom into the masked area, considering these additional pixels to create more coherent and well-integrated details.",
|
||||
"default": 32,
|
||||
"min": 32,
|
||||
"max": 128,
|
||||
}),
|
||||
"batchSize": ("INT", {
|
||||
"tooltip": "The number of images to generate in a single request.",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 10,
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"Lora": ("RUNWARELORA", {
|
||||
"tooltip": "Connect a Runware Lora From Lora Search Node Or Lora Combine For Multiple Lora's Together.",
|
||||
}),
|
||||
"ControlNet": ("RUNWARECONTROLNET", {
|
||||
"tooltip": "Connect a Runware ControlNet Guidance Node to help the model generate images that align with the desired structure.",
|
||||
}),
|
||||
"Refiner": ("RUNWAREREFINER", {
|
||||
"tooltip": "Connect a Runware Refiner Node to help create higher quality image outputs by incorporating specialized models designed to enhance image details and overall coherence.",
|
||||
}),
|
||||
"seedImage": ("IMAGE", {
|
||||
"tooltip": "Specifies the seed image to be used for the diffusion process, when doing Image-to-Image, Inpainting or Outpainting, this parameter is required.",
|
||||
}),
|
||||
"maskImage": ("MASK", {
|
||||
"tooltip": "Specifies the mask image to be used for the inpainting process, when doing Inpainting, this parameter is required.",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, positivePrompt, negativePrompt):
|
||||
if (positivePrompt is None or len(positivePrompt) < 3 or len(positivePrompt) > 2000):
|
||||
raise Exception("Positive Prompt Must Be Between 3 And 2000 characters!")
|
||||
if (negativePrompt is not None and negativePrompt != "" and (len(negativePrompt) < 3 or len(negativePrompt) > 2000)):
|
||||
raise Exception("Negative Prompt Must Be Between 3 And 2000 characters!")
|
||||
return True
|
||||
|
||||
DESCRIPTION = "Generates Images Lightning Fast With Runware Image Inference Sonic Engine."
|
||||
FUNCTION = "generateImage"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def generateImage(self, **kwargs):
|
||||
runwareModel = kwargs.get("Model")
|
||||
positivePrompt = kwargs.get("positivePrompt")
|
||||
negativePrompt = kwargs.get("negativePrompt", None)
|
||||
promptWeighting = kwargs.get("Prompt Weighting", False)
|
||||
runwareControlNet = kwargs.get("ControlNet", None)
|
||||
runwareLora = kwargs.get("Lora", None)
|
||||
runwareRefiner = kwargs.get("Refiner", None)
|
||||
seedImage = kwargs.get("seedImage", None)
|
||||
seedImageStrength = kwargs.get("strength", 0.8)
|
||||
maskImage = kwargs.get("maskImage", None)
|
||||
enableMaskMargin = kwargs.get("Mask Margin", False)
|
||||
maskImageMargin = kwargs.get("maskMargin", 32)
|
||||
clipSkip = kwargs.get("clipSkip", 0)
|
||||
height = kwargs.get("height", 512)
|
||||
width = kwargs.get("width", 512)
|
||||
steps = kwargs.get("steps", 4)
|
||||
scheduler = kwargs.get("scheduler", "Default")
|
||||
cfgScale = kwargs.get("cfgScale", 6.5)
|
||||
seed = kwargs.get("seed")
|
||||
batchSize = kwargs.get("batchSize", 1)
|
||||
|
||||
if(maskImage is not None and seedImage is None):
|
||||
raise Exception("Mask Image Requires Seed Image To Be Provided!")
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageInference",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"positivePrompt": positivePrompt,
|
||||
"height": height,
|
||||
"width": width,
|
||||
"model": runwareModel,
|
||||
"steps": steps,
|
||||
"CFGScale": cfgScale,
|
||||
"scheduler": scheduler,
|
||||
"clipSkip": clipSkip,
|
||||
"seed": seed,
|
||||
"outputFormat": "WEBP",
|
||||
"outputType": "base64Data",
|
||||
"numberResults": batchSize,
|
||||
}
|
||||
]
|
||||
|
||||
# For Debugging Purposes Only
|
||||
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
|
||||
|
||||
if(negativePrompt is not None and negativePrompt != ""):
|
||||
genConfig[0]["negativePrompt"] = negativePrompt
|
||||
if(promptWeighting):
|
||||
genConfig[0]["promptWeighting"] = "sdEmbeds"
|
||||
if(runwareLora is not None):
|
||||
if(isinstance(runwareLora, list)):
|
||||
genConfig[0]["lora"] = runwareLora
|
||||
elif(isinstance(runwareLora, dict)):
|
||||
genConfig[0]["lora"] = [runwareLora]
|
||||
if(runwareControlNet is not None):
|
||||
genConfig[0]["controlNet"] = runwareControlNet
|
||||
if(runwareRefiner is not None):
|
||||
genConfig[0]["refiner"] = runwareRefiner
|
||||
if(seedImage is not None):
|
||||
seedImage = rwUtils.convertTensor2IMG(seedImage)
|
||||
genConfig[0]["seedImage"] = seedImage
|
||||
genConfig[0]["strength"] = seedImageStrength
|
||||
if(maskImage is not None):
|
||||
maskImage = rwUtils.convertTensor2IMG(maskImage)
|
||||
genConfig[0]["maskImage"] = maskImage
|
||||
if(enableMaskMargin):
|
||||
genConfig[0]["maskMargin"] = maskImageMargin
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return images
|
||||
@@ -0,0 +1,79 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class imageMasking:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Image": ("IMAGE", {
|
||||
"tooltip": "Specifies the input image to be processed for mask generation."
|
||||
}),
|
||||
"Detection Model": ([
|
||||
"face_yolov8n", "face_yolov8s", "hand_yolov8n", "person_yolov8n-seg", "person_yolov8s-seg",
|
||||
"mediapipe_face_full", "mediapipe_face_short", "mediapipe_face_mesh"
|
||||
], {
|
||||
"tooltip": "Specifies the specialized detection model to use for mask generation.",
|
||||
"default": "face_yolov8n",
|
||||
}),
|
||||
"Confidence": ("FLOAT", {
|
||||
"tooltip": "Confidence threshold for detections. Only detections with confidence scores above this threshold will be included in the mask.",
|
||||
"default": 0.25,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.01,
|
||||
}),
|
||||
"Max Detections": ("INT", {
|
||||
"tooltip": "Limits the maximum number of elements (faces, hands, or people) that will be detected and masked in the image. If there are more elements than this value, only the ones with highest confidence scores will be included.",
|
||||
"default": 6,
|
||||
"min": 1,
|
||||
"max": 20,
|
||||
}),
|
||||
"Mask Padding": ("INT", {
|
||||
"tooltip": "Extends or reduces the detected mask area by the specified number of pixels. Positive values create a larger masked region (useful when you want to ensure complete coverage of the element), while negative values shrink the mask (useful for tighter, more focused areas).",
|
||||
"default": 4,
|
||||
"min": -40,
|
||||
"max": 40,
|
||||
}),
|
||||
"Mask Blur": ("INT", {
|
||||
"tooltip": "Extends the mask by the specified number of pixels with a gradual fade-out effect, creating smooth transitions between masked and unmasked regions in the final result.",
|
||||
"default": 4,
|
||||
"min": 0,
|
||||
"max": 20,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Image Masking provides intelligent detection and mask generation for specific elements in images, particularly optimized for faces, hands, and people. Built on advanced detection models, this feature enhances the inpainting workflow by automatically creating precise masks around detected elements, enabling targeted enhancement and detailing."
|
||||
|
||||
FUNCTION = "imageMasking"
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "MASK")
|
||||
RETURN_NAMES = ("Image", "Mask Preview", "Mask")
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def imageMasking(self, **kwargs):
|
||||
image = kwargs.get("Image")
|
||||
detectionModel = kwargs.get("Detection Model")
|
||||
confidence = kwargs.get("Confidence")
|
||||
maxDetections = kwargs.get("Max Detections")
|
||||
maskPadding = kwargs.get("Mask Padding")
|
||||
maskBlur = kwargs.get("Mask Blur")
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageMasking",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"inputImage": rwUtils.convertTensor2IMG(image),
|
||||
"model": detectionModel,
|
||||
"confidence": confidence,
|
||||
"maxDetections": maxDetections,
|
||||
"outputFormat": "WEBP",
|
||||
"outputType": "base64Data",
|
||||
}
|
||||
]
|
||||
|
||||
genConfig[0]["maskPadding"] = maskPadding
|
||||
genConfig[0]["maskBlur"] = maskBlur
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (image, images[0], images[0])
|
||||
@@ -0,0 +1,37 @@
|
||||
class loraCombine:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Lora 1": ("RUNWARELORA", {
|
||||
"tooltip": "Connect a Runware Lora From Lora Search Node.",
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"Lora 2": ("RUNWARELORA", {
|
||||
"tooltip": "Connect a Runware Lora From Lora Search Node.",
|
||||
}),
|
||||
"Lora 3": ("RUNWARELORA", {
|
||||
"tooltip": "Connect a Runware Lora From Lora Search Node.",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Combine One or More Lora's To Connect It With Runware Image Inference."
|
||||
FUNCTION = "loraCombine"
|
||||
RETURN_TYPES = ("RUNWARELORA",)
|
||||
RETURN_NAMES = ("Runware Lora's",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def loraCombine(self, **kwargs):
|
||||
runwareLora1 = kwargs.get("Lora 1")
|
||||
runwareLora2 = kwargs.get("Lora 2", None)
|
||||
runwareLora3 = kwargs.get("Lora 3", None)
|
||||
|
||||
loraObjectArray = []
|
||||
loraObjectArray.append(runwareLora1)
|
||||
if(runwareLora2):
|
||||
loraObjectArray.append(runwareLora2)
|
||||
if(runwareLora3):
|
||||
loraObjectArray.append(runwareLora3)
|
||||
return (loraObjectArray,)
|
||||
@@ -0,0 +1,72 @@
|
||||
class loraSearch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Lora Search": ("STRING", {
|
||||
"tooltip": "Searchg For Specific Lora By Name Or Civit AIR Code (eg: Cyberpunk).",
|
||||
}),
|
||||
"Model Architecture": ([
|
||||
"All",
|
||||
"FLUX.1-Schnell",
|
||||
"FLUX.1-Dev",
|
||||
"Pony",
|
||||
"SD 1.5",
|
||||
"SD 1.5 Hyper",
|
||||
"SD 1.5 LCM",
|
||||
"SD 3",
|
||||
"SDXL 1.0",
|
||||
"SDXL 1.0 LCM",
|
||||
"SDXL Distilled",
|
||||
"SDXL Hyper",
|
||||
"SDXL Lightning",
|
||||
"SDXL Turbo",
|
||||
], {
|
||||
"tooltip": "Choose Lora Model Architecture To Filter Results.",
|
||||
"default": "All",
|
||||
}),
|
||||
"LoraType": ([
|
||||
"Lora",
|
||||
], {
|
||||
"tooltip": "Choose Lora Type To Filter Results.",
|
||||
"default": "Lora",
|
||||
}),
|
||||
"LoraList": ([
|
||||
"civitai:58390@62833 (Detail Tweaker LoRA (细节调整LoRA))",
|
||||
"civitai:82098@87153 (Add More Details - Detail Enhancer / Tweaker (细节调整) LoRA)",
|
||||
"civitai:122359@135867 (Detail Tweaker XL)",
|
||||
"civitai:14171@16677 (Cute_girl_mix4)",
|
||||
"civitai:13941@16576 (epi_noiseoffset)",
|
||||
"civitai:25995@32988 (blindbox/大概是盲盒)",
|
||||
], {
|
||||
"tooltip": "Lora Results Will Show UP Here So You Could Choose From.",
|
||||
"default": "civitai:58390@62833 (Detail Tweaker LoRA (细节调整LoRA))",
|
||||
}),
|
||||
"weight": ("FLOAT", {
|
||||
"tooltip": "Defines the strength or influence of the LoRA model in the generation process.",
|
||||
"default": 1.0,
|
||||
"min": -4.0,
|
||||
"max": 4.0,
|
||||
"step": 0.1,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Directly Search and Connect Lora's to Runware Inference Nodes In ComfyUI."
|
||||
FUNCTION = "loraSearch"
|
||||
RETURN_TYPES = ("RUNWARELORA",)
|
||||
RETURN_NAMES = ("Runware Lora",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, LoraList):
|
||||
return True
|
||||
|
||||
def loraSearch(self, **kwargs):
|
||||
currentModel = kwargs.get("LoraList")
|
||||
loraWeight = kwargs.get("weight")
|
||||
loraAIRCode = currentModel.split(" ")[0]
|
||||
return ({
|
||||
"model": loraAIRCode,
|
||||
"weight": round(loraWeight, 2),
|
||||
},)
|
||||
@@ -0,0 +1,63 @@
|
||||
class modelSearch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Model Search": ("STRING", {
|
||||
"tooltip": "Searchg For Specific Model By Name Or Civit AIR Code (eg: Juggernaut).",
|
||||
}),
|
||||
"Model Architecture": ([
|
||||
"All",
|
||||
"FLUX.1-Schnell",
|
||||
"FLUX.1-Dev",
|
||||
"Pony",
|
||||
"SD 1.5",
|
||||
"SD 1.5 Hyper",
|
||||
"SD 1.5 LCM",
|
||||
"SD 3",
|
||||
"SDXL 1.0",
|
||||
"SDXL 1.0 LCM",
|
||||
"SDXL Distilled",
|
||||
"SDXL Hyper",
|
||||
"SDXL Lightning",
|
||||
"SDXL Turbo",
|
||||
], {
|
||||
"tooltip": "Choose Model Architecture To Filter Results.",
|
||||
"default": "All",
|
||||
}),
|
||||
"ModelType": ([
|
||||
"Base Model",
|
||||
"Inpainting Model",
|
||||
], {
|
||||
"tooltip": "Choose Model Type To Filter Results.",
|
||||
"default": "Base Model",
|
||||
}),
|
||||
"ModelList": ([
|
||||
"runware:100@1 (Flux Schnell)",
|
||||
"runware:101@1 (Flux Dev)",
|
||||
"runware:5@1 (SD3)",
|
||||
"civitai:4384@128713 (SDXL 1.5 DreamShaper)",
|
||||
"civitai:43331@176425 (SD 1.5 majicMIX realistic 麦橘写实)",
|
||||
"civitai:101055@128078 (SDXL v1.0 VAE fix)",
|
||||
"civitai:133005@288982 (SDXL Juggernaut XL V8)",
|
||||
], {
|
||||
"tooltip": "Model Results Will Show UP Here So You Could Choose From. If You didn't Search For Anything this will show featured Model List.",
|
||||
"default": "runware:100@1 (Flux Schnell)",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Directly Search and Connect Model Checkpoints to Runware Inference Nodes In ComfyUI."
|
||||
FUNCTION = "modelSearch"
|
||||
RETURN_TYPES = ("RUNWAREMODEL",)
|
||||
RETURN_NAMES = ("Runware Model",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, ModelList):
|
||||
return True
|
||||
|
||||
def modelSearch(self, **kwargs):
|
||||
currentModel = kwargs.get("ModelList")
|
||||
modelAIRCode = currentModel.split(" ")[0]
|
||||
return (modelAIRCode,)
|
||||
@@ -0,0 +1,179 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class photoMaker:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Model": ("RUNWAREMODEL", {
|
||||
"tooltip": "Connect a Runware Model From Runware Model Node.",
|
||||
}),
|
||||
"Image 1": ("IMAGE", {
|
||||
"tooltip": "Specifies Input Image 1 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process."
|
||||
}),
|
||||
"positivePrompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"placeholder": "Positive Prompt: a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides positive guidance for the task. This parameter is essential to shape the desired results.\n\nThe PhotoMaker positive prompt must follow a specific format: the class word (like \"man\", \"woman\", \"girl\") must be followed by the trigger word \"img\".\nExample: man img, wearing a suit",
|
||||
"tooltip": "Positive Prompt: a text instruction to guide the model on generating the image.\nDo not Forget To Add trigger word \"img\" after the class word (like \"man\", \"woman\", \"girl\")",
|
||||
}),
|
||||
"negativePrompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"placeholder": "Negative Prompt: a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides negative guidance for the task. This parameter helps to avoid certain undesired results.",
|
||||
"tooltip": "Negative Prompt: a text instruction to guide the model on generating the image."
|
||||
}),
|
||||
"style": ([
|
||||
"No style", "Cinematic", "Disney Character", "Digital Art", "Photographic", "Fantasy art",
|
||||
"Neonpunk", "Enhance", "Comic book", "Lowpoly", "Line art"
|
||||
], {
|
||||
"default": "No style",
|
||||
"tooltip": "Specifies the artistic style to be applied to the generated images."
|
||||
}),
|
||||
"dimensions": ([
|
||||
"Square (512x512)", "Square HD (1024x1024)", "Portrait 3:4 (768x1024)",
|
||||
"Portrait 9:16 (576x1024)", "Landscape 4:3 (1024x768)",
|
||||
"Landscape 16:9 (1024x576)", "Custom"
|
||||
], {
|
||||
"default": "Square HD (1024x1024)",
|
||||
"tooltip": "Adjust the dimensions of the generated image by specifying its width and height in pixels, or select from the predefined options. Image dimensions must be multiples of 64 (e.g., 512x512, 1024x768).",
|
||||
}),
|
||||
"width": ("INT", {
|
||||
"tooltip": "The Width of the image in pixels.",
|
||||
"default": 1024,
|
||||
"min": 512,
|
||||
"max": 2048,
|
||||
"step": 64,
|
||||
}),
|
||||
"height": ("INT", {
|
||||
"tooltip": "The Height of the image in pixels.",
|
||||
"default": 1024,
|
||||
"min": 512,
|
||||
"max": 2048,
|
||||
"step": 64,
|
||||
}),
|
||||
"steps": ("INT", {
|
||||
"tooltip": "Guidance scale represents how closely the images will resemble the prompt or how much freedom the AI model has. Higher values are closer to the prompt. Low values may reduce the quality of the results.",
|
||||
"default": 25,
|
||||
"min": 1,
|
||||
"max": 100,
|
||||
}),
|
||||
"scheduler": ([
|
||||
"Default", "DDIMScheduler", "DDIMInverseScheduler", "DDPMScheduler", "DEISMultistepScheduler",
|
||||
"DPMSolverSinglestepScheduler", "DPMSolverMultistepScheduler", "DPMSolverMultistepInverse", "DPMSolverSDEScheduler", "HeunDiscreteScheduler", "KDPM2DiscreteScheduler", "KDPM2AncestralDiscreteScheduler", "KarrasVeScheduler", "LMSDiscreteScheduler", "PNDMScheduler", "ScoreSdeVeScheduler", "ScoreSdeVpScheduler", "IPNDMScheduler", "EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "EDMEulerScheduler", "FlowMatchEulerDiscreteScheduler", "VQDiffusionScheduler", "UniPCMultistepScheduler", "RePaintScheduler", "DPM++ 2M Karras", "DPM++ 2M SDE Karras", "DPM++ 2M SDE", "DPM++ SDE Karras", "DPM++ SDE", "LCMScheduler", "EDMDPMSolverMultistepScheduler", "TCDScheduler"
|
||||
], {
|
||||
"tooltip": "An scheduler is a component that manages the inference process. Different schedulers can be used to achieve different results like more detailed images, faster inference, or more accurate results.",
|
||||
"default": "Default",
|
||||
}),
|
||||
"cfgScale": ("FLOAT", {
|
||||
"tooltip": "The scale of the image generation. The higher the scale, the more detailed the image will be.",
|
||||
"default": 6.5,
|
||||
"min": 1.0,
|
||||
"max": 30.0,
|
||||
"step": 0.5,
|
||||
}),
|
||||
"strength": ("INT", {
|
||||
"default": 15,
|
||||
"min": 15,
|
||||
"max": 50,
|
||||
"tooltip": "Controls the balance between preserving the subject's original features and the creative transformation specified in the prompt.\n- Lower values provide stronger subject fidelity.\n- Higher values allow more creative freedom in the transformation."
|
||||
}),
|
||||
"clipSkip": ("INT", {
|
||||
"tooltip": "Enables skipping layers of the CLIP embedding process, leading to quicker and more varied image generation.",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 2,
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"tooltip": "A value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value.",
|
||||
"default": rwUtils.genRandSeed(),
|
||||
"min": 1,
|
||||
"max": 9223372036854776000,
|
||||
}),
|
||||
"batchSize": ("INT", {
|
||||
"tooltip": "The number of images to generate in a single request.",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 10,
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"Image 2": ("IMAGE", {
|
||||
"tooltip": "Specifies Input Image 2 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process."
|
||||
}),
|
||||
"Image 3": ("IMAGE", {
|
||||
"tooltip": "Specifies Input Image 3 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process."
|
||||
}),
|
||||
"Image 4": ("IMAGE", {
|
||||
"tooltip": "Specifies Input Image 4 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process."
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
DESCRIPTION = "Transform and style images using PhotoMaker's advanced personalization technology. Create consistent, high-quality image variations with precise subject fidelity and style control."
|
||||
FUNCTION = "photoMaker"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, positivePrompt, negativePrompt):
|
||||
if (positivePrompt is None or len(positivePrompt) < 3 or len(positivePrompt) > 2000):
|
||||
raise Exception("Positive Prompt Must Be Between 3 And 2000 characters!")
|
||||
if (negativePrompt is not None and negativePrompt != "" and (len(negativePrompt) < 3 or len(negativePrompt) > 2000)):
|
||||
raise Exception("Negative Prompt Must Be Between 3 And 2000 characters!")
|
||||
return True
|
||||
|
||||
def photoMaker(self, **kwargs):
|
||||
runwareModel = kwargs.get("Model")
|
||||
image1 = kwargs.get("Image 1")
|
||||
image2 = kwargs.get("Image 2", None)
|
||||
image3 = kwargs.get("Image 3", None)
|
||||
image4 = kwargs.get("Image 4", None)
|
||||
positivePrompt = kwargs.get("positivePrompt")
|
||||
negativePrompt = kwargs.get("negativePrompt", None)
|
||||
style = kwargs.get("style", "No style")
|
||||
dimensions = kwargs.get("dimensions", "Square HD (1024x1024)")
|
||||
width = kwargs.get("width", 1024)
|
||||
height = kwargs.get("height", 1024)
|
||||
steps = kwargs.get("steps", 25)
|
||||
scheduler = kwargs.get("scheduler", "Default")
|
||||
cfgScale = kwargs.get("cfgScale", 6.5)
|
||||
strength = kwargs.get("strength", 15)
|
||||
clipSkip = kwargs.get("clipSkip", 0)
|
||||
seed = kwargs.get("seed")
|
||||
batchSize = kwargs.get("batchSize", 1)
|
||||
|
||||
imageList = [rwUtils.convertTensor2IMG(image1)]
|
||||
if(image2 is not None):
|
||||
imageList.append(rwUtils.convertTensor2IMG(image2))
|
||||
if(image3 is not None):
|
||||
imageList.append(rwUtils.convertTensor2IMG(image3))
|
||||
if(image4 is not None):
|
||||
imageList.append(rwUtils.convertTensor2IMG(image4))
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "photoMaker",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"model": runwareModel,
|
||||
"inputImages": imageList,
|
||||
"positivePrompt": positivePrompt,
|
||||
"style": style,
|
||||
"strength": strength,
|
||||
"height": height,
|
||||
"width": width,
|
||||
"steps": steps,
|
||||
"scheduler": scheduler,
|
||||
"seed": seed,
|
||||
"CFGScale": cfgScale,
|
||||
"clipSkip": clipSkip,
|
||||
"numberResults": batchSize,
|
||||
"outputFormat": "WEBP",
|
||||
"outputType": "base64Data",
|
||||
}
|
||||
]
|
||||
|
||||
if(negativePrompt is not None and negativePrompt != ""):
|
||||
genConfig[0]["negativePrompt"] = negativePrompt
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return images
|
||||
@@ -0,0 +1,49 @@
|
||||
class refiner:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Model": ("RUNWAREMODEL", {
|
||||
"tooltip": "Connect a Runware Model From Model Search Node.",
|
||||
}),
|
||||
"startStep": ("INT", {
|
||||
"tooltip": "Represents the step number at which the refinement process begins. The initial model will generate the image up to this step, after which the refiner model takes over to enhance the result.",
|
||||
"min": 0,
|
||||
"max": 99,
|
||||
"default": 0,
|
||||
}),
|
||||
"startStepPercentage": ("INT", {
|
||||
"tooltip": "Represents the percentage of total steps at which the refinement process begins. The initial model will generate the image up to this percentage of steps before the refiner takes over.",
|
||||
"min": 0,
|
||||
"max": 99,
|
||||
"default": 0
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Refiner models help create higher quality image outputs by incorporating specialized models designed to enhance image details and overall coherence. This can be particularly useful when you need results with superior quality, photorealism, or specific aesthetic refinements. (Note that refiner models are only SDXL based)"
|
||||
FUNCTION = "refiner"
|
||||
RETURN_TYPES = ("RUNWAREREFINER",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, startStep, startStepPercentage):
|
||||
if(startStep != 0 and startStepPercentage != 0):
|
||||
raise Exception("Please provide either startStep or startStepPercentage, not both.")
|
||||
elif(startStep == 0 and startStepPercentage == 0):
|
||||
raise Exception("Please provide either startStep or startStepPercentage.")
|
||||
else:
|
||||
return True
|
||||
|
||||
def refiner(self, **kwargs):
|
||||
runwareModel = kwargs.get("Model")
|
||||
startStep = kwargs.get("startStep")
|
||||
startStepPercentage = kwargs.get("startStepPercentage")
|
||||
refinerResult = {
|
||||
"model": runwareModel,
|
||||
}
|
||||
if(startStep != 0):
|
||||
refinerResult["startStep"] = startStep
|
||||
else:
|
||||
refinerResult["startStepPercentage"] = startStepPercentage
|
||||
return (refinerResult,)
|
||||
@@ -0,0 +1,42 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class upscaler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Image": ("IMAGE", {
|
||||
"tooltip": "Specifies the input image to be upscaled."
|
||||
}),
|
||||
"upscaleFactor": ("INT", {
|
||||
"tooltip": "Each level will increase the size of the image by the corresponding factor.",
|
||||
"default": 2,
|
||||
"min": 2,
|
||||
"max": 4,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
DESCRIPTION = "Enhance the resolution and quality of your images using Runware's advanced upscaling API. Transform low-resolution images into sharp, high-definition visuals."
|
||||
FUNCTION = "upscale"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def upscale(self, **kwargs):
|
||||
image = kwargs.get("Image")
|
||||
upscaleFactor = kwargs.get("upscaleFactor", 2)
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageUpscale",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"inputImage": rwUtils.convertTensor2IMG(image),
|
||||
"upscaleFactor": upscaleFactor,
|
||||
"outputFormat": "WEBP",
|
||||
"outputType": "base64Data",
|
||||
}
|
||||
]
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return images
|
||||
@@ -0,0 +1,111 @@
|
||||
from requests.adapters import HTTPAdapter
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import requests
|
||||
import random
|
||||
import base64
|
||||
import torch
|
||||
import uuid
|
||||
import os
|
||||
import io
|
||||
|
||||
load_dotenv()
|
||||
|
||||
SESSION_TIMEOUT = 60
|
||||
RUNWARE_API_BASE_URL = "https://api.runware.ai/v1"
|
||||
session = requests.Session()
|
||||
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=10)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
|
||||
def getAPIKey():
|
||||
apiKey = os.getenv("RUNWARE_API_KEY")
|
||||
if apiKey and isinstance(apiKey, str) and len(apiKey) > 24:
|
||||
return apiKey
|
||||
return False
|
||||
|
||||
RUNWARE_API_KEY = getAPIKey()
|
||||
|
||||
def setAPIKey(apiKey: str):
|
||||
global RUNWARE_API_KEY
|
||||
comfyNodeRoot = Path(__file__).parent.parent.parent
|
||||
envFilePath = comfyNodeRoot / '.env'
|
||||
if not envFilePath.exists():
|
||||
envFilePath.touch()
|
||||
with open(envFilePath, 'r') as f:
|
||||
lines = f.readlines()
|
||||
key_exists = False
|
||||
new_lines = []
|
||||
for line in lines:
|
||||
if line.startswith('RUNWARE_API_KEY='):
|
||||
key_exists = True
|
||||
new_lines.append(f'RUNWARE_API_KEY={apiKey}\n')
|
||||
else:
|
||||
new_lines.append(line)
|
||||
if not key_exists:
|
||||
new_lines.append(f'RUNWARE_API_KEY={apiKey}\n')
|
||||
with open(envFilePath, 'w') as f:
|
||||
f.writelines(new_lines)
|
||||
RUNWARE_API_KEY = apiKey
|
||||
os.environ["RUNWARE_API_KEY"] = apiKey
|
||||
return True
|
||||
|
||||
def genRandSeed(minSeed = 1000, maxSeed = 9223372036854776000):
|
||||
return random.randint(minSeed, maxSeed)
|
||||
|
||||
def genRandUUID():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
def inferenecRequest(genConfig):
|
||||
global RUNWARE_API_KEY
|
||||
RUNWARE_API_KEY = os.getenv("RUNWARE_API_KEY")
|
||||
headers = {
|
||||
"Authorization": f"Bearer {RUNWARE_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
}
|
||||
|
||||
try:
|
||||
genResult = session.post(RUNWARE_API_BASE_URL, headers=headers, json=genConfig, timeout=SESSION_TIMEOUT, allow_redirects=False, stream=True)
|
||||
genResult = genResult.json()
|
||||
if("errors" in genResult):
|
||||
raise Exception(genResult["errors"][0]["message"])
|
||||
else:
|
||||
return genResult
|
||||
except requests.exceptions.Timeout:
|
||||
raise Exception(f"Error: Request Timed Out After {SESSION_TIMEOUT} Seconds - Please Try Again!")
|
||||
except requests.exceptions.RequestException:
|
||||
raise Exception("Error: Runware Request Failed!")
|
||||
except Exception as e:
|
||||
if "invalid api key" in str(e).lower():
|
||||
raise Exception("Invalid API Key - Please Make Sure you set your correctly API Key Either in the env file or through the API Manager Node!\n\nIf you don't have an API Key, you can get it from: https://my.runware.ai/keys")
|
||||
else:
|
||||
raise Exception(f"Error: {e}")
|
||||
return False
|
||||
|
||||
def convertTensor2IMG(tensorImage):
|
||||
imageNP = (tensorImage.squeeze().numpy() * 255).astype(np.uint8)
|
||||
image = Image.fromarray(imageNP)
|
||||
with io.BytesIO() as buffer:
|
||||
image.save(buffer, format="webp")
|
||||
imgb64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||||
imgDataURI = f'data:image/webp;base64,{imgb64}'
|
||||
return imgDataURI
|
||||
|
||||
def convertIMG2Tensor(b64img):
|
||||
imgbytes = base64.b64decode(b64img)
|
||||
image = Image.open(io.BytesIO(imgbytes))
|
||||
imageNP = np.array(image).astype(np.float32) / 255.0
|
||||
tensorImage = torch.from_numpy(imageNP).squeeze()
|
||||
return tensorImage
|
||||
|
||||
def convertImageB64List(imageDataObject):
|
||||
images = ()
|
||||
for result in imageDataObject["data"]:
|
||||
generatedImage = result.get("imageBase64Data", result.get("maskImageBase64Data", result.get("guideImageBase64Data", False)))
|
||||
generatedImage = convertIMG2Tensor(generatedImage)
|
||||
images += (generatedImage,)
|
||||
images = torch.stack(images, dim=0)
|
||||
return (images,)
|
||||
Reference in New Issue
Block a user