diff --git a/README.md b/README.md index a05ef22..ed3607b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ These custom nodes allow for scheduling ControlNet strength across latents in th Custom weights can also be applied to ControlNets and T2IAdapters to mimic the "My prompt is more important" functionality in AUTOMATIC1111's ControlNet extension. TODO: -- Add combined Latent Timestep Keyframe nodes to allow faster setup for easier workflows and to use a single ControlNet for a batch of images; too inefficient and redundant right now for common workflows - Other handy nodes - Finish and update this README for other workflows diff --git a/__init__.py b/__init__.py index 3d998ea..e70bf90 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,3 @@ -from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +from .control.nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/control.py b/control/control.py similarity index 100% rename from control.py rename to control/control.py diff --git a/control/deprecated_nodes.py b/control/deprecated_nodes.py new file mode 100644 index 0000000..25b7169 --- /dev/null +++ b/control/deprecated_nodes.py @@ -0,0 +1,70 @@ +import os + +import torch + +import numpy as np +from PIL import Image, ImageOps +from .logger import logger + + +class LoadImagesFromDirectory: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "directory": ("STRING", {"default": ""}), + }, + "optional": { + "image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}), + "start_index": ("INT", {"default": 0, "min": 0, "step": 1}), + } + } + + RETURN_TYPES = ("IMAGE", "MASK", "INT") + FUNCTION = "load_images" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/deprecated" + + def load_images(self, directory: str, image_load_cap: int = 0, start_index: int = 0): + if not os.path.isdir(directory): + raise FileNotFoundError(f"Directory '{directory} cannot be found.'") + dir_files = os.listdir(directory) + if len(dir_files) == 0: + raise FileNotFoundError(f"No files in directory '{directory}'.") + + dir_files = sorted(dir_files) + dir_files = [os.path.join(directory, x) for x in dir_files] + # start at start_index + dir_files = dir_files[start_index:] + + images = [] + masks = [] + + limit_images = False + if image_load_cap > 0: + limit_images = True + image_count = 0 + + for image_path in dir_files: + if os.path.isdir(image_path): + continue + if limit_images and image_count >= image_load_cap: + break + i = Image.open(image_path) + i = ImageOps.exif_transpose(i) + image = i.convert("RGB") + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + if 'A' in i.getbands(): + mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0 + mask = 1. - torch.from_numpy(mask) + else: + mask = torch.zeros((64,64), dtype=torch.float32, device="cpu") + images.append(image) + masks.append(mask) + image_count += 1 + + if len(images) == 0: + raise FileNotFoundError(f"No images could be loaded from directory '{directory}'.") + + return (torch.cat(images, dim=0), torch.stack(masks, dim=0), image_count) diff --git a/control/latent_keyframe_nodes.py b/control/latent_keyframe_nodes.py new file mode 100644 index 0000000..d12935c --- /dev/null +++ b/control/latent_keyframe_nodes.py @@ -0,0 +1,238 @@ +from typing import Union +import numpy as np +from collections.abc import Iterable + +from .control import LatentKeyframe, LatentKeyframeGroup +from .logger import logger + + +class LatentKeyframeNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "batch_index": ("INT", {"default": 0, "min": -1000, "max": 1000, "step": 1}), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.00001}, ), + }, + "optional": { + "prev_latent_keyframe": ("LATENT_KEYFRAME", ), + } + } + + RETURN_TYPES = ("LATENT_KEYFRAME", ) + FUNCTION = "load_keyframe" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" + + def load_keyframe(self, + batch_index: int, + strength: float, + prev_latent_keyframe: LatentKeyframeGroup=None): + if not prev_latent_keyframe: + prev_latent_keyframe = LatentKeyframeGroup() + keyframe = LatentKeyframe(batch_index, strength) + prev_latent_keyframe.add(keyframe) + return (prev_latent_keyframe,) + + +class LatentKeyframeGroupNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "index_strengths": ("STRING", {"multiline": True, "default": ""}), + }, + "optional": { + "prev_latent_keyframe": ("LATENT_KEYFRAME", ), + "latent_optional": ("LATENT", ), + } + } + + RETURN_TYPES = ("LATENT_KEYFRAME", ) + FUNCTION = "load_keyframes" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" + + def validate_index(self, index: int, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int: + # if part of range, do nothing + if is_range: + return index + # otherwise, validate index + # validate not out of range - only when latent_count is passed in + if latent_count > 0 and index > latent_count-1: + raise IndexError(f"Index '{index}' out of range for the total {latent_count} latents.") + # if negative, validate not out of range + if index < 0: + if not allow_negative: + raise IndexError(f"Negative indeces not allowed, but was {index}.") + conv_index = latent_count+index + if conv_index < 0: + raise IndexError(f"Index '{index}', converted to '{conv_index}' out of range for the total {latent_count} latents.") + index = conv_index + return index + + def convert_to_index_int(self, raw_index: str, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int: + try: + return self.validate_index(int(raw_index), latent_count=latent_count, is_range=is_range, allow_negative=allow_negative) + except ValueError as e: + raise ValueError(f"index '{raw_index}' must be an integer.", e) + + def convert_to_latent_keyframes(self, latent_indeces: str, latent_count: int) -> set[LatentKeyframe]: + if not latent_indeces: + return set() + all_indeces = [i for i in range(0, latent_count)] + allow_negative = latent_count > 0 + chosen_indeces = set() + # parse string - allow positive ints, negative ints, and ranges separated by ':' + groups = latent_indeces.split(",") + groups = [g.strip() for g in groups] + for g in groups: + # parse strengths - default to 1.0 if no strength given + strength = 1.0 + if '=' in g: + g, strength_str = g.split("=", 1) + g = g.strip() + try: + strength = float(strength_str.strip()) + except ValueError as e: + raise ValueError(f"strength '{strength_str}' must be a float.", e) + if strength < 0: + raise ValueError(f"Strength '{strength}' cannot be negative.") + # parse range of indeces (e.g. 2:16) + if ':' in g: + index_range = g.split(":", 1) + index_range = [r.strip() for r in index_range] + start_index = self.convert_to_index_int(index_range[0], latent_count=latent_count, is_range=True, allow_negative=allow_negative) + end_index = self.convert_to_index_int(index_range[1], latent_count=latent_count, is_range=True, allow_negative=allow_negative) + for i in all_indeces[start_index:end_index]: + chosen_indeces.add(LatentKeyframe(i, strength)) + # parse individual indeces + else: + chosen_indeces.add(LatentKeyframe(self.convert_to_index_int(g, latent_count=latent_count, allow_negative=allow_negative), strength)) + return chosen_indeces + + def load_keyframes(self, + index_strengths: str, + prev_latent_keyframe: LatentKeyframeGroup=None, + latent_image_opt=None): + if not prev_latent_keyframe: + prev_latent_keyframe = LatentKeyframeGroup() + curr_latent_keyframe = LatentKeyframeGroup() + + latent_count = -1 + if latent_image_opt: + latent_count = latent_image_opt['samples'].size()[0] + latent_keyframes = self.convert_to_latent_keyframes(index_strengths, latent_count=latent_count) + + for latent_keyframe in latent_keyframes: + logger.info(f"keyframe {latent_keyframe.batch_index}:{latent_keyframe.strength}") + curr_latent_keyframe.add(latent_keyframe) + + for latent_keyframe in prev_latent_keyframe.keyframes: + curr_latent_keyframe.add(latent_keyframe) + + return (curr_latent_keyframe,) + + +class LatentKeyframeInterpolationNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "batch_index_from": ("INT", {"default": 0, "min": -10000, "max": 10000, "step": 1}), + "batch_index_to_excl": ("INT", {"default": 0, "min": -10000, "max": 10000, "step": 1}), + "strength_from": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.0001}, ), + "strength_to": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.0001}, ), + "interpolation": (["linear", "ease-in", "ease-out", "ease-in-out"], ), + }, + "optional": { + "prev_latent_keyframe": ("LATENT_KEYFRAME", ), + } + } + + RETURN_TYPES = ("LATENT_KEYFRAME", ) + FUNCTION = "load_keyframe" + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" + + def load_keyframe(self, + batch_index_from: int, + strength_from: float, + batch_index_to_excl: int, + strength_to: float, + interpolation: str, + prev_latent_keyframe: LatentKeyframeGroup=None): + + if (batch_index_from > batch_index_to_excl): + raise ValueError("batch_index_from must be less than or equal to batch_index_to.") + + if (batch_index_from < 0 and batch_index_to_excl >= 0): + raise ValueError("batch_index_from and batch_index_to must be either both positive or both negative.") + + if not prev_latent_keyframe: + prev_latent_keyframe = LatentKeyframeGroup() + curr_latent_keyframe = LatentKeyframeGroup() + + steps = batch_index_to_excl - batch_index_from + diff = strength_to - strength_from + if interpolation == "linear": + weights = np.linspace(strength_from, strength_to, steps) + elif interpolation == "ease-in": + index = np.linspace(0, 1, steps) + weights = diff * np.power(index, 2) + strength_from + elif interpolation == "ease-out": + index = np.linspace(0, 1, steps) + weights = diff * (1 - np.power(1 - index, 2)) + strength_from + elif interpolation == "ease-in-out": + index = np.linspace(0, 1, steps) + weights = diff * ((1 - np.cos(index * np.pi)) / 2) + strength_from + + for i in range(steps): + keyframe = LatentKeyframe(batch_index_from + i, float(weights[i])) + logger.info(f"keyframe {batch_index_from + i}:{weights[i]}") + curr_latent_keyframe.add(keyframe) + + # replace values with prev_latent_keyframes + for latent_keyframe in prev_latent_keyframe.keyframes: + curr_latent_keyframe.add(latent_keyframe) + + return (curr_latent_keyframe,) + + +class LatentKeyframeBatchedGroupNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "strengths": ("FLOAT", {"default": -1, "min": -1, "step": 0.0001}), + }, + "optional": { + "prev_latent_keyframe": ("LATENT_KEYFRAME", ), + } + } + + RETURN_TYPES = ("LATENT_KEYFRAME", ) + FUNCTION = "load_keyframe" + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" + + def load_keyframe(self, strengths: Union[float, list[float]], prev_latent_keyframe: LatentKeyframeGroup=None): + if not prev_latent_keyframe: + prev_latent_keyframe = LatentKeyframeGroup() + curr_latent_keyframe = LatentKeyframeGroup() + + # if received a normal float input, do nothing + if type(strengths) in (float, int): + logger.info("No batched strengths passed into Latent Keyframe Batch Group node; will not create any new keyframes.") + # if iterable, attempt to create LatentKeyframes with chosen strengths + elif isinstance(strengths, Iterable): + for idx, strength in enumerate(strengths): + keyframe = LatentKeyframe(idx, strength) + curr_latent_keyframe.add(keyframe) + logger.info(f"keyframe {keyframe.batch_index}:{keyframe.strength}") + else: + raise ValueError(f"Expected strengths to be an iterable input, but was {type(strengths).__repr__}.") + + # replace values with prev_latent_keyframes + for latent_keyframe in prev_latent_keyframe.keyframes: + curr_latent_keyframe.add(latent_keyframe) + + return (curr_latent_keyframe,) diff --git a/logger.py b/control/logger.py similarity index 100% rename from logger.py rename to control/logger.py diff --git a/control/nodes.py b/control/nodes.py new file mode 100644 index 0000000..36289e3 --- /dev/null +++ b/control/nodes.py @@ -0,0 +1,194 @@ +import numpy as np + +import folder_paths + +from .control import ControlNetAdvanced, T2IAdapterAdvanced, load_controlnet, ControlNetWeightsType, T2IAdapterWeightsType,\ + LatentKeyframeGroup, TimestepKeyframe, TimestepKeyframeGroup +from .weight_nodes import ScaledSoftControlNetWeights, SoftControlNetWeights, CustomControlNetWeights, \ + SoftT2IAdapterWeights, CustomT2IAdapterWeights +from .latent_keyframe_nodes import LatentKeyframeGroupNode, LatentKeyframeInterpolationNode, LatentKeyframeBatchedGroupNode, LatentKeyframeNode +from .deprecated_nodes import LoadImagesFromDirectory +from .logger import logger + + +class TimestepKeyframeNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}, ), + }, + "optional": { + "control_net_weights": ("CONTROL_NET_WEIGHTS", ), + "t2i_adapter_weights": ("T2I_ADAPTER_WEIGHTS", ), + "latent_keyframe": ("LATENT_KEYFRAME", ), + "prev_timestep_keyframe": ("TIMESTEP_KEYFRAME", ), + } + } + + RETURN_TYPES = ("TIMESTEP_KEYFRAME", ) + FUNCTION = "load_keyframe" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" + + def load_keyframe(self, + start_percent: float, + control_net_weights: ControlNetWeightsType=None, + t2i_adapter_weights: T2IAdapterWeightsType=None, + latent_keyframe: LatentKeyframeGroup=None, + prev_timestep_keyframe: TimestepKeyframeGroup=None): + if not prev_timestep_keyframe: + prev_timestep_keyframe = TimestepKeyframeGroup() + keyframe = TimestepKeyframe(start_percent, control_net_weights, t2i_adapter_weights, latent_keyframe) + prev_timestep_keyframe.add(keyframe) + return (prev_timestep_keyframe,) + + +class ControlNetLoaderAdvanced: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "control_net_name": (folder_paths.get_filename_list("controlnet"), ), + }, + "optional": { + "timestep_keyframe": ("TIMESTEP_KEYFRAME", ), + } + } + + RETURN_TYPES = ("CONTROL_NET", ) + FUNCTION = "load_controlnet" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/loaders" + + def load_controlnet(self, control_net_name, timestep_keyframe: TimestepKeyframeGroup=None): + controlnet_path = folder_paths.get_full_path("controlnet", control_net_name) + controlnet = load_controlnet(controlnet_path, timestep_keyframe) + return (controlnet,) + + +class DiffControlNetLoaderAdvanced: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "control_net_name": (folder_paths.get_filename_list("controlnet"), ) + }, + "optional": { + "timestep_keyframe": ("TIMESTEP_KEYFRAME", ), + } + } + + RETURN_TYPES = ("CONTROL_NET", ) + FUNCTION = "load_controlnet" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/loaders" + + def load_controlnet(self, control_net_name, timestep_keyframe: TimestepKeyframeGroup, model): + controlnet_path = folder_paths.get_full_path("controlnet", control_net_name) + controlnet = load_controlnet(controlnet_path, timestep_keyframe, model) + return (controlnet,) + + +class ControlNetApplyAdvanced_AdvControlNet: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "positive": ("CONDITIONING", ), + "negative": ("CONDITIONING", ), + "control_net": ("CONTROL_NET", ), + "image": ("IMAGE", ), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}) + }, + "optional": { + "mask_opt": ("MASK", ), + } + } + + RETURN_TYPES = ("CONDITIONING","CONDITIONING") + RETURN_NAMES = ("positive", "negative") + FUNCTION = "apply_controlnet" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/loaders/conditioning" + + def apply_controlnet(self, positive, negative, control_net, image, strength, start_percent, end_percent, mask_opt=None): + if strength == 0: + return (positive, negative) + + if mask_opt is not None: + mask_hint = mask_opt.movedim(-1,1) + control_hint = image.movedim(-1,1) + cnets = {} + + out = [] + for conditioning in [positive, negative]: + c = [] + for t in conditioning: + d = t[1].copy() + + prev_cnet = d.get('control', None) + if prev_cnet in cnets: + c_net = cnets[prev_cnet] + else: + c_net = control_net.copy().set_cond_hint(control_hint, strength, (1.0 - start_percent, 1.0 - end_percent)) + # TODO: finish mask implemention, does nothing right now + if mask_opt is not None: + if isinstance(c_net, ControlNetAdvanced) or isinstance(c_net, T2IAdapterAdvanced): + c_net.set_cond_hint_mask(mask_hint) + else: + logger + c_net.set_previous_controlnet(prev_cnet) + cnets[prev_cnet] = c_net + + d['control'] = c_net + d['control_apply_to_uncond'] = False + n = [t[0], d] + c.append(n) + out.append(c) + return (out[0], out[1]) + + +# NODE MAPPING +NODE_CLASS_MAPPINGS = { + # Keyframes + "TimestepKeyframe": TimestepKeyframeNode, + "LatentKeyframe": LatentKeyframeNode, + "LatentKeyframeGroup": LatentKeyframeGroupNode, + "LatentKeyframeBatchedGroup": LatentKeyframeBatchedGroupNode, + "LatentKeyframeTiming": LatentKeyframeInterpolationNode, + # Loaders + "ControlNetLoaderAdvanced": ControlNetLoaderAdvanced, + "DiffControlNetLoaderAdvanced": DiffControlNetLoaderAdvanced, + # Weights + "ScaledSoftControlNetWeights": ScaledSoftControlNetWeights, + "SoftControlNetWeights": SoftControlNetWeights, + "CustomControlNetWeights": CustomControlNetWeights, + "SoftT2IAdapterWeights": SoftT2IAdapterWeights, + "CustomT2IAdapterWeights": CustomT2IAdapterWeights, + # Image + "LoadImagesFromDirectory": LoadImagesFromDirectory +} + +NODE_DISPLAY_NAME_MAPPINGS = { + # Keyframes + "TimestepKeyframe": "Timestep Keyframe 🛂🅐🅒🅝", + "LatentKeyframe": "Latent Keyframe 🛂🅐🅒🅝", + "LatentKeyframeGroup": "Latent Keyframe Group 🛂🅐🅒🅝", + "LatentKeyframeBatchedGroup": "Latent Keyframe Batched Group 🛂🅐🅒🅝", + "LatentKeyframeTiming": "Latent Keyframe Interpolation 🛂🅐🅒🅝", + # Loaders + "ControlNetLoaderAdvanced": "Load ControlNet Model (Advanced) 🛂🅐🅒🅝", + "DiffControlNetLoaderAdvanced": "Load ControlNet Model (diff Advanced) 🛂🅐🅒🅝", + # Weights + "ScaledSoftControlNetWeights": "Scaled Soft ControlNet Weights 🛂🅐🅒🅝", + "SoftControlNetWeights": "Soft ControlNet Weights 🛂🅐🅒🅝", + "CustomControlNetWeights": "Custom ControlNet Weights 🛂🅐🅒🅝", + "SoftT2IAdapterWeights": "Soft T2IAdapter Weights 🛂🅐🅒🅝", + "CustomT2IAdapterWeights": "Custom T2IAdapter Weights 🛂🅐🅒🅝", + # Image + "LoadImagesFromDirectory": "Load Images [DEPRECATED] 🛂🅐🅒🅝" +} diff --git a/control/weight_nodes.py b/control/weight_nodes.py new file mode 100644 index 0000000..2015c8a --- /dev/null +++ b/control/weight_nodes.py @@ -0,0 +1,157 @@ +from .control import TimestepKeyframe, TimestepKeyframeGroup +from .logger import logger + + +def get_properly_arranged_t2i_weights(initial_weights: list[float]): + new_weights = [] + new_weights.extend([initial_weights[0]]*3) + new_weights.extend([initial_weights[1]]*3) + new_weights.extend([initial_weights[2]]*3) + new_weights.extend([initial_weights[3]]*3) + return new_weights + + +class ScaledSoftControlNetWeights: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "base_multiplier": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "flip_weights": ("BOOLEAN", {"default": False}), + }, + } + + RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",) + FUNCTION = "load_weights" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" + + def load_weights(self, base_multiplier, flip_weights): + weights = [(base_multiplier ** float(12 - i)) for i in range(13)] + if flip_weights: + weights.reverse() + return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_net_weights=weights))) + + +class SoftControlNetWeights: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "weight_00": ("FLOAT", {"default": 0.09941396206337118, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_01": ("FLOAT", {"default": 0.12050177219802567, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_02": ("FLOAT", {"default": 0.14606275417942507, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_03": ("FLOAT", {"default": 0.17704576264172736, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_04": ("FLOAT", {"default": 0.214600924414215, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_05": ("FLOAT", {"default": 0.26012233262329093, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_06": ("FLOAT", {"default": 0.3152997971191405, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_07": ("FLOAT", {"default": 0.3821815722656249, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_08": ("FLOAT", {"default": 0.4632503906249999, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_09": ("FLOAT", {"default": 0.561515625, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_10": ("FLOAT", {"default": 0.6806249999999999, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_11": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_12": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "flip_weights": ("BOOLEAN", {"default": False}), + }, + } + + RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",) + FUNCTION = "load_weights" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" + + def load_weights(self, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, + weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights): + weights = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, + weight_07, weight_08, weight_09, weight_10, weight_11, weight_12] + if flip_weights: + weights.reverse() + return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_net_weights=weights))) + + +class CustomControlNetWeights: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "weight_00": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_01": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_02": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_04": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_05": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_06": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_07": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_08": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_09": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_10": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_11": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_12": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "flip_weights": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",) + FUNCTION = "load_weights" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" + + def load_weights(self, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, + weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights): + weights = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, + weight_07, weight_08, weight_09, weight_10, weight_11, weight_12] + if flip_weights: + weights.reverse() + return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_net_weights=weights))) + + +class SoftT2IAdapterWeights: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "weight_00": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_01": ("FLOAT", {"default": 0.62, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_02": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "flip_weights": ("BOOLEAN", {"default": False}), + }, + } + + RETURN_TYPES = ("T2I_ADAPTER_WEIGHTS", "TIMESTEP_KEYFRAME",) + FUNCTION = "load_weights" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" + + def load_weights(self, weight_00, weight_01, weight_02, weight_03, flip_weights): + weights = [weight_00, weight_01, weight_02, weight_03] + if flip_weights: + weights.reverse() + weights = get_properly_arranged_t2i_weights(weights) + return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(t2i_adapter_weights=weights))) + + +class CustomT2IAdapterWeights: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "weight_00": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_01": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_02": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), + "flip_weights": ("BOOLEAN", {"default": False}), + }, + } + + RETURN_TYPES = ("T2I_ADAPTER_WEIGHTS", "TIMESTEP_KEYFRAME",) + FUNCTION = "load_weights" + + CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" + + def load_weights(self, weight_00, weight_01, weight_02, weight_03, flip_weights): + weights = [weight_00, weight_01, weight_02, weight_03] + if flip_weights: + weights.reverse() + weights = get_properly_arranged_t2i_weights(weights) + return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(t2i_adapter_weights=weights))) diff --git a/nodes.py b/nodes.py deleted file mode 100644 index 2bfba08..0000000 --- a/nodes.py +++ /dev/null @@ -1,605 +0,0 @@ -import sys -import os - -import torch - -import numpy as np -from PIL import Image, ImageOps - -import folder_paths - -from .control import ControlNetAdvanced, T2IAdapterAdvanced, load_controlnet, ControlNetWeightsType, T2IAdapterWeightsType,\ - LatentKeyframe, LatentKeyframeGroup, TimestepKeyframe, TimestepKeyframeGroup -from .logger import logger - -def get_properly_arranged_t2i_weights(initial_weights: list[float]): - new_weights = [] - new_weights.extend([initial_weights[0]]*3) - new_weights.extend([initial_weights[1]]*3) - new_weights.extend([initial_weights[2]]*3) - new_weights.extend([initial_weights[3]]*3) - return new_weights - - -class ScaledSoftControlNetWeights: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "base_multiplier": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "flip_weights": ("BOOLEAN", {"default": False}), - }, - } - - RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",) - FUNCTION = "load_weights" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" - - def load_weights(self, base_multiplier, flip_weights): - weights = [(base_multiplier ** float(12 - i)) for i in range(13)] - if flip_weights: - weights.reverse() - return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_net_weights=weights))) - - -class SoftControlNetWeights: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "weight_00": ("FLOAT", {"default": 0.09941396206337118, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_01": ("FLOAT", {"default": 0.12050177219802567, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_02": ("FLOAT", {"default": 0.14606275417942507, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_03": ("FLOAT", {"default": 0.17704576264172736, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_04": ("FLOAT", {"default": 0.214600924414215, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_05": ("FLOAT", {"default": 0.26012233262329093, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_06": ("FLOAT", {"default": 0.3152997971191405, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_07": ("FLOAT", {"default": 0.3821815722656249, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_08": ("FLOAT", {"default": 0.4632503906249999, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_09": ("FLOAT", {"default": 0.561515625, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_10": ("FLOAT", {"default": 0.6806249999999999, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_11": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_12": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "flip_weights": ("BOOLEAN", {"default": False}), - }, - } - - RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",) - FUNCTION = "load_weights" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" - - def load_weights(self, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, - weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights): - weights = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, - weight_07, weight_08, weight_09, weight_10, weight_11, weight_12] - if flip_weights: - weights.reverse() - return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_net_weights=weights))) - - -class CustomControlNetWeights: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "weight_00": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_01": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_02": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_04": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_05": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_06": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_07": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_08": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_09": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_10": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_11": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_12": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "flip_weights": ("BOOLEAN", {"default": False}), - } - } - - RETURN_TYPES = ("CONTROL_NET_WEIGHTS", "TIMESTEP_KEYFRAME",) - FUNCTION = "load_weights" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" - - def load_weights(self, weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, - weight_07, weight_08, weight_09, weight_10, weight_11, weight_12, flip_weights): - weights = [weight_00, weight_01, weight_02, weight_03, weight_04, weight_05, weight_06, - weight_07, weight_08, weight_09, weight_10, weight_11, weight_12] - if flip_weights: - weights.reverse() - return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(control_net_weights=weights))) - - -class SoftT2IAdapterWeights: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "weight_00": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_01": ("FLOAT", {"default": 0.62, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_02": ("FLOAT", {"default": 0.825, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "flip_weights": ("BOOLEAN", {"default": False}), - }, - } - - RETURN_TYPES = ("T2I_ADAPTER_WEIGHTS", "TIMESTEP_KEYFRAME",) - FUNCTION = "load_weights" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" - - def load_weights(self, weight_00, weight_01, weight_02, weight_03, flip_weights): - weights = [weight_00, weight_01, weight_02, weight_03] - if flip_weights: - weights.reverse() - weights = get_properly_arranged_t2i_weights(weights) - return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(t2i_adapter_weights=weights))) - - -class CustomT2IAdapterWeights: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "weight_00": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_01": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_02": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "weight_03": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "flip_weights": ("BOOLEAN", {"default": False}), - }, - } - - RETURN_TYPES = ("T2I_ADAPTER_WEIGHTS", "TIMESTEP_KEYFRAME",) - FUNCTION = "load_weights" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/weights" - - def load_weights(self, weight_00, weight_01, weight_02, weight_03, flip_weights): - weights = [weight_00, weight_01, weight_02, weight_03] - if flip_weights: - weights.reverse() - weights = get_properly_arranged_t2i_weights(weights) - return (weights, TimestepKeyframeGroup.default(TimestepKeyframe(t2i_adapter_weights=weights))) - - -class TimestepKeyframeNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}, ), - }, - "optional": { - "control_net_weights": ("CONTROL_NET_WEIGHTS", ), - "t2i_adapter_weights": ("T2I_ADAPTER_WEIGHTS", ), - "latent_keyframe": ("LATENT_KEYFRAME", ), - "prev_timestep_keyframe": ("TIMESTEP_KEYFRAME", ), - } - } - - RETURN_TYPES = ("TIMESTEP_KEYFRAME", ) - FUNCTION = "load_keyframe" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" - - def load_keyframe(self, - start_percent: float, - control_net_weights: ControlNetWeightsType=None, - t2i_adapter_weights: T2IAdapterWeightsType=None, - latent_keyframe: LatentKeyframeGroup=None, - prev_timestep_keyframe: TimestepKeyframeGroup=None): - if not prev_timestep_keyframe: - prev_timestep_keyframe = TimestepKeyframeGroup() - keyframe = TimestepKeyframe(start_percent, control_net_weights, t2i_adapter_weights, latent_keyframe) - prev_timestep_keyframe.add(keyframe) - return (prev_timestep_keyframe,) - - -class LatentKeyframeNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "batch_index": ("INT", {"default": 0, "min": -1000, "max": 1000, "step": 1}), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.00001}, ), - }, - "optional": { - "prev_latent_keyframe": ("LATENT_KEYFRAME", ), - } - } - - RETURN_TYPES = ("LATENT_KEYFRAME", ) - FUNCTION = "load_keyframe" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" - - def load_keyframe(self, - batch_index: int, - strength: float, - prev_latent_keyframe: LatentKeyframeGroup=None): - if not prev_latent_keyframe: - prev_latent_keyframe = LatentKeyframeGroup() - keyframe = LatentKeyframe(batch_index, strength) - prev_latent_keyframe.add(keyframe) - return (prev_latent_keyframe,) - - -class LatentKeyframeGroupNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "index_strengths": ("STRING", {"multiline": True, "default": ""}), - }, - "optional": { - "prev_latent_keyframe": ("LATENT_KEYFRAME", ), - "latent_optional": ("LATENT", ), - } - } - - RETURN_TYPES = ("LATENT_KEYFRAME", ) - FUNCTION = "load_keyframes" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" - - def validate_index(self, index: int, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int: - # if part of range, do nothing - if is_range: - return index - # otherwise, validate index - # validate not out of range - only when latent_count is passed in - if latent_count > 0 and index > latent_count-1: - raise IndexError(f"Index '{index}' out of range for the total {latent_count} latents.") - # if negative, validate not out of range - if index < 0: - if not allow_negative: - raise IndexError(f"Negative indeces not allowed, but was {index}.") - conv_index = latent_count+index - if conv_index < 0: - raise IndexError(f"Index '{index}', converted to '{conv_index}' out of range for the total {latent_count} latents.") - index = conv_index - return index - - def convert_to_index_int(self, raw_index: str, latent_count: int = 0, is_range: bool = False, allow_negative = False) -> int: - try: - return self.validate_index(int(raw_index), latent_count=latent_count, is_range=is_range, allow_negative=allow_negative) - except ValueError as e: - raise ValueError(f"index '{raw_index}' must be an integer.", e) - - def convert_to_latent_keyframes(self, latent_indeces: str, latent_count: int) -> set[LatentKeyframe]: - if not latent_indeces: - return set() - all_indeces = [i for i in range(0, latent_count)] - allow_negative = latent_count > 0 - chosen_indeces = set() - # parse string - allow positive ints, negative ints, and ranges separated by ':' - groups = latent_indeces.split(",") - groups = [g.strip() for g in groups] - for g in groups: - # parse strengths - default to 1.0 if no strength given - strength = 1.0 - if '=' in g: - g, strength_str = g.split("=", 1) - g = g.strip() - try: - strength = float(strength_str.strip()) - except ValueError as e: - raise ValueError(f"strength '{strength_str}' must be a float.", e) - if strength < 0: - raise ValueError(f"Strength '{strength}' cannot be negative.") - # parse range of indeces (e.g. 2:16) - if ':' in g: - index_range = g.split(":", 1) - index_range = [r.strip() for r in index_range] - start_index = self.convert_to_index_int(index_range[0], latent_count=latent_count, is_range=True, allow_negative=allow_negative) - end_index = self.convert_to_index_int(index_range[1], latent_count=latent_count, is_range=True, allow_negative=allow_negative) - for i in all_indeces[start_index:end_index]: - chosen_indeces.add(LatentKeyframe(i, strength)) - # parse individual indeces - else: - chosen_indeces.add(LatentKeyframe(self.convert_to_index_int(g, latent_count=latent_count, allow_negative=allow_negative), strength)) - return chosen_indeces - - def load_keyframes(self, - index_strengths: str, - prev_latent_keyframe: LatentKeyframeGroup=None, - latent_image_opt=None): - if not prev_latent_keyframe: - prev_latent_keyframe = LatentKeyframeGroup() - curr_latent_keyframe = LatentKeyframeGroup() - - latent_count = -1 - if latent_image_opt: - latent_count = latent_image_opt['samples'].size()[0] - latent_keyframes = self.convert_to_latent_keyframes(index_strengths, latent_count=latent_count) - - for latent_keyframe in latent_keyframes: - logger.info(f"keyframe {latent_keyframe.batch_index}:{latent_keyframe.strength}") - curr_latent_keyframe.add(latent_keyframe) - - for latent_keyframe in prev_latent_keyframe.keyframes: - curr_latent_keyframe.add(latent_keyframe) - - return (curr_latent_keyframe,) - - -class LatentKeyframeInterpolationNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "batch_index_from": ("INT", {"default": 0, "min": -10000, "max": 10000, "step": 1}), - "batch_index_to_excl": ("INT", {"default": 0, "min": -10000, "max": 10000, "step": 1}), - "strength_from": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.0001}, ), - "strength_to": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.0001}, ), - "interpolation": (["linear", "ease-in", "ease-out", "ease-in-out"], ), - }, - "optional": { - "prev_latent_keyframe": ("LATENT_KEYFRAME", ), - } - } - - RETURN_TYPES = ("LATENT_KEYFRAME", ) - FUNCTION = "load_keyframe" - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/keyframes" - - def load_keyframe(self, - batch_index_from: int, - strength_from: float, - batch_index_to_excl: int, - strength_to: float, - interpolation: str, - prev_latent_keyframe: LatentKeyframeGroup=None): - - if (batch_index_from > batch_index_to_excl): - raise ValueError("batch_index_from must be less than or equal to batch_index_to.") - - if (batch_index_from < 0 and batch_index_to_excl >= 0): - raise ValueError("batch_index_from and batch_index_to must be either both positive or both negative.") - - if not prev_latent_keyframe: - prev_latent_keyframe = LatentKeyframeGroup() - curr_latent_keyframe = LatentKeyframeGroup() - - steps = batch_index_to_excl - batch_index_from - diff = strength_to - strength_from - if interpolation == "linear": - weights = np.linspace(strength_from, strength_to, steps) - elif interpolation == "ease-in": - index = np.linspace(0, 1, steps) - weights = diff * np.power(index, 2) + strength_from - elif interpolation == "ease-out": - index = np.linspace(0, 1, steps) - weights = diff * (1 - np.power(1 - index, 2)) + strength_from - elif interpolation == "ease-in-out": - index = np.linspace(0, 1, steps) - weights = diff * ((1 - np.cos(index * np.pi)) / 2) + strength_from - - for i in range(steps): - keyframe = LatentKeyframe(batch_index_from + i, float(weights[i])) - logger.info(f"keyframe {batch_index_from + i}:{weights[i]}") - curr_latent_keyframe.add(keyframe) - - # replace values with prev_latent_keyframes - for latent_keyframe in prev_latent_keyframe.keyframes: - curr_latent_keyframe.add(latent_keyframe) - - return (curr_latent_keyframe,) - - -class ControlNetLoaderAdvanced: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "control_net_name": (folder_paths.get_filename_list("controlnet"), ), - }, - "optional": { - "timestep_keyframe": ("TIMESTEP_KEYFRAME", ), - } - } - - RETURN_TYPES = ("CONTROL_NET", ) - FUNCTION = "load_controlnet" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/loaders" - - def load_controlnet(self, control_net_name, timestep_keyframe: TimestepKeyframeGroup=None): - controlnet_path = folder_paths.get_full_path("controlnet", control_net_name) - controlnet = load_controlnet(controlnet_path, timestep_keyframe) - return (controlnet,) - - -class DiffControlNetLoaderAdvanced: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "control_net_name": (folder_paths.get_filename_list("controlnet"), ) - }, - "optional": { - "timestep_keyframe": ("TIMESTEP_KEYFRAME", ), - } - } - - RETURN_TYPES = ("CONTROL_NET", ) - FUNCTION = "load_controlnet" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/loaders" - - def load_controlnet(self, control_net_name, timestep_keyframe: TimestepKeyframeGroup, model): - controlnet_path = folder_paths.get_full_path("controlnet", control_net_name) - controlnet = load_controlnet(controlnet_path, timestep_keyframe, model) - return (controlnet,) - - -class ControlNetApplyAdvanced_AdvControlNet: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "control_net": ("CONTROL_NET", ), - "image": ("IMAGE", ), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}) - }, - "optional": { - "mask_opt": ("MASK", ), - } - } - - RETURN_TYPES = ("CONDITIONING","CONDITIONING") - RETURN_NAMES = ("positive", "negative") - FUNCTION = "apply_controlnet" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/loaders/conditioning" - - def apply_controlnet(self, positive, negative, control_net, image, strength, start_percent, end_percent, mask_opt=None): - if strength == 0: - return (positive, negative) - - if mask_opt is not None: - mask_hint = mask_opt.movedim(-1,1) - control_hint = image.movedim(-1,1) - cnets = {} - - out = [] - for conditioning in [positive, negative]: - c = [] - for t in conditioning: - d = t[1].copy() - - prev_cnet = d.get('control', None) - if prev_cnet in cnets: - c_net = cnets[prev_cnet] - else: - c_net = control_net.copy().set_cond_hint(control_hint, strength, (1.0 - start_percent, 1.0 - end_percent)) - # TODO: finish mask implemention, does nothing right now - if mask_opt is not None: - if isinstance(c_net, ControlNetAdvanced) or isinstance(c_net, T2IAdapterAdvanced): - c_net.set_cond_hint_mask(mask_hint) - else: - logger - c_net.set_previous_controlnet(prev_cnet) - cnets[prev_cnet] = c_net - - d['control'] = c_net - d['control_apply_to_uncond'] = False - n = [t[0], d] - c.append(n) - out.append(c) - return (out[0], out[1]) - - -class LoadImagesFromDirectory: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "directory": ("STRING", {"default": ""}), - }, - "optional": { - "image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}), - "start_index": ("INT", {"default": 0, "min": 0, "step": 1}), - } - } - - RETURN_TYPES = ("IMAGE", "MASK", "INT") - FUNCTION = "load_images" - - CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/deprecated" - - def load_images(self, directory: str, image_load_cap: int = 0, start_index: int = 0): - if not os.path.isdir(directory): - raise FileNotFoundError(f"Directory '{directory} cannot be found.'") - dir_files = os.listdir(directory) - if len(dir_files) == 0: - raise FileNotFoundError(f"No files in directory '{directory}'.") - - dir_files = sorted(dir_files) - dir_files = [os.path.join(directory, x) for x in dir_files] - # start at start_index - dir_files = dir_files[start_index:] - - images = [] - masks = [] - - limit_images = False - if image_load_cap > 0: - limit_images = True - image_count = 0 - - for image_path in dir_files: - if os.path.isdir(image_path): - continue - if limit_images and image_count >= image_load_cap: - break - i = Image.open(image_path) - i = ImageOps.exif_transpose(i) - image = i.convert("RGB") - image = np.array(image).astype(np.float32) / 255.0 - image = torch.from_numpy(image)[None,] - if 'A' in i.getbands(): - mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0 - mask = 1. - torch.from_numpy(mask) - else: - mask = torch.zeros((64,64), dtype=torch.float32, device="cpu") - images.append(image) - masks.append(mask) - image_count += 1 - - if len(images) == 0: - raise FileNotFoundError(f"No images could be loaded from directory '{directory}'.") - - return (torch.cat(images, dim=0), torch.stack(masks, dim=0), image_count) - - - - -# NODE MAPPING -NODE_CLASS_MAPPINGS = { - # Keyframes - "TimestepKeyframe": TimestepKeyframeNode, - "LatentKeyframe": LatentKeyframeNode, - "LatentKeyframeGroup": LatentKeyframeGroupNode, - "LatentKeyframeTiming": LatentKeyframeInterpolationNode, - # Loaders - "ControlNetLoaderAdvanced": ControlNetLoaderAdvanced, - "DiffControlNetLoaderAdvanced": DiffControlNetLoaderAdvanced, - # Weights - "ScaledSoftControlNetWeights": ScaledSoftControlNetWeights, - "SoftControlNetWeights": SoftControlNetWeights, - "CustomControlNetWeights": CustomControlNetWeights, - "SoftT2IAdapterWeights": SoftT2IAdapterWeights, - "CustomT2IAdapterWeights": CustomT2IAdapterWeights, - # Image - "LoadImagesFromDirectory": LoadImagesFromDirectory -} - -NODE_DISPLAY_NAME_MAPPINGS = { - # Keyframes - "TimestepKeyframe": "Timestep Keyframe 🛂🅐🅒🅝", - "LatentKeyframe": "Latent Keyframe 🛂🅐🅒🅝", - "LatentKeyframeGroup": "Latent Keyframe Group 🛂🅐🅒🅝", - "LatentKeyframeTiming": "Latent Keyframe Interpolation 🛂🅐🅒🅝", - # Loaders - "ControlNetLoaderAdvanced": "Load ControlNet Model (Advanced) 🛂🅐🅒🅝", - "DiffControlNetLoaderAdvanced": "Load ControlNet Model (diff Advanced) 🛂🅐🅒🅝", - # Weights - "ScaledSoftControlNetWeights": "Scaled Soft ControlNet Weights 🛂🅐🅒🅝", - "SoftControlNetWeights": "Soft ControlNet Weights 🛂🅐🅒🅝", - "CustomControlNetWeights": "Custom ControlNet Weights 🛂🅐🅒🅝", - "SoftT2IAdapterWeights": "Soft T2IAdapter Weights 🛂🅐🅒🅝", - "CustomT2IAdapterWeights": "Custom T2IAdapter Weights 🛂🅐🅒🅝", - # Image - "LoadImagesFromDirectory": "Load Images [DEPRECATED] 🛂🅐🅒🅝" -}