462 lines
14 KiB
Python
462 lines
14 KiB
Python
# IMAGE PassThrough (Aegis72)
|
|
# this node takes an image as an input andpasses it through. It is used for remote
|
|
# targeting with an "Anything Everywhere" node sender
|
|
|
|
import sys
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image, ImageFilter
|
|
|
|
|
|
p310_plus = (sys.version_info >= (3, 10))
|
|
|
|
MANIFEST = {
|
|
"name": "Aegisflow Utility Nodes",
|
|
"version": (1, 1, 0),
|
|
"author": "Aegis72",
|
|
"project": "https://majorstudio.gumroad.com",
|
|
"description": "UtilityNodes for Aegisflow comfyui workflow, based heavily on WASquatch's image batch node",
|
|
}
|
|
|
|
|
|
class aegisflow_multi_pass:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
},
|
|
"optional": {
|
|
"image": ("IMAGE",),
|
|
"mask": ("MASK",),
|
|
"latent": ("LATENT",),
|
|
"model": ("MODEL",),
|
|
"vae": ("VAE",),
|
|
"clip": ("CLIP",),
|
|
"positive": ("CONDITIONING",),
|
|
"negative": ("CONDITIONING",),
|
|
"sdxl tuple": ("SDXL_TUPLE",),
|
|
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", "MASK", "LATENT", "MODEL", "VAE", "CLIP", "CONDITIONING", "CONDITIONING", "SDXL_TUPLE",)
|
|
RETURN_NAMES = ("image", "mask", "latent", "model", "vae", "clip", "positive", "negative", "sdxl tuple",)
|
|
FUNCTION = "af_passnodes"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def af_passnodes(self, **kwargs):
|
|
output_order = ("image", "mask", "latent", "model", "vae", "clip", "positive", "negative", "sdxl tuple",)
|
|
return [kwargs.setdefault(key, '0') for key in output_order]
|
|
|
|
|
|
# model PassThrough (Aegis72)
|
|
# this node takes a model as an input and passes it through. It is used for remote
|
|
# targeting with an "Anything Everywhere" node sender
|
|
|
|
|
|
class aegisflow_model_pass:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
},
|
|
"optional": {
|
|
"model": ("MODEL",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("MODEL",)
|
|
RETURN_NAMES = ("model",)
|
|
FUNCTION = "model_passer"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def model_passer(self, **kwargs):
|
|
return [kwargs[key] for key in kwargs if kwargs[key] is not None]
|
|
|
|
# model PassThrough (Aegis72)
|
|
# this node takes a model as an input and passes it through. It is used for remote
|
|
# targeting with an "Anything Everywhere" node sender
|
|
|
|
|
|
class aegisflow_clip_pass:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
},
|
|
"optional": {
|
|
"clip": ("CLIP",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("CLIP",)
|
|
RETURN_NAMES = ("clip",)
|
|
FUNCTION = "clip_passer"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def clip_passer(self, **kwargs):
|
|
return [kwargs[key] for key in kwargs if kwargs[key] is not None]
|
|
|
|
|
|
# vae PassThrough (Aegis72)
|
|
# this node takes a vae as an input and passes it through. It is used for remote
|
|
# targeting with an "Anything Everywhere" node sender
|
|
|
|
|
|
class aegisflow_vae_pass:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
},
|
|
"optional": {
|
|
"vae": ("VAE",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("VAE",)
|
|
RETURN_NAMES = ("vae",)
|
|
FUNCTION = "vae_passer"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def vae_passer(self, **kwargs):
|
|
return [kwargs[key] for key in kwargs if kwargs[key] is not None]
|
|
|
|
|
|
class aegisflow_image_pass:
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
},
|
|
"optional": {
|
|
"image_input": ("IMAGE",),
|
|
"mask_input": ("MASK",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", "MASK",)
|
|
RETURN_NAMES = ("image_output", "mask_output",)
|
|
FUNCTION = "image_passer"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def image_passer(self, **kwargs):
|
|
return [kwargs[key] for key in kwargs if kwargs[key] is not None]
|
|
|
|
|
|
# LATENT PassThrough (Aegis72)
|
|
# this node takes an latent as an input and passes it through. It is used for remote
|
|
# targeting with an "Anything Everywhere" node sender
|
|
|
|
class aegisflow_latent_pass:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
},
|
|
"optional": {
|
|
"latent to pass": ("LATENT",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("LATENT",)
|
|
RETURN_NAMES = ("latent pass",)
|
|
FUNCTION = "latent_passer"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def latent_passer(self, **kwargs):
|
|
return [kwargs[key] for key in kwargs if kwargs[key] is not None]
|
|
|
|
# MASK PassThrough (Aegis72)
|
|
# this node takes a mask as an input and passes it through. It is used for remote
|
|
# targeting with an "Anything Everywhere" node sender
|
|
|
|
|
|
class aegisflow_mask_pass:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
},
|
|
"optional": {
|
|
"mask to pass": ("MASK",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("MASK",)
|
|
RETURN_NAMES = ("mask pass-->",)
|
|
FUNCTION = "mask_passer"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def mask_passer(self, **kwargs):
|
|
return [kwargs[key] for key in kwargs if kwargs[key] is not None]
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------#
|
|
# This is an input switch for Controlnet Preprocessors. Can pick an input and that image will be the one picked for the workflow.
|
|
class af_preproc_chooser:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"Input": ("INT", {"default": 1, "min": 1, "max": 9}),
|
|
},
|
|
"optional": {
|
|
"c1_passthrough": ("IMAGE",),
|
|
"c2_normal_lineart": ("IMAGE",),
|
|
"c3_anime_lineart": ("IMAGE",),
|
|
"c4_manga_lineart": ("IMAGE",),
|
|
"c5_midas_depthmap": ("IMAGE",),
|
|
"c6_color_palette": ("IMAGE",),
|
|
"c7_canny_edge": ("IMAGE",),
|
|
"c8_openpose_recognizer": ("IMAGE",),
|
|
"c9_scribble_lines": ("IMAGE",),
|
|
"c10_yourchoice1": ("IMAGE",),
|
|
"c11_yourchoice2": ("IMAGE",),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "af_preproc_chooser"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def af_preproc_chooser(self, Input, to_process=None, c1_passthrough=None, c2_normal_lineart=None, c3_anime_lineart=None, c4_manga_lineart=None, c5_midas_depthmap=None, c6_color_palette=None, c7_canny_edge=None, c8_openpose_recognizer=None, c9_scribble_lines=None, c10_yourchoice1=None, c11_yourchoice2=None,):
|
|
if Input == 1:
|
|
return (c1_passthrough, )
|
|
elif Input == 2:
|
|
return (c2_normal_lineart, )
|
|
elif Input == 3:
|
|
return (c3_anime_lineart, )
|
|
elif Input == 4:
|
|
return (c4_manga_lineart, )
|
|
elif Input == 5:
|
|
return (c5_midas_depthmap, )
|
|
elif Input == 6:
|
|
return (c6_color_palette, )
|
|
elif Input == 7:
|
|
return (c7_canny_edge, )
|
|
elif Input == 8:
|
|
return (c8_openpose_recognizer, )
|
|
elif Input == 9:
|
|
return (c9_scribble_lines, )
|
|
elif Input == 10:
|
|
return (c10_yourchoice1, )
|
|
else:
|
|
return (c11_yourchoice2, )
|
|
|
|
# Developed by Ally - https://www.patreon.com/theally
|
|
# https://civitai.com/user/theally
|
|
|
|
# This node provides a simple interface to adjust the brightness/contrast of the output image prior to saving
|
|
# many users were having difficulties with both installing and keeping theAlly nodes consistent and so I am integrating the three required them into this node set.
|
|
|
|
|
|
class BrightnessContrast_theAlly:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
"""
|
|
Input Types
|
|
|
|
"""
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"mode": (["brightness", "contrast"],),
|
|
"strength": ("FLOAT", {"default": 0.5, "min": -1.0, "max": 1.0, "step": 0.01}),
|
|
"enabled": ("BOOLEAN", {"default": True},),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "apply_filter"
|
|
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def apply_filter(self, image, mode, strength, enabled):
|
|
# Choose a filter based on the 'mode' value
|
|
if enabled:
|
|
if mode == "brightness":
|
|
image = np.clip(image + strength, 0.0, 1.0)
|
|
elif mode == "contrast":
|
|
image = np.clip(image * strength, 0.0, 1.0)
|
|
else:
|
|
print(f"Invalid filter option: {mode}. No changes applied.")
|
|
return (image,)
|
|
|
|
|
|
# Developed by Ally - https://www.patreon.com/theally
|
|
# https://civitai.com/user/theally
|
|
|
|
# This node provides a simple interface to flip the image horizontally or vertically prior to saving
|
|
|
|
class ImageFlip_theAlly:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"flip_type": (["horizontal", "vertical"],),
|
|
"enabled": ("BOOLEAN", {"default": True},),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "flip_image"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def flip_image(self, image, flip_type, enabled):
|
|
|
|
# Convert the input image tensor to a NumPy array
|
|
image_np = 255. * image.cpu().numpy().squeeze()
|
|
|
|
if not enabled:
|
|
return (image,)
|
|
|
|
if flip_type == "horizontal":
|
|
flipped_image_np = np.flip(image_np, axis=1)
|
|
elif flip_type == "vertical":
|
|
flipped_image_np = np.flip(image_np, axis=0)
|
|
else:
|
|
print("Invalid flip_type. Must be either 'horizontal' or 'vertical'. No changes applied.")
|
|
return (image,)
|
|
# Convert the flipped NumPy array back to a tensor
|
|
flipped_image_np = flipped_image_np.astype(np.float32) / 255.0
|
|
flipped_image_tensor = torch.from_numpy(flipped_image_np).unsqueeze(0)
|
|
|
|
return (flipped_image_tensor,)
|
|
|
|
|
|
# Developed by Ally - https://www.patreon.com/theally
|
|
# https://civitai.com/user/theally
|
|
|
|
# This node provides a simple interface to apply a gaussian blur approximation (with box blur) to the image prior to output
|
|
|
|
class GaussianBlur_theAlly:
|
|
"""
|
|
This node provides a simple interface to apply Gaussian blur to the output image.
|
|
"""
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
"""
|
|
Input Types
|
|
"""
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 200.0, "step": 0.01}),
|
|
"enabled": ("BOOLEAN", {"default": True},),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "apply_filter"
|
|
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def apply_filter(self, image, strength, enabled):
|
|
|
|
if not enabled:
|
|
return (image,)
|
|
|
|
i = 255. * image.cpu().numpy().squeeze()
|
|
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
|
|
|
# Apply Gaussian blur using the strength value
|
|
blurred_img = img.filter(ImageFilter.GaussianBlur(radius=strength))
|
|
|
|
# Convert the blurred PIL Image back to a tensor
|
|
blurred_image_np = np.array(blurred_img).astype(np.float32) / 255.0
|
|
blurred_image_tensor = torch.from_numpy(blurred_image_np).unsqueeze(0)
|
|
return (blurred_image_tensor,)
|
|
|
|
|
|
class af_placeholdertuple:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {},
|
|
"optional": {}}
|
|
|
|
RETURN_TYPES = ("SDXL_TUPLE",)
|
|
FUNCTION = "placeholdertuple"
|
|
CATEGORY = "AegisFlow"
|
|
|
|
def placeholdertuple(self,):
|
|
|
|
provided_tuple_string = "(<comfy.model_patcher.ModelPatcher object at 0x00000215AF92E410>, " \
|
|
"<comfy.sd.CLIP object at 0x0000021582576110>, " \
|
|
"[[tensor([[[-0.3921, 0.0278, -0.0675, ..., -0.4916, -0.3165, 0.0655], " \
|
|
"[-0.6300, -0.3306, 0.3012, ..., 0.2379, -0.3163, 0.4271], " \
|
|
"[ 0.2102, 0.3428, 0.3694, ..., -1.1688, -1.4279, -0.7521], " \
|
|
"..., " \
|
|
"[-0.3279, -0.1775, -1.6074, ..., -0.3802, -1.1385, -0.0408], " \
|
|
"[-0.3222, -0.1721, -1.5919, ..., -0.3691, -1.1436, -0.0270], " \
|
|
"[-0.3520, -0.0728, -1.5434, ..., -0.3932, -1.0915, -0.0713]]]), {'pooled_output': None}]], " \
|
|
"[[tensor([[[-0.3921, 0.0278, -0.0675, ..., -0.4916, -0.3165, 0.0655], " \
|
|
"[-0.6300, -0.3306, 0.3012, ..., 0.2379, -0.3163, 0.4271], " \
|
|
"[ 0.2102, 0.3428, 0.3694, ..., -1.1688, -1.4279, -0.7521], " \
|
|
"..., " \
|
|
"[-0.2891, -0.6821, -1.5167, ..., -0.6290, -1.7984, 0.3385], " \
|
|
"[-0.2864, -0.6799, -1.5096, ..., -0.6233, -1.7977, 0.3522], " \
|
|
"[-0.2866, -0.5871, -1.4560, ..., -0.6451, -1.7306, 0.2990]]]), {'pooled_output': None}]], " \
|
|
"None, None, None, None)"
|
|
result = tuple(provided_tuple_string.split(", "))
|
|
return (result,)
|
|
|
|
|
|
# A dictionary that contains all nodes you want to export with their names
|
|
# NOTE: names should be globally unique
|
|
NODE_CLASS_MAPPINGS = {
|
|
"aegisflow Multi_Pass": aegisflow_multi_pass,
|
|
"Aegisflow Image Pass": aegisflow_image_pass,
|
|
"Aegisflow Latent Pass": aegisflow_latent_pass,
|
|
"Aegisflow Model Pass": aegisflow_model_pass,
|
|
"Aegisflow VAE Pass": aegisflow_vae_pass,
|
|
"Aegisflow CLIP Pass": aegisflow_clip_pass,
|
|
"Aegisflow controlnet preprocessor bus": af_preproc_chooser,
|
|
"Brightness & Contrast_Ally": BrightnessContrast_theAlly,
|
|
"Image Flip_ally": ImageFlip_theAlly,
|
|
"Gaussian Blur_Ally": GaussianBlur_theAlly,
|
|
"Placeholder Tuple": af_placeholdertuple
|
|
}
|
|
|
|
WEB_DIRECTORY = "./js"
|
|
__all__ = ["NODE_CLASS_MAPPINGS", "WEB_DIRECTORY"]
|