diff --git a/comfy_extras/chainner_models/model_loading.py b/comfy_extras/chainner_models/model_loading.py deleted file mode 100644 index 1bec4476f6171e9f1b2a4a3b967fc63bbf9e9c3c..0000000000000000000000000000000000000000 --- a/comfy_extras/chainner_models/model_loading.py +++ /dev/null @@ -1,6 +0,0 @@ -import logging -from spandrel import ModelLoader - -def load_state_dict(state_dict): - logging.warning("comfy_extras.chainner_models is deprecated and has been replaced by the spandrel library.") - return ModelLoader().load_from_state_dict(state_dict).eval() diff --git a/comfy_extras/nodes_ace.py b/comfy_extras/nodes_ace.py deleted file mode 100644 index cbfec15a2198e542171508704e931021d6df569c..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_ace.py +++ /dev/null @@ -1,49 +0,0 @@ -import torch -import comfy.model_management -import node_helpers - -class TextEncodeAceStepAudio: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "clip": ("CLIP", ), - "tags": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "lyrics": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "lyrics_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "conditioning" - - def encode(self, clip, tags, lyrics, lyrics_strength): - tokens = clip.tokenize(tags, lyrics=lyrics) - conditioning = clip.encode_from_tokens_scheduled(tokens) - conditioning = node_helpers.conditioning_set_values(conditioning, {"lyrics_strength": lyrics_strength}) - return (conditioning, ) - - -class EmptyAceStepLatentAudio: - def __init__(self): - self.device = comfy.model_management.intermediate_device() - - @classmethod - def INPUT_TYPES(s): - return {"required": {"seconds": ("FLOAT", {"default": 120.0, "min": 1.0, "max": 1000.0, "step": 0.1}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}), - }} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/audio" - - def generate(self, seconds, batch_size): - length = int(seconds * 44100 / 512 / 8) - latent = torch.zeros([batch_size, 8, 16, length], device=self.device) - return ({"samples": latent, "type": "audio"}, ) - - -NODE_CLASS_MAPPINGS = { - "TextEncodeAceStepAudio": TextEncodeAceStepAudio, - "EmptyAceStepLatentAudio": EmptyAceStepLatentAudio, -} diff --git a/comfy_extras/nodes_advanced_samplers.py b/comfy_extras/nodes_advanced_samplers.py deleted file mode 100644 index 5fbb096fbf808e0c6d96159bee024873fb1f4c3e..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_advanced_samplers.py +++ /dev/null @@ -1,111 +0,0 @@ -import comfy.samplers -import comfy.utils -import torch -import numpy as np -from tqdm.auto import trange - - -@torch.no_grad() -def sample_lcm_upscale(model, x, sigmas, extra_args=None, callback=None, disable=None, total_upscale=2.0, upscale_method="bislerp", upscale_steps=None): - extra_args = {} if extra_args is None else extra_args - - if upscale_steps is None: - upscale_steps = max(len(sigmas) // 2 + 1, 2) - else: - upscale_steps += 1 - upscale_steps = min(upscale_steps, len(sigmas) + 1) - - upscales = np.linspace(1.0, total_upscale, upscale_steps)[1:] - - orig_shape = x.size() - s_in = x.new_ones([x.shape[0]]) - for i in trange(len(sigmas) - 1, disable=disable): - denoised = model(x, sigmas[i] * s_in, **extra_args) - if callback is not None: - callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) - - x = denoised - if i < len(upscales): - x = comfy.utils.common_upscale(x, round(orig_shape[-1] * upscales[i]), round(orig_shape[-2] * upscales[i]), upscale_method, "disabled") - - if sigmas[i + 1] > 0: - x += sigmas[i + 1] * torch.randn_like(x) - return x - - -class SamplerLCMUpscale: - upscale_methods = ["bislerp", "nearest-exact", "bilinear", "area", "bicubic"] - - @classmethod - def INPUT_TYPES(s): - return {"required": - {"scale_ratio": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 20.0, "step": 0.01}), - "scale_steps": ("INT", {"default": -1, "min": -1, "max": 1000, "step": 1}), - "upscale_method": (s.upscale_methods,), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, scale_ratio, scale_steps, upscale_method): - if scale_steps < 0: - scale_steps = None - sampler = comfy.samplers.KSAMPLER(sample_lcm_upscale, extra_options={"total_upscale": scale_ratio, "upscale_steps": scale_steps, "upscale_method": upscale_method}) - return (sampler, ) - -from comfy.k_diffusion.sampling import to_d -import comfy.model_patcher - -@torch.no_grad() -def sample_euler_pp(model, x, sigmas, extra_args=None, callback=None, disable=None): - extra_args = {} if extra_args is None else extra_args - - temp = [0] - def post_cfg_function(args): - temp[0] = args["uncond_denoised"] - return args["denoised"] - - model_options = extra_args.get("model_options", {}).copy() - extra_args["model_options"] = comfy.model_patcher.set_model_options_post_cfg_function(model_options, post_cfg_function, disable_cfg1_optimization=True) - - s_in = x.new_ones([x.shape[0]]) - for i in trange(len(sigmas) - 1, disable=disable): - sigma_hat = sigmas[i] - denoised = model(x, sigma_hat * s_in, **extra_args) - d = to_d(x - denoised + temp[0], sigmas[i], denoised) - if callback is not None: - callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised}) - dt = sigmas[i + 1] - sigma_hat - x = x + d * dt - return x - - -class SamplerEulerCFGpp: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"version": (["regular", "alternative"],),} - } - RETURN_TYPES = ("SAMPLER",) - # CATEGORY = "sampling/custom_sampling/samplers" - CATEGORY = "_for_testing" - - FUNCTION = "get_sampler" - - def get_sampler(self, version): - if version == "alternative": - sampler = comfy.samplers.KSAMPLER(sample_euler_pp) - else: - sampler = comfy.samplers.ksampler("euler_cfg_pp") - return (sampler, ) - -NODE_CLASS_MAPPINGS = { - "SamplerLCMUpscale": SamplerLCMUpscale, - "SamplerEulerCFGpp": SamplerEulerCFGpp, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "SamplerEulerCFGpp": "SamplerEulerCFG++", -} diff --git a/comfy_extras/nodes_align_your_steps.py b/comfy_extras/nodes_align_your_steps.py deleted file mode 100644 index 8d856d0e8592414df823af27d53d421af7753f27..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_align_your_steps.py +++ /dev/null @@ -1,53 +0,0 @@ -#from: https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html -import numpy as np -import torch - -def loglinear_interp(t_steps, num_steps): - """ - Performs log-linear interpolation of a given array of decreasing numbers. - """ - xs = np.linspace(0, 1, len(t_steps)) - ys = np.log(t_steps[::-1]) - - new_xs = np.linspace(0, 1, num_steps) - new_ys = np.interp(new_xs, xs, ys) - - interped_ys = np.exp(new_ys)[::-1].copy() - return interped_ys - -NOISE_LEVELS = {"SD1": [14.6146412293, 6.4745760956, 3.8636745985, 2.6946151520, 1.8841921177, 1.3943805092, 0.9642583904, 0.6523686016, 0.3977456272, 0.1515232662, 0.0291671582], - "SDXL":[14.6146412293, 6.3184485287, 3.7681790315, 2.1811480769, 1.3405244945, 0.8620721141, 0.5550693289, 0.3798540708, 0.2332364134, 0.1114188177, 0.0291671582], - "SVD": [700.00, 54.5, 15.886, 7.977, 4.248, 1.789, 0.981, 0.403, 0.173, 0.034, 0.002]} - -class AlignYourStepsScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model_type": (["SD1", "SDXL", "SVD"], ), - "steps": ("INT", {"default": 10, "min": 1, "max": 10000}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, model_type, steps, denoise): - total_steps = steps - if denoise < 1.0: - if denoise <= 0.0: - return (torch.FloatTensor([]),) - total_steps = round(steps * denoise) - - sigmas = NOISE_LEVELS[model_type][:] - if (steps + 1) != len(sigmas): - sigmas = loglinear_interp(sigmas, steps + 1) - - sigmas = sigmas[-(total_steps + 1):] - sigmas[-1] = 0 - return (torch.FloatTensor(sigmas), ) - -NODE_CLASS_MAPPINGS = { - "AlignYourStepsScheduler": AlignYourStepsScheduler, -} diff --git a/comfy_extras/nodes_apg.py b/comfy_extras/nodes_apg.py deleted file mode 100644 index 25b21b1b8b2b6ecc61911e7c594be59deeab60dc..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_apg.py +++ /dev/null @@ -1,76 +0,0 @@ -import torch - -def project(v0, v1): - v1 = torch.nn.functional.normalize(v1, dim=[-1, -2, -3]) - v0_parallel = (v0 * v1).sum(dim=[-1, -2, -3], keepdim=True) * v1 - v0_orthogonal = v0 - v0_parallel - return v0_parallel, v0_orthogonal - -class APG: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "eta": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "tooltip": "Controls the scale of the parallel guidance vector. Default CFG behavior at a setting of 1."}), - "norm_threshold": ("FLOAT", {"default": 5.0, "min": 0.0, "max": 50.0, "step": 0.1, "tooltip": "Normalize guidance vector to this value, normalization disable at a setting of 0."}), - "momentum": ("FLOAT", {"default": 0.0, "min": -5.0, "max": 1.0, "step": 0.01, "tooltip":"Controls a running average of guidance during diffusion, disabled at a setting of 0."}), - } - } - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - CATEGORY = "sampling/custom_sampling" - - def patch(self, model, eta, norm_threshold, momentum): - running_avg = 0 - prev_sigma = None - - def pre_cfg_function(args): - nonlocal running_avg, prev_sigma - - if len(args["conds_out"]) == 1: return args["conds_out"] - - cond = args["conds_out"][0] - uncond = args["conds_out"][1] - sigma = args["sigma"][0] - cond_scale = args["cond_scale"] - - if prev_sigma is not None and sigma > prev_sigma: - running_avg = 0 - prev_sigma = sigma - - guidance = cond - uncond - - if momentum != 0: - if not torch.is_tensor(running_avg): - running_avg = guidance - else: - running_avg = momentum * running_avg + guidance - guidance = running_avg - - if norm_threshold > 0: - guidance_norm = guidance.norm(p=2, dim=[-1, -2, -3], keepdim=True) - scale = torch.minimum( - torch.ones_like(guidance_norm), - norm_threshold / guidance_norm - ) - guidance = guidance * scale - - guidance_parallel, guidance_orthogonal = project(guidance, cond) - modified_guidance = guidance_orthogonal + eta * guidance_parallel - - modified_cond = (uncond + modified_guidance) + (cond - uncond) / cond_scale - - return [modified_cond, uncond] + args["conds_out"][2:] - - m = model.clone() - m.set_model_sampler_pre_cfg_function(pre_cfg_function) - return (m,) - -NODE_CLASS_MAPPINGS = { - "APG": APG, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "APG": "Adaptive Projected Guidance", -} diff --git a/comfy_extras/nodes_attention_multiply.py b/comfy_extras/nodes_attention_multiply.py deleted file mode 100644 index 4747eb39568afe9e19d34fd583531fcab1acba69..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_attention_multiply.py +++ /dev/null @@ -1,120 +0,0 @@ - -def attention_multiply(attn, model, q, k, v, out): - m = model.clone() - sd = model.model_state_dict() - - for key in sd: - if key.endswith("{}.to_q.bias".format(attn)) or key.endswith("{}.to_q.weight".format(attn)): - m.add_patches({key: (None,)}, 0.0, q) - if key.endswith("{}.to_k.bias".format(attn)) or key.endswith("{}.to_k.weight".format(attn)): - m.add_patches({key: (None,)}, 0.0, k) - if key.endswith("{}.to_v.bias".format(attn)) or key.endswith("{}.to_v.weight".format(attn)): - m.add_patches({key: (None,)}, 0.0, v) - if key.endswith("{}.to_out.0.bias".format(attn)) or key.endswith("{}.to_out.0.weight".format(attn)): - m.add_patches({key: (None,)}, 0.0, out) - - return m - - -class UNetSelfAttentionMultiply: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "_for_testing/attention_experiments" - - def patch(self, model, q, k, v, out): - m = attention_multiply("attn1", model, q, k, v, out) - return (m, ) - -class UNetCrossAttentionMultiply: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "_for_testing/attention_experiments" - - def patch(self, model, q, k, v, out): - m = attention_multiply("attn2", model, q, k, v, out) - return (m, ) - -class CLIPAttentionMultiply: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip": ("CLIP",), - "q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("CLIP",) - FUNCTION = "patch" - - CATEGORY = "_for_testing/attention_experiments" - - def patch(self, clip, q, k, v, out): - m = clip.clone() - sd = m.patcher.model_state_dict() - - for key in sd: - if key.endswith("self_attn.q_proj.weight") or key.endswith("self_attn.q_proj.bias"): - m.add_patches({key: (None,)}, 0.0, q) - if key.endswith("self_attn.k_proj.weight") or key.endswith("self_attn.k_proj.bias"): - m.add_patches({key: (None,)}, 0.0, k) - if key.endswith("self_attn.v_proj.weight") or key.endswith("self_attn.v_proj.bias"): - m.add_patches({key: (None,)}, 0.0, v) - if key.endswith("self_attn.out_proj.weight") or key.endswith("self_attn.out_proj.bias"): - m.add_patches({key: (None,)}, 0.0, out) - return (m, ) - -class UNetTemporalAttentionMultiply: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "self_structural": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "self_temporal": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "cross_structural": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "cross_temporal": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "_for_testing/attention_experiments" - - def patch(self, model, self_structural, self_temporal, cross_structural, cross_temporal): - m = model.clone() - sd = model.model_state_dict() - - for k in sd: - if (k.endswith("attn1.to_out.0.bias") or k.endswith("attn1.to_out.0.weight")): - if '.time_stack.' in k: - m.add_patches({k: (None,)}, 0.0, self_temporal) - else: - m.add_patches({k: (None,)}, 0.0, self_structural) - elif (k.endswith("attn2.to_out.0.bias") or k.endswith("attn2.to_out.0.weight")): - if '.time_stack.' in k: - m.add_patches({k: (None,)}, 0.0, cross_temporal) - else: - m.add_patches({k: (None,)}, 0.0, cross_structural) - return (m, ) - -NODE_CLASS_MAPPINGS = { - "UNetSelfAttentionMultiply": UNetSelfAttentionMultiply, - "UNetCrossAttentionMultiply": UNetCrossAttentionMultiply, - "CLIPAttentionMultiply": CLIPAttentionMultiply, - "UNetTemporalAttentionMultiply": UNetTemporalAttentionMultiply, -} diff --git a/comfy_extras/nodes_audio.py b/comfy_extras/nodes_audio.py deleted file mode 100644 index 8cd6478465b6a582f29d7afd81b55efdcbce3924..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_audio.py +++ /dev/null @@ -1,334 +0,0 @@ -from __future__ import annotations - -import av -import torchaudio -import torch -import comfy.model_management -import folder_paths -import os -import io -import json -import random -import hashlib -import node_helpers -from comfy.cli_args import args -from comfy.comfy_types import FileLocator - -class EmptyLatentAudio: - def __init__(self): - self.device = comfy.model_management.intermediate_device() - - @classmethod - def INPUT_TYPES(s): - return {"required": {"seconds": ("FLOAT", {"default": 47.6, "min": 1.0, "max": 1000.0, "step": 0.1}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}), - }} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/audio" - - def generate(self, seconds, batch_size): - length = round((seconds * 44100 / 2048) / 2) * 2 - latent = torch.zeros([batch_size, 64, length], device=self.device) - return ({"samples":latent, "type": "audio"}, ) - -class ConditioningStableAudio: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "seconds_start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.1}), - "seconds_total": ("FLOAT", {"default": 47.0, "min": 0.0, "max": 1000.0, "step": 0.1}), - }} - - RETURN_TYPES = ("CONDITIONING","CONDITIONING") - RETURN_NAMES = ("positive", "negative") - - FUNCTION = "append" - - CATEGORY = "conditioning" - - def append(self, positive, negative, seconds_start, seconds_total): - positive = node_helpers.conditioning_set_values(positive, {"seconds_start": seconds_start, "seconds_total": seconds_total}) - negative = node_helpers.conditioning_set_values(negative, {"seconds_start": seconds_start, "seconds_total": seconds_total}) - return (positive, negative) - -class VAEEncodeAudio: - @classmethod - def INPUT_TYPES(s): - return {"required": { "audio": ("AUDIO", ), "vae": ("VAE", )}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "encode" - - CATEGORY = "latent/audio" - - def encode(self, vae, audio): - sample_rate = audio["sample_rate"] - if 44100 != sample_rate: - waveform = torchaudio.functional.resample(audio["waveform"], sample_rate, 44100) - else: - waveform = audio["waveform"] - - t = vae.encode(waveform.movedim(1, -1)) - return ({"samples":t}, ) - -class VAEDecodeAudio: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples": ("LATENT", ), "vae": ("VAE", )}} - RETURN_TYPES = ("AUDIO",) - FUNCTION = "decode" - - CATEGORY = "latent/audio" - - def decode(self, vae, samples): - audio = vae.decode(samples["samples"]).movedim(-1, 1) - std = torch.std(audio, dim=[1,2], keepdim=True) * 5.0 - std[std < 1.0] = 1.0 - audio /= std - return ({"waveform": audio, "sample_rate": 44100}, ) - - -def save_audio(self, audio, filename_prefix="ComfyUI", format="flac", prompt=None, extra_pnginfo=None, quality="128k"): - - filename_prefix += self.prefix_append - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir) - results: list[FileLocator] = [] - - # Prepare metadata dictionary - metadata = {} - if not args.disable_metadata: - if prompt is not None: - metadata["prompt"] = json.dumps(prompt) - if extra_pnginfo is not None: - for x in extra_pnginfo: - metadata[x] = json.dumps(extra_pnginfo[x]) - - # Opus supported sample rates - OPUS_RATES = [8000, 12000, 16000, 24000, 48000] - - for (batch_number, waveform) in enumerate(audio["waveform"].cpu()): - filename_with_batch_num = filename.replace("%batch_num%", str(batch_number)) - file = f"{filename_with_batch_num}_{counter:05}_.{format}" - output_path = os.path.join(full_output_folder, file) - - # Use original sample rate initially - sample_rate = audio["sample_rate"] - - # Handle Opus sample rate requirements - if format == "opus": - if sample_rate > 48000: - sample_rate = 48000 - elif sample_rate not in OPUS_RATES: - # Find the next highest supported rate - for rate in sorted(OPUS_RATES): - if rate > sample_rate: - sample_rate = rate - break - if sample_rate not in OPUS_RATES: # Fallback if still not supported - sample_rate = 48000 - - # Resample if necessary - if sample_rate != audio["sample_rate"]: - waveform = torchaudio.functional.resample(waveform, audio["sample_rate"], sample_rate) - - # Create output with specified format - output_buffer = io.BytesIO() - output_container = av.open(output_buffer, mode='w', format=format) - - # Set metadata on the container - for key, value in metadata.items(): - output_container.metadata[key] = value - - # Set up the output stream with appropriate properties - if format == "opus": - out_stream = output_container.add_stream("libopus", rate=sample_rate) - if quality == "64k": - out_stream.bit_rate = 64000 - elif quality == "96k": - out_stream.bit_rate = 96000 - elif quality == "128k": - out_stream.bit_rate = 128000 - elif quality == "192k": - out_stream.bit_rate = 192000 - elif quality == "320k": - out_stream.bit_rate = 320000 - elif format == "mp3": - out_stream = output_container.add_stream("libmp3lame", rate=sample_rate) - if quality == "V0": - #TODO i would really love to support V3 and V5 but there doesn't seem to be a way to set the qscale level, the property below is a bool - out_stream.codec_context.qscale = 1 - elif quality == "128k": - out_stream.bit_rate = 128000 - elif quality == "320k": - out_stream.bit_rate = 320000 - else: #format == "flac": - out_stream = output_container.add_stream("flac", rate=sample_rate) - - frame = av.AudioFrame.from_ndarray(waveform.movedim(0, 1).reshape(1, -1).float().numpy(), format='flt', layout='mono' if waveform.shape[0] == 1 else 'stereo') - frame.sample_rate = sample_rate - frame.pts = 0 - output_container.mux(out_stream.encode(frame)) - - # Flush encoder - output_container.mux(out_stream.encode(None)) - - # Close containers - output_container.close() - - # Write the output to file - output_buffer.seek(0) - with open(output_path, 'wb') as f: - f.write(output_buffer.getbuffer()) - - results.append({ - "filename": file, - "subfolder": subfolder, - "type": self.type - }) - counter += 1 - - return { "ui": { "audio": results } } - -class SaveAudio: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - @classmethod - def INPUT_TYPES(s): - return {"required": { "audio": ("AUDIO", ), - "filename_prefix": ("STRING", {"default": "audio/ComfyUI"}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "save_flac" - - OUTPUT_NODE = True - - CATEGORY = "audio" - - def save_flac(self, audio, filename_prefix="ComfyUI", format="flac", prompt=None, extra_pnginfo=None): - return save_audio(self, audio, filename_prefix, format, prompt, extra_pnginfo) - -class SaveAudioMP3: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - @classmethod - def INPUT_TYPES(s): - return {"required": { "audio": ("AUDIO", ), - "filename_prefix": ("STRING", {"default": "audio/ComfyUI"}), - "quality": (["V0", "128k", "320k"], {"default": "V0"}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "save_mp3" - - OUTPUT_NODE = True - - CATEGORY = "audio" - - def save_mp3(self, audio, filename_prefix="ComfyUI", format="mp3", prompt=None, extra_pnginfo=None, quality="128k"): - return save_audio(self, audio, filename_prefix, format, prompt, extra_pnginfo, quality) - -class SaveAudioOpus: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - @classmethod - def INPUT_TYPES(s): - return {"required": { "audio": ("AUDIO", ), - "filename_prefix": ("STRING", {"default": "audio/ComfyUI"}), - "quality": (["64k", "96k", "128k", "192k", "320k"], {"default": "128k"}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "save_opus" - - OUTPUT_NODE = True - - CATEGORY = "audio" - - def save_opus(self, audio, filename_prefix="ComfyUI", format="opus", prompt=None, extra_pnginfo=None, quality="V3"): - return save_audio(self, audio, filename_prefix, format, prompt, extra_pnginfo, quality) - -class PreviewAudio(SaveAudio): - def __init__(self): - self.output_dir = folder_paths.get_temp_directory() - self.type = "temp" - self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) - - @classmethod - def INPUT_TYPES(s): - return {"required": - {"audio": ("AUDIO", ), }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - -class LoadAudio: - @classmethod - def INPUT_TYPES(s): - input_dir = folder_paths.get_input_directory() - files = folder_paths.filter_files_content_types(os.listdir(input_dir), ["audio", "video"]) - return {"required": {"audio": (sorted(files), {"audio_upload": True})}} - - CATEGORY = "audio" - - RETURN_TYPES = ("AUDIO", ) - FUNCTION = "load" - - def load(self, audio): - audio_path = folder_paths.get_annotated_filepath(audio) - waveform, sample_rate = torchaudio.load(audio_path) - audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate} - return (audio, ) - - @classmethod - def IS_CHANGED(s, audio): - image_path = folder_paths.get_annotated_filepath(audio) - m = hashlib.sha256() - with open(image_path, 'rb') as f: - m.update(f.read()) - return m.digest().hex() - - @classmethod - def VALIDATE_INPUTS(s, audio): - if not folder_paths.exists_annotated_filepath(audio): - return "Invalid audio file: {}".format(audio) - return True - -NODE_CLASS_MAPPINGS = { - "EmptyLatentAudio": EmptyLatentAudio, - "VAEEncodeAudio": VAEEncodeAudio, - "VAEDecodeAudio": VAEDecodeAudio, - "SaveAudio": SaveAudio, - "SaveAudioMP3": SaveAudioMP3, - "SaveAudioOpus": SaveAudioOpus, - "LoadAudio": LoadAudio, - "PreviewAudio": PreviewAudio, - "ConditioningStableAudio": ConditioningStableAudio, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "EmptyLatentAudio": "Empty Latent Audio", - "VAEEncodeAudio": "VAE Encode Audio", - "VAEDecodeAudio": "VAE Decode Audio", - "PreviewAudio": "Preview Audio", - "LoadAudio": "Load Audio", - "SaveAudio": "Save Audio (FLAC)", - "SaveAudioMP3": "Save Audio (MP3)", - "SaveAudioOpus": "Save Audio (Opus)", -} diff --git a/comfy_extras/nodes_camera_trajectory.py b/comfy_extras/nodes_camera_trajectory.py deleted file mode 100644 index 5e0e39f914d8ad23eca3b04a433c35decf527546..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_camera_trajectory.py +++ /dev/null @@ -1,218 +0,0 @@ -import nodes -import torch -import numpy as np -from einops import rearrange -import comfy.model_management - - - -MAX_RESOLUTION = nodes.MAX_RESOLUTION - -CAMERA_DICT = { - "base_T_norm": 1.5, - "base_angle": np.pi/3, - "Static": { "angle":[0., 0., 0.], "T":[0., 0., 0.]}, - "Pan Up": { "angle":[0., 0., 0.], "T":[0., -1., 0.]}, - "Pan Down": { "angle":[0., 0., 0.], "T":[0.,1.,0.]}, - "Pan Left": { "angle":[0., 0., 0.], "T":[-1.,0.,0.]}, - "Pan Right": { "angle":[0., 0., 0.], "T": [1.,0.,0.]}, - "Zoom In": { "angle":[0., 0., 0.], "T": [0.,0.,2.]}, - "Zoom Out": { "angle":[0., 0., 0.], "T": [0.,0.,-2.]}, - "Anti Clockwise (ACW)": { "angle": [0., 0., -1.], "T":[0., 0., 0.]}, - "ClockWise (CW)": { "angle": [0., 0., 1.], "T":[0., 0., 0.]}, -} - - -def process_pose_params(cam_params, width=672, height=384, original_pose_width=1280, original_pose_height=720, device='cpu'): - - def get_relative_pose(cam_params): - """Copied from https://github.com/hehao13/CameraCtrl/blob/main/inference.py - """ - abs_w2cs = [cam_param.w2c_mat for cam_param in cam_params] - abs_c2ws = [cam_param.c2w_mat for cam_param in cam_params] - cam_to_origin = 0 - target_cam_c2w = np.array([ - [1, 0, 0, 0], - [0, 1, 0, -cam_to_origin], - [0, 0, 1, 0], - [0, 0, 0, 1] - ]) - abs2rel = target_cam_c2w @ abs_w2cs[0] - ret_poses = [target_cam_c2w, ] + [abs2rel @ abs_c2w for abs_c2w in abs_c2ws[1:]] - ret_poses = np.array(ret_poses, dtype=np.float32) - return ret_poses - - """Modified from https://github.com/hehao13/CameraCtrl/blob/main/inference.py - """ - cam_params = [Camera(cam_param) for cam_param in cam_params] - - sample_wh_ratio = width / height - pose_wh_ratio = original_pose_width / original_pose_height # Assuming placeholder ratios, change as needed - - if pose_wh_ratio > sample_wh_ratio: - resized_ori_w = height * pose_wh_ratio - for cam_param in cam_params: - cam_param.fx = resized_ori_w * cam_param.fx / width - else: - resized_ori_h = width / pose_wh_ratio - for cam_param in cam_params: - cam_param.fy = resized_ori_h * cam_param.fy / height - - intrinsic = np.asarray([[cam_param.fx * width, - cam_param.fy * height, - cam_param.cx * width, - cam_param.cy * height] - for cam_param in cam_params], dtype=np.float32) - - K = torch.as_tensor(intrinsic)[None] # [1, 1, 4] - c2ws = get_relative_pose(cam_params) # Assuming this function is defined elsewhere - c2ws = torch.as_tensor(c2ws)[None] # [1, n_frame, 4, 4] - plucker_embedding = ray_condition(K, c2ws, height, width, device=device)[0].permute(0, 3, 1, 2).contiguous() # V, 6, H, W - plucker_embedding = plucker_embedding[None] - plucker_embedding = rearrange(plucker_embedding, "b f c h w -> b f h w c")[0] - return plucker_embedding - -class Camera(object): - """Copied from https://github.com/hehao13/CameraCtrl/blob/main/inference.py - """ - def __init__(self, entry): - fx, fy, cx, cy = entry[1:5] - self.fx = fx - self.fy = fy - self.cx = cx - self.cy = cy - c2w_mat = np.array(entry[7:]).reshape(4, 4) - self.c2w_mat = c2w_mat - self.w2c_mat = np.linalg.inv(c2w_mat) - -def ray_condition(K, c2w, H, W, device): - """Copied from https://github.com/hehao13/CameraCtrl/blob/main/inference.py - """ - # c2w: B, V, 4, 4 - # K: B, V, 4 - - B = K.shape[0] - - j, i = torch.meshgrid( - torch.linspace(0, H - 1, H, device=device, dtype=c2w.dtype), - torch.linspace(0, W - 1, W, device=device, dtype=c2w.dtype), - indexing='ij' - ) - i = i.reshape([1, 1, H * W]).expand([B, 1, H * W]) + 0.5 # [B, HxW] - j = j.reshape([1, 1, H * W]).expand([B, 1, H * W]) + 0.5 # [B, HxW] - - fx, fy, cx, cy = K.chunk(4, dim=-1) # B,V, 1 - - zs = torch.ones_like(i) # [B, HxW] - xs = (i - cx) / fx * zs - ys = (j - cy) / fy * zs - zs = zs.expand_as(ys) - - directions = torch.stack((xs, ys, zs), dim=-1) # B, V, HW, 3 - directions = directions / directions.norm(dim=-1, keepdim=True) # B, V, HW, 3 - - rays_d = directions @ c2w[..., :3, :3].transpose(-1, -2) # B, V, 3, HW - rays_o = c2w[..., :3, 3] # B, V, 3 - rays_o = rays_o[:, :, None].expand_as(rays_d) # B, V, 3, HW - # c2w @ dirctions - rays_dxo = torch.cross(rays_o, rays_d) - plucker = torch.cat([rays_dxo, rays_d], dim=-1) - plucker = plucker.reshape(B, c2w.shape[1], H, W, 6) # B, V, H, W, 6 - # plucker = plucker.permute(0, 1, 4, 2, 3) - return plucker - -def get_camera_motion(angle, T, speed, n=81): - def compute_R_form_rad_angle(angles): - theta_x, theta_y, theta_z = angles - Rx = np.array([[1, 0, 0], - [0, np.cos(theta_x), -np.sin(theta_x)], - [0, np.sin(theta_x), np.cos(theta_x)]]) - - Ry = np.array([[np.cos(theta_y), 0, np.sin(theta_y)], - [0, 1, 0], - [-np.sin(theta_y), 0, np.cos(theta_y)]]) - - Rz = np.array([[np.cos(theta_z), -np.sin(theta_z), 0], - [np.sin(theta_z), np.cos(theta_z), 0], - [0, 0, 1]]) - - R = np.dot(Rz, np.dot(Ry, Rx)) - return R - RT = [] - for i in range(n): - _angle = (i/n)*speed*(CAMERA_DICT["base_angle"])*angle - R = compute_R_form_rad_angle(_angle) - _T=(i/n)*speed*(CAMERA_DICT["base_T_norm"])*(T.reshape(3,1)) - _RT = np.concatenate([R,_T], axis=1) - RT.append(_RT) - RT = np.stack(RT) - return RT - -class WanCameraEmbedding: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "camera_pose":(["Static","Pan Up","Pan Down","Pan Left","Pan Right","Zoom In","Zoom Out","Anti Clockwise (ACW)", "ClockWise (CW)"],{"default":"Static"}), - "width": ("INT", {"default": 832, "min": 16, "max": MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": MAX_RESOLUTION, "step": 4}), - }, - "optional":{ - "speed":("FLOAT",{"default":1.0, "min": 0, "max": 10.0, "step": 0.1}), - "fx":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.000000001}), - "fy":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.000000001}), - "cx":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.01}), - "cy":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.01}), - } - - } - - RETURN_TYPES = ("WAN_CAMERA_EMBEDDING","INT","INT","INT") - RETURN_NAMES = ("camera_embedding","width","height","length") - FUNCTION = "run" - CATEGORY = "camera" - - def run(self, camera_pose, width, height, length, speed=1.0, fx=0.5, fy=0.5, cx=0.5, cy=0.5): - """ - Use Camera trajectory as extrinsic parameters to calculate Plücker embeddings (Sitzmannet al., 2021) - Adapted from https://github.com/aigc-apps/VideoX-Fun/blob/main/comfyui/comfyui_nodes.py - """ - motion_list = [camera_pose] - speed = speed - angle = np.array(CAMERA_DICT[motion_list[0]]["angle"]) - T = np.array(CAMERA_DICT[motion_list[0]]["T"]) - RT = get_camera_motion(angle, T, speed, length) - - trajs=[] - for cp in RT.tolist(): - traj=[fx,fy,cx,cy,0,0] - traj.extend(cp[0]) - traj.extend(cp[1]) - traj.extend(cp[2]) - traj.extend([0,0,0,1]) - trajs.append(traj) - - cam_params = np.array([[float(x) for x in pose] for pose in trajs]) - cam_params = np.concatenate([np.zeros_like(cam_params[:, :1]), cam_params], 1) - control_camera_video = process_pose_params(cam_params, width=width, height=height) - control_camera_video = control_camera_video.permute([3, 0, 1, 2]).unsqueeze(0).to(device=comfy.model_management.intermediate_device()) - - control_camera_video = torch.concat( - [ - torch.repeat_interleave(control_camera_video[:, :, 0:1], repeats=4, dim=2), - control_camera_video[:, :, 1:] - ], dim=2 - ).transpose(1, 2) - - # Reshape, transpose, and view into desired shape - b, f, c, h, w = control_camera_video.shape - control_camera_video = control_camera_video.contiguous().view(b, f // 4, 4, c, h, w).transpose(2, 3) - control_camera_video = control_camera_video.contiguous().view(b, f // 4, c * 4, h, w).transpose(1, 2) - - return (control_camera_video, width, height, length) - - -NODE_CLASS_MAPPINGS = { - "WanCameraEmbedding": WanCameraEmbedding, -} diff --git a/comfy_extras/nodes_canny.py b/comfy_extras/nodes_canny.py deleted file mode 100644 index d85e6b85691dcdcc55e31705039934846d466fc1..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_canny.py +++ /dev/null @@ -1,25 +0,0 @@ -from kornia.filters import canny -import comfy.model_management - - -class Canny: - @classmethod - def INPUT_TYPES(s): - return {"required": {"image": ("IMAGE",), - "low_threshold": ("FLOAT", {"default": 0.4, "min": 0.01, "max": 0.99, "step": 0.01}), - "high_threshold": ("FLOAT", {"default": 0.8, "min": 0.01, "max": 0.99, "step": 0.01}) - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "detect_edge" - - CATEGORY = "image/preprocessors" - - def detect_edge(self, image, low_threshold, high_threshold): - output = canny(image.to(comfy.model_management.get_torch_device()).movedim(-1, 1), low_threshold, high_threshold) - img_out = output[1].to(comfy.model_management.intermediate_device()).repeat(1, 3, 1, 1).movedim(1, -1) - return (img_out,) - -NODE_CLASS_MAPPINGS = { - "Canny": Canny, -} diff --git a/comfy_extras/nodes_cfg.py b/comfy_extras/nodes_cfg.py deleted file mode 100644 index 5abdc115ab4f9e82ca43151ec8826d234a16b172..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_cfg.py +++ /dev/null @@ -1,72 +0,0 @@ -import torch - -# https://github.com/WeichenFan/CFG-Zero-star -def optimized_scale(positive, negative): - positive_flat = positive.reshape(positive.shape[0], -1) - negative_flat = negative.reshape(negative.shape[0], -1) - - # Calculate dot production - dot_product = torch.sum(positive_flat * negative_flat, dim=1, keepdim=True) - - # Squared norm of uncondition - squared_norm = torch.sum(negative_flat ** 2, dim=1, keepdim=True) + 1e-8 - - # st_star = v_cond^T * v_uncond / ||v_uncond||^2 - st_star = dot_product / squared_norm - - return st_star.reshape([positive.shape[0]] + [1] * (positive.ndim - 1)) - -class CFGZeroStar: - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL",), - }} - RETURN_TYPES = ("MODEL",) - RETURN_NAMES = ("patched_model",) - FUNCTION = "patch" - CATEGORY = "advanced/guidance" - - def patch(self, model): - m = model.clone() - def cfg_zero_star(args): - guidance_scale = args['cond_scale'] - x = args['input'] - cond_p = args['cond_denoised'] - uncond_p = args['uncond_denoised'] - out = args["denoised"] - alpha = optimized_scale(x - cond_p, x - uncond_p) - - return out + uncond_p * (alpha - 1.0) + guidance_scale * uncond_p * (1.0 - alpha) - m.set_model_sampler_post_cfg_function(cfg_zero_star) - return (m, ) - -class CFGNorm: - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL",), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - RETURN_NAMES = ("patched_model",) - FUNCTION = "patch" - CATEGORY = "advanced/guidance" - EXPERIMENTAL = True - - def patch(self, model, strength): - m = model.clone() - def cfg_norm(args): - cond_p = args['cond_denoised'] - pred_text_ = args["denoised"] - - norm_full_cond = torch.norm(cond_p, dim=1, keepdim=True) - norm_pred_text = torch.norm(pred_text_, dim=1, keepdim=True) - scale = (norm_full_cond / (norm_pred_text + 1e-8)).clamp(min=0.0, max=1.0) - return pred_text_ * scale * strength - - m.set_model_sampler_post_cfg_function(cfg_norm) - return (m, ) - -NODE_CLASS_MAPPINGS = { - "CFGZeroStar": CFGZeroStar, - "CFGNorm": CFGNorm, -} diff --git a/comfy_extras/nodes_clip_sdxl.py b/comfy_extras/nodes_clip_sdxl.py deleted file mode 100644 index 14269caf352edee45d9f1ca49db3effd5b60b7fb..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_clip_sdxl.py +++ /dev/null @@ -1,54 +0,0 @@ -from nodes import MAX_RESOLUTION - -class CLIPTextEncodeSDXLRefiner: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "ascore": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}), - "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", ), - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "advanced/conditioning" - - def encode(self, clip, ascore, width, height, text): - tokens = clip.tokenize(text) - return (clip.encode_from_tokens_scheduled(tokens, add_dict={"aesthetic_score": ascore, "width": width, "height": height}), ) - -class CLIPTextEncodeSDXL: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "clip": ("CLIP", ), - "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), - "crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), - "target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "text_g": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "text_l": ("STRING", {"multiline": True, "dynamicPrompts": True}), - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "advanced/conditioning" - - def encode(self, clip, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l): - tokens = clip.tokenize(text_g) - tokens["l"] = clip.tokenize(text_l)["l"] - if len(tokens["l"]) != len(tokens["g"]): - empty = clip.tokenize("") - while len(tokens["l"]) < len(tokens["g"]): - tokens["l"] += empty["l"] - while len(tokens["l"]) > len(tokens["g"]): - tokens["g"] += empty["g"] - return (clip.encode_from_tokens_scheduled(tokens, add_dict={"width": width, "height": height, "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width, "target_height": target_height}), ) - -NODE_CLASS_MAPPINGS = { - "CLIPTextEncodeSDXLRefiner": CLIPTextEncodeSDXLRefiner, - "CLIPTextEncodeSDXL": CLIPTextEncodeSDXL, -} diff --git a/comfy_extras/nodes_compositing.py b/comfy_extras/nodes_compositing.py deleted file mode 100644 index 2f994fa11d370d31dc5412da90d0582fe6bd6159..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_compositing.py +++ /dev/null @@ -1,214 +0,0 @@ -import torch -import comfy.utils -from enum import Enum - -def resize_mask(mask, shape): - return torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(shape[0], shape[1]), mode="bilinear").squeeze(1) - -class PorterDuffMode(Enum): - ADD = 0 - CLEAR = 1 - DARKEN = 2 - DST = 3 - DST_ATOP = 4 - DST_IN = 5 - DST_OUT = 6 - DST_OVER = 7 - LIGHTEN = 8 - MULTIPLY = 9 - OVERLAY = 10 - SCREEN = 11 - SRC = 12 - SRC_ATOP = 13 - SRC_IN = 14 - SRC_OUT = 15 - SRC_OVER = 16 - XOR = 17 - - -def porter_duff_composite(src_image: torch.Tensor, src_alpha: torch.Tensor, dst_image: torch.Tensor, dst_alpha: torch.Tensor, mode: PorterDuffMode): - # convert mask to alpha - src_alpha = 1 - src_alpha - dst_alpha = 1 - dst_alpha - # premultiply alpha - src_image = src_image * src_alpha - dst_image = dst_image * dst_alpha - - # composite ops below assume alpha-premultiplied images - if mode == PorterDuffMode.ADD: - out_alpha = torch.clamp(src_alpha + dst_alpha, 0, 1) - out_image = torch.clamp(src_image + dst_image, 0, 1) - elif mode == PorterDuffMode.CLEAR: - out_alpha = torch.zeros_like(dst_alpha) - out_image = torch.zeros_like(dst_image) - elif mode == PorterDuffMode.DARKEN: - out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha - out_image = (1 - dst_alpha) * src_image + (1 - src_alpha) * dst_image + torch.min(src_image, dst_image) - elif mode == PorterDuffMode.DST: - out_alpha = dst_alpha - out_image = dst_image - elif mode == PorterDuffMode.DST_ATOP: - out_alpha = src_alpha - out_image = src_alpha * dst_image + (1 - dst_alpha) * src_image - elif mode == PorterDuffMode.DST_IN: - out_alpha = src_alpha * dst_alpha - out_image = dst_image * src_alpha - elif mode == PorterDuffMode.DST_OUT: - out_alpha = (1 - src_alpha) * dst_alpha - out_image = (1 - src_alpha) * dst_image - elif mode == PorterDuffMode.DST_OVER: - out_alpha = dst_alpha + (1 - dst_alpha) * src_alpha - out_image = dst_image + (1 - dst_alpha) * src_image - elif mode == PorterDuffMode.LIGHTEN: - out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha - out_image = (1 - dst_alpha) * src_image + (1 - src_alpha) * dst_image + torch.max(src_image, dst_image) - elif mode == PorterDuffMode.MULTIPLY: - out_alpha = src_alpha * dst_alpha - out_image = src_image * dst_image - elif mode == PorterDuffMode.OVERLAY: - out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha - out_image = torch.where(2 * dst_image < dst_alpha, 2 * src_image * dst_image, - src_alpha * dst_alpha - 2 * (dst_alpha - src_image) * (src_alpha - dst_image)) - elif mode == PorterDuffMode.SCREEN: - out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha - out_image = src_image + dst_image - src_image * dst_image - elif mode == PorterDuffMode.SRC: - out_alpha = src_alpha - out_image = src_image - elif mode == PorterDuffMode.SRC_ATOP: - out_alpha = dst_alpha - out_image = dst_alpha * src_image + (1 - src_alpha) * dst_image - elif mode == PorterDuffMode.SRC_IN: - out_alpha = src_alpha * dst_alpha - out_image = src_image * dst_alpha - elif mode == PorterDuffMode.SRC_OUT: - out_alpha = (1 - dst_alpha) * src_alpha - out_image = (1 - dst_alpha) * src_image - elif mode == PorterDuffMode.SRC_OVER: - out_alpha = src_alpha + (1 - src_alpha) * dst_alpha - out_image = src_image + (1 - src_alpha) * dst_image - elif mode == PorterDuffMode.XOR: - out_alpha = (1 - dst_alpha) * src_alpha + (1 - src_alpha) * dst_alpha - out_image = (1 - dst_alpha) * src_image + (1 - src_alpha) * dst_image - else: - return None, None - - # back to non-premultiplied alpha - out_image = torch.where(out_alpha > 1e-5, out_image / out_alpha, torch.zeros_like(out_image)) - out_image = torch.clamp(out_image, 0, 1) - # convert alpha to mask - out_alpha = 1 - out_alpha - return out_image, out_alpha - - -class PorterDuffImageComposite: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "source": ("IMAGE",), - "source_alpha": ("MASK",), - "destination": ("IMAGE",), - "destination_alpha": ("MASK",), - "mode": ([mode.name for mode in PorterDuffMode], {"default": PorterDuffMode.DST.name}), - }, - } - - RETURN_TYPES = ("IMAGE", "MASK") - FUNCTION = "composite" - CATEGORY = "mask/compositing" - - def composite(self, source: torch.Tensor, source_alpha: torch.Tensor, destination: torch.Tensor, destination_alpha: torch.Tensor, mode): - batch_size = min(len(source), len(source_alpha), len(destination), len(destination_alpha)) - out_images = [] - out_alphas = [] - - for i in range(batch_size): - src_image = source[i] - dst_image = destination[i] - - assert src_image.shape[2] == dst_image.shape[2] # inputs need to have same number of channels - - src_alpha = source_alpha[i].unsqueeze(2) - dst_alpha = destination_alpha[i].unsqueeze(2) - - if dst_alpha.shape[:2] != dst_image.shape[:2]: - upscale_input = dst_alpha.unsqueeze(0).permute(0, 3, 1, 2) - upscale_output = comfy.utils.common_upscale(upscale_input, dst_image.shape[1], dst_image.shape[0], upscale_method='bicubic', crop='center') - dst_alpha = upscale_output.permute(0, 2, 3, 1).squeeze(0) - if src_image.shape != dst_image.shape: - upscale_input = src_image.unsqueeze(0).permute(0, 3, 1, 2) - upscale_output = comfy.utils.common_upscale(upscale_input, dst_image.shape[1], dst_image.shape[0], upscale_method='bicubic', crop='center') - src_image = upscale_output.permute(0, 2, 3, 1).squeeze(0) - if src_alpha.shape != dst_alpha.shape: - upscale_input = src_alpha.unsqueeze(0).permute(0, 3, 1, 2) - upscale_output = comfy.utils.common_upscale(upscale_input, dst_alpha.shape[1], dst_alpha.shape[0], upscale_method='bicubic', crop='center') - src_alpha = upscale_output.permute(0, 2, 3, 1).squeeze(0) - - out_image, out_alpha = porter_duff_composite(src_image, src_alpha, dst_image, dst_alpha, PorterDuffMode[mode]) - - out_images.append(out_image) - out_alphas.append(out_alpha.squeeze(2)) - - result = (torch.stack(out_images), torch.stack(out_alphas)) - return result - - -class SplitImageWithAlpha: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - } - } - - CATEGORY = "mask/compositing" - RETURN_TYPES = ("IMAGE", "MASK") - FUNCTION = "split_image_with_alpha" - - def split_image_with_alpha(self, image: torch.Tensor): - out_images = [i[:,:,:3] for i in image] - out_alphas = [i[:,:,3] if i.shape[2] > 3 else torch.ones_like(i[:,:,0]) for i in image] - result = (torch.stack(out_images), 1.0 - torch.stack(out_alphas)) - return result - - -class JoinImageWithAlpha: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "alpha": ("MASK",), - } - } - - CATEGORY = "mask/compositing" - RETURN_TYPES = ("IMAGE",) - FUNCTION = "join_image_with_alpha" - - def join_image_with_alpha(self, image: torch.Tensor, alpha: torch.Tensor): - batch_size = min(len(image), len(alpha)) - out_images = [] - - alpha = 1.0 - resize_mask(alpha, image.shape[1:]) - for i in range(batch_size): - out_images.append(torch.cat((image[i][:,:,:3], alpha[i].unsqueeze(2)), dim=2)) - - result = (torch.stack(out_images),) - return result - - -NODE_CLASS_MAPPINGS = { - "PorterDuffImageComposite": PorterDuffImageComposite, - "SplitImageWithAlpha": SplitImageWithAlpha, - "JoinImageWithAlpha": JoinImageWithAlpha, -} - - -NODE_DISPLAY_NAME_MAPPINGS = { - "PorterDuffImageComposite": "Porter-Duff Image Composite", - "SplitImageWithAlpha": "Split Image with Alpha", - "JoinImageWithAlpha": "Join Image with Alpha", -} diff --git a/comfy_extras/nodes_cond.py b/comfy_extras/nodes_cond.py deleted file mode 100644 index 58c16f621cd1c5f5abd66dba639ebf5740862789..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_cond.py +++ /dev/null @@ -1,49 +0,0 @@ - - -class CLIPTextEncodeControlnet: - @classmethod - def INPUT_TYPES(s): - return {"required": {"clip": ("CLIP", ), "conditioning": ("CONDITIONING", ), "text": ("STRING", {"multiline": True, "dynamicPrompts": True})}} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "_for_testing/conditioning" - - def encode(self, clip, conditioning, text): - tokens = clip.tokenize(text) - cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) - c = [] - for t in conditioning: - n = [t[0], t[1].copy()] - n[1]['cross_attn_controlnet'] = cond - n[1]['pooled_output_controlnet'] = pooled - c.append(n) - return (c, ) - -class T5TokenizerOptions: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "clip": ("CLIP", ), - "min_padding": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}), - "min_length": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}), - } - } - - CATEGORY = "_for_testing/conditioning" - RETURN_TYPES = ("CLIP",) - FUNCTION = "set_options" - - def set_options(self, clip, min_padding, min_length): - clip = clip.clone() - for t5_type in ["t5xxl", "pile_t5xl", "t5base", "mt5xl", "umt5xxl"]: - clip.set_tokenizer_option("{}_min_padding".format(t5_type), min_padding) - clip.set_tokenizer_option("{}_min_length".format(t5_type), min_length) - - return (clip, ) - -NODE_CLASS_MAPPINGS = { - "CLIPTextEncodeControlnet": CLIPTextEncodeControlnet, - "T5TokenizerOptions": T5TokenizerOptions, -} diff --git a/comfy_extras/nodes_controlnet.py b/comfy_extras/nodes_controlnet.py deleted file mode 100644 index 2d20e1fed7c26c8115f4b9878b0b54911e7a2e7f..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_controlnet.py +++ /dev/null @@ -1,60 +0,0 @@ -from comfy.cldm.control_types import UNION_CONTROLNET_TYPES -import nodes -import comfy.utils - -class SetUnionControlNetType: - @classmethod - def INPUT_TYPES(s): - return {"required": {"control_net": ("CONTROL_NET", ), - "type": (["auto"] + list(UNION_CONTROLNET_TYPES.keys()),) - }} - - CATEGORY = "conditioning/controlnet" - RETURN_TYPES = ("CONTROL_NET",) - - FUNCTION = "set_controlnet_type" - - def set_controlnet_type(self, control_net, type): - control_net = control_net.copy() - type_number = UNION_CONTROLNET_TYPES.get(type, -1) - if type_number >= 0: - control_net.set_extra_arg("control_type", [type_number]) - else: - control_net.set_extra_arg("control_type", []) - - return (control_net,) - -class ControlNetInpaintingAliMamaApply(nodes.ControlNetApplyAdvanced): - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "control_net": ("CONTROL_NET", ), - "vae": ("VAE", ), - "image": ("IMAGE", ), - "mask": ("MASK", ), - "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}) - }} - - FUNCTION = "apply_inpaint_controlnet" - - CATEGORY = "conditioning/controlnet" - - def apply_inpaint_controlnet(self, positive, negative, control_net, vae, image, mask, strength, start_percent, end_percent): - extra_concat = [] - if control_net.concat_mask: - mask = 1.0 - mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])) - mask_apply = comfy.utils.common_upscale(mask, image.shape[2], image.shape[1], "bilinear", "center").round() - image = image * mask_apply.movedim(1, -1).repeat(1, 1, 1, image.shape[3]) - extra_concat = [mask] - - return self.apply_controlnet(positive, negative, control_net, image, strength, start_percent, end_percent, vae=vae, extra_concat=extra_concat) - - - -NODE_CLASS_MAPPINGS = { - "SetUnionControlNetType": SetUnionControlNetType, - "ControlNetInpaintingAliMamaApply": ControlNetInpaintingAliMamaApply, -} diff --git a/comfy_extras/nodes_cosmos.py b/comfy_extras/nodes_cosmos.py deleted file mode 100644 index 4f49605510e4986f5d030d7bbde14fbf2f7a9c2d..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_cosmos.py +++ /dev/null @@ -1,128 +0,0 @@ -import nodes -import torch -import comfy.model_management -import comfy.utils -import comfy.latent_formats - - -class EmptyCosmosLatentVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": { "width": ("INT", {"default": 1280, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 704, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 121, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 8}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/video" - - def generate(self, width, height, length, batch_size=1): - latent = torch.zeros([batch_size, 16, ((length - 1) // 8) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - return ({"samples": latent}, ) - - -def vae_encode_with_padding(vae, image, width, height, length, padding=0): - pixels = comfy.utils.common_upscale(image[..., :3].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - pixel_len = min(pixels.shape[0], length) - padded_length = min(length, (((pixel_len - 1) // 8) + 1 + padding) * 8 - 7) - padded_pixels = torch.ones((padded_length, height, width, 3)) * 0.5 - padded_pixels[:pixel_len] = pixels[:pixel_len] - latent_len = ((pixel_len - 1) // 8) + 1 - latent_temp = vae.encode(padded_pixels) - return latent_temp[:, :, :latent_len] - - -class CosmosImageToVideoLatent: - @classmethod - def INPUT_TYPES(s): - return {"required": {"vae": ("VAE", ), - "width": ("INT", {"default": 1280, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 704, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 121, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 8}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"start_image": ("IMAGE", ), - "end_image": ("IMAGE", ), - }} - - - RETURN_TYPES = ("LATENT",) - FUNCTION = "encode" - - CATEGORY = "conditioning/inpaint" - - def encode(self, vae, width, height, length, batch_size, start_image=None, end_image=None): - latent = torch.zeros([1, 16, ((length - 1) // 8) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - if start_image is None and end_image is None: - out_latent = {} - out_latent["samples"] = latent - return (out_latent,) - - mask = torch.ones([latent.shape[0], 1, ((length - 1) // 8) + 1, latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device()) - - if start_image is not None: - latent_temp = vae_encode_with_padding(vae, start_image, width, height, length, padding=1) - latent[:, :, :latent_temp.shape[-3]] = latent_temp - mask[:, :, :latent_temp.shape[-3]] *= 0.0 - - if end_image is not None: - latent_temp = vae_encode_with_padding(vae, end_image, width, height, length, padding=0) - latent[:, :, -latent_temp.shape[-3]:] = latent_temp - mask[:, :, -latent_temp.shape[-3]:] *= 0.0 - - out_latent = {} - out_latent["samples"] = latent.repeat((batch_size, ) + (1,) * (latent.ndim - 1)) - out_latent["noise_mask"] = mask.repeat((batch_size, ) + (1,) * (mask.ndim - 1)) - return (out_latent,) - -class CosmosPredict2ImageToVideoLatent: - @classmethod - def INPUT_TYPES(s): - return {"required": {"vae": ("VAE", ), - "width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 93, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"start_image": ("IMAGE", ), - "end_image": ("IMAGE", ), - }} - - - RETURN_TYPES = ("LATENT",) - FUNCTION = "encode" - - CATEGORY = "conditioning/inpaint" - - def encode(self, vae, width, height, length, batch_size, start_image=None, end_image=None): - latent = torch.zeros([1, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - if start_image is None and end_image is None: - out_latent = {} - out_latent["samples"] = latent - return (out_latent,) - - mask = torch.ones([latent.shape[0], 1, ((length - 1) // 4) + 1, latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device()) - - if start_image is not None: - latent_temp = vae_encode_with_padding(vae, start_image, width, height, length, padding=1) - latent[:, :, :latent_temp.shape[-3]] = latent_temp - mask[:, :, :latent_temp.shape[-3]] *= 0.0 - - if end_image is not None: - latent_temp = vae_encode_with_padding(vae, end_image, width, height, length, padding=0) - latent[:, :, -latent_temp.shape[-3]:] = latent_temp - mask[:, :, -latent_temp.shape[-3]:] *= 0.0 - - out_latent = {} - latent_format = comfy.latent_formats.Wan21() - latent = latent_format.process_out(latent) * mask + latent * (1.0 - mask) - out_latent["samples"] = latent.repeat((batch_size, ) + (1,) * (latent.ndim - 1)) - out_latent["noise_mask"] = mask.repeat((batch_size, ) + (1,) * (mask.ndim - 1)) - return (out_latent,) - -NODE_CLASS_MAPPINGS = { - "EmptyCosmosLatentVideo": EmptyCosmosLatentVideo, - "CosmosImageToVideoLatent": CosmosImageToVideoLatent, - "CosmosPredict2ImageToVideoLatent": CosmosPredict2ImageToVideoLatent, -} diff --git a/comfy_extras/nodes_custom_sampler.py b/comfy_extras/nodes_custom_sampler.py deleted file mode 100644 index d011f433b5db84d665dc20349b9b9aa75a703df9..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_custom_sampler.py +++ /dev/null @@ -1,932 +0,0 @@ -import math -import comfy.samplers -import comfy.sample -from comfy.k_diffusion import sampling as k_diffusion_sampling -from comfy.k_diffusion import sa_solver -from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict -import latent_preview -import torch -import comfy.utils -import node_helpers - - -class BasicScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "scheduler": (comfy.samplers.SCHEDULER_NAMES, ), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, model, scheduler, steps, denoise): - total_steps = steps - if denoise < 1.0: - if denoise <= 0.0: - return (torch.FloatTensor([]),) - total_steps = int(steps/denoise) - - sigmas = comfy.samplers.calculate_sigmas(model.get_model_object("model_sampling"), scheduler, total_steps).cpu() - sigmas = sigmas[-(steps + 1):] - return (sigmas, ) - - -class KarrasScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, steps, sigma_max, sigma_min, rho): - sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho) - return (sigmas, ) - -class ExponentialScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, steps, sigma_max, sigma_min): - sigmas = k_diffusion_sampling.get_sigmas_exponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max) - return (sigmas, ) - -class PolyexponentialScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "rho": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, steps, sigma_max, sigma_min, rho): - sigmas = k_diffusion_sampling.get_sigmas_polyexponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho) - return (sigmas, ) - -class LaplaceScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "mu": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step":0.1, "round": False}), - "beta": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step":0.1, "round": False}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, steps, sigma_max, sigma_min, mu, beta): - sigmas = k_diffusion_sampling.get_sigmas_laplace(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, mu=mu, beta=beta) - return (sigmas, ) - - -class SDTurboScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "steps": ("INT", {"default": 1, "min": 1, "max": 10}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, model, steps, denoise): - start_step = 10 - int(10 * denoise) - timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[start_step:start_step + steps] - sigmas = model.get_model_object("model_sampling").sigma(timesteps) - sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) - return (sigmas, ) - -class BetaSamplingScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "alpha": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 50.0, "step":0.01, "round": False}), - "beta": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 50.0, "step":0.01, "round": False}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, model, steps, alpha, beta): - sigmas = comfy.samplers.beta_scheduler(model.get_model_object("model_sampling"), steps, alpha=alpha, beta=beta) - return (sigmas, ) - -class VPScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), #TODO: fix default values - "beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), - "eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step":0.0001, "round": False}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, steps, beta_d, beta_min, eps_s): - sigmas = k_diffusion_sampling.get_sigmas_vp(n=steps, beta_d=beta_d, beta_min=beta_min, eps_s=eps_s) - return (sigmas, ) - -class SplitSigmas: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"sigmas": ("SIGMAS", ), - "step": ("INT", {"default": 0, "min": 0, "max": 10000}), - } - } - RETURN_TYPES = ("SIGMAS","SIGMAS") - RETURN_NAMES = ("high_sigmas", "low_sigmas") - CATEGORY = "sampling/custom_sampling/sigmas" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, sigmas, step): - sigmas1 = sigmas[:step + 1] - sigmas2 = sigmas[step:] - return (sigmas1, sigmas2) - -class SplitSigmasDenoise: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"sigmas": ("SIGMAS", ), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - RETURN_TYPES = ("SIGMAS","SIGMAS") - RETURN_NAMES = ("high_sigmas", "low_sigmas") - CATEGORY = "sampling/custom_sampling/sigmas" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, sigmas, denoise): - steps = max(sigmas.shape[-1] - 1, 0) - total_steps = round(steps * denoise) - sigmas1 = sigmas[:-(total_steps)] - sigmas2 = sigmas[-(total_steps + 1):] - return (sigmas1, sigmas2) - -class FlipSigmas: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"sigmas": ("SIGMAS", ), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/sigmas" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, sigmas): - if len(sigmas) == 0: - return (sigmas,) - - sigmas = sigmas.flip(0) - if sigmas[0] == 0: - sigmas[0] = 0.0001 - return (sigmas,) - -class SetFirstSigma: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"sigmas": ("SIGMAS", ), - "sigma": ("FLOAT", {"default": 136.0, "min": 0.0, "max": 20000.0, "step": 0.001, "round": False}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/sigmas" - - FUNCTION = "set_first_sigma" - - def set_first_sigma(self, sigmas, sigma): - sigmas = sigmas.clone() - sigmas[0] = sigma - return (sigmas, ) - -class ExtendIntermediateSigmas: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"sigmas": ("SIGMAS", ), - "steps": ("INT", {"default": 2, "min": 1, "max": 100}), - "start_at_sigma": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 20000.0, "step": 0.01, "round": False}), - "end_at_sigma": ("FLOAT", {"default": 12.0, "min": 0.0, "max": 20000.0, "step": 0.01, "round": False}), - "spacing": (['linear', 'cosine', 'sine'],), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/sigmas" - - FUNCTION = "extend" - - def extend(self, sigmas: torch.Tensor, steps: int, start_at_sigma: float, end_at_sigma: float, spacing: str): - if start_at_sigma < 0: - start_at_sigma = float("inf") - - interpolator = { - 'linear': lambda x: x, - 'cosine': lambda x: torch.sin(x*math.pi/2), - 'sine': lambda x: 1 - torch.cos(x*math.pi/2) - }[spacing] - - # linear space for our interpolation function - x = torch.linspace(0, 1, steps + 1, device=sigmas.device)[1:-1] - computed_spacing = interpolator(x) - - extended_sigmas = [] - for i in range(len(sigmas) - 1): - sigma_current = sigmas[i] - sigma_next = sigmas[i+1] - - extended_sigmas.append(sigma_current) - - if end_at_sigma <= sigma_current <= start_at_sigma: - interpolated_steps = computed_spacing * (sigma_next - sigma_current) + sigma_current - extended_sigmas.extend(interpolated_steps.tolist()) - - # Add the last sigma value - if len(sigmas) > 0: - extended_sigmas.append(sigmas[-1]) - - extended_sigmas = torch.FloatTensor(extended_sigmas) - - return (extended_sigmas,) - - -class SamplingPercentToSigma: - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": { - "model": (IO.MODEL, {}), - "sampling_percent": (IO.FLOAT, {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.0001}), - "return_actual_sigma": (IO.BOOLEAN, {"default": False, "tooltip": "Return the actual sigma value instead of the value used for interval checks.\nThis only affects results at 0.0 and 1.0."}), - } - } - - RETURN_TYPES = (IO.FLOAT,) - RETURN_NAMES = ("sigma_value",) - CATEGORY = "sampling/custom_sampling/sigmas" - - FUNCTION = "get_sigma" - - def get_sigma(self, model, sampling_percent, return_actual_sigma): - model_sampling = model.get_model_object("model_sampling") - sigma_val = model_sampling.percent_to_sigma(sampling_percent) - if return_actual_sigma: - if sampling_percent == 0.0: - sigma_val = model_sampling.sigma_max.item() - elif sampling_percent == 1.0: - sigma_val = model_sampling.sigma_min.item() - return (sigma_val,) - - -class KSamplerSelect: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"sampler_name": (comfy.samplers.SAMPLER_NAMES, ), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, sampler_name): - sampler = comfy.samplers.sampler_object(sampler_name) - return (sampler, ) - -class SamplerDPMPP_3M_SDE: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "noise_device": (['gpu', 'cpu'], ), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, eta, s_noise, noise_device): - if noise_device == 'cpu': - sampler_name = "dpmpp_3m_sde" - else: - sampler_name = "dpmpp_3m_sde_gpu" - sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise}) - return (sampler, ) - -class SamplerDPMPP_2M_SDE: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"solver_type": (['midpoint', 'heun'], ), - "eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "noise_device": (['gpu', 'cpu'], ), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, solver_type, eta, s_noise, noise_device): - if noise_device == 'cpu': - sampler_name = "dpmpp_2m_sde" - else: - sampler_name = "dpmpp_2m_sde_gpu" - sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise, "solver_type": solver_type}) - return (sampler, ) - - -class SamplerDPMPP_SDE: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "r": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "noise_device": (['gpu', 'cpu'], ), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, eta, s_noise, r, noise_device): - if noise_device == 'cpu': - sampler_name = "dpmpp_sde" - else: - sampler_name = "dpmpp_sde_gpu" - sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise, "r": r}) - return (sampler, ) - -class SamplerDPMPP_2S_Ancestral: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, eta, s_noise): - sampler = comfy.samplers.ksampler("dpmpp_2s_ancestral", {"eta": eta, "s_noise": s_noise}) - return (sampler, ) - -class SamplerEulerAncestral: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, eta, s_noise): - sampler = comfy.samplers.ksampler("euler_ancestral", {"eta": eta, "s_noise": s_noise}) - return (sampler, ) - -class SamplerEulerAncestralCFGPP: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step":0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step":0.01, "round": False}), - }} - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, eta, s_noise): - sampler = comfy.samplers.ksampler( - "euler_ancestral_cfg_pp", - {"eta": eta, "s_noise": s_noise}) - return (sampler, ) - -class SamplerLMS: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"order": ("INT", {"default": 4, "min": 1, "max": 100}), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, order): - sampler = comfy.samplers.ksampler("lms", {"order": order}) - return (sampler, ) - -class SamplerDPMAdaptative: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"order": ("INT", {"default": 3, "min": 2, "max": 3}), - "rtol": ("FLOAT", {"default": 0.05, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "atol": ("FLOAT", {"default": 0.0078, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "h_init": ("FLOAT", {"default": 0.05, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "pcoeff": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "icoeff": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "dcoeff": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "accept_safety": ("FLOAT", {"default": 0.81, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "eta": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}), - } - } - RETURN_TYPES = ("SAMPLER",) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise): - sampler = comfy.samplers.ksampler("dpm_adaptive", {"order": order, "rtol": rtol, "atol": atol, "h_init": h_init, "pcoeff": pcoeff, - "icoeff": icoeff, "dcoeff": dcoeff, "accept_safety": accept_safety, "eta": eta, - "s_noise":s_noise }) - return (sampler, ) - - -class SamplerER_SDE(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": { - "solver_type": (IO.COMBO, {"options": ["ER-SDE", "Reverse-time SDE", "ODE"]}), - "max_stage": (IO.INT, {"default": 3, "min": 1, "max": 3}), - "eta": ( - IO.FLOAT, - {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": False, "tooltip": "Stochastic strength of reverse-time SDE.\nWhen eta=0, it reduces to deterministic ODE. This setting doesn't apply to ER-SDE solver type."}, - ), - "s_noise": (IO.FLOAT, {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": False}), - } - } - - RETURN_TYPES = (IO.SAMPLER,) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, solver_type, max_stage, eta, s_noise): - if solver_type == "ODE" or (solver_type == "Reverse-time SDE" and eta == 0): - eta = 0 - s_noise = 0 - - def reverse_time_sde_noise_scaler(x): - return x ** (eta + 1) - - if solver_type == "ER-SDE": - # Use the default one in sample_er_sde() - noise_scaler = None - else: - noise_scaler = reverse_time_sde_noise_scaler - - sampler_name = "er_sde" - sampler = comfy.samplers.ksampler(sampler_name, {"s_noise": s_noise, "noise_scaler": noise_scaler, "max_stage": max_stage}) - return (sampler,) - - -class SamplerSASolver(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": { - "model": (IO.MODEL, {}), - "eta": (IO.FLOAT, {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False},), - "sde_start_percent": (IO.FLOAT, {"default": 0.2, "min": 0.0, "max": 1.0, "step": 0.001},), - "sde_end_percent": (IO.FLOAT, {"default": 0.8, "min": 0.0, "max": 1.0, "step": 0.001},), - "s_noise": (IO.FLOAT, {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": False},), - "predictor_order": (IO.INT, {"default": 3, "min": 1, "max": 6}), - "corrector_order": (IO.INT, {"default": 4, "min": 0, "max": 6}), - "use_pece": (IO.BOOLEAN, {}), - "simple_order_2": (IO.BOOLEAN, {}), - } - } - - RETURN_TYPES = (IO.SAMPLER,) - CATEGORY = "sampling/custom_sampling/samplers" - - FUNCTION = "get_sampler" - - def get_sampler(self, model, eta, sde_start_percent, sde_end_percent, s_noise, predictor_order, corrector_order, use_pece, simple_order_2): - model_sampling = model.get_model_object("model_sampling") - start_sigma = model_sampling.percent_to_sigma(sde_start_percent) - end_sigma = model_sampling.percent_to_sigma(sde_end_percent) - tau_func = sa_solver.get_tau_interval_func(start_sigma, end_sigma, eta=eta) - - sampler_name = "sa_solver" - sampler = comfy.samplers.ksampler( - sampler_name, - { - "tau_func": tau_func, - "s_noise": s_noise, - "predictor_order": predictor_order, - "corrector_order": corrector_order, - "use_pece": use_pece, - "simple_order_2": simple_order_2, - }, - ) - return (sampler,) - - -class Noise_EmptyNoise: - def __init__(self): - self.seed = 0 - - def generate_noise(self, input_latent): - latent_image = input_latent["samples"] - return torch.zeros(latent_image.shape, dtype=latent_image.dtype, layout=latent_image.layout, device="cpu") - - -class Noise_RandomNoise: - def __init__(self, seed): - self.seed = seed - - def generate_noise(self, input_latent): - latent_image = input_latent["samples"] - batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None - return comfy.sample.prepare_noise(latent_image, self.seed, batch_inds) - -class SamplerCustom: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "add_noise": ("BOOLEAN", {"default": True}), - "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}), - "positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "sampler": ("SAMPLER", ), - "sigmas": ("SIGMAS", ), - "latent_image": ("LATENT", ), - } - } - - RETURN_TYPES = ("LATENT","LATENT") - RETURN_NAMES = ("output", "denoised_output") - - FUNCTION = "sample" - - CATEGORY = "sampling/custom_sampling" - - def sample(self, model, add_noise, noise_seed, cfg, positive, negative, sampler, sigmas, latent_image): - latent = latent_image - latent_image = latent["samples"] - latent = latent.copy() - latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image) - latent["samples"] = latent_image - - if not add_noise: - noise = Noise_EmptyNoise().generate_noise(latent) - else: - noise = Noise_RandomNoise(noise_seed).generate_noise(latent) - - noise_mask = None - if "noise_mask" in latent: - noise_mask = latent["noise_mask"] - - x0_output = {} - callback = latent_preview.prepare_callback(model, sigmas.shape[-1] - 1, x0_output) - - disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED - samples = comfy.sample.sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise_seed) - - out = latent.copy() - out["samples"] = samples - if "x0" in x0_output: - out_denoised = latent.copy() - out_denoised["samples"] = model.model.process_latent_out(x0_output["x0"].cpu()) - else: - out_denoised = out - return (out, out_denoised) - -class Guider_Basic(comfy.samplers.CFGGuider): - def set_conds(self, positive): - self.inner_set_conds({"positive": positive}) - -class BasicGuider: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "conditioning": ("CONDITIONING", ), - } - } - - RETURN_TYPES = ("GUIDER",) - - FUNCTION = "get_guider" - CATEGORY = "sampling/custom_sampling/guiders" - - def get_guider(self, model, conditioning): - guider = Guider_Basic(model) - guider.set_conds(conditioning) - return (guider,) - -class CFGGuider: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}), - } - } - - RETURN_TYPES = ("GUIDER",) - - FUNCTION = "get_guider" - CATEGORY = "sampling/custom_sampling/guiders" - - def get_guider(self, model, positive, negative, cfg): - guider = comfy.samplers.CFGGuider(model) - guider.set_conds(positive, negative) - guider.set_cfg(cfg) - return (guider,) - -class Guider_DualCFG(comfy.samplers.CFGGuider): - def set_cfg(self, cfg1, cfg2, nested=False): - self.cfg1 = cfg1 - self.cfg2 = cfg2 - self.nested = nested - - def set_conds(self, positive, middle, negative): - middle = node_helpers.conditioning_set_values(middle, {"prompt_type": "negative"}) - self.inner_set_conds({"positive": positive, "middle": middle, "negative": negative}) - - def predict_noise(self, x, timestep, model_options={}, seed=None): - negative_cond = self.conds.get("negative", None) - middle_cond = self.conds.get("middle", None) - positive_cond = self.conds.get("positive", None) - - if self.nested: - out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, middle_cond, positive_cond], x, timestep, model_options) - pred_text = comfy.samplers.cfg_function(self.inner_model, out[2], out[1], self.cfg1, x, timestep, model_options=model_options, cond=positive_cond, uncond=middle_cond) - return out[0] + self.cfg2 * (pred_text - out[0]) - else: - if model_options.get("disable_cfg1_optimization", False) == False: - if math.isclose(self.cfg2, 1.0): - negative_cond = None - if math.isclose(self.cfg1, 1.0): - middle_cond = None - - out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, middle_cond, positive_cond], x, timestep, model_options) - return comfy.samplers.cfg_function(self.inner_model, out[1], out[0], self.cfg2, x, timestep, model_options=model_options, cond=middle_cond, uncond=negative_cond) + (out[2] - out[1]) * self.cfg1 - -class DualCFGGuider: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "cond1": ("CONDITIONING", ), - "cond2": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "cfg_conds": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}), - "cfg_cond2_negative": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}), - "style": (["regular", "nested"],), - } - } - - RETURN_TYPES = ("GUIDER",) - - FUNCTION = "get_guider" - CATEGORY = "sampling/custom_sampling/guiders" - - def get_guider(self, model, cond1, cond2, negative, cfg_conds, cfg_cond2_negative, style): - guider = Guider_DualCFG(model) - guider.set_conds(cond1, cond2, negative) - guider.set_cfg(cfg_conds, cfg_cond2_negative, nested=(style == "nested")) - return (guider,) - -class DisableNoise: - @classmethod - def INPUT_TYPES(s): - return {"required":{ - } - } - - RETURN_TYPES = ("NOISE",) - FUNCTION = "get_noise" - CATEGORY = "sampling/custom_sampling/noise" - - def get_noise(self): - return (Noise_EmptyNoise(),) - - -class RandomNoise(DisableNoise): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "noise_seed": ("INT", { - "default": 0, - "min": 0, - "max": 0xffffffffffffffff, - "control_after_generate": True, - }), - } - } - - def get_noise(self, noise_seed): - return (Noise_RandomNoise(noise_seed),) - - -class SamplerCustomAdvanced: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"noise": ("NOISE", ), - "guider": ("GUIDER", ), - "sampler": ("SAMPLER", ), - "sigmas": ("SIGMAS", ), - "latent_image": ("LATENT", ), - } - } - - RETURN_TYPES = ("LATENT","LATENT") - RETURN_NAMES = ("output", "denoised_output") - - FUNCTION = "sample" - - CATEGORY = "sampling/custom_sampling" - - def sample(self, noise, guider, sampler, sigmas, latent_image): - latent = latent_image - latent_image = latent["samples"] - latent = latent.copy() - latent_image = comfy.sample.fix_empty_latent_channels(guider.model_patcher, latent_image) - latent["samples"] = latent_image - - noise_mask = None - if "noise_mask" in latent: - noise_mask = latent["noise_mask"] - - x0_output = {} - callback = latent_preview.prepare_callback(guider.model_patcher, sigmas.shape[-1] - 1, x0_output) - - disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED - samples = guider.sample(noise.generate_noise(latent), latent_image, sampler, sigmas, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise.seed) - samples = samples.to(comfy.model_management.intermediate_device()) - - out = latent.copy() - out["samples"] = samples - if "x0" in x0_output: - out_denoised = latent.copy() - out_denoised["samples"] = guider.model_patcher.model.process_latent_out(x0_output["x0"].cpu()) - else: - out_denoised = out - return (out, out_denoised) - -class AddNoise: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "noise": ("NOISE", ), - "sigmas": ("SIGMAS", ), - "latent_image": ("LATENT", ), - } - } - - RETURN_TYPES = ("LATENT",) - - FUNCTION = "add_noise" - - CATEGORY = "_for_testing/custom_sampling/noise" - - def add_noise(self, model, noise, sigmas, latent_image): - if len(sigmas) == 0: - return latent_image - - latent = latent_image - latent_image = latent["samples"] - - noisy = noise.generate_noise(latent) - - model_sampling = model.get_model_object("model_sampling") - process_latent_out = model.get_model_object("process_latent_out") - process_latent_in = model.get_model_object("process_latent_in") - - if len(sigmas) > 1: - scale = torch.abs(sigmas[0] - sigmas[-1]) - else: - scale = sigmas[0] - - if torch.count_nonzero(latent_image) > 0: #Don't shift the empty latent image. - latent_image = process_latent_in(latent_image) - noisy = model_sampling.noise_scaling(scale, noisy, latent_image) - noisy = process_latent_out(noisy) - noisy = torch.nan_to_num(noisy, nan=0.0, posinf=0.0, neginf=0.0) - - out = latent.copy() - out["samples"] = noisy - return (out,) - - -NODE_CLASS_MAPPINGS = { - "SamplerCustom": SamplerCustom, - "BasicScheduler": BasicScheduler, - "KarrasScheduler": KarrasScheduler, - "ExponentialScheduler": ExponentialScheduler, - "PolyexponentialScheduler": PolyexponentialScheduler, - "LaplaceScheduler": LaplaceScheduler, - "VPScheduler": VPScheduler, - "BetaSamplingScheduler": BetaSamplingScheduler, - "SDTurboScheduler": SDTurboScheduler, - "KSamplerSelect": KSamplerSelect, - "SamplerEulerAncestral": SamplerEulerAncestral, - "SamplerEulerAncestralCFGPP": SamplerEulerAncestralCFGPP, - "SamplerLMS": SamplerLMS, - "SamplerDPMPP_3M_SDE": SamplerDPMPP_3M_SDE, - "SamplerDPMPP_2M_SDE": SamplerDPMPP_2M_SDE, - "SamplerDPMPP_SDE": SamplerDPMPP_SDE, - "SamplerDPMPP_2S_Ancestral": SamplerDPMPP_2S_Ancestral, - "SamplerDPMAdaptative": SamplerDPMAdaptative, - "SamplerER_SDE": SamplerER_SDE, - "SamplerSASolver": SamplerSASolver, - "SplitSigmas": SplitSigmas, - "SplitSigmasDenoise": SplitSigmasDenoise, - "FlipSigmas": FlipSigmas, - "SetFirstSigma": SetFirstSigma, - "ExtendIntermediateSigmas": ExtendIntermediateSigmas, - "SamplingPercentToSigma": SamplingPercentToSigma, - - "CFGGuider": CFGGuider, - "DualCFGGuider": DualCFGGuider, - "BasicGuider": BasicGuider, - "RandomNoise": RandomNoise, - "DisableNoise": DisableNoise, - "AddNoise": AddNoise, - "SamplerCustomAdvanced": SamplerCustomAdvanced, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "SamplerEulerAncestralCFGPP": "SamplerEulerAncestralCFG++", -} diff --git a/comfy_extras/nodes_differential_diffusion.py b/comfy_extras/nodes_differential_diffusion.py deleted file mode 100644 index 98dbbf102dac861cfb65ed19ad1af499abf7465d..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_differential_diffusion.py +++ /dev/null @@ -1,42 +0,0 @@ -# code adapted from https://github.com/exx8/differential-diffusion - -import torch - -class DifferentialDiffusion(): - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL", ), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "apply" - CATEGORY = "_for_testing" - INIT = False - - def apply(self, model): - model = model.clone() - model.set_model_denoise_mask_function(self.forward) - return (model,) - - def forward(self, sigma: torch.Tensor, denoise_mask: torch.Tensor, extra_options: dict): - model = extra_options["model"] - step_sigmas = extra_options["sigmas"] - sigma_to = model.inner_model.model_sampling.sigma_min - if step_sigmas[-1] > sigma_to: - sigma_to = step_sigmas[-1] - sigma_from = step_sigmas[0] - - ts_from = model.inner_model.model_sampling.timestep(sigma_from) - ts_to = model.inner_model.model_sampling.timestep(sigma_to) - current_ts = model.inner_model.model_sampling.timestep(sigma[0]) - - threshold = (current_ts - ts_to) / (ts_from - ts_to) - - return (denoise_mask >= threshold).to(denoise_mask.dtype) - - -NODE_CLASS_MAPPINGS = { - "DifferentialDiffusion": DifferentialDiffusion, -} -NODE_DISPLAY_NAME_MAPPINGS = { - "DifferentialDiffusion": "Differential Diffusion", -} diff --git a/comfy_extras/nodes_edit_model.py b/comfy_extras/nodes_edit_model.py deleted file mode 100644 index b69f7971591b383774d322b022e3b3b39ec0d704..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_edit_model.py +++ /dev/null @@ -1,26 +0,0 @@ -import node_helpers - - -class ReferenceLatent: - @classmethod - def INPUT_TYPES(s): - return {"required": {"conditioning": ("CONDITIONING", ), - }, - "optional": {"latent": ("LATENT", ),} - } - - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "append" - - CATEGORY = "advanced/conditioning/edit_models" - DESCRIPTION = "This node sets the guiding latent for an edit model. If the model supports it you can chain multiple to set multiple reference images." - - def append(self, conditioning, latent=None): - if latent is not None: - conditioning = node_helpers.conditioning_set_values(conditioning, {"reference_latents": [latent["samples"]]}, append=True) - return (conditioning, ) - - -NODE_CLASS_MAPPINGS = { - "ReferenceLatent": ReferenceLatent, -} diff --git a/comfy_extras/nodes_flux.py b/comfy_extras/nodes_flux.py deleted file mode 100644 index 8a8a1769801c046c8001d1f3f7ad913794997147..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_flux.py +++ /dev/null @@ -1,108 +0,0 @@ -import node_helpers -import comfy.utils - -class CLIPTextEncodeFlux: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "clip": ("CLIP", ), - "clip_l": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "t5xxl": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "guidance": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.1}), - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "advanced/conditioning/flux" - - def encode(self, clip, clip_l, t5xxl, guidance): - tokens = clip.tokenize(clip_l) - tokens["t5xxl"] = clip.tokenize(t5xxl)["t5xxl"] - - return (clip.encode_from_tokens_scheduled(tokens, add_dict={"guidance": guidance}), ) - -class FluxGuidance: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "conditioning": ("CONDITIONING", ), - "guidance": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.1}), - }} - - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "append" - - CATEGORY = "advanced/conditioning/flux" - - def append(self, conditioning, guidance): - c = node_helpers.conditioning_set_values(conditioning, {"guidance": guidance}) - return (c, ) - - -class FluxDisableGuidance: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "conditioning": ("CONDITIONING", ), - }} - - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "append" - - CATEGORY = "advanced/conditioning/flux" - DESCRIPTION = "This node completely disables the guidance embed on Flux and Flux like models" - - def append(self, conditioning): - c = node_helpers.conditioning_set_values(conditioning, {"guidance": None}) - return (c, ) - - -PREFERED_KONTEXT_RESOLUTIONS = [ - (672, 1568), - (688, 1504), - (720, 1456), - (752, 1392), - (800, 1328), - (832, 1248), - (880, 1184), - (944, 1104), - (1024, 1024), - (1104, 944), - (1184, 880), - (1248, 832), - (1328, 800), - (1392, 752), - (1456, 720), - (1504, 688), - (1568, 672), -] - - -class FluxKontextImageScale: - @classmethod - def INPUT_TYPES(s): - return {"required": {"image": ("IMAGE", ), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "scale" - - CATEGORY = "advanced/conditioning/flux" - DESCRIPTION = "This node resizes the image to one that is more optimal for flux kontext." - - def scale(self, image): - width = image.shape[2] - height = image.shape[1] - aspect_ratio = width / height - _, width, height = min((abs(aspect_ratio - w / h), w, h) for w, h in PREFERED_KONTEXT_RESOLUTIONS) - image = comfy.utils.common_upscale(image.movedim(-1, 1), width, height, "lanczos", "center").movedim(1, -1) - return (image, ) - - -NODE_CLASS_MAPPINGS = { - "CLIPTextEncodeFlux": CLIPTextEncodeFlux, - "FluxGuidance": FluxGuidance, - "FluxDisableGuidance": FluxDisableGuidance, - "FluxKontextImageScale": FluxKontextImageScale, -} diff --git a/comfy_extras/nodes_freelunch.py b/comfy_extras/nodes_freelunch.py deleted file mode 100644 index e3ac58447b29f604debb5bfc0aed3a5f100a4ae9..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_freelunch.py +++ /dev/null @@ -1,113 +0,0 @@ -#code originally taken from: https://github.com/ChenyangSi/FreeU (under MIT License) - -import torch -import logging - -def Fourier_filter(x, threshold, scale): - # FFT - x_freq = torch.fft.fftn(x.float(), dim=(-2, -1)) - x_freq = torch.fft.fftshift(x_freq, dim=(-2, -1)) - - B, C, H, W = x_freq.shape - mask = torch.ones((B, C, H, W), device=x.device) - - crow, ccol = H // 2, W //2 - mask[..., crow - threshold:crow + threshold, ccol - threshold:ccol + threshold] = scale - x_freq = x_freq * mask - - # IFFT - x_freq = torch.fft.ifftshift(x_freq, dim=(-2, -1)) - x_filtered = torch.fft.ifftn(x_freq, dim=(-2, -1)).real - - return x_filtered.to(x.dtype) - - -class FreeU: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "b1": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 10.0, "step": 0.01}), - "b2": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 10.0, "step": 0.01}), - "s1": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 10.0, "step": 0.01}), - "s2": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "model_patches/unet" - - def patch(self, model, b1, b2, s1, s2): - model_channels = model.model.model_config.unet_config["model_channels"] - scale_dict = {model_channels * 4: (b1, s1), model_channels * 2: (b2, s2)} - on_cpu_devices = {} - - def output_block_patch(h, hsp, transformer_options): - scale = scale_dict.get(int(h.shape[1]), None) - if scale is not None: - h[:,:h.shape[1] // 2] = h[:,:h.shape[1] // 2] * scale[0] - if hsp.device not in on_cpu_devices: - try: - hsp = Fourier_filter(hsp, threshold=1, scale=scale[1]) - except: - logging.warning("Device {} does not support the torch.fft functions used in the FreeU node, switching to CPU.".format(hsp.device)) - on_cpu_devices[hsp.device] = True - hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device) - else: - hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device) - - return h, hsp - - m = model.clone() - m.set_model_output_block_patch(output_block_patch) - return (m, ) - -class FreeU_V2: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "b1": ("FLOAT", {"default": 1.3, "min": 0.0, "max": 10.0, "step": 0.01}), - "b2": ("FLOAT", {"default": 1.4, "min": 0.0, "max": 10.0, "step": 0.01}), - "s1": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 10.0, "step": 0.01}), - "s2": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "model_patches/unet" - - def patch(self, model, b1, b2, s1, s2): - model_channels = model.model.model_config.unet_config["model_channels"] - scale_dict = {model_channels * 4: (b1, s1), model_channels * 2: (b2, s2)} - on_cpu_devices = {} - - def output_block_patch(h, hsp, transformer_options): - scale = scale_dict.get(int(h.shape[1]), None) - if scale is not None: - hidden_mean = h.mean(1).unsqueeze(1) - B = hidden_mean.shape[0] - hidden_max, _ = torch.max(hidden_mean.view(B, -1), dim=-1, keepdim=True) - hidden_min, _ = torch.min(hidden_mean.view(B, -1), dim=-1, keepdim=True) - hidden_mean = (hidden_mean - hidden_min.unsqueeze(2).unsqueeze(3)) / (hidden_max - hidden_min).unsqueeze(2).unsqueeze(3) - - h[:,:h.shape[1] // 2] = h[:,:h.shape[1] // 2] * ((scale[0] - 1 ) * hidden_mean + 1) - - if hsp.device not in on_cpu_devices: - try: - hsp = Fourier_filter(hsp, threshold=1, scale=scale[1]) - except: - logging.warning("Device {} does not support the torch.fft functions used in the FreeU node, switching to CPU.".format(hsp.device)) - on_cpu_devices[hsp.device] = True - hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device) - else: - hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device) - - return h, hsp - - m = model.clone() - m.set_model_output_block_patch(output_block_patch) - return (m, ) - -NODE_CLASS_MAPPINGS = { - "FreeU": FreeU, - "FreeU_V2": FreeU_V2, -} diff --git a/comfy_extras/nodes_fresca.py b/comfy_extras/nodes_fresca.py deleted file mode 100644 index 65c2d0d0ea3f35b2795ac92a208424078085c5b2..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_fresca.py +++ /dev/null @@ -1,103 +0,0 @@ -# Code based on https://github.com/WikiChao/FreSca (MIT License) -import torch -import torch.fft as fft - - -def Fourier_filter(x, scale_low=1.0, scale_high=1.5, freq_cutoff=20): - """ - Apply frequency-dependent scaling to an image tensor using Fourier transforms. - - Parameters: - x: Input tensor of shape (B, C, H, W) - scale_low: Scaling factor for low-frequency components (default: 1.0) - scale_high: Scaling factor for high-frequency components (default: 1.5) - freq_cutoff: Number of frequency indices around center to consider as low-frequency (default: 20) - - Returns: - x_filtered: Filtered version of x in spatial domain with frequency-specific scaling applied. - """ - # Preserve input dtype and device - dtype, device = x.dtype, x.device - - # Convert to float32 for FFT computations - x = x.to(torch.float32) - - # 1) Apply FFT and shift low frequencies to center - x_freq = fft.fftn(x, dim=(-2, -1)) - x_freq = fft.fftshift(x_freq, dim=(-2, -1)) - - # Initialize mask with high-frequency scaling factor - mask = torch.ones(x_freq.shape, device=device) * scale_high - m = mask - for d in range(len(x_freq.shape) - 2): - dim = d + 2 - cc = x_freq.shape[dim] // 2 - f_c = min(freq_cutoff, cc) - m = m.narrow(dim, cc - f_c, f_c * 2) - - # Apply low-frequency scaling factor to center region - m[:] = scale_low - - # 3) Apply frequency-specific scaling - x_freq = x_freq * mask - - # 4) Convert back to spatial domain - x_freq = fft.ifftshift(x_freq, dim=(-2, -1)) - x_filtered = fft.ifftn(x_freq, dim=(-2, -1)).real - - # 5) Restore original dtype - x_filtered = x_filtered.to(dtype) - - return x_filtered - - -class FreSca: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "scale_low": ("FLOAT", {"default": 1.0, "min": 0, "max": 10, "step": 0.01, - "tooltip": "Scaling factor for low-frequency components"}), - "scale_high": ("FLOAT", {"default": 1.25, "min": 0, "max": 10, "step": 0.01, - "tooltip": "Scaling factor for high-frequency components"}), - "freq_cutoff": ("INT", {"default": 20, "min": 1, "max": 10000, "step": 1, - "tooltip": "Number of frequency indices around center to consider as low-frequency"}), - } - } - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - CATEGORY = "_for_testing" - DESCRIPTION = "Applies frequency-dependent scaling to the guidance" - def patch(self, model, scale_low, scale_high, freq_cutoff): - def custom_cfg_function(args): - conds_out = args["conds_out"] - if len(conds_out) <= 1 or None in args["conds"][:2]: - return conds_out - cond = conds_out[0] - uncond = conds_out[1] - - guidance = cond - uncond - filtered_guidance = Fourier_filter( - guidance, - scale_low=scale_low, - scale_high=scale_high, - freq_cutoff=freq_cutoff, - ) - filtered_cond = filtered_guidance + uncond - - return [filtered_cond, uncond] + conds_out[2:] - - m = model.clone() - m.set_model_sampler_pre_cfg_function(custom_cfg_function) - - return (m,) - - -NODE_CLASS_MAPPINGS = { - "FreSca": FreSca, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "FreSca": "FreSca", -} diff --git a/comfy_extras/nodes_gits.py b/comfy_extras/nodes_gits.py deleted file mode 100644 index 47b1dd049702cc481550dd04d2c4edebfdcf7a0e..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_gits.py +++ /dev/null @@ -1,369 +0,0 @@ -# from https://github.com/zju-pi/diff-sampler/tree/main/gits-main -import numpy as np -import torch - -def loglinear_interp(t_steps, num_steps): - """ - Performs log-linear interpolation of a given array of decreasing numbers. - """ - xs = np.linspace(0, 1, len(t_steps)) - ys = np.log(t_steps[::-1]) - - new_xs = np.linspace(0, 1, num_steps) - new_ys = np.interp(new_xs, xs, ys) - - interped_ys = np.exp(new_ys)[::-1].copy() - return interped_ys - -NOISE_LEVELS = { - 0.80: [ - [14.61464119, 7.49001646, 0.02916753], - [14.61464119, 11.54541874, 6.77309084, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 3.07277966, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 2.05039096, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 2.05039096, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.1956799, 1.98035145, 0.86115354, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.1956799, 1.98035145, 0.86115354, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.07277966, 1.84880662, 0.83188516, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.07277966, 1.84880662, 0.83188516, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.75677586, 2.84484982, 1.78698075, 0.803307, 0.02916753], - ], - 0.85: [ - [14.61464119, 7.49001646, 0.02916753], - [14.61464119, 7.49001646, 1.84880662, 0.02916753], - [14.61464119, 11.54541874, 6.77309084, 1.56271636, 0.02916753], - [14.61464119, 11.54541874, 7.11996698, 3.07277966, 1.24153244, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.60512662, 2.6383388, 1.56271636, 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, 0.02916753], - ], - 0.90: [ - [14.61464119, 6.77309084, 0.02916753], - [14.61464119, 7.49001646, 1.56271636, 0.02916753], - [14.61464119, 7.49001646, 3.07277966, 0.95350921, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.09240818, 3.07277966, 1.61558151, 0.69515091, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.11996698, 4.86714602, 3.07277966, 1.61558151, 0.69515091, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 2.95596409, 1.61558151, 0.69515091, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.24153244, 0.57119018, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.24153244, 0.57119018, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.24153244, 0.57119018, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.75677586, 2.84484982, 1.84880662, 1.08895338, 0.52423614, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.75677586, 2.84484982, 1.84880662, 1.08895338, 0.52423614, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, 4.45427561, 3.32507086, 2.45070267, 1.61558151, 0.95350921, 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, 4.45427561, 3.32507086, 2.45070267, 1.61558151, 0.95350921, 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, 0.95350921, 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, 0.95350921, 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, 0.95350921, 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.19988537, 1.51179266, 0.89115214, 0.43325692, 0.02916753], - ], - 0.95: [ - [14.61464119, 6.77309084, 0.02916753], - [14.61464119, 6.77309084, 1.56271636, 0.02916753], - [14.61464119, 7.49001646, 2.84484982, 0.89115214, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.36326075, 0.803307, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.91321158, 1.08895338, 0.50118381, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, 1.08895338, 0.50118381, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, 1.08895338, 0.50118381, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.41535246, 0.803307, 0.38853383, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.46139455, 2.6383388, 1.84880662, 1.24153244, 0.72133851, 0.34370604, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.46139455, 2.6383388, 1.84880662, 1.24153244, 0.72133851, 0.34370604, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 6.14220476, 4.86714602, 3.75677586, 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 6.44769001, 5.58536053, 4.65472794, 3.60512662, 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, 4.65472794, 3.60512662, 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, 4.65472794, 3.75677586, 3.07277966, 2.45070267, 1.78698075, 1.24153244, 0.83188516, 0.50118381, 0.22545385, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.36326075, 1.72759056, 1.24153244, 0.83188516, 0.50118381, 0.22545385, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.36326075, 1.72759056, 1.24153244, 0.83188516, 0.50118381, 0.22545385, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, 4.45427561, 3.75677586, 3.07277966, 2.45070267, 1.91321158, 1.46270394, 1.05362725, 0.72133851, 0.43325692, 0.19894916, 0.02916753], - ], - 1.00: [ - [14.61464119, 1.56271636, 0.02916753], - [14.61464119, 6.77309084, 0.95350921, 0.02916753], - [14.61464119, 6.77309084, 2.36326075, 0.803307, 0.02916753], - [14.61464119, 7.11996698, 3.07277966, 1.56271636, 0.59516323, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.41535246, 0.57119018, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.72133851, 0.34370604, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.98035145, 1.24153244, 0.72133851, 0.34370604, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.27973175, 1.51179266, 0.95350921, 0.54755926, 0.25053367, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, 1.61558151, 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, 1.61558151, 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.12350607, 1.56271636, 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.19988537, 1.61558151, 1.162866, 0.803307, 0.50118381, 0.27464288, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.75677586, 3.07277966, 2.45070267, 1.84880662, 1.36964464, 1.01931262, 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 5.09240818, 4.26497746, 3.46139455, 2.84484982, 2.19988537, 1.67050016, 1.24153244, 0.92192322, 0.64427125, 0.43325692, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 5.09240818, 4.26497746, 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 12.2308979, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 5.09240818, 4.26497746, 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 12.2308979, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, 4.26497746, 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], - ], - 1.05: [ - [14.61464119, 0.95350921, 0.02916753], - [14.61464119, 6.77309084, 0.89115214, 0.02916753], - [14.61464119, 6.77309084, 2.05039096, 0.72133851, 0.02916753], - [14.61464119, 6.77309084, 2.84484982, 1.28281462, 0.52423614, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.803307, 0.34370604, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.56271636, 0.803307, 0.34370604, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.52423614, 0.22545385, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.74807048, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.27973175, 1.51179266, 0.95350921, 0.59516323, 0.34370604, 0.13792117, 0.02916753], - [14.61464119, 7.49001646, 5.09240818, 3.46139455, 2.45070267, 1.61558151, 1.08895338, 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.09240818, 3.46139455, 2.45070267, 1.61558151, 1.08895338, 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, 1.61558151, 1.08895338, 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.72759056, 1.24153244, 0.86115354, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.19988537, 1.61558151, 1.162866, 0.83188516, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.19988537, 1.67050016, 1.28281462, 0.95350921, 0.72133851, 0.52423614, 0.34370604, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.36326075, 1.84880662, 1.41535246, 1.08895338, 0.83188516, 0.61951244, 0.45573691, 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.20157266, 0.95350921, 0.74807048, 0.57119018, 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.30717278, 7.11996698, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.20157266, 0.95350921, 0.74807048, 0.57119018, 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.30717278, 7.11996698, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.45070267, 1.98035145, 1.61558151, 1.32549286, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.41087446, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - ], - 1.10: [ - [14.61464119, 0.89115214, 0.02916753], - [14.61464119, 2.36326075, 0.72133851, 0.02916753], - [14.61464119, 5.85520077, 1.61558151, 0.57119018, 0.02916753], - [14.61464119, 6.77309084, 2.45070267, 1.08895338, 0.45573691, 0.02916753], - [14.61464119, 6.77309084, 2.95596409, 1.56271636, 0.803307, 0.34370604, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.89115214, 0.4783645, 0.19894916, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.08895338, 0.64427125, 0.34370604, 0.13792117, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.54755926, 0.27464288, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.4783645, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.05039096, 1.41535246, 0.95350921, 0.64427125, 0.41087446, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.27973175, 1.61558151, 1.12534678, 0.803307, 0.54755926, 0.36617002, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.32507086, 2.45070267, 1.72759056, 1.24153244, 0.89115214, 0.64427125, 0.45573691, 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.09240818, 3.60512662, 2.84484982, 2.05039096, 1.51179266, 1.08895338, 0.803307, 0.59516323, 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.09240818, 3.60512662, 2.84484982, 2.12350607, 1.61558151, 1.24153244, 0.95350921, 0.72133851, 0.54755926, 0.41087446, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.08895338, 0.83188516, 0.64427125, 0.50118381, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.91321158, 1.51179266, 1.20157266, 0.95350921, 0.74807048, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, 1.72759056, 1.36964464, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.43325692, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, 1.72759056, 1.36964464, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.43325692, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, 1.72759056, 1.36964464, 1.08895338, 0.89115214, 0.72133851, 0.59516323, 0.4783645, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - ], - 1.15: [ - [14.61464119, 0.83188516, 0.02916753], - [14.61464119, 1.84880662, 0.59516323, 0.02916753], - [14.61464119, 5.85520077, 1.56271636, 0.52423614, 0.02916753], - [14.61464119, 5.85520077, 1.91321158, 0.83188516, 0.34370604, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.59516323, 0.25053367, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.51179266, 0.803307, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.56271636, 0.89115214, 0.50118381, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.12534678, 0.72133851, 0.43325692, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.05039096, 1.36964464, 0.95350921, 0.69515091, 0.4783645, 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.803307, 0.59516323, 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.803307, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, 0.74807048, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.78698075, 1.32549286, 1.01931262, 0.803307, 0.64427125, 0.50118381, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.78698075, 1.32549286, 1.01931262, 0.803307, 0.64427125, 0.52423614, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, 0.72133851, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, 0.72133851, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, 0.72133851, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.20: [ - [14.61464119, 0.803307, 0.02916753], - [14.61464119, 1.56271636, 0.52423614, 0.02916753], - [14.61464119, 2.36326075, 0.92192322, 0.36617002, 0.02916753], - [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.25053367, 0.02916753], - [14.61464119, 5.85520077, 2.05039096, 0.95350921, 0.45573691, 0.17026083, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.64427125, 0.29807833, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.803307, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.95350921, 0.59516323, 0.36617002, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.50118381, 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.24153244, 0.83188516, 0.59516323, 0.41087446, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 1.98035145, 1.36964464, 0.95350921, 0.69515091, 0.50118381, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 6.77309084, 3.46139455, 2.36326075, 1.56271636, 1.08895338, 0.803307, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 6.77309084, 3.46139455, 2.45070267, 1.61558151, 1.162866, 0.86115354, 0.64427125, 0.50118381, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, 0.64427125, 0.50118381, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, 0.64427125, 0.50118381, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, 0.64427125, 0.50118381, 0.41087446, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.20157266, 0.92192322, 0.72133851, 0.57119018, 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, 0.74807048, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, 0.74807048, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.25: [ - [14.61464119, 0.72133851, 0.02916753], - [14.61464119, 1.56271636, 0.50118381, 0.02916753], - [14.61464119, 2.05039096, 0.803307, 0.32104823, 0.02916753], - [14.61464119, 2.36326075, 0.95350921, 0.43325692, 0.17026083, 0.02916753], - [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.27464288, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.51179266, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.36326075, 1.24153244, 0.72133851, 0.41087446, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.52423614, 0.34370604, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.98595673, 0.64427125, 0.43325692, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.52423614, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.803307, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.24153244, 0.86115354, 0.64427125, 0.4783645, 0.36617002, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.28281462, 0.92192322, 0.69515091, 0.52423614, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.72133851, 0.54755926, 0.43325692, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.72133851, 0.57119018, 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.74807048, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.41535246, 1.05362725, 0.803307, 0.61951244, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.41535246, 1.05362725, 0.803307, 0.64427125, 0.52423614, 0.43325692, 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.46270394, 1.08895338, 0.83188516, 0.66947293, 0.54755926, 0.45573691, 0.38853383, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.30: [ - [14.61464119, 0.72133851, 0.02916753], - [14.61464119, 1.24153244, 0.43325692, 0.02916753], - [14.61464119, 1.56271636, 0.59516323, 0.22545385, 0.02916753], - [14.61464119, 1.84880662, 0.803307, 0.36617002, 0.13792117, 0.02916753], - [14.61464119, 2.36326075, 1.01931262, 0.52423614, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.36964464, 0.74807048, 0.41087446, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.56271636, 0.89115214, 0.54755926, 0.34370604, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 0.95350921, 0.61951244, 0.41087446, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.54755926, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.41535246, 0.92192322, 0.64427125, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.56271636, 1.01931262, 0.72133851, 0.50118381, 0.36617002, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.05362725, 0.74807048, 0.54755926, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.77538133, 0.57119018, 0.43325692, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.59516323, 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.83188516, 0.64427125, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.83188516, 0.64427125, 0.52423614, 0.43325692, 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.78698075, 1.24153244, 0.92192322, 0.72133851, 0.57119018, 0.45573691, 0.38853383, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.78698075, 1.24153244, 0.92192322, 0.72133851, 0.57119018, 0.4783645, 0.41087446, 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.35: [ - [14.61464119, 0.69515091, 0.02916753], - [14.61464119, 0.95350921, 0.34370604, 0.02916753], - [14.61464119, 1.56271636, 0.57119018, 0.19894916, 0.02916753], - [14.61464119, 1.61558151, 0.69515091, 0.29807833, 0.09824532, 0.02916753], - [14.61464119, 1.84880662, 0.83188516, 0.43325692, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.162866, 0.64427125, 0.36617002, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.36964464, 0.803307, 0.50118381, 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.41535246, 0.83188516, 0.54755926, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.32104823, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.34370604, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 1.01931262, 0.72133851, 0.52423614, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 1.01931262, 0.72133851, 0.52423614, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 1.05362725, 0.74807048, 0.54755926, 0.43325692, 0.34370604, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.72759056, 1.12534678, 0.803307, 0.59516323, 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.72759056, 1.12534678, 0.803307, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.51179266, 1.01931262, 0.74807048, 0.57119018, 0.45573691, 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.43325692, 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.45573691, 0.38853383, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.40: [ - [14.61464119, 0.59516323, 0.02916753], - [14.61464119, 0.95350921, 0.34370604, 0.02916753], - [14.61464119, 1.08895338, 0.43325692, 0.13792117, 0.02916753], - [14.61464119, 1.56271636, 0.64427125, 0.27464288, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 2.05039096, 0.95350921, 0.54755926, 0.34370604, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.72133851, 0.43325692, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.52423614, 0.36617002, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.54755926, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.41535246, 0.86115354, 0.59516323, 0.43325692, 0.32104823, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.64427125, 0.45573691, 0.34370604, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.64427125, 0.4783645, 0.36617002, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.98595673, 0.69515091, 0.52423614, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.72133851, 0.54755926, 0.43325692, 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.05362725, 0.74807048, 0.57119018, 0.45573691, 0.38853383, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.41087446, 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.43325692, 0.38853383, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.45573691, 0.41087446, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.45: [ - [14.61464119, 0.59516323, 0.02916753], - [14.61464119, 0.803307, 0.25053367, 0.02916753], - [14.61464119, 0.95350921, 0.34370604, 0.09824532, 0.02916753], - [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 1.91321158, 0.95350921, 0.57119018, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 2.19988537, 1.08895338, 0.64427125, 0.41087446, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.36617002, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.54755926, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.83188516, 0.59516323, 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.83188516, 0.59516323, 0.45573691, 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.69515091, 0.52423614, 0.41087446, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.69515091, 0.52423614, 0.43325692, 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.98595673, 0.72133851, 0.54755926, 0.45573691, 0.38853383, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.74807048, 0.57119018, 0.4783645, 0.41087446, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.74807048, 0.59516323, 0.50118381, 0.43325692, 0.38853383, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.50: [ - [14.61464119, 0.54755926, 0.02916753], - [14.61464119, 0.803307, 0.25053367, 0.02916753], - [14.61464119, 0.86115354, 0.32104823, 0.09824532, 0.02916753], - [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.83188516, 0.52423614, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.38853383, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.41087446, 0.29807833, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 1.84880662, 0.95350921, 0.61951244, 0.43325692, 0.32104823, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.19988537, 1.12534678, 0.72133851, 0.50118381, 0.36617002, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.19988537, 1.12534678, 0.72133851, 0.50118381, 0.36617002, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.59516323, 0.45573691, 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.59516323, 0.45573691, 0.38853383, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.32549286, 0.86115354, 0.64427125, 0.50118381, 0.41087446, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.36964464, 0.92192322, 0.69515091, 0.54755926, 0.45573691, 0.41087446, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.41535246, 0.95350921, 0.72133851, 0.57119018, 0.4783645, 0.43325692, 0.38853383, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], -} - -class GITSScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"coeff": ("FLOAT", {"default": 1.20, "min": 0.80, "max": 1.50, "step": 0.05}), - "steps": ("INT", {"default": 10, "min": 2, "max": 1000}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, coeff, steps, denoise): - total_steps = steps - if denoise < 1.0: - if denoise <= 0.0: - return (torch.FloatTensor([]),) - total_steps = round(steps * denoise) - - if steps <= 20: - sigmas = NOISE_LEVELS[round(coeff, 2)][steps-2][:] - else: - sigmas = NOISE_LEVELS[round(coeff, 2)][-1][:] - sigmas = loglinear_interp(sigmas, steps + 1) - - sigmas = sigmas[-(total_steps + 1):] - sigmas[-1] = 0 - return (torch.FloatTensor(sigmas), ) - -NODE_CLASS_MAPPINGS = { - "GITSScheduler": GITSScheduler, -} diff --git a/comfy_extras/nodes_hidream.py b/comfy_extras/nodes_hidream.py deleted file mode 100644 index dfb98597b8427622360588994c5fc8f75c3e6a1e..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_hidream.py +++ /dev/null @@ -1,55 +0,0 @@ -import folder_paths -import comfy.sd -import comfy.model_management - - -class QuadrupleCLIPLoader: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip_name1": (folder_paths.get_filename_list("text_encoders"), ), - "clip_name2": (folder_paths.get_filename_list("text_encoders"), ), - "clip_name3": (folder_paths.get_filename_list("text_encoders"), ), - "clip_name4": (folder_paths.get_filename_list("text_encoders"), ) - }} - RETURN_TYPES = ("CLIP",) - FUNCTION = "load_clip" - - CATEGORY = "advanced/loaders" - - DESCRIPTION = "[Recipes]\n\nhidream: long clip-l, long clip-g, t5xxl, llama_8b_3.1_instruct" - - def load_clip(self, clip_name1, clip_name2, clip_name3, clip_name4): - clip_path1 = folder_paths.get_full_path_or_raise("text_encoders", clip_name1) - clip_path2 = folder_paths.get_full_path_or_raise("text_encoders", clip_name2) - clip_path3 = folder_paths.get_full_path_or_raise("text_encoders", clip_name3) - clip_path4 = folder_paths.get_full_path_or_raise("text_encoders", clip_name4) - clip = comfy.sd.load_clip(ckpt_paths=[clip_path1, clip_path2, clip_path3, clip_path4], embedding_directory=folder_paths.get_folder_paths("embeddings")) - return (clip,) - -class CLIPTextEncodeHiDream: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "clip": ("CLIP", ), - "clip_l": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "clip_g": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "t5xxl": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "llama": ("STRING", {"multiline": True, "dynamicPrompts": True}) - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "advanced/conditioning" - - def encode(self, clip, clip_l, clip_g, t5xxl, llama): - - tokens = clip.tokenize(clip_g) - tokens["l"] = clip.tokenize(clip_l)["l"] - tokens["t5xxl"] = clip.tokenize(t5xxl)["t5xxl"] - tokens["llama"] = clip.tokenize(llama)["llama"] - return (clip.encode_from_tokens_scheduled(tokens), ) - -NODE_CLASS_MAPPINGS = { - "QuadrupleCLIPLoader": QuadrupleCLIPLoader, - "CLIPTextEncodeHiDream": CLIPTextEncodeHiDream, -} diff --git a/comfy_extras/nodes_hooks.py b/comfy_extras/nodes_hooks.py deleted file mode 100644 index 1edc06f3d7ae6b0682b03afe666ef936b16f2f28..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_hooks.py +++ /dev/null @@ -1,745 +0,0 @@ -from __future__ import annotations -from typing import TYPE_CHECKING, Union -import logging -import torch -from collections.abc import Iterable - -if TYPE_CHECKING: - from comfy.sd import CLIP - -import comfy.hooks -import comfy.sd -import comfy.utils -import folder_paths - -########################################### -# Mask, Combine, and Hook Conditioning -#------------------------------------------ -class PairConditioningSetProperties: - NodeId = 'PairConditioningSetProperties' - NodeName = 'Cond Pair Set Props' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "positive_NEW": ("CONDITIONING", ), - "negative_NEW": ("CONDITIONING", ), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "set_cond_area": (["default", "mask bounds"],), - }, - "optional": { - "mask": ("MASK", ), - "hooks": ("HOOKS",), - "timesteps": ("TIMESTEPS_RANGE",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("positive", "negative") - CATEGORY = "advanced/hooks/cond pair" - FUNCTION = "set_properties" - - def set_properties(self, positive_NEW, negative_NEW, - strength: float, set_cond_area: str, - mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None): - final_positive, final_negative = comfy.hooks.set_conds_props(conds=[positive_NEW, negative_NEW], - strength=strength, set_cond_area=set_cond_area, - mask=mask, hooks=hooks, timesteps_range=timesteps) - return (final_positive, final_negative) - -class PairConditioningSetPropertiesAndCombine: - NodeId = 'PairConditioningSetPropertiesAndCombine' - NodeName = 'Cond Pair Set Props Combine' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "positive_NEW": ("CONDITIONING", ), - "negative_NEW": ("CONDITIONING", ), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "set_cond_area": (["default", "mask bounds"],), - }, - "optional": { - "mask": ("MASK", ), - "hooks": ("HOOKS",), - "timesteps": ("TIMESTEPS_RANGE",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("positive", "negative") - CATEGORY = "advanced/hooks/cond pair" - FUNCTION = "set_properties" - - def set_properties(self, positive, negative, positive_NEW, negative_NEW, - strength: float, set_cond_area: str, - mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None): - final_positive, final_negative = comfy.hooks.set_conds_props_and_combine(conds=[positive, negative], new_conds=[positive_NEW, negative_NEW], - strength=strength, set_cond_area=set_cond_area, - mask=mask, hooks=hooks, timesteps_range=timesteps) - return (final_positive, final_negative) - -class ConditioningSetProperties: - NodeId = 'ConditioningSetProperties' - NodeName = 'Cond Set Props' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "cond_NEW": ("CONDITIONING", ), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "set_cond_area": (["default", "mask bounds"],), - }, - "optional": { - "mask": ("MASK", ), - "hooks": ("HOOKS",), - "timesteps": ("TIMESTEPS_RANGE",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING",) - CATEGORY = "advanced/hooks/cond single" - FUNCTION = "set_properties" - - def set_properties(self, cond_NEW, - strength: float, set_cond_area: str, - mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None): - (final_cond,) = comfy.hooks.set_conds_props(conds=[cond_NEW], - strength=strength, set_cond_area=set_cond_area, - mask=mask, hooks=hooks, timesteps_range=timesteps) - return (final_cond,) - -class ConditioningSetPropertiesAndCombine: - NodeId = 'ConditioningSetPropertiesAndCombine' - NodeName = 'Cond Set Props Combine' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "cond": ("CONDITIONING", ), - "cond_NEW": ("CONDITIONING", ), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "set_cond_area": (["default", "mask bounds"],), - }, - "optional": { - "mask": ("MASK", ), - "hooks": ("HOOKS",), - "timesteps": ("TIMESTEPS_RANGE",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING",) - CATEGORY = "advanced/hooks/cond single" - FUNCTION = "set_properties" - - def set_properties(self, cond, cond_NEW, - strength: float, set_cond_area: str, - mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None): - (final_cond,) = comfy.hooks.set_conds_props_and_combine(conds=[cond], new_conds=[cond_NEW], - strength=strength, set_cond_area=set_cond_area, - mask=mask, hooks=hooks, timesteps_range=timesteps) - return (final_cond,) - -class PairConditioningCombine: - NodeId = 'PairConditioningCombine' - NodeName = 'Cond Pair Combine' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "positive_A": ("CONDITIONING",), - "negative_A": ("CONDITIONING",), - "positive_B": ("CONDITIONING",), - "negative_B": ("CONDITIONING",), - }, - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("positive", "negative") - CATEGORY = "advanced/hooks/cond pair" - FUNCTION = "combine" - - def combine(self, positive_A, negative_A, positive_B, negative_B): - final_positive, final_negative = comfy.hooks.set_conds_props_and_combine(conds=[positive_A, negative_A], new_conds=[positive_B, negative_B],) - return (final_positive, final_negative,) - -class PairConditioningSetDefaultAndCombine: - NodeId = 'PairConditioningSetDefaultCombine' - NodeName = 'Cond Pair Set Default Combine' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "positive_DEFAULT": ("CONDITIONING",), - "negative_DEFAULT": ("CONDITIONING",), - }, - "optional": { - "hooks": ("HOOKS",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("positive", "negative") - CATEGORY = "advanced/hooks/cond pair" - FUNCTION = "set_default_and_combine" - - def set_default_and_combine(self, positive, negative, positive_DEFAULT, negative_DEFAULT, - hooks: comfy.hooks.HookGroup=None): - final_positive, final_negative = comfy.hooks.set_default_conds_and_combine(conds=[positive, negative], new_conds=[positive_DEFAULT, negative_DEFAULT], - hooks=hooks) - return (final_positive, final_negative) - -class ConditioningSetDefaultAndCombine: - NodeId = 'ConditioningSetDefaultCombine' - NodeName = 'Cond Set Default Combine' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "cond": ("CONDITIONING",), - "cond_DEFAULT": ("CONDITIONING",), - }, - "optional": { - "hooks": ("HOOKS",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING",) - CATEGORY = "advanced/hooks/cond single" - FUNCTION = "set_default_and_combine" - - def set_default_and_combine(self, cond, cond_DEFAULT, - hooks: comfy.hooks.HookGroup=None): - (final_conditioning,) = comfy.hooks.set_default_conds_and_combine(conds=[cond], new_conds=[cond_DEFAULT], - hooks=hooks) - return (final_conditioning,) - -class SetClipHooks: - NodeId = 'SetClipHooks' - NodeName = 'Set CLIP Hooks' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "clip": ("CLIP",), - "apply_to_conds": ("BOOLEAN", {"default": True}), - "schedule_clip": ("BOOLEAN", {"default": False}) - }, - "optional": { - "hooks": ("HOOKS",) - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CLIP",) - CATEGORY = "advanced/hooks/clip" - FUNCTION = "apply_hooks" - - def apply_hooks(self, clip: CLIP, schedule_clip: bool, apply_to_conds: bool, hooks: comfy.hooks.HookGroup=None): - if hooks is not None: - clip = clip.clone() - if apply_to_conds: - clip.apply_hooks_to_conds = hooks - clip.patcher.forced_hooks = hooks.clone() - clip.use_clip_schedule = schedule_clip - if not clip.use_clip_schedule: - clip.patcher.forced_hooks.set_keyframes_on_hooks(None) - clip.patcher.register_all_hook_patches(hooks, comfy.hooks.create_target_dict(comfy.hooks.EnumWeightTarget.Clip)) - return (clip,) - -class ConditioningTimestepsRange: - NodeId = 'ConditioningTimestepsRange' - NodeName = 'Timesteps Range' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "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}) - }, - } - - EXPERIMENTAL = True - RETURN_TYPES = ("TIMESTEPS_RANGE", "TIMESTEPS_RANGE", "TIMESTEPS_RANGE") - RETURN_NAMES = ("TIMESTEPS_RANGE", "BEFORE_RANGE", "AFTER_RANGE") - CATEGORY = "advanced/hooks" - FUNCTION = "create_range" - - def create_range(self, start_percent: float, end_percent: float): - return ((start_percent, end_percent), (0.0, start_percent), (end_percent, 1.0)) -#------------------------------------------ -########################################### - - -########################################### -# Create Hooks -#------------------------------------------ -class CreateHookLora: - NodeId = 'CreateHookLora' - NodeName = 'Create Hook LoRA' - def __init__(self): - self.loaded_lora = None - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "lora_name": (folder_paths.get_filename_list("loras"), ), - "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}), - "strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}), - }, - "optional": { - "prev_hooks": ("HOOKS",) - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/create" - FUNCTION = "create_hook" - - def create_hook(self, lora_name: str, strength_model: float, strength_clip: float, prev_hooks: comfy.hooks.HookGroup=None): - if prev_hooks is None: - prev_hooks = comfy.hooks.HookGroup() - prev_hooks.clone() - - if strength_model == 0 and strength_clip == 0: - return (prev_hooks,) - - lora_path = folder_paths.get_full_path("loras", lora_name) - lora = None - if self.loaded_lora is not None: - if self.loaded_lora[0] == lora_path: - lora = self.loaded_lora[1] - else: - temp = self.loaded_lora - self.loaded_lora = None - del temp - - if lora is None: - lora = comfy.utils.load_torch_file(lora_path, safe_load=True) - self.loaded_lora = (lora_path, lora) - - hooks = comfy.hooks.create_hook_lora(lora=lora, strength_model=strength_model, strength_clip=strength_clip) - return (prev_hooks.clone_and_combine(hooks),) - -class CreateHookLoraModelOnly(CreateHookLora): - NodeId = 'CreateHookLoraModelOnly' - NodeName = 'Create Hook LoRA (MO)' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "lora_name": (folder_paths.get_filename_list("loras"), ), - "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}), - }, - "optional": { - "prev_hooks": ("HOOKS",) - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/create" - FUNCTION = "create_hook_model_only" - - def create_hook_model_only(self, lora_name: str, strength_model: float, prev_hooks: comfy.hooks.HookGroup=None): - return self.create_hook(lora_name=lora_name, strength_model=strength_model, strength_clip=0, prev_hooks=prev_hooks) - -class CreateHookModelAsLora: - NodeId = 'CreateHookModelAsLora' - NodeName = 'Create Hook Model as LoRA' - - def __init__(self): - # when not None, will be in following format: - # (ckpt_path: str, weights_model: dict, weights_clip: dict) - self.loaded_weights = None - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ), - "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}), - "strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}), - }, - "optional": { - "prev_hooks": ("HOOKS",) - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/create" - FUNCTION = "create_hook" - - def create_hook(self, ckpt_name: str, strength_model: float, strength_clip: float, - prev_hooks: comfy.hooks.HookGroup=None): - if prev_hooks is None: - prev_hooks = comfy.hooks.HookGroup() - prev_hooks.clone() - - ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name) - weights_model = None - weights_clip = None - if self.loaded_weights is not None: - if self.loaded_weights[0] == ckpt_path: - weights_model = self.loaded_weights[1] - weights_clip = self.loaded_weights[2] - else: - temp = self.loaded_weights - self.loaded_weights = None - del temp - - if weights_model is None: - out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings")) - weights_model = comfy.hooks.get_patch_weights_from_model(out[0]) - weights_clip = comfy.hooks.get_patch_weights_from_model(out[1].patcher if out[1] else out[1]) - self.loaded_weights = (ckpt_path, weights_model, weights_clip) - - hooks = comfy.hooks.create_hook_model_as_lora(weights_model=weights_model, weights_clip=weights_clip, - strength_model=strength_model, strength_clip=strength_clip) - return (prev_hooks.clone_and_combine(hooks),) - -class CreateHookModelAsLoraModelOnly(CreateHookModelAsLora): - NodeId = 'CreateHookModelAsLoraModelOnly' - NodeName = 'Create Hook Model as LoRA (MO)' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ), - "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}), - }, - "optional": { - "prev_hooks": ("HOOKS",) - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/create" - FUNCTION = "create_hook_model_only" - - def create_hook_model_only(self, ckpt_name: str, strength_model: float, - prev_hooks: comfy.hooks.HookGroup=None): - return self.create_hook(ckpt_name=ckpt_name, strength_model=strength_model, strength_clip=0.0, prev_hooks=prev_hooks) -#------------------------------------------ -########################################### - - -########################################### -# Schedule Hooks -#------------------------------------------ -class SetHookKeyframes: - NodeId = 'SetHookKeyframes' - NodeName = 'Set Hook Keyframes' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "hooks": ("HOOKS",), - }, - "optional": { - "hook_kf": ("HOOK_KEYFRAMES",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/scheduling" - FUNCTION = "set_hook_keyframes" - - def set_hook_keyframes(self, hooks: comfy.hooks.HookGroup, hook_kf: comfy.hooks.HookKeyframeGroup=None): - if hook_kf is not None: - hooks = hooks.clone() - hooks.set_keyframes_on_hooks(hook_kf=hook_kf) - return (hooks,) - -class CreateHookKeyframe: - NodeId = 'CreateHookKeyframe' - NodeName = 'Create Hook Keyframe' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "strength_mult": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}), - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - }, - "optional": { - "prev_hook_kf": ("HOOK_KEYFRAMES",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOK_KEYFRAMES",) - RETURN_NAMES = ("HOOK_KF",) - CATEGORY = "advanced/hooks/scheduling" - FUNCTION = "create_hook_keyframe" - - def create_hook_keyframe(self, strength_mult: float, start_percent: float, prev_hook_kf: comfy.hooks.HookKeyframeGroup=None): - if prev_hook_kf is None: - prev_hook_kf = comfy.hooks.HookKeyframeGroup() - prev_hook_kf = prev_hook_kf.clone() - keyframe = comfy.hooks.HookKeyframe(strength=strength_mult, start_percent=start_percent) - prev_hook_kf.add(keyframe) - return (prev_hook_kf,) - -class CreateHookKeyframesInterpolated: - NodeId = 'CreateHookKeyframesInterpolated' - NodeName = 'Create Hook Keyframes Interp.' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "strength_start": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "strength_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ), - "interpolation": (comfy.hooks.InterpolationMethod._LIST, ), - "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}), - "keyframes_count": ("INT", {"default": 5, "min": 2, "max": 100, "step": 1}), - "print_keyframes": ("BOOLEAN", {"default": False}), - }, - "optional": { - "prev_hook_kf": ("HOOK_KEYFRAMES",), - }, - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOK_KEYFRAMES",) - RETURN_NAMES = ("HOOK_KF",) - CATEGORY = "advanced/hooks/scheduling" - FUNCTION = "create_hook_keyframes" - - def create_hook_keyframes(self, strength_start: float, strength_end: float, interpolation: str, - start_percent: float, end_percent: float, keyframes_count: int, - print_keyframes=False, prev_hook_kf: comfy.hooks.HookKeyframeGroup=None): - if prev_hook_kf is None: - prev_hook_kf = comfy.hooks.HookKeyframeGroup() - prev_hook_kf = prev_hook_kf.clone() - percents = comfy.hooks.InterpolationMethod.get_weights(num_from=start_percent, num_to=end_percent, length=keyframes_count, - method=comfy.hooks.InterpolationMethod.LINEAR) - strengths = comfy.hooks.InterpolationMethod.get_weights(num_from=strength_start, num_to=strength_end, length=keyframes_count, method=interpolation) - - is_first = True - for percent, strength in zip(percents, strengths): - guarantee_steps = 0 - if is_first: - guarantee_steps = 1 - is_first = False - prev_hook_kf.add(comfy.hooks.HookKeyframe(strength=strength, start_percent=percent, guarantee_steps=guarantee_steps)) - if print_keyframes: - logging.info(f"Hook Keyframe - start_percent:{percent} = {strength}") - return (prev_hook_kf,) - -class CreateHookKeyframesFromFloats: - NodeId = 'CreateHookKeyframesFromFloats' - NodeName = 'Create Hook Keyframes From Floats' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "floats_strength": ("FLOATS", {"default": -1, "min": -1, "step": 0.001, "forceInput": True}), - "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}), - "print_keyframes": ("BOOLEAN", {"default": False}), - }, - "optional": { - "prev_hook_kf": ("HOOK_KEYFRAMES",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOK_KEYFRAMES",) - RETURN_NAMES = ("HOOK_KF",) - CATEGORY = "advanced/hooks/scheduling" - FUNCTION = "create_hook_keyframes" - - def create_hook_keyframes(self, floats_strength: Union[float, list[float]], - start_percent: float, end_percent: float, - prev_hook_kf: comfy.hooks.HookKeyframeGroup=None, print_keyframes=False): - if prev_hook_kf is None: - prev_hook_kf = comfy.hooks.HookKeyframeGroup() - prev_hook_kf = prev_hook_kf.clone() - if type(floats_strength) in (float, int): - floats_strength = [float(floats_strength)] - elif isinstance(floats_strength, Iterable): - pass - else: - raise Exception(f"floats_strength must be either an iterable input or a float, but was{type(floats_strength).__repr__}.") - percents = comfy.hooks.InterpolationMethod.get_weights(num_from=start_percent, num_to=end_percent, length=len(floats_strength), - method=comfy.hooks.InterpolationMethod.LINEAR) - - is_first = True - for percent, strength in zip(percents, floats_strength): - guarantee_steps = 0 - if is_first: - guarantee_steps = 1 - is_first = False - prev_hook_kf.add(comfy.hooks.HookKeyframe(strength=strength, start_percent=percent, guarantee_steps=guarantee_steps)) - if print_keyframes: - logging.info(f"Hook Keyframe - start_percent:{percent} = {strength}") - return (prev_hook_kf,) -#------------------------------------------ -########################################### - - -class SetModelHooksOnCond: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "conditioning": ("CONDITIONING",), - "hooks": ("HOOKS",), - }, - } - - EXPERIMENTAL = True - RETURN_TYPES = ("CONDITIONING",) - CATEGORY = "advanced/hooks/manual" - FUNCTION = "attach_hook" - - def attach_hook(self, conditioning, hooks: comfy.hooks.HookGroup): - return (comfy.hooks.set_hooks_for_conditioning(conditioning, hooks),) - - -########################################### -# Combine Hooks -#------------------------------------------ -class CombineHooks: - NodeId = 'CombineHooks2' - NodeName = 'Combine Hooks [2]' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - }, - "optional": { - "hooks_A": ("HOOKS",), - "hooks_B": ("HOOKS",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/combine" - FUNCTION = "combine_hooks" - - def combine_hooks(self, - hooks_A: comfy.hooks.HookGroup=None, - hooks_B: comfy.hooks.HookGroup=None): - candidates = [hooks_A, hooks_B] - return (comfy.hooks.HookGroup.combine_all_hooks(candidates),) - -class CombineHooksFour: - NodeId = 'CombineHooks4' - NodeName = 'Combine Hooks [4]' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - }, - "optional": { - "hooks_A": ("HOOKS",), - "hooks_B": ("HOOKS",), - "hooks_C": ("HOOKS",), - "hooks_D": ("HOOKS",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/combine" - FUNCTION = "combine_hooks" - - def combine_hooks(self, - hooks_A: comfy.hooks.HookGroup=None, - hooks_B: comfy.hooks.HookGroup=None, - hooks_C: comfy.hooks.HookGroup=None, - hooks_D: comfy.hooks.HookGroup=None): - candidates = [hooks_A, hooks_B, hooks_C, hooks_D] - return (comfy.hooks.HookGroup.combine_all_hooks(candidates),) - -class CombineHooksEight: - NodeId = 'CombineHooks8' - NodeName = 'Combine Hooks [8]' - @classmethod - def INPUT_TYPES(s): - return { - "required": { - }, - "optional": { - "hooks_A": ("HOOKS",), - "hooks_B": ("HOOKS",), - "hooks_C": ("HOOKS",), - "hooks_D": ("HOOKS",), - "hooks_E": ("HOOKS",), - "hooks_F": ("HOOKS",), - "hooks_G": ("HOOKS",), - "hooks_H": ("HOOKS",), - } - } - - EXPERIMENTAL = True - RETURN_TYPES = ("HOOKS",) - CATEGORY = "advanced/hooks/combine" - FUNCTION = "combine_hooks" - - def combine_hooks(self, - hooks_A: comfy.hooks.HookGroup=None, - hooks_B: comfy.hooks.HookGroup=None, - hooks_C: comfy.hooks.HookGroup=None, - hooks_D: comfy.hooks.HookGroup=None, - hooks_E: comfy.hooks.HookGroup=None, - hooks_F: comfy.hooks.HookGroup=None, - hooks_G: comfy.hooks.HookGroup=None, - hooks_H: comfy.hooks.HookGroup=None): - candidates = [hooks_A, hooks_B, hooks_C, hooks_D, hooks_E, hooks_F, hooks_G, hooks_H] - return (comfy.hooks.HookGroup.combine_all_hooks(candidates),) -#------------------------------------------ -########################################### - -node_list = [ - # Create - CreateHookLora, - CreateHookLoraModelOnly, - CreateHookModelAsLora, - CreateHookModelAsLoraModelOnly, - # Scheduling - SetHookKeyframes, - CreateHookKeyframe, - CreateHookKeyframesInterpolated, - CreateHookKeyframesFromFloats, - # Combine - CombineHooks, - CombineHooksFour, - CombineHooksEight, - # Attach - ConditioningSetProperties, - ConditioningSetPropertiesAndCombine, - PairConditioningSetProperties, - PairConditioningSetPropertiesAndCombine, - ConditioningSetDefaultAndCombine, - PairConditioningSetDefaultAndCombine, - PairConditioningCombine, - SetClipHooks, - # Other - ConditioningTimestepsRange, -] -NODE_CLASS_MAPPINGS = {} -NODE_DISPLAY_NAME_MAPPINGS = {} - -for node in node_list: - NODE_CLASS_MAPPINGS[node.NodeId] = node - NODE_DISPLAY_NAME_MAPPINGS[node.NodeId] = node.NodeName diff --git a/comfy_extras/nodes_hunyuan.py b/comfy_extras/nodes_hunyuan.py deleted file mode 100644 index d7278e7a7d866dcc0519c66f1f5894d8b7344e1c..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_hunyuan.py +++ /dev/null @@ -1,123 +0,0 @@ -import nodes -import node_helpers -import torch -import comfy.model_management - - -class CLIPTextEncodeHunyuanDiT: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "clip": ("CLIP", ), - "bert": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "mt5xl": ("STRING", {"multiline": True, "dynamicPrompts": True}), - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "advanced/conditioning" - - def encode(self, clip, bert, mt5xl): - tokens = clip.tokenize(bert) - tokens["mt5xl"] = clip.tokenize(mt5xl)["mt5xl"] - - return (clip.encode_from_tokens_scheduled(tokens), ) - -class EmptyHunyuanLatentVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": { "width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 25, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/video" - - def generate(self, width, height, length, batch_size=1): - latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - return ({"samples":latent}, ) - -PROMPT_TEMPLATE_ENCODE_VIDEO_I2V = ( - "<|start_header_id|>system<|end_header_id|>\n\n\nDescribe the video by detailing the following aspects according to the reference image: " - "1. The main content and theme of the video." - "2. The color, shape, size, texture, quantity, text, and spatial relationships of the objects." - "3. Actions, events, behaviors temporal relationships, physical movement changes of the objects." - "4. background environment, light, style and atmosphere." - "5. camera angles, movements, and transitions used in the video:<|eot_id|>\n\n" - "<|start_header_id|>user<|end_header_id|>\n\n{}<|eot_id|>" - "<|start_header_id|>assistant<|end_header_id|>\n\n" -) - -class TextEncodeHunyuanVideo_ImageToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "clip": ("CLIP", ), - "clip_vision_output": ("CLIP_VISION_OUTPUT", ), - "prompt": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "image_interleave": ("INT", {"default": 2, "min": 1, "max": 512, "tooltip": "How much the image influences things vs the text prompt. Higher number means more influence from the text prompt."}), - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "advanced/conditioning" - - def encode(self, clip, clip_vision_output, prompt, image_interleave): - tokens = clip.tokenize(prompt, llama_template=PROMPT_TEMPLATE_ENCODE_VIDEO_I2V, image_embeds=clip_vision_output.mm_projected, image_interleave=image_interleave) - return (clip.encode_from_tokens_scheduled(tokens), ) - -class HunyuanImageToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 53, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - "guidance_type": (["v1 (concat)", "v2 (replace)", "custom"], ) - }, - "optional": {"start_image": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, positive, vae, width, height, length, batch_size, guidance_type, start_image=None): - latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - out_latent = {} - - if start_image is not None: - start_image = comfy.utils.common_upscale(start_image[:length, :, :, :3].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - - concat_latent_image = vae.encode(start_image) - mask = torch.ones((1, 1, latent.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), device=start_image.device, dtype=start_image.dtype) - mask[:, :, :((start_image.shape[0] - 1) // 4) + 1] = 0.0 - - if guidance_type == "v1 (concat)": - cond = {"concat_latent_image": concat_latent_image, "concat_mask": mask} - elif guidance_type == "v2 (replace)": - cond = {'guiding_frame_index': 0} - latent[:, :, :concat_latent_image.shape[2]] = concat_latent_image - out_latent["noise_mask"] = mask - elif guidance_type == "custom": - cond = {"ref_latent": concat_latent_image} - - positive = node_helpers.conditioning_set_values(positive, cond) - - out_latent["samples"] = latent - return (positive, out_latent) - - - -NODE_CLASS_MAPPINGS = { - "CLIPTextEncodeHunyuanDiT": CLIPTextEncodeHunyuanDiT, - "TextEncodeHunyuanVideo_ImageToVideo": TextEncodeHunyuanVideo_ImageToVideo, - "EmptyHunyuanLatentVideo": EmptyHunyuanLatentVideo, - "HunyuanImageToVideo": HunyuanImageToVideo, -} diff --git a/comfy_extras/nodes_hunyuan3d.py b/comfy_extras/nodes_hunyuan3d.py deleted file mode 100644 index 51e45336ad4a450b8f83be1e22f0033ad3cd4433..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_hunyuan3d.py +++ /dev/null @@ -1,634 +0,0 @@ -import torch -import os -import json -import struct -import numpy as np -from comfy.ldm.modules.diffusionmodules.mmdit import get_1d_sincos_pos_embed_from_grid_torch -import folder_paths -import comfy.model_management -from comfy.cli_args import args - - -class EmptyLatentHunyuan3Dv2: - @classmethod - def INPUT_TYPES(s): - return {"required": {"resolution": ("INT", {"default": 3072, "min": 1, "max": 8192}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}), - }} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/3d" - - def generate(self, resolution, batch_size): - latent = torch.zeros([batch_size, 64, resolution], device=comfy.model_management.intermediate_device()) - return ({"samples": latent, "type": "hunyuan3dv2"}, ) - - -class Hunyuan3Dv2Conditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": {"clip_vision_output": ("CLIP_VISION_OUTPUT",), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("positive", "negative") - - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, clip_vision_output): - embeds = clip_vision_output.last_hidden_state - positive = [[embeds, {}]] - negative = [[torch.zeros_like(embeds), {}]] - return (positive, negative) - - -class Hunyuan3Dv2ConditioningMultiView: - @classmethod - def INPUT_TYPES(s): - return {"required": {}, - "optional": {"front": ("CLIP_VISION_OUTPUT",), - "left": ("CLIP_VISION_OUTPUT",), - "back": ("CLIP_VISION_OUTPUT",), - "right": ("CLIP_VISION_OUTPUT",), }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("positive", "negative") - - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, front=None, left=None, back=None, right=None): - all_embeds = [front, left, back, right] - out = [] - pos_embeds = None - for i, e in enumerate(all_embeds): - if e is not None: - if pos_embeds is None: - pos_embeds = get_1d_sincos_pos_embed_from_grid_torch(e.last_hidden_state.shape[-1], torch.arange(4)) - out.append(e.last_hidden_state + pos_embeds[i].reshape(1, 1, -1)) - - embeds = torch.cat(out, dim=1) - positive = [[embeds, {}]] - negative = [[torch.zeros_like(embeds), {}]] - return (positive, negative) - - -class VOXEL: - def __init__(self, data): - self.data = data - - -class VAEDecodeHunyuan3D: - @classmethod - def INPUT_TYPES(s): - return {"required": {"samples": ("LATENT", ), - "vae": ("VAE", ), - "num_chunks": ("INT", {"default": 8000, "min": 1000, "max": 500000}), - "octree_resolution": ("INT", {"default": 256, "min": 16, "max": 512}), - }} - RETURN_TYPES = ("VOXEL",) - FUNCTION = "decode" - - CATEGORY = "latent/3d" - - def decode(self, vae, samples, num_chunks, octree_resolution): - voxels = VOXEL(vae.decode(samples["samples"], vae_options={"num_chunks": num_chunks, "octree_resolution": octree_resolution})) - return (voxels, ) - - -def voxel_to_mesh(voxels, threshold=0.5, device=None): - if device is None: - device = torch.device("cpu") - voxels = voxels.to(device) - - binary = (voxels > threshold).float() - padded = torch.nn.functional.pad(binary, (1, 1, 1, 1, 1, 1), 'constant', 0) - - D, H, W = binary.shape - - neighbors = torch.tensor([ - [0, 0, 1], - [0, 0, -1], - [0, 1, 0], - [0, -1, 0], - [1, 0, 0], - [-1, 0, 0] - ], device=device) - - z, y, x = torch.meshgrid( - torch.arange(D, device=device), - torch.arange(H, device=device), - torch.arange(W, device=device), - indexing='ij' - ) - voxel_indices = torch.stack([z.flatten(), y.flatten(), x.flatten()], dim=1) - - solid_mask = binary.flatten() > 0 - solid_indices = voxel_indices[solid_mask] - - corner_offsets = [ - torch.tensor([ - [0, 0, 1], [0, 1, 1], [1, 1, 1], [1, 0, 1] - ], device=device), - torch.tensor([ - [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0] - ], device=device), - torch.tensor([ - [0, 1, 0], [1, 1, 0], [1, 1, 1], [0, 1, 1] - ], device=device), - torch.tensor([ - [0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0] - ], device=device), - torch.tensor([ - [1, 0, 1], [1, 1, 1], [1, 1, 0], [1, 0, 0] - ], device=device), - torch.tensor([ - [0, 1, 0], [0, 1, 1], [0, 0, 1], [0, 0, 0] - ], device=device) - ] - - all_vertices = [] - all_indices = [] - - vertex_count = 0 - - for face_idx, offset in enumerate(neighbors): - neighbor_indices = solid_indices + offset - - padded_indices = neighbor_indices + 1 - - is_exposed = padded[ - padded_indices[:, 0], - padded_indices[:, 1], - padded_indices[:, 2] - ] == 0 - - if not is_exposed.any(): - continue - - exposed_indices = solid_indices[is_exposed] - - corners = corner_offsets[face_idx].unsqueeze(0) - - face_vertices = exposed_indices.unsqueeze(1) + corners - - all_vertices.append(face_vertices.reshape(-1, 3)) - - num_faces = exposed_indices.shape[0] - face_indices = torch.arange( - vertex_count, - vertex_count + 4 * num_faces, - device=device - ).reshape(-1, 4) - - all_indices.append(torch.stack([face_indices[:, 0], face_indices[:, 1], face_indices[:, 2]], dim=1)) - all_indices.append(torch.stack([face_indices[:, 0], face_indices[:, 2], face_indices[:, 3]], dim=1)) - - vertex_count += 4 * num_faces - - if len(all_vertices) > 0: - vertices = torch.cat(all_vertices, dim=0) - faces = torch.cat(all_indices, dim=0) - else: - vertices = torch.zeros((1, 3)) - faces = torch.zeros((1, 3)) - - v_min = 0 - v_max = max(voxels.shape) - - vertices = vertices - (v_min + v_max) / 2 - - scale = (v_max - v_min) / 2 - if scale > 0: - vertices = vertices / scale - - vertices = torch.fliplr(vertices) - return vertices, faces - -def voxel_to_mesh_surfnet(voxels, threshold=0.5, device=None): - if device is None: - device = torch.device("cpu") - voxels = voxels.to(device) - - D, H, W = voxels.shape - - padded = torch.nn.functional.pad(voxels, (1, 1, 1, 1, 1, 1), 'constant', 0) - z, y, x = torch.meshgrid( - torch.arange(D, device=device), - torch.arange(H, device=device), - torch.arange(W, device=device), - indexing='ij' - ) - cell_positions = torch.stack([z.flatten(), y.flatten(), x.flatten()], dim=1) - - corner_offsets = torch.tensor([ - [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], - [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1] - ], device=device) - - corner_values = torch.zeros((cell_positions.shape[0], 8), device=device) - for c, (dz, dy, dx) in enumerate(corner_offsets): - corner_values[:, c] = padded[ - cell_positions[:, 0] + dz, - cell_positions[:, 1] + dy, - cell_positions[:, 2] + dx - ] - - corner_signs = corner_values > threshold - has_inside = torch.any(corner_signs, dim=1) - has_outside = torch.any(~corner_signs, dim=1) - contains_surface = has_inside & has_outside - - active_cells = cell_positions[contains_surface] - active_signs = corner_signs[contains_surface] - active_values = corner_values[contains_surface] - - if active_cells.shape[0] == 0: - return torch.zeros((0, 3), device=device), torch.zeros((0, 3), dtype=torch.long, device=device) - - edges = torch.tensor([ - [0, 1], [0, 2], [0, 4], [1, 3], - [1, 5], [2, 3], [2, 6], [3, 7], - [4, 5], [4, 6], [5, 7], [6, 7] - ], device=device) - - cell_vertices = {} - progress = comfy.utils.ProgressBar(100) - - for edge_idx, (e1, e2) in enumerate(edges): - progress.update(1) - crossing = active_signs[:, e1] != active_signs[:, e2] - if not crossing.any(): - continue - - cell_indices = torch.nonzero(crossing, as_tuple=True)[0] - - v1 = active_values[cell_indices, e1] - v2 = active_values[cell_indices, e2] - - t = torch.zeros_like(v1, device=device) - denom = v2 - v1 - valid = denom != 0 - t[valid] = (threshold - v1[valid]) / denom[valid] - t[~valid] = 0.5 - - p1 = corner_offsets[e1].float() - p2 = corner_offsets[e2].float() - - intersection = p1.unsqueeze(0) + t.unsqueeze(1) * (p2.unsqueeze(0) - p1.unsqueeze(0)) - - for i, point in zip(cell_indices.tolist(), intersection): - if i not in cell_vertices: - cell_vertices[i] = [] - cell_vertices[i].append(point) - - # Calculate the final vertices as the average of intersection points for each cell - vertices = [] - vertex_lookup = {} - - vert_progress_mod = round(len(cell_vertices)/50) - - for i, points in cell_vertices.items(): - if not i % vert_progress_mod: - progress.update(1) - - if points: - vertex = torch.stack(points).mean(dim=0) - vertex = vertex + active_cells[i].float() - vertex_lookup[tuple(active_cells[i].tolist())] = len(vertices) - vertices.append(vertex) - - if not vertices: - return torch.zeros((0, 3), device=device), torch.zeros((0, 3), dtype=torch.long, device=device) - - final_vertices = torch.stack(vertices) - - inside_corners_mask = active_signs - outside_corners_mask = ~active_signs - - inside_counts = inside_corners_mask.sum(dim=1, keepdim=True).float() - outside_counts = outside_corners_mask.sum(dim=1, keepdim=True).float() - - inside_pos = torch.zeros((active_cells.shape[0], 3), device=device) - outside_pos = torch.zeros((active_cells.shape[0], 3), device=device) - - for i in range(8): - mask_inside = inside_corners_mask[:, i].unsqueeze(1) - mask_outside = outside_corners_mask[:, i].unsqueeze(1) - inside_pos += corner_offsets[i].float().unsqueeze(0) * mask_inside - outside_pos += corner_offsets[i].float().unsqueeze(0) * mask_outside - - inside_pos /= inside_counts - outside_pos /= outside_counts - gradients = inside_pos - outside_pos - - pos_dirs = torch.tensor([ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ], device=device) - - cross_products = [ - torch.linalg.cross(pos_dirs[i].float(), pos_dirs[j].float()) - for i in range(3) for j in range(i+1, 3) - ] - - faces = [] - all_keys = set(vertex_lookup.keys()) - - face_progress_mod = round(len(active_cells)/38*3) - - for pair_idx, (i, j) in enumerate([(0,1), (0,2), (1,2)]): - dir_i = pos_dirs[i] - dir_j = pos_dirs[j] - cross_product = cross_products[pair_idx] - - ni_positions = active_cells + dir_i - nj_positions = active_cells + dir_j - diag_positions = active_cells + dir_i + dir_j - - alignments = torch.matmul(gradients, cross_product) - - valid_quads = [] - quad_indices = [] - - for idx, active_cell in enumerate(active_cells): - if not idx % face_progress_mod: - progress.update(1) - cell_key = tuple(active_cell.tolist()) - ni_key = tuple(ni_positions[idx].tolist()) - nj_key = tuple(nj_positions[idx].tolist()) - diag_key = tuple(diag_positions[idx].tolist()) - - if cell_key in all_keys and ni_key in all_keys and nj_key in all_keys and diag_key in all_keys: - v0 = vertex_lookup[cell_key] - v1 = vertex_lookup[ni_key] - v2 = vertex_lookup[nj_key] - v3 = vertex_lookup[diag_key] - - valid_quads.append((v0, v1, v2, v3)) - quad_indices.append(idx) - - for q_idx, (v0, v1, v2, v3) in enumerate(valid_quads): - cell_idx = quad_indices[q_idx] - if alignments[cell_idx] > 0: - faces.append(torch.tensor([v0, v1, v3], device=device, dtype=torch.long)) - faces.append(torch.tensor([v0, v3, v2], device=device, dtype=torch.long)) - else: - faces.append(torch.tensor([v0, v3, v1], device=device, dtype=torch.long)) - faces.append(torch.tensor([v0, v2, v3], device=device, dtype=torch.long)) - - if faces: - faces = torch.stack(faces) - else: - faces = torch.zeros((0, 3), dtype=torch.long, device=device) - - v_min = 0 - v_max = max(D, H, W) - - final_vertices = final_vertices - (v_min + v_max) / 2 - - scale = (v_max - v_min) / 2 - if scale > 0: - final_vertices = final_vertices / scale - - final_vertices = torch.fliplr(final_vertices) - - return final_vertices, faces - -class MESH: - def __init__(self, vertices, faces): - self.vertices = vertices - self.faces = faces - - -class VoxelToMeshBasic: - @classmethod - def INPUT_TYPES(s): - return {"required": {"voxel": ("VOXEL", ), - "threshold": ("FLOAT", {"default": 0.6, "min": -1.0, "max": 1.0, "step": 0.01}), - }} - RETURN_TYPES = ("MESH",) - FUNCTION = "decode" - - CATEGORY = "3d" - - def decode(self, voxel, threshold): - vertices = [] - faces = [] - for x in voxel.data: - v, f = voxel_to_mesh(x, threshold=threshold, device=None) - vertices.append(v) - faces.append(f) - - return (MESH(torch.stack(vertices), torch.stack(faces)), ) - -class VoxelToMesh: - @classmethod - def INPUT_TYPES(s): - return {"required": {"voxel": ("VOXEL", ), - "algorithm": (["surface net", "basic"], ), - "threshold": ("FLOAT", {"default": 0.6, "min": -1.0, "max": 1.0, "step": 0.01}), - }} - RETURN_TYPES = ("MESH",) - FUNCTION = "decode" - - CATEGORY = "3d" - - def decode(self, voxel, algorithm, threshold): - vertices = [] - faces = [] - - if algorithm == "basic": - mesh_function = voxel_to_mesh - elif algorithm == "surface net": - mesh_function = voxel_to_mesh_surfnet - - for x in voxel.data: - v, f = mesh_function(x, threshold=threshold, device=None) - vertices.append(v) - faces.append(f) - - return (MESH(torch.stack(vertices), torch.stack(faces)), ) - - -def save_glb(vertices, faces, filepath, metadata=None): - """ - Save PyTorch tensor vertices and faces as a GLB file without external dependencies. - - Parameters: - vertices: torch.Tensor of shape (N, 3) - The vertex coordinates - faces: torch.Tensor of shape (M, 3) - The face indices (triangle faces) - filepath: str - Output filepath (should end with .glb) - """ - - # Convert tensors to numpy arrays - vertices_np = vertices.cpu().numpy().astype(np.float32) - faces_np = faces.cpu().numpy().astype(np.uint32) - - vertices_buffer = vertices_np.tobytes() - indices_buffer = faces_np.tobytes() - - def pad_to_4_bytes(buffer): - padding_length = (4 - (len(buffer) % 4)) % 4 - return buffer + b'\x00' * padding_length - - vertices_buffer_padded = pad_to_4_bytes(vertices_buffer) - indices_buffer_padded = pad_to_4_bytes(indices_buffer) - - buffer_data = vertices_buffer_padded + indices_buffer_padded - - vertices_byte_length = len(vertices_buffer) - vertices_byte_offset = 0 - indices_byte_length = len(indices_buffer) - indices_byte_offset = len(vertices_buffer_padded) - - gltf = { - "asset": {"version": "2.0", "generator": "ComfyUI"}, - "buffers": [ - { - "byteLength": len(buffer_data) - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteOffset": vertices_byte_offset, - "byteLength": vertices_byte_length, - "target": 34962 # ARRAY_BUFFER - }, - { - "buffer": 0, - "byteOffset": indices_byte_offset, - "byteLength": indices_byte_length, - "target": 34963 # ELEMENT_ARRAY_BUFFER - } - ], - "accessors": [ - { - "bufferView": 0, - "byteOffset": 0, - "componentType": 5126, # FLOAT - "count": len(vertices_np), - "type": "VEC3", - "max": vertices_np.max(axis=0).tolist(), - "min": vertices_np.min(axis=0).tolist() - }, - { - "bufferView": 1, - "byteOffset": 0, - "componentType": 5125, # UNSIGNED_INT - "count": faces_np.size, - "type": "SCALAR" - } - ], - "meshes": [ - { - "primitives": [ - { - "attributes": { - "POSITION": 0 - }, - "indices": 1, - "mode": 4 # TRIANGLES - } - ] - } - ], - "nodes": [ - { - "mesh": 0 - } - ], - "scenes": [ - { - "nodes": [0] - } - ], - "scene": 0 - } - - if metadata is not None: - gltf["asset"]["extras"] = metadata - - # Convert the JSON to bytes - gltf_json = json.dumps(gltf).encode('utf8') - - def pad_json_to_4_bytes(buffer): - padding_length = (4 - (len(buffer) % 4)) % 4 - return buffer + b' ' * padding_length - - gltf_json_padded = pad_json_to_4_bytes(gltf_json) - - # Create the GLB header - # Magic glTF - glb_header = struct.pack('<4sII', b'glTF', 2, 12 + 8 + len(gltf_json_padded) + 8 + len(buffer_data)) - - # Create JSON chunk header (chunk type 0) - json_chunk_header = struct.pack(' int: - min_value = min(min_value, value) - - # All big divisors of value (inclusive) - divisors = [i for i in range(min_value, value + 1) if value % i == 0] - - ns = [value // i for i in divisors[:max_options]] # has at least 1 element - - if len(ns) - 1 > 0: - idx = randint(low=0, high=len(ns) - 1, size=(1,)).item() - else: - idx = 0 - - return ns[idx] - -class HyperTile: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "tile_size": ("INT", {"default": 256, "min": 1, "max": 2048}), - "swap_size": ("INT", {"default": 2, "min": 1, "max": 128}), - "max_depth": ("INT", {"default": 0, "min": 0, "max": 10}), - "scale_depth": ("BOOLEAN", {"default": False}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "model_patches/unet" - - def patch(self, model, tile_size, swap_size, max_depth, scale_depth): - latent_tile_size = max(32, tile_size) // 8 - self.temp = None - - def hypertile_in(q, k, v, extra_options): - model_chans = q.shape[-2] - orig_shape = extra_options['original_shape'] - apply_to = [] - for i in range(max_depth + 1): - apply_to.append((orig_shape[-2] / (2 ** i)) * (orig_shape[-1] / (2 ** i))) - - if model_chans in apply_to: - shape = extra_options["original_shape"] - aspect_ratio = shape[-1] / shape[-2] - - hw = q.size(1) - h, w = round(math.sqrt(hw * aspect_ratio)), round(math.sqrt(hw / aspect_ratio)) - - factor = (2 ** apply_to.index(model_chans)) if scale_depth else 1 - nh = random_divisor(h, latent_tile_size * factor, swap_size) - nw = random_divisor(w, latent_tile_size * factor, swap_size) - - if nh * nw > 1: - q = rearrange(q, "b (nh h nw w) c -> (b nh nw) (h w) c", h=h // nh, w=w // nw, nh=nh, nw=nw) - self.temp = (nh, nw, h, w) - return q, k, v - - return q, k, v - def hypertile_out(out, extra_options): - if self.temp is not None: - nh, nw, h, w = self.temp - self.temp = None - out = rearrange(out, "(b nh nw) hw c -> b nh nw hw c", nh=nh, nw=nw) - out = rearrange(out, "b nh nw (h w) c -> b (nh h nw w) c", h=h // nh, w=w // nw) - return out - - - m = model.clone() - m.set_model_attn1_patch(hypertile_in) - m.set_model_attn1_output_patch(hypertile_out) - return (m, ) - -NODE_CLASS_MAPPINGS = { - "HyperTile": HyperTile, -} diff --git a/comfy_extras/nodes_images.py b/comfy_extras/nodes_images.py deleted file mode 100644 index fba80e2aeafe88a6f3d68c1d0715312b34f25b7f..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_images.py +++ /dev/null @@ -1,642 +0,0 @@ -from __future__ import annotations - -import nodes -import folder_paths -from comfy.cli_args import args - -from PIL import Image -from PIL.PngImagePlugin import PngInfo - -import numpy as np -import json -import os -import re -from io import BytesIO -from inspect import cleandoc -import torch -import comfy.utils - -from comfy.comfy_types import FileLocator, IO -from server import PromptServer - -MAX_RESOLUTION = nodes.MAX_RESOLUTION - -class ImageCrop: - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": ("IMAGE",), - "width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}), - "height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}), - "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "crop" - - CATEGORY = "image/transform" - - def crop(self, image, width, height, x, y): - x = min(x, image.shape[2] - 1) - y = min(y, image.shape[1] - 1) - to_x = width + x - to_y = height + y - img = image[:,y:to_y, x:to_x, :] - return (img,) - -class RepeatImageBatch: - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": ("IMAGE",), - "amount": ("INT", {"default": 1, "min": 1, "max": 4096}), - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "repeat" - - CATEGORY = "image/batch" - - def repeat(self, image, amount): - s = image.repeat((amount, 1,1,1)) - return (s,) - -class ImageFromBatch: - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": ("IMAGE",), - "batch_index": ("INT", {"default": 0, "min": 0, "max": 4095}), - "length": ("INT", {"default": 1, "min": 1, "max": 4096}), - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "frombatch" - - CATEGORY = "image/batch" - - def frombatch(self, image, batch_index, length): - s_in = image - batch_index = min(s_in.shape[0] - 1, batch_index) - length = min(s_in.shape[0] - batch_index, length) - s = s_in[batch_index:batch_index + length].clone() - return (s,) - - -class ImageAddNoise: - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": ("IMAGE",), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True, "tooltip": "The random seed used for creating the noise."}), - "strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "repeat" - - CATEGORY = "image" - - def repeat(self, image, seed, strength): - generator = torch.manual_seed(seed) - s = torch.clip((image + strength * torch.randn(image.size(), generator=generator, device="cpu").to(image)), min=0.0, max=1.0) - return (s,) - -class SaveAnimatedWEBP: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - methods = {"default": 4, "fastest": 0, "slowest": 6} - @classmethod - def INPUT_TYPES(s): - return {"required": - {"images": ("IMAGE", ), - "filename_prefix": ("STRING", {"default": "ComfyUI"}), - "fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}), - "lossless": ("BOOLEAN", {"default": True}), - "quality": ("INT", {"default": 80, "min": 0, "max": 100}), - "method": (list(s.methods.keys()),), - # "num_frames": ("INT", {"default": 0, "min": 0, "max": 8192}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "save_images" - - OUTPUT_NODE = True - - CATEGORY = "image/animation" - - def save_images(self, images, fps, filename_prefix, lossless, quality, method, num_frames=0, prompt=None, extra_pnginfo=None): - method = self.methods.get(method) - filename_prefix += self.prefix_append - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) - results: list[FileLocator] = [] - pil_images = [] - for image in images: - i = 255. * image.cpu().numpy() - img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) - pil_images.append(img) - - metadata = pil_images[0].getexif() - if not args.disable_metadata: - if prompt is not None: - metadata[0x0110] = "prompt:{}".format(json.dumps(prompt)) - if extra_pnginfo is not None: - inital_exif = 0x010f - for x in extra_pnginfo: - metadata[inital_exif] = "{}:{}".format(x, json.dumps(extra_pnginfo[x])) - inital_exif -= 1 - - if num_frames == 0: - num_frames = len(pil_images) - - c = len(pil_images) - for i in range(0, c, num_frames): - file = f"{filename}_{counter:05}_.webp" - pil_images[i].save(os.path.join(full_output_folder, file), save_all=True, duration=int(1000.0/fps), append_images=pil_images[i + 1:i + num_frames], exif=metadata, lossless=lossless, quality=quality, method=method) - results.append({ - "filename": file, - "subfolder": subfolder, - "type": self.type - }) - counter += 1 - - animated = num_frames != 1 - return { "ui": { "images": results, "animated": (animated,) } } - -class SaveAnimatedPNG: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - @classmethod - def INPUT_TYPES(s): - return {"required": - {"images": ("IMAGE", ), - "filename_prefix": ("STRING", {"default": "ComfyUI"}), - "fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}), - "compress_level": ("INT", {"default": 4, "min": 0, "max": 9}) - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "save_images" - - OUTPUT_NODE = True - - CATEGORY = "image/animation" - - def save_images(self, images, fps, compress_level, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None): - filename_prefix += self.prefix_append - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) - results = list() - pil_images = [] - for image in images: - i = 255. * image.cpu().numpy() - img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) - pil_images.append(img) - - metadata = None - if not args.disable_metadata: - metadata = PngInfo() - if prompt is not None: - metadata.add(b"comf", "prompt".encode("latin-1", "strict") + b"\0" + json.dumps(prompt).encode("latin-1", "strict"), after_idat=True) - if extra_pnginfo is not None: - for x in extra_pnginfo: - metadata.add(b"comf", x.encode("latin-1", "strict") + b"\0" + json.dumps(extra_pnginfo[x]).encode("latin-1", "strict"), after_idat=True) - - file = f"{filename}_{counter:05}_.png" - pil_images[0].save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=compress_level, save_all=True, duration=int(1000.0/fps), append_images=pil_images[1:]) - results.append({ - "filename": file, - "subfolder": subfolder, - "type": self.type - }) - - return { "ui": { "images": results, "animated": (True,)} } - -class SVG: - """ - Stores SVG representations via a list of BytesIO objects. - """ - def __init__(self, data: list[BytesIO]): - self.data = data - - def combine(self, other: 'SVG') -> 'SVG': - return SVG(self.data + other.data) - - @staticmethod - def combine_all(svgs: list['SVG']) -> 'SVG': - all_svgs_list: list[BytesIO] = [] - for svg_item in svgs: - all_svgs_list.extend(svg_item.data) - return SVG(all_svgs_list) - - -class ImageStitch: - """Upstreamed from https://github.com/kijai/ComfyUI-KJNodes""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image1": ("IMAGE",), - "direction": (["right", "down", "left", "up"], {"default": "right"}), - "match_image_size": ("BOOLEAN", {"default": True}), - "spacing_width": ( - "INT", - {"default": 0, "min": 0, "max": 1024, "step": 2}, - ), - "spacing_color": ( - ["white", "black", "red", "green", "blue"], - {"default": "white"}, - ), - }, - "optional": { - "image2": ("IMAGE",), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "stitch" - CATEGORY = "image/transform" - DESCRIPTION = """ -Stitches image2 to image1 in the specified direction. -If image2 is not provided, returns image1 unchanged. -Optional spacing can be added between images. -""" - - def stitch( - self, - image1, - direction, - match_image_size, - spacing_width, - spacing_color, - image2=None, - ): - if image2 is None: - return (image1,) - - # Handle batch size differences - if image1.shape[0] != image2.shape[0]: - max_batch = max(image1.shape[0], image2.shape[0]) - if image1.shape[0] < max_batch: - image1 = torch.cat( - [image1, image1[-1:].repeat(max_batch - image1.shape[0], 1, 1, 1)] - ) - if image2.shape[0] < max_batch: - image2 = torch.cat( - [image2, image2[-1:].repeat(max_batch - image2.shape[0], 1, 1, 1)] - ) - - # Match image sizes if requested - if match_image_size: - h1, w1 = image1.shape[1:3] - h2, w2 = image2.shape[1:3] - aspect_ratio = w2 / h2 - - if direction in ["left", "right"]: - target_h, target_w = h1, int(h1 * aspect_ratio) - else: # up, down - target_w, target_h = w1, int(w1 / aspect_ratio) - - image2 = comfy.utils.common_upscale( - image2.movedim(-1, 1), target_w, target_h, "lanczos", "disabled" - ).movedim(1, -1) - - color_map = { - "white": 1.0, - "black": 0.0, - "red": (1.0, 0.0, 0.0), - "green": (0.0, 1.0, 0.0), - "blue": (0.0, 0.0, 1.0), - } - - color_val = color_map[spacing_color] - - # When not matching sizes, pad to align non-concat dimensions - if not match_image_size: - h1, w1 = image1.shape[1:3] - h2, w2 = image2.shape[1:3] - pad_value = 0.0 - if not isinstance(color_val, tuple): - pad_value = color_val - - if direction in ["left", "right"]: - # For horizontal concat, pad heights to match - if h1 != h2: - target_h = max(h1, h2) - if h1 < target_h: - pad_h = target_h - h1 - pad_top, pad_bottom = pad_h // 2, pad_h - pad_h // 2 - image1 = torch.nn.functional.pad(image1, (0, 0, 0, 0, pad_top, pad_bottom), mode='constant', value=pad_value) - if h2 < target_h: - pad_h = target_h - h2 - pad_top, pad_bottom = pad_h // 2, pad_h - pad_h // 2 - image2 = torch.nn.functional.pad(image2, (0, 0, 0, 0, pad_top, pad_bottom), mode='constant', value=pad_value) - else: # up, down - # For vertical concat, pad widths to match - if w1 != w2: - target_w = max(w1, w2) - if w1 < target_w: - pad_w = target_w - w1 - pad_left, pad_right = pad_w // 2, pad_w - pad_w // 2 - image1 = torch.nn.functional.pad(image1, (0, 0, pad_left, pad_right), mode='constant', value=pad_value) - if w2 < target_w: - pad_w = target_w - w2 - pad_left, pad_right = pad_w // 2, pad_w - pad_w // 2 - image2 = torch.nn.functional.pad(image2, (0, 0, pad_left, pad_right), mode='constant', value=pad_value) - - # Ensure same number of channels - if image1.shape[-1] != image2.shape[-1]: - max_channels = max(image1.shape[-1], image2.shape[-1]) - if image1.shape[-1] < max_channels: - image1 = torch.cat( - [ - image1, - torch.ones( - *image1.shape[:-1], - max_channels - image1.shape[-1], - device=image1.device, - ), - ], - dim=-1, - ) - if image2.shape[-1] < max_channels: - image2 = torch.cat( - [ - image2, - torch.ones( - *image2.shape[:-1], - max_channels - image2.shape[-1], - device=image2.device, - ), - ], - dim=-1, - ) - - # Add spacing if specified - if spacing_width > 0: - spacing_width = spacing_width + (spacing_width % 2) # Ensure even - - if direction in ["left", "right"]: - spacing_shape = ( - image1.shape[0], - max(image1.shape[1], image2.shape[1]), - spacing_width, - image1.shape[-1], - ) - else: - spacing_shape = ( - image1.shape[0], - spacing_width, - max(image1.shape[2], image2.shape[2]), - image1.shape[-1], - ) - - spacing = torch.full(spacing_shape, 0.0, device=image1.device) - if isinstance(color_val, tuple): - for i, c in enumerate(color_val): - if i < spacing.shape[-1]: - spacing[..., i] = c - if spacing.shape[-1] == 4: # Add alpha - spacing[..., 3] = 1.0 - else: - spacing[..., : min(3, spacing.shape[-1])] = color_val - if spacing.shape[-1] == 4: - spacing[..., 3] = 1.0 - - # Concatenate images - images = [image2, image1] if direction in ["left", "up"] else [image1, image2] - if spacing_width > 0: - images.insert(1, spacing) - - concat_dim = 2 if direction in ["left", "right"] else 1 - return (torch.cat(images, dim=concat_dim),) - -class ResizeAndPadImage: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "image": ("IMAGE",), - "target_width": ("INT", { - "default": 512, - "min": 1, - "max": MAX_RESOLUTION, - "step": 1 - }), - "target_height": ("INT", { - "default": 512, - "min": 1, - "max": MAX_RESOLUTION, - "step": 1 - }), - "padding_color": (["white", "black"],), - "interpolation": (["area", "bicubic", "nearest-exact", "bilinear", "lanczos"],), - } - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "resize_and_pad" - CATEGORY = "image/transform" - - def resize_and_pad(self, image, target_width, target_height, padding_color, interpolation): - batch_size, orig_height, orig_width, channels = image.shape - - scale_w = target_width / orig_width - scale_h = target_height / orig_height - scale = min(scale_w, scale_h) - - new_width = int(orig_width * scale) - new_height = int(orig_height * scale) - - image_permuted = image.permute(0, 3, 1, 2) - - resized = comfy.utils.common_upscale(image_permuted, new_width, new_height, interpolation, "disabled") - - pad_value = 0.0 if padding_color == "black" else 1.0 - padded = torch.full( - (batch_size, channels, target_height, target_width), - pad_value, - dtype=image.dtype, - device=image.device - ) - - y_offset = (target_height - new_height) // 2 - x_offset = (target_width - new_width) // 2 - - padded[:, :, y_offset:y_offset + new_height, x_offset:x_offset + new_width] = resized - - output = padded.permute(0, 2, 3, 1) - return (output,) - -class SaveSVGNode: - """ - Save SVG files on disk. - """ - - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - RETURN_TYPES = () - DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value - FUNCTION = "save_svg" - CATEGORY = "image/save" # Changed - OUTPUT_NODE = True - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "svg": ("SVG",), # Changed - "filename_prefix": ("STRING", {"default": "svg/ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."}) - }, - "hidden": { - "prompt": "PROMPT", - "extra_pnginfo": "EXTRA_PNGINFO" - } - } - - def save_svg(self, svg: SVG, filename_prefix="svg/ComfyUI", prompt=None, extra_pnginfo=None): - filename_prefix += self.prefix_append - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir) - results = list() - - # Prepare metadata JSON - metadata_dict = {} - if prompt is not None: - metadata_dict["prompt"] = prompt - if extra_pnginfo is not None: - metadata_dict.update(extra_pnginfo) - - # Convert metadata to JSON string - metadata_json = json.dumps(metadata_dict, indent=2) if metadata_dict else None - - for batch_number, svg_bytes in enumerate(svg.data): - filename_with_batch_num = filename.replace("%batch_num%", str(batch_number)) - file = f"{filename_with_batch_num}_{counter:05}_.svg" - - # Read SVG content - svg_bytes.seek(0) - svg_content = svg_bytes.read().decode('utf-8') - - # Inject metadata if available - if metadata_json: - # Create metadata element with CDATA section - metadata_element = f""" - - - """ - # Insert metadata after opening svg tag using regex with a replacement function - def replacement(match): - # match.group(1) contains the captured tag - return match.group(1) + '\n' + metadata_element - - # Apply the substitution - svg_content = re.sub(r'(]*>)', replacement, svg_content, flags=re.UNICODE) - - # Write the modified SVG to file - with open(os.path.join(full_output_folder, file), 'wb') as svg_file: - svg_file.write(svg_content.encode('utf-8')) - - results.append({ - "filename": file, - "subfolder": subfolder, - "type": self.type - }) - counter += 1 - return { "ui": { "images": results } } - -class GetImageSize: - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": (IO.IMAGE,), - }, - "hidden": { - "unique_id": "UNIQUE_ID", - } - } - - RETURN_TYPES = (IO.INT, IO.INT, IO.INT) - RETURN_NAMES = ("width", "height", "batch_size") - FUNCTION = "get_size" - - CATEGORY = "image" - DESCRIPTION = """Returns width and height of the image, and passes it through unchanged.""" - - def get_size(self, image, unique_id=None) -> tuple[int, int]: - height = image.shape[1] - width = image.shape[2] - batch_size = image.shape[0] - - # Send progress text to display size on the node - if unique_id: - PromptServer.instance.send_progress_text(f"width: {width}, height: {height}\n batch size: {batch_size}", unique_id) - - return width, height, batch_size - -class ImageRotate: - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": (IO.IMAGE,), - "rotation": (["none", "90 degrees", "180 degrees", "270 degrees"],), - }} - RETURN_TYPES = (IO.IMAGE,) - FUNCTION = "rotate" - - CATEGORY = "image/transform" - - def rotate(self, image, rotation): - rotate_by = 0 - if rotation.startswith("90"): - rotate_by = 1 - elif rotation.startswith("180"): - rotate_by = 2 - elif rotation.startswith("270"): - rotate_by = 3 - - image = torch.rot90(image, k=rotate_by, dims=[2, 1]) - return (image,) - -class ImageFlip: - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": (IO.IMAGE,), - "flip_method": (["x-axis: vertically", "y-axis: horizontally"],), - }} - RETURN_TYPES = (IO.IMAGE,) - FUNCTION = "flip" - - CATEGORY = "image/transform" - - def flip(self, image, flip_method): - if flip_method.startswith("x"): - image = torch.flip(image, dims=[1]) - elif flip_method.startswith("y"): - image = torch.flip(image, dims=[2]) - - return (image,) - - -NODE_CLASS_MAPPINGS = { - "ImageCrop": ImageCrop, - "RepeatImageBatch": RepeatImageBatch, - "ImageFromBatch": ImageFromBatch, - "ImageAddNoise": ImageAddNoise, - "SaveAnimatedWEBP": SaveAnimatedWEBP, - "SaveAnimatedPNG": SaveAnimatedPNG, - "SaveSVGNode": SaveSVGNode, - "ImageStitch": ImageStitch, - "ResizeAndPadImage": ResizeAndPadImage, - "GetImageSize": GetImageSize, - "ImageRotate": ImageRotate, - "ImageFlip": ImageFlip, -} diff --git a/comfy_extras/nodes_ip2p.py b/comfy_extras/nodes_ip2p.py deleted file mode 100644 index c2e70a84c10ca5cc1b3ca853a97adc3c64fbb315..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_ip2p.py +++ /dev/null @@ -1,45 +0,0 @@ -import torch - -class InstructPixToPixConditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "pixels": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING","CONDITIONING","LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/instructpix2pix" - - def encode(self, positive, negative, pixels, vae): - x = (pixels.shape[1] // 8) * 8 - y = (pixels.shape[2] // 8) * 8 - - if pixels.shape[1] != x or pixels.shape[2] != y: - x_offset = (pixels.shape[1] % 8) // 2 - y_offset = (pixels.shape[2] % 8) // 2 - pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:] - - concat_latent = vae.encode(pixels) - - out_latent = {} - out_latent["samples"] = torch.zeros_like(concat_latent) - - out = [] - for conditioning in [positive, negative]: - c = [] - for t in conditioning: - d = t[1].copy() - d["concat_latent_image"] = concat_latent - n = [t[0], d] - c.append(n) - out.append(c) - return (out[0], out[1], out_latent) - -NODE_CLASS_MAPPINGS = { - "InstructPixToPixConditioning": InstructPixToPixConditioning, -} diff --git a/comfy_extras/nodes_latent.py b/comfy_extras/nodes_latent.py deleted file mode 100644 index f33ed1beea538ee28871e6fcb04da73ae8d4b91c..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_latent.py +++ /dev/null @@ -1,288 +0,0 @@ -import comfy.utils -import comfy_extras.nodes_post_processing -import torch - - -def reshape_latent_to(target_shape, latent, repeat_batch=True): - if latent.shape[1:] != target_shape[1:]: - latent = comfy.utils.common_upscale(latent, target_shape[-1], target_shape[-2], "bilinear", "center") - if repeat_batch: - return comfy.utils.repeat_to_batch_size(latent, target_shape[0]) - else: - return latent - - -class LatentAdd: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "op" - - CATEGORY = "latent/advanced" - - def op(self, samples1, samples2): - samples_out = samples1.copy() - - s1 = samples1["samples"] - s2 = samples2["samples"] - - s2 = reshape_latent_to(s1.shape, s2) - samples_out["samples"] = s1 + s2 - return (samples_out,) - -class LatentSubtract: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "op" - - CATEGORY = "latent/advanced" - - def op(self, samples1, samples2): - samples_out = samples1.copy() - - s1 = samples1["samples"] - s2 = samples2["samples"] - - s2 = reshape_latent_to(s1.shape, s2) - samples_out["samples"] = s1 - s2 - return (samples_out,) - -class LatentMultiply: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples": ("LATENT",), - "multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - }} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "op" - - CATEGORY = "latent/advanced" - - def op(self, samples, multiplier): - samples_out = samples.copy() - - s1 = samples["samples"] - samples_out["samples"] = s1 * multiplier - return (samples_out,) - -class LatentInterpolate: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples1": ("LATENT",), - "samples2": ("LATENT",), - "ratio": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - }} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "op" - - CATEGORY = "latent/advanced" - - def op(self, samples1, samples2, ratio): - samples_out = samples1.copy() - - s1 = samples1["samples"] - s2 = samples2["samples"] - - s2 = reshape_latent_to(s1.shape, s2) - - m1 = torch.linalg.vector_norm(s1, dim=(1)) - m2 = torch.linalg.vector_norm(s2, dim=(1)) - - s1 = torch.nan_to_num(s1 / m1) - s2 = torch.nan_to_num(s2 / m2) - - t = (s1 * ratio + s2 * (1.0 - ratio)) - mt = torch.linalg.vector_norm(t, dim=(1)) - st = torch.nan_to_num(t / mt) - - samples_out["samples"] = st * (m1 * ratio + m2 * (1.0 - ratio)) - return (samples_out,) - -class LatentBatch: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "batch" - - CATEGORY = "latent/batch" - - def batch(self, samples1, samples2): - samples_out = samples1.copy() - s1 = samples1["samples"] - s2 = samples2["samples"] - - s2 = reshape_latent_to(s1.shape, s2, repeat_batch=False) - s = torch.cat((s1, s2), dim=0) - samples_out["samples"] = s - samples_out["batch_index"] = samples1.get("batch_index", [x for x in range(0, s1.shape[0])]) + samples2.get("batch_index", [x for x in range(0, s2.shape[0])]) - return (samples_out,) - -class LatentBatchSeedBehavior: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples": ("LATENT",), - "seed_behavior": (["random", "fixed"],{"default": "fixed"}),}} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "op" - - CATEGORY = "latent/advanced" - - def op(self, samples, seed_behavior): - samples_out = samples.copy() - latent = samples["samples"] - if seed_behavior == "random": - if 'batch_index' in samples_out: - samples_out.pop('batch_index') - elif seed_behavior == "fixed": - batch_number = samples_out.get("batch_index", [0])[0] - samples_out["batch_index"] = [batch_number] * latent.shape[0] - - return (samples_out,) - -class LatentApplyOperation: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples": ("LATENT",), - "operation": ("LATENT_OPERATION",), - }} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "op" - - CATEGORY = "latent/advanced/operations" - EXPERIMENTAL = True - - def op(self, samples, operation): - samples_out = samples.copy() - - s1 = samples["samples"] - samples_out["samples"] = operation(latent=s1) - return (samples_out,) - -class LatentApplyOperationCFG: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "operation": ("LATENT_OPERATION",), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "latent/advanced/operations" - EXPERIMENTAL = True - - def patch(self, model, operation): - m = model.clone() - - def pre_cfg_function(args): - conds_out = args["conds_out"] - if len(conds_out) == 2: - conds_out[0] = operation(latent=(conds_out[0] - conds_out[1])) + conds_out[1] - else: - conds_out[0] = operation(latent=conds_out[0]) - return conds_out - - m.set_model_sampler_pre_cfg_function(pre_cfg_function) - return (m, ) - -class LatentOperationTonemapReinhard: - @classmethod - def INPUT_TYPES(s): - return {"required": { "multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), - }} - - RETURN_TYPES = ("LATENT_OPERATION",) - FUNCTION = "op" - - CATEGORY = "latent/advanced/operations" - EXPERIMENTAL = True - - def op(self, multiplier): - def tonemap_reinhard(latent, **kwargs): - latent_vector_magnitude = (torch.linalg.vector_norm(latent, dim=(1)) + 0.0000000001)[:,None] - normalized_latent = latent / latent_vector_magnitude - - mean = torch.mean(latent_vector_magnitude, dim=(1,2,3), keepdim=True) - std = torch.std(latent_vector_magnitude, dim=(1,2,3), keepdim=True) - - top = (std * 5 + mean) * multiplier - - #reinhard - latent_vector_magnitude *= (1.0 / top) - new_magnitude = latent_vector_magnitude / (latent_vector_magnitude + 1.0) - new_magnitude *= top - - return normalized_latent * new_magnitude - return (tonemap_reinhard,) - -class LatentOperationSharpen: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "sharpen_radius": ("INT", { - "default": 9, - "min": 1, - "max": 31, - "step": 1 - }), - "sigma": ("FLOAT", { - "default": 1.0, - "min": 0.1, - "max": 10.0, - "step": 0.1 - }), - "alpha": ("FLOAT", { - "default": 0.1, - "min": 0.0, - "max": 5.0, - "step": 0.01 - }), - }} - - RETURN_TYPES = ("LATENT_OPERATION",) - FUNCTION = "op" - - CATEGORY = "latent/advanced/operations" - EXPERIMENTAL = True - - def op(self, sharpen_radius, sigma, alpha): - def sharpen(latent, **kwargs): - luminance = (torch.linalg.vector_norm(latent, dim=(1)) + 1e-6)[:,None] - normalized_latent = latent / luminance - channels = latent.shape[1] - - kernel_size = sharpen_radius * 2 + 1 - kernel = comfy_extras.nodes_post_processing.gaussian_kernel(kernel_size, sigma, device=luminance.device) - center = kernel_size // 2 - - kernel *= alpha * -10 - kernel[center, center] = kernel[center, center] - kernel.sum() + 1.0 - - padded_image = torch.nn.functional.pad(normalized_latent, (sharpen_radius,sharpen_radius,sharpen_radius,sharpen_radius), 'reflect') - sharpened = torch.nn.functional.conv2d(padded_image, kernel.repeat(channels, 1, 1).unsqueeze(1), padding=kernel_size // 2, groups=channels)[:,:,sharpen_radius:-sharpen_radius, sharpen_radius:-sharpen_radius] - - return luminance * sharpened - return (sharpen,) - -NODE_CLASS_MAPPINGS = { - "LatentAdd": LatentAdd, - "LatentSubtract": LatentSubtract, - "LatentMultiply": LatentMultiply, - "LatentInterpolate": LatentInterpolate, - "LatentBatch": LatentBatch, - "LatentBatchSeedBehavior": LatentBatchSeedBehavior, - "LatentApplyOperation": LatentApplyOperation, - "LatentApplyOperationCFG": LatentApplyOperationCFG, - "LatentOperationTonemapReinhard": LatentOperationTonemapReinhard, - "LatentOperationSharpen": LatentOperationSharpen, -} diff --git a/comfy_extras/nodes_load_3d.py b/comfy_extras/nodes_load_3d.py deleted file mode 100644 index 899608149aba1861b9627f22d517dc0feb41d9cc..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_load_3d.py +++ /dev/null @@ -1,182 +0,0 @@ -import nodes -import folder_paths -import os - -from comfy.comfy_types import IO -from comfy_api.input_impl import VideoFromFile - -from pathlib import Path - - -def normalize_path(path): - return path.replace('\\', '/') - -class Load3D(): - @classmethod - def INPUT_TYPES(s): - input_dir = os.path.join(folder_paths.get_input_directory(), "3d") - - os.makedirs(input_dir, exist_ok=True) - - input_path = Path(input_dir) - base_path = Path(folder_paths.get_input_directory()) - - files = [ - normalize_path(str(file_path.relative_to(base_path))) - for file_path in input_path.rglob("*") - if file_path.suffix.lower() in {'.gltf', '.glb', '.obj', '.fbx', '.stl'} - ] - - return {"required": { - "model_file": (sorted(files), {"file_upload": True}), - "image": ("LOAD_3D", {}), - "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - }} - - RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO) - RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "lineart", "camera_info", "recording_video") - - FUNCTION = "process" - EXPERIMENTAL = True - - CATEGORY = "3d" - - def process(self, model_file, image, **kwargs): - image_path = folder_paths.get_annotated_filepath(image['image']) - mask_path = folder_paths.get_annotated_filepath(image['mask']) - normal_path = folder_paths.get_annotated_filepath(image['normal']) - lineart_path = folder_paths.get_annotated_filepath(image['lineart']) - - load_image_node = nodes.LoadImage() - output_image, ignore_mask = load_image_node.load_image(image=image_path) - ignore_image, output_mask = load_image_node.load_image(image=mask_path) - normal_image, ignore_mask2 = load_image_node.load_image(image=normal_path) - lineart_image, ignore_mask3 = load_image_node.load_image(image=lineart_path) - - video = None - - if image['recording'] != "": - recording_video_path = folder_paths.get_annotated_filepath(image['recording']) - - video = VideoFromFile(recording_video_path) - - return output_image, output_mask, model_file, normal_image, lineart_image, image['camera_info'], video - -class Load3DAnimation(): - @classmethod - def INPUT_TYPES(s): - input_dir = os.path.join(folder_paths.get_input_directory(), "3d") - - os.makedirs(input_dir, exist_ok=True) - - input_path = Path(input_dir) - base_path = Path(folder_paths.get_input_directory()) - - files = [ - normalize_path(str(file_path.relative_to(base_path))) - for file_path in input_path.rglob("*") - if file_path.suffix.lower() in {'.gltf', '.glb', '.fbx'} - ] - - return {"required": { - "model_file": (sorted(files), {"file_upload": True}), - "image": ("LOAD_3D_ANIMATION", {}), - "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - }} - - RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO) - RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "camera_info", "recording_video") - - FUNCTION = "process" - EXPERIMENTAL = True - - CATEGORY = "3d" - - def process(self, model_file, image, **kwargs): - image_path = folder_paths.get_annotated_filepath(image['image']) - mask_path = folder_paths.get_annotated_filepath(image['mask']) - normal_path = folder_paths.get_annotated_filepath(image['normal']) - - load_image_node = nodes.LoadImage() - output_image, ignore_mask = load_image_node.load_image(image=image_path) - ignore_image, output_mask = load_image_node.load_image(image=mask_path) - normal_image, ignore_mask2 = load_image_node.load_image(image=normal_path) - - video = None - - if image['recording'] != "": - recording_video_path = folder_paths.get_annotated_filepath(image['recording']) - - video = VideoFromFile(recording_video_path) - - return output_image, output_mask, model_file, normal_image, image['camera_info'], video - -class Preview3D(): - @classmethod - def INPUT_TYPES(s): - return {"required": { - "model_file": ("STRING", {"default": "", "multiline": False}), - }, - "optional": { - "camera_info": ("LOAD3D_CAMERA", {}) - }} - - OUTPUT_NODE = True - RETURN_TYPES = () - - CATEGORY = "3d" - - FUNCTION = "process" - EXPERIMENTAL = True - - def process(self, model_file, **kwargs): - camera_info = kwargs.get("camera_info", None) - - return { - "ui": { - "result": [model_file, camera_info] - } - } - -class Preview3DAnimation(): - @classmethod - def INPUT_TYPES(s): - return {"required": { - "model_file": ("STRING", {"default": "", "multiline": False}), - }, - "optional": { - "camera_info": ("LOAD3D_CAMERA", {}) - }} - - OUTPUT_NODE = True - RETURN_TYPES = () - - CATEGORY = "3d" - - FUNCTION = "process" - EXPERIMENTAL = True - - def process(self, model_file, **kwargs): - camera_info = kwargs.get("camera_info", None) - - return { - "ui": { - "result": [model_file, camera_info] - } - } - -NODE_CLASS_MAPPINGS = { - "Load3D": Load3D, - "Load3DAnimation": Load3DAnimation, - "Preview3D": Preview3D, - "Preview3DAnimation": Preview3DAnimation -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "Load3D": "Load 3D", - "Load3DAnimation": "Load 3D - Animation", - "Preview3D": "Preview 3D", - "Preview3DAnimation": "Preview 3D - Animation" -} diff --git a/comfy_extras/nodes_lora_extract.py b/comfy_extras/nodes_lora_extract.py deleted file mode 100644 index dfd4fe9f4a5c4b7aff37d244fc25033e9a286119..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_lora_extract.py +++ /dev/null @@ -1,119 +0,0 @@ -import torch -import comfy.model_management -import comfy.utils -import folder_paths -import os -import logging -from enum import Enum - -CLAMP_QUANTILE = 0.99 - -def extract_lora(diff, rank): - conv2d = (len(diff.shape) == 4) - kernel_size = None if not conv2d else diff.size()[2:4] - conv2d_3x3 = conv2d and kernel_size != (1, 1) - out_dim, in_dim = diff.size()[0:2] - rank = min(rank, in_dim, out_dim) - - if conv2d: - if conv2d_3x3: - diff = diff.flatten(start_dim=1) - else: - diff = diff.squeeze() - - - U, S, Vh = torch.linalg.svd(diff.float()) - U = U[:, :rank] - S = S[:rank] - U = U @ torch.diag(S) - Vh = Vh[:rank, :] - - dist = torch.cat([U.flatten(), Vh.flatten()]) - hi_val = torch.quantile(dist, CLAMP_QUANTILE) - low_val = -hi_val - - U = U.clamp(low_val, hi_val) - Vh = Vh.clamp(low_val, hi_val) - if conv2d: - U = U.reshape(out_dim, rank, 1, 1) - Vh = Vh.reshape(rank, in_dim, kernel_size[0], kernel_size[1]) - return (U, Vh) - -class LORAType(Enum): - STANDARD = 0 - FULL_DIFF = 1 - -LORA_TYPES = {"standard": LORAType.STANDARD, - "full_diff": LORAType.FULL_DIFF} - -def calc_lora_model(model_diff, rank, prefix_model, prefix_lora, output_sd, lora_type, bias_diff=False): - comfy.model_management.load_models_gpu([model_diff], force_patch_weights=True) - sd = model_diff.model_state_dict(filter_prefix=prefix_model) - - for k in sd: - if k.endswith(".weight"): - weight_diff = sd[k] - if lora_type == LORAType.STANDARD: - if weight_diff.ndim < 2: - if bias_diff: - output_sd["{}{}.diff".format(prefix_lora, k[len(prefix_model):-7])] = weight_diff.contiguous().half().cpu() - continue - try: - out = extract_lora(weight_diff, rank) - output_sd["{}{}.lora_up.weight".format(prefix_lora, k[len(prefix_model):-7])] = out[0].contiguous().half().cpu() - output_sd["{}{}.lora_down.weight".format(prefix_lora, k[len(prefix_model):-7])] = out[1].contiguous().half().cpu() - except: - logging.warning("Could not generate lora weights for key {}, is the weight difference a zero?".format(k)) - elif lora_type == LORAType.FULL_DIFF: - output_sd["{}{}.diff".format(prefix_lora, k[len(prefix_model):-7])] = weight_diff.contiguous().half().cpu() - - elif bias_diff and k.endswith(".bias"): - output_sd["{}{}.diff_b".format(prefix_lora, k[len(prefix_model):-5])] = sd[k].contiguous().half().cpu() - return output_sd - -class LoraSave: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - - @classmethod - def INPUT_TYPES(s): - return {"required": {"filename_prefix": ("STRING", {"default": "loras/ComfyUI_extracted_lora"}), - "rank": ("INT", {"default": 8, "min": 1, "max": 4096, "step": 1}), - "lora_type": (tuple(LORA_TYPES.keys()),), - "bias_diff": ("BOOLEAN", {"default": True}), - }, - "optional": {"model_diff": ("MODEL", {"tooltip": "The ModelSubtract output to be converted to a lora."}), - "text_encoder_diff": ("CLIP", {"tooltip": "The CLIPSubtract output to be converted to a lora."})}, - } - RETURN_TYPES = () - FUNCTION = "save" - OUTPUT_NODE = True - - CATEGORY = "_for_testing" - - def save(self, filename_prefix, rank, lora_type, bias_diff, model_diff=None, text_encoder_diff=None): - if model_diff is None and text_encoder_diff is None: - return {} - - lora_type = LORA_TYPES.get(lora_type) - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir) - - output_sd = {} - if model_diff is not None: - output_sd = calc_lora_model(model_diff, rank, "diffusion_model.", "diffusion_model.", output_sd, lora_type, bias_diff=bias_diff) - if text_encoder_diff is not None: - output_sd = calc_lora_model(text_encoder_diff.patcher, rank, "", "text_encoders.", output_sd, lora_type, bias_diff=bias_diff) - - output_checkpoint = f"{filename}_{counter:05}_.safetensors" - output_checkpoint = os.path.join(full_output_folder, output_checkpoint) - - comfy.utils.save_torch_file(output_sd, output_checkpoint, metadata=None) - return {} - -NODE_CLASS_MAPPINGS = { - "LoraSave": LoraSave -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "LoraSave": "Extract and Save Lora" -} diff --git a/comfy_extras/nodes_lotus.py b/comfy_extras/nodes_lotus.py deleted file mode 100644 index 739dbdd3dd49d1fd007c511e0f6d0da5bb619550..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_lotus.py +++ /dev/null @@ -1,29 +0,0 @@ -import torch -import comfy.model_management as mm - -class LotusConditioning: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - }, - } - - RETURN_TYPES = ("CONDITIONING",) - RETURN_NAMES = ("conditioning",) - FUNCTION = "conditioning" - CATEGORY = "conditioning/lotus" - - def conditioning(self): - device = mm.get_torch_device() - #lotus uses a frozen encoder and null conditioning, i'm just inlining the results of that operation since it doesn't change - #and getting parity with the reference implementation would otherwise require inference and 800mb of tensors - prompt_embeds = torch.tensor([[[-0.3134765625, -0.447509765625, -0.00823974609375, -0.22802734375, 0.1785888671875, -0.2342529296875, -0.2188720703125, -0.0089111328125, -0.31396484375, 0.196533203125, -0.055877685546875, -0.3828125, -0.0965576171875, 0.0073394775390625, -0.284423828125, 0.07470703125, -0.086181640625, -0.211181640625, 0.0599365234375, 0.10693359375, 0.0007929801940917969, -0.78076171875, -0.382568359375, -0.1851806640625, -0.140625, -0.0936279296875, -0.1229248046875, -0.152099609375, -0.203857421875, -0.2349853515625, -0.2437744140625, -0.10858154296875, -0.08990478515625, 0.08892822265625, -0.2391357421875, -0.1611328125, -0.427978515625, -0.1336669921875, -0.27685546875, -0.1781005859375, -0.3857421875, 0.251953125, -0.055999755859375, -0.0712890625, -0.00130462646484375, 0.033477783203125, -0.26416015625, 0.07171630859375, -0.0090789794921875, -0.2025146484375, -0.2763671875, -0.09869384765625, -0.45751953125, -0.23095703125, 0.004528045654296875, -0.369140625, -0.366943359375, -0.205322265625, -0.1505126953125, -0.45166015625, -0.2059326171875, 0.0168609619140625, -0.305419921875, -0.150634765625, 0.02685546875, -0.609375, -0.019012451171875, 0.050445556640625, -0.0084381103515625, -0.31005859375, -0.184326171875, -0.15185546875, 0.06732177734375, 0.150390625, -0.10919189453125, -0.08837890625, -0.50537109375, -0.389892578125, -0.0294342041015625, -0.10491943359375, -0.187255859375, -0.43212890625, -0.328125, -1.060546875, 0.011871337890625, 0.04730224609375, -0.09521484375, -0.07452392578125, -0.29296875, -0.109130859375, -0.250244140625, -0.3828125, -0.171875, -0.03399658203125, -0.15478515625, -0.1861572265625, -0.2398681640625, 0.1053466796875, -0.22314453125, -0.1932373046875, -0.18798828125, -0.430419921875, -0.05364990234375, -0.474609375, -0.261474609375, -0.1077880859375, -0.439208984375, 0.08966064453125, -0.185302734375, -0.338134765625, -0.297119140625, -0.298583984375, -0.175537109375, -0.373291015625, -0.1397705078125, -0.260498046875, -0.383544921875, -0.09979248046875, -0.319580078125, -0.06884765625, -0.4365234375, -0.183837890625, -0.393310546875, -0.002277374267578125, 0.11236572265625, -0.260498046875, -0.2242431640625, -0.19384765625, -0.51123046875, 0.03216552734375, -0.048004150390625, -0.279052734375, -0.2978515625, -0.255615234375, 0.115478515625, -4.08984375, -0.1668701171875, -0.278076171875, -0.5712890625, -0.1385498046875, -0.244384765625, -0.41455078125, -0.244140625, -0.0677490234375, -0.141357421875, -0.11590576171875, -0.1439208984375, -0.0185394287109375, -2.490234375, -0.1549072265625, -0.2305908203125, -0.3828125, -0.1173095703125, -0.08258056640625, -0.1719970703125, -0.325439453125, -0.292724609375, -0.08154296875, -0.412353515625, -0.3115234375, -0.00832366943359375, 0.00489044189453125, -0.2236328125, -0.151123046875, -0.457275390625, -0.135009765625, -0.163330078125, -0.0819091796875, 0.06689453125, 0.0209197998046875, -0.11907958984375, -0.10369873046875, -0.2998046875, -0.478759765625, -0.07940673828125, -0.01517486572265625, -0.3017578125, -0.343994140625, -0.258544921875, -0.44775390625, -0.392822265625, -0.0255584716796875, -0.2998046875, 0.10833740234375, -0.271728515625, -0.36181640625, -0.255859375, -0.2056884765625, -0.055450439453125, 0.060516357421875, -0.45751953125, -0.2322998046875, -0.1737060546875, -0.40576171875, -0.2286376953125, -0.053070068359375, -0.0283660888671875, -0.1898193359375, -4.291534423828125e-05, -0.6591796875, -0.1717529296875, -0.479736328125, -0.1400146484375, -0.40771484375, 0.154296875, 0.003101348876953125, 0.00661468505859375, -0.2073974609375, -0.493408203125, 2.171875, -0.45361328125, -0.283935546875, -0.302001953125, -0.25146484375, -0.207275390625, -0.1524658203125, -0.72998046875, -0.08203125, 0.053192138671875, -0.2685546875, 0.1834716796875, -0.270263671875, -0.091552734375, -0.08319091796875, -0.1297607421875, -0.453857421875, 0.0687255859375, 0.0268096923828125, -0.16552734375, -0.4208984375, -0.1552734375, -0.057373046875, -0.300537109375, -0.04541015625, -0.486083984375, -0.2205810546875, -0.39013671875, 0.007488250732421875, -0.005329132080078125, -0.09759521484375, -0.1448974609375, -0.21923828125, -0.429443359375, -0.40087890625, -0.19384765625, -0.064453125, -0.0306243896484375, -0.045806884765625, -0.056793212890625, 0.119384765625, -0.2073974609375, -0.356201171875, -0.168212890625, -0.291748046875, -0.289794921875, -0.205322265625, -0.419677734375, -0.478271484375, -0.2037353515625, -0.368408203125, -0.186279296875, -0.427734375, -0.1756591796875, 0.07501220703125, -0.2457275390625, -0.03692626953125, 0.003997802734375, -5.7578125, -0.01052093505859375, -0.2305908203125, -0.2252197265625, -0.197509765625, -0.1566162109375, -0.1668701171875, -0.383056640625, -0.05413818359375, 0.12188720703125, -0.369873046875, -0.0184478759765625, -0.150146484375, -0.51123046875, -0.45947265625, -0.1561279296875, 0.060455322265625, 0.043487548828125, -0.1370849609375, -0.069091796875, -0.285888671875, -0.44482421875, -0.2374267578125, -0.2191162109375, -0.434814453125, -0.0360107421875, 0.1298828125, 0.0217742919921875, -0.51220703125, -0.13525390625, -0.09381103515625, -0.276611328125, -0.171875, -0.17138671875, -0.4443359375, -0.2178955078125, -0.269775390625, -0.38623046875, -0.31591796875, -0.42333984375, -0.280029296875, -0.255615234375, -0.17041015625, 0.06268310546875, -0.1878662109375, -0.00677490234375, -0.23583984375, -0.08795166015625, -0.2232666015625, -0.1719970703125, -0.484130859375, -0.328857421875, 0.04669189453125, -0.0419921875, -0.11114501953125, 0.02313232421875, -0.0033130645751953125, -0.6005859375, 0.09051513671875, -0.1884765625, -0.262939453125, -0.375732421875, -0.525390625, -0.1170654296875, -0.3779296875, -0.242919921875, -0.419921875, 0.0665283203125, -0.343017578125, 0.06658935546875, -0.346435546875, -0.1363525390625, -0.2000732421875, -0.3837890625, 0.028167724609375, 0.043853759765625, -0.0171051025390625, -0.477294921875, -0.107421875, -0.129150390625, -0.319580078125, -0.32177734375, -0.4951171875, -0.010589599609375, -0.1778564453125, -0.40234375, -0.0810546875, 0.03314208984375, -0.13720703125, -0.31591796875, -0.048248291015625, -0.274658203125, -0.0689697265625, -0.027130126953125, -0.0953369140625, 0.146728515625, -0.38671875, -0.025390625, -0.42333984375, -0.41748046875, -0.379638671875, -0.1978759765625, -0.533203125, -0.33544921875, 0.0694580078125, -0.322998046875, -0.1876220703125, 0.0094451904296875, 0.1839599609375, -0.254150390625, -0.30078125, -0.09228515625, -0.0885009765625, 0.12371826171875, 0.1500244140625, -0.12152099609375, -0.29833984375, 0.03924560546875, -0.1470947265625, -0.1610107421875, -0.2049560546875, -0.01708984375, -0.2470703125, -0.1522216796875, -0.25830078125, 0.10870361328125, -0.302490234375, -0.2376708984375, -0.360107421875, -0.443359375, -0.0784912109375, -0.63623046875, -0.0980224609375, -0.332275390625, -0.1749267578125, -0.30859375, -0.1968994140625, -0.250244140625, -0.447021484375, -0.18408203125, -0.006908416748046875, -0.2044677734375, -0.2548828125, -0.369140625, -0.11328125, -0.1103515625, -0.27783203125, -0.325439453125, 0.01381683349609375, 0.036773681640625, -0.1458740234375, -0.34619140625, -0.232177734375, -0.0562744140625, -0.4482421875, -0.21875, -0.0855712890625, -0.276123046875, -0.1544189453125, -0.223388671875, -0.259521484375, 0.0865478515625, -0.0038013458251953125, -0.340087890625, -0.076171875, -0.25341796875, -0.0007548332214355469, -0.060455322265625, -0.352294921875, 0.035736083984375, -0.2181396484375, -0.2318115234375, -0.1707763671875, 0.018646240234375, 0.093505859375, -0.197021484375, 0.033477783203125, -0.035247802734375, 0.0440673828125, -0.2056884765625, -0.040924072265625, -0.05865478515625, 0.056884765625, -0.08807373046875, -0.10845947265625, 0.09564208984375, -0.10888671875, -0.332275390625, -0.1119384765625, -0.115478515625, 13.0234375, 0.0030040740966796875, -0.53662109375, -0.1856689453125, -0.068115234375, -0.143798828125, -0.177978515625, -0.32666015625, -0.353515625, -0.1563720703125, -0.3203125, 0.0085906982421875, -0.1043701171875, -0.365478515625, -0.303466796875, -0.34326171875, -0.410888671875, -0.03790283203125, -0.11419677734375, -0.2939453125, 0.074462890625, -0.21826171875, 0.0242767333984375, -0.226318359375, -0.353515625, -0.177734375, -0.169189453125, -0.2423095703125, -0.12115478515625, -0.07843017578125, -0.341064453125, -0.2117919921875, -0.505859375, -0.544921875, -0.3935546875, -0.10772705078125, -0.2054443359375, -0.136474609375, -0.1796875, -0.396240234375, -0.1971435546875, -0.68408203125, -0.032684326171875, -0.03863525390625, -0.0709228515625, -0.1005859375, -0.156005859375, -0.3837890625, -0.319580078125, 0.11102294921875, -0.394287109375, 0.0799560546875, -0.50341796875, -0.1572265625, 0.004131317138671875, -0.12286376953125, -0.2347412109375, -0.29150390625, -0.10321044921875, -0.286376953125, 0.018798828125, -0.152099609375, -0.321044921875, 0.0191650390625, -0.11376953125, -0.54736328125, 0.15869140625, -0.257568359375, -0.2490234375, -0.3115234375, -0.09765625, -0.350830078125, -0.36376953125, -0.0771484375, -0.2298583984375, -0.30615234375, -0.052154541015625, -0.12091064453125, -0.40283203125, -0.1649169921875, 0.0206451416015625, -0.312744140625, -0.10308837890625, -0.50341796875, -0.1754150390625, -0.2003173828125, -0.173583984375, -0.204833984375, -0.1876220703125, -0.12176513671875, -0.06201171875, -0.03485107421875, -0.20068359375, -0.21484375, -0.246337890625, -0.006587982177734375, -0.09674072265625, -0.4658203125, -0.3994140625, -0.2210693359375, -0.09588623046875, -0.126220703125, -0.09222412109375, -0.145751953125, -0.217529296875, -0.289306640625, -0.28271484375, -0.1787109375, -0.169189453125, -0.359375, -0.21826171875, -0.043792724609375, -0.205322265625, -0.2900390625, -0.055419921875, -0.1490478515625, -0.340576171875, -0.045928955078125, -0.30517578125, -0.51123046875, -0.1046142578125, -0.349853515625, -0.10882568359375, -0.16748046875, -0.267333984375, -0.122314453125, -0.0985107421875, -0.3076171875, -0.1766357421875, -0.251708984375, 0.1964111328125, -0.2220458984375, -0.2349853515625, -0.035980224609375, -0.1749267578125, -0.237060546875, -0.480224609375, -0.240234375, -0.09539794921875, -0.2481689453125, -0.389404296875, -0.1748046875, -0.370849609375, -0.010650634765625, -0.147705078125, -0.0035457611083984375, -0.32568359375, -0.29931640625, -0.1395263671875, -0.28173828125, -0.09820556640625, -0.0176239013671875, -0.05926513671875, -0.0755615234375, -0.1746826171875, -0.283203125, -0.1617431640625, -0.4404296875, 0.046234130859375, -0.183837890625, -0.052032470703125, -0.24658203125, -0.11224365234375, -0.100830078125, -0.162841796875, -0.29736328125, -0.396484375, 0.11798095703125, -0.006496429443359375, -0.32568359375, -0.347900390625, -0.04595947265625, -0.09637451171875, -0.344970703125, -0.01166534423828125, -0.346435546875, -0.2861328125, -0.1845703125, -0.276611328125, -0.01312255859375, -0.395263671875, -0.50927734375, -0.1114501953125, -0.1861572265625, -0.2158203125, -0.1812744140625, 0.055419921875, -0.294189453125, 0.06500244140625, -0.1444091796875, -0.06365966796875, -0.18408203125, -0.0091705322265625, -0.1640625, -0.1856689453125, 0.090087890625, 0.024566650390625, -0.0195159912109375, -0.5546875, -0.301025390625, -0.438232421875, -0.072021484375, 0.030517578125, -0.1490478515625, 0.04888916015625, -0.23681640625, -0.1553955078125, -0.018096923828125, -0.229736328125, -0.2919921875, -0.355712890625, -0.285400390625, -0.1756591796875, -0.08355712890625, -0.416259765625, 0.022674560546875, -0.417236328125, 0.410400390625, -0.249755859375, 0.015625, -0.033599853515625, -0.040313720703125, -0.51708984375, -0.0518798828125, -0.08843994140625, -0.2022705078125, -0.3740234375, -0.285888671875, -0.176025390625, -0.292724609375, -0.369140625, -0.08367919921875, -0.356689453125, -0.38623046875, 0.06549072265625, 0.1669921875, -0.2099609375, -0.007434844970703125, 0.12890625, -0.0040740966796875, -0.2174072265625, -0.025115966796875, -0.2364501953125, -0.1695556640625, -0.0469970703125, -0.03924560546875, -0.36181640625, -0.047515869140625, -0.3154296875, -0.275634765625, -0.25634765625, -0.061920166015625, -0.12164306640625, -0.47314453125, -0.10784912109375, -0.74755859375, -0.13232421875, -0.32421875, -0.04998779296875, -0.286376953125, 0.10345458984375, -0.1710205078125, -0.388916015625, 0.12744140625, -0.3359375, -0.302490234375, -0.238525390625, -0.1455078125, -0.15869140625, -0.2427978515625, -0.0355224609375, -0.11944580078125, -0.31298828125, 0.11456298828125, -0.287841796875, -0.5439453125, -0.3076171875, -0.08642578125, -0.2408447265625, -0.283447265625, -0.428466796875, -0.085693359375, -0.1683349609375, 0.255126953125, 0.07635498046875, -0.38623046875, -0.2025146484375, -0.1331787109375, -0.10821533203125, -0.49951171875, 0.09130859375, -0.19677734375, -0.01904296875, -0.151123046875, -0.344482421875, -0.316650390625, -0.03900146484375, 0.1397705078125, 0.1334228515625, -0.037200927734375, -0.01861572265625, -0.1351318359375, -0.07037353515625, -0.380615234375, -0.34033203125, -0.06903076171875, 0.219970703125, 0.0132598876953125, -0.15869140625, -0.6376953125, 0.158935546875, -0.5283203125, -0.2320556640625, -0.185791015625, -0.2132568359375, -0.436767578125, -0.430908203125, -0.1763916015625, -0.0007672309875488281, -0.424072265625, -0.06719970703125, -0.347900390625, -0.14453125, -0.3056640625, -0.36474609375, -0.35986328125, -0.46240234375, -0.446044921875, -0.1905517578125, -0.1114501953125, -0.42919921875, -0.0643310546875, -0.3662109375, -0.4296875, -0.10968017578125, -0.2998046875, -0.1756591796875, -0.4052734375, -0.0841064453125, -0.252197265625, -0.047393798828125, 0.00434112548828125, -0.10040283203125, -0.271484375, -0.185302734375, -0.1910400390625, 0.10260009765625, 0.01393890380859375, -0.03350830078125, -0.33935546875, -0.329345703125, 0.0574951171875, -0.18896484375, -0.17724609375, -0.42919921875, -0.26708984375, -0.4189453125, -0.149169921875, -0.265625, -0.198974609375, -0.1722412109375, 0.1563720703125, -0.20947265625, -0.267822265625, -0.06353759765625, -0.365478515625, -0.340087890625, -0.3095703125, -0.320068359375, -0.0880126953125, -0.353759765625, -0.0005812644958496094, -0.1617431640625, -0.1866455078125, -0.201416015625, -0.181396484375, -0.2349853515625, -0.384765625, -0.5244140625, 0.01227569580078125, -0.21337890625, -0.30810546875, -0.17578125, -0.3037109375, -0.52978515625, -0.1561279296875, -0.296142578125, 0.057342529296875, -0.369384765625, -0.107666015625, -0.338623046875, -0.2060546875, -0.0213775634765625, -0.394775390625, -0.219482421875, -0.125732421875, -0.03997802734375, -0.42431640625, -0.134521484375, -0.2418212890625, -0.10504150390625, 0.1552734375, 0.1126708984375, -0.1427001953125, -0.133544921875, -0.111083984375, -0.375732421875, -0.2783203125, -0.036834716796875, -0.11053466796875, 0.2471923828125, -0.2529296875, -0.56494140625, -0.374755859375, -0.326416015625, 0.2137451171875, -0.09454345703125, -0.337158203125, -0.3359375, -0.34375, -0.0999755859375, -0.388671875, 0.0103302001953125, 0.14990234375, -0.2041015625, -0.39501953125, -0.39013671875, -0.1258544921875, 0.1453857421875, -0.250732421875, -0.06732177734375, -0.10638427734375, -0.032379150390625, -0.35888671875, -0.098876953125, -0.172607421875, 0.05126953125, -0.1956787109375, -0.183837890625, -0.37060546875, 0.1556396484375, -0.34375, -0.28662109375, -0.06982421875, -0.302490234375, -0.281005859375, -0.1640625, -0.5302734375, -0.1368408203125, -0.1268310546875, -0.35302734375, -0.1473388671875, -0.45556640625, -0.35986328125, -0.273681640625, -0.2249755859375, -0.1893310546875, 0.09356689453125, -0.248291015625, -0.197998046875, -0.3525390625, -0.30126953125, -0.228271484375, -0.2421875, -0.0906982421875, 0.227783203125, -0.296875, -0.009796142578125, -0.2939453125, -0.1021728515625, -0.215576171875, -0.267822265625, -0.052642822265625, 0.203369140625, -0.1417236328125, 0.18505859375, 0.12347412109375, -0.0972900390625, -0.54052734375, -0.430419921875, -0.0906982421875, -0.5419921875, -0.22900390625, -0.0625, -0.12152099609375, -0.495849609375, -0.206787109375, -0.025848388671875, 0.039031982421875, -0.453857421875, -0.318359375, -0.426025390625, -0.3701171875, -0.2169189453125, 0.0845947265625, -0.045654296875, 0.11090087890625, 0.0012454986572265625, 0.2066650390625, -0.046356201171875, -0.2337646484375, -0.295654296875, 0.057891845703125, -0.1639404296875, -0.0535888671875, -0.2607421875, -0.1488037109375, -0.16015625, -0.54345703125, -0.2305908203125, -0.55029296875, -0.178955078125, -0.222412109375, -0.0711669921875, -0.12298583984375, -0.119140625, -0.253662109375, -0.33984375, -0.11322021484375, -0.10723876953125, -0.205078125, -0.360595703125, 0.085205078125, -0.252197265625, -0.365966796875, -0.26953125, 0.2000732421875, -0.50634765625, 0.05706787109375, -0.3115234375, 0.0242919921875, -0.1689453125, -0.2401123046875, -0.3759765625, -0.2125244140625, 0.076416015625, -0.489013671875, -0.11749267578125, -0.55908203125, -0.313232421875, -0.572265625, -0.1387939453125, -0.037078857421875, -0.385498046875, 0.0323486328125, -0.39404296875, -0.05072021484375, -0.10430908203125, -0.10919189453125, -0.28759765625, -0.37451171875, -0.016937255859375, -0.2200927734375, -0.296875, -0.0286712646484375, -0.213134765625, 0.052001953125, -0.052337646484375, -0.253662109375, 0.07269287109375, -0.2498779296875, -0.150146484375, -0.09930419921875, -0.343505859375, 0.254150390625, -0.032440185546875, -0.296142578125], [1.4111328125, 0.00757598876953125, -0.428955078125, 0.089599609375, 0.0227813720703125, -0.0350341796875, -1.0986328125, 0.194091796875, 2.115234375, -0.75439453125, 0.269287109375, -0.73486328125, -1.1025390625, -0.050262451171875, -0.5830078125, 0.0268707275390625, -0.603515625, -0.6025390625, -1.1689453125, 0.25048828125, -0.4189453125, -0.5517578125, -0.30322265625, 0.7724609375, 0.931640625, -0.1422119140625, 2.27734375, -0.56591796875, 1.013671875, -0.9638671875, -0.66796875, -0.8125, 1.3740234375, -1.060546875, -1.029296875, -1.6796875, 0.62890625, 0.49365234375, 0.671875, 0.99755859375, -1.0185546875, -0.047027587890625, -0.374267578125, 0.2354736328125, 1.4970703125, -1.5673828125, 0.448974609375, 0.2078857421875, -1.060546875, -0.171875, -0.6201171875, -0.1607666015625, 0.7548828125, -0.58935546875, -0.2052001953125, 0.060791015625, 0.200439453125, 3.154296875, -3.87890625, 2.03515625, 1.126953125, 0.1640625, -1.8447265625, 0.002620697021484375, 0.7998046875, -0.337158203125, 0.47216796875, -0.5849609375, 0.9970703125, 0.3935546875, 1.22265625, -1.5048828125, -0.65673828125, 1.1474609375, -1.73046875, -1.8701171875, 1.529296875, -0.6787109375, -1.4453125, 1.556640625, -0.327392578125, 2.986328125, -0.146240234375, -2.83984375, 0.303466796875, -0.71728515625, -0.09698486328125, -0.2423095703125, 0.6767578125, -2.197265625, -0.86279296875, -0.53857421875, -1.2236328125, 1.669921875, -1.1689453125, -0.291259765625, -0.54736328125, -0.036346435546875, 1.041015625, -1.7265625, -0.6064453125, -0.1634521484375, 0.2381591796875, 0.65087890625, -1.169921875, 1.9208984375, 0.5634765625, 0.37841796875, 0.798828125, -1.021484375, -0.4091796875, 2.275390625, -0.302734375, -1.7783203125, 1.0458984375, 1.478515625, 0.708984375, -1.541015625, -0.0006041526794433594, 1.1884765625, 2.041015625, 0.560546875, -0.1131591796875, 1.0341796875, 0.06121826171875, 2.6796875, -0.53369140625, -1.2490234375, -0.7333984375, -1.017578125, -1.0078125, 1.3212890625, -0.47607421875, -1.4189453125, 0.54052734375, -0.796875, -0.73095703125, -1.412109375, -0.94873046875, -2.2734375, -1.1220703125, -1.3837890625, -0.5087890625, -1.0380859375, -0.93603515625, -0.58349609375, -1.0703125, -1.10546875, -2.60546875, 0.062225341796875, 0.38232421875, -0.411376953125, -0.369140625, -0.9833984375, -0.7294921875, -0.181396484375, -0.47216796875, -0.56884765625, -0.11041259765625, -2.673828125, 0.27783203125, -0.857421875, 0.9296875, 1.9580078125, 0.1385498046875, -1.91796875, -1.529296875, 0.53857421875, 0.509765625, -0.90380859375, -0.0947265625, -2.083984375, 0.9228515625, -0.28564453125, -0.80859375, -0.093505859375, -0.6015625, -1.255859375, 0.6533203125, 0.327880859375, -0.07598876953125, -0.22705078125, -0.30078125, -0.5185546875, -1.6044921875, 1.5927734375, 1.416015625, -0.91796875, -0.276611328125, -0.75830078125, -1.1689453125, -1.7421875, 1.0546875, -0.26513671875, -0.03314208984375, 0.278076171875, -1.337890625, 0.055023193359375, 0.10546875, -1.064453125, 1.048828125, -1.4052734375, -1.1240234375, -0.51416015625, -1.05859375, -1.7265625, -1.1328125, 0.43310546875, -2.576171875, -2.140625, -0.79345703125, 0.50146484375, 1.96484375, 0.98583984375, 0.337646484375, -0.77978515625, 0.85498046875, -0.65185546875, -0.484375, 2.708984375, 0.55810546875, -0.147216796875, -0.5537109375, -0.75439453125, -1.736328125, 1.1259765625, -1.095703125, -0.2587890625, 2.978515625, 0.335205078125, 0.357666015625, -0.09356689453125, 0.295654296875, -0.23779296875, 1.5751953125, 0.10400390625, 1.7001953125, -0.72900390625, -1.466796875, -0.2012939453125, 0.634765625, -0.1556396484375, -2.01171875, 0.32666015625, 0.047454833984375, -0.1671142578125, -0.78369140625, -0.994140625, 0.7802734375, -0.1429443359375, -0.115234375, 0.53271484375, -0.96142578125, -0.064208984375, 1.396484375, 1.654296875, -1.6015625, -0.77392578125, 0.276123046875, -0.42236328125, 0.8642578125, 0.533203125, 0.397216796875, -1.21484375, 0.392578125, -0.501953125, -0.231689453125, 1.474609375, 1.6669921875, 1.8662109375, -1.2998046875, 0.223876953125, -0.51318359375, -0.437744140625, -1.16796875, -0.7724609375, 1.6826171875, 0.62255859375, 2.189453125, -0.599609375, -0.65576171875, -1.1005859375, -0.45263671875, -0.292236328125, 2.58203125, -1.3779296875, 0.23486328125, -1.708984375, -1.4111328125, -0.5078125, -0.8525390625, -0.90771484375, 0.861328125, -2.22265625, -1.380859375, 0.7275390625, 0.85595703125, -0.77978515625, 2.044921875, -0.430908203125, 0.78857421875, -1.21484375, -0.09130859375, 0.5146484375, -1.92578125, -0.1396484375, 0.289306640625, 0.60498046875, 0.93896484375, -0.09295654296875, -0.45751953125, -0.986328125, -0.66259765625, 1.48046875, 0.274169921875, -0.267333984375, -1.3017578125, -1.3623046875, -1.982421875, -0.86083984375, -0.41259765625, -0.2939453125, -1.91015625, 1.6826171875, 0.437255859375, 1.0029296875, 0.376220703125, -0.010467529296875, -0.82861328125, -0.513671875, -3.134765625, 1.0205078125, -1.26171875, -1.009765625, 1.0869140625, -0.95703125, 0.0103759765625, 1.642578125, 0.78564453125, 1.029296875, 0.496826171875, 1.2880859375, 0.5234375, 0.05322265625, -0.206787109375, -0.79443359375, -1.1669921875, 0.049530029296875, -0.27978515625, 0.0237884521484375, -0.74169921875, -1.068359375, 0.86083984375, 1.1787109375, 0.91064453125, -0.453857421875, -1.822265625, -0.9228515625, -0.50048828125, 0.359130859375, 0.802734375, -1.3564453125, -0.322509765625, -1.1123046875, -1.0390625, -0.52685546875, -1.291015625, -0.343017578125, -1.2109375, -0.19091796875, 2.146484375, -0.04315185546875, -0.3701171875, -2.044921875, -0.429931640625, -0.56103515625, -0.166015625, -0.4658203125, -2.29296875, -1.078125, -1.0927734375, -0.1033935546875, -0.56103515625, -0.05743408203125, -1.986328125, -0.513671875, 0.70361328125, -2.484375, -1.3037109375, -1.6650390625, 0.4814453125, -0.84912109375, -2.697265625, -0.197998046875, 0.0869140625, -0.172607421875, -1.326171875, -1.197265625, 1.23828125, -0.38720703125, -0.075927734375, 0.02569580078125, -1.2119140625, 0.09027099609375, -2.12890625, -1.640625, -0.1524658203125, 0.2373046875, 1.37109375, 2.248046875, 1.4619140625, 0.3134765625, 0.50244140625, -0.1383056640625, -1.2705078125, 0.7353515625, 0.65771484375, -0.431396484375, -1.341796875, 0.10089111328125, 0.208984375, -0.0099945068359375, 0.83203125, 1.314453125, -0.422607421875, -1.58984375, -0.6044921875, 0.23681640625, -1.60546875, -0.61083984375, -1.5615234375, 1.62890625, -0.6728515625, -0.68212890625, -0.5224609375, -0.9150390625, -0.468994140625, 0.268310546875, 0.287353515625, -0.025543212890625, 0.443603515625, 1.62109375, -1.08984375, -0.5556640625, 1.03515625, -0.31298828125, -0.041778564453125, 0.260986328125, 0.34716796875, -2.326171875, 0.228271484375, -0.85107421875, -2.255859375, 0.3486328125, -0.25830078125, -0.3671875, -0.796875, -1.115234375, 1.8369140625, -0.19775390625, -1.236328125, -0.0447998046875, 0.69921875, 1.37890625, 1.11328125, 0.0928955078125, 0.6318359375, -0.62353515625, 0.55859375, -0.286865234375, 1.5361328125, -0.391357421875, -0.052215576171875, -1.12890625, 0.55517578125, -0.28515625, -0.3603515625, 0.68896484375, 0.67626953125, 0.003070831298828125, 1.2236328125, 0.1597900390625, -1.3076171875, 0.99951171875, -2.5078125, -1.2119140625, 0.1749267578125, -1.1865234375, -1.234375, -0.1180419921875, -1.751953125, 0.033050537109375, 0.234130859375, -3.107421875, -1.0380859375, 0.61181640625, -0.87548828125, 0.3154296875, -1.103515625, 0.261474609375, -1.130859375, -0.7470703125, -0.43408203125, 1.3828125, -0.41259765625, -1.7587890625, 0.765625, 0.004852294921875, 0.135498046875, -0.76953125, -0.1314697265625, 0.400390625, 1.43359375, 0.07135009765625, 0.0645751953125, -0.5869140625, -0.5810546875, -0.2900390625, -1.3037109375, 0.1287841796875, -0.27490234375, 0.59228515625, 2.333984375, -0.54541015625, -0.556640625, 0.447265625, -0.806640625, 0.09149169921875, -0.70654296875, -0.357177734375, -1.099609375, -0.5576171875, -0.44189453125, 0.400390625, -0.666015625, -1.4619140625, 0.728515625, -1.5986328125, 0.153076171875, -0.126708984375, -2.83984375, -1.84375, -0.2469482421875, 0.677734375, 0.43701171875, 3.298828125, 1.1591796875, -0.7158203125, -0.8251953125, 0.451171875, -2.376953125, -0.58642578125, -0.86767578125, 0.0789794921875, 0.1351318359375, -0.325439453125, 0.484375, 1.166015625, -0.1610107421875, -0.15234375, -0.54638671875, -0.806640625, 0.285400390625, 0.1661376953125, -0.50146484375, -1.0478515625, 1.5751953125, 0.0313720703125, 0.2396240234375, -0.6572265625, -0.1258544921875, -1.060546875, 1.3076171875, -0.301513671875, -1.2412109375, 0.6376953125, -1.5693359375, 0.354248046875, 0.2427978515625, -0.392333984375, 0.61962890625, -0.58837890625, -1.71484375, -0.2098388671875, -0.828125, 0.330810546875, 0.16357421875, -0.2259521484375, 0.0972900390625, -0.451416015625, 1.79296875, -1.673828125, -1.58203125, -2.099609375, -0.487548828125, -0.87060546875, 0.62646484375, -1.470703125, -0.1558837890625, 0.4609375, 1.3369140625, 0.2322998046875, 0.1632080078125, 0.65966796875, 1.0810546875, 0.1041259765625, 0.63232421875, -0.32421875, -1.04296875, -1.046875, -1.3720703125, -0.8486328125, 0.1290283203125, 0.137939453125, 0.1549072265625, -1.0908203125, 0.0167694091796875, -0.31689453125, 1.390625, 0.07269287109375, 1.0390625, 1.1162109375, -0.455810546875, -0.06689453125, -0.053741455078125, 0.5048828125, -0.8408203125, -1.19921875, 0.87841796875, 0.7421875, 0.2030029296875, 0.109619140625, -0.59912109375, -1.337890625, -0.74169921875, -0.64453125, -1.326171875, 0.21044921875, -1.3583984375, -1.685546875, -0.472900390625, -0.270263671875, 0.99365234375, -0.96240234375, 1.1279296875, -0.45947265625, -0.45654296875, -0.99169921875, -3.515625, -1.9853515625, 0.73681640625, 0.92333984375, -0.56201171875, -1.4453125, -2.078125, 0.94189453125, -1.333984375, 0.0982666015625, 0.60693359375, 0.367431640625, 3.015625, -1.1357421875, -1.5634765625, 0.90234375, -0.1783447265625, 0.1802978515625, -0.317138671875, -0.513671875, 1.2353515625, -0.033203125, 1.4482421875, 1.0087890625, 0.9248046875, 0.10418701171875, 0.7626953125, -1.3798828125, 0.276123046875, 0.55224609375, 1.1005859375, -0.62158203125, -0.806640625, 0.65087890625, 0.270263671875, -0.339111328125, -0.9384765625, -0.09381103515625, -0.7216796875, 1.37890625, -0.398193359375, -0.3095703125, -1.4912109375, 0.96630859375, 0.43798828125, 0.62255859375, 0.0213470458984375, 0.235595703125, -1.2958984375, 0.0157318115234375, -0.810546875, 1.9736328125, -0.2462158203125, 0.720703125, 0.822265625, -0.755859375, -0.658203125, 0.344482421875, -2.892578125, -0.282470703125, 1.2529296875, -0.294189453125, 0.6748046875, -0.80859375, 0.9287109375, 1.27734375, -1.71875, -0.166015625, 0.47412109375, -0.41259765625, -1.3681640625, -0.978515625, -0.77978515625, -1.044921875, -0.90380859375, -0.08184814453125, -0.86181640625, -0.10772705078125, -0.299560546875, -0.4306640625, -0.47119140625, 0.95703125, 1.107421875, 0.91796875, 0.76025390625, 0.7392578125, -0.09161376953125, -0.7392578125, 0.9716796875, -0.395751953125, -0.75390625, -0.164306640625, -0.087646484375, 0.028564453125, -0.91943359375, -0.66796875, 2.486328125, 0.427734375, 0.626953125, 0.474853515625, 0.0926513671875, 0.830078125, -0.6923828125, 0.7841796875, -0.89208984375, -2.482421875, 0.034912109375, -1.3447265625, -0.475341796875, -0.286376953125, -0.732421875, 0.190673828125, -0.491455078125, -3.091796875, -1.2783203125, -0.66015625, -0.1507568359375, 0.042236328125, -1.025390625, 0.12744140625, -1.984375, -0.393798828125, -1.25, -1.140625, 1.77734375, 0.2457275390625, -0.8017578125, 0.7763671875, -0.387939453125, -0.3662109375, 1.1572265625, 0.123291015625, -0.07135009765625, 1.412109375, -0.685546875, -3.078125, 0.031524658203125, -0.70458984375, 0.78759765625, 0.433837890625, -1.861328125, -1.33203125, 2.119140625, -1.3544921875, -0.6591796875, -1.4970703125, 0.40625, -2.078125, -1.30859375, 0.050262451171875, -0.60107421875, 1.0078125, 0.05657958984375, -0.96826171875, 0.0264892578125, 0.159912109375, 0.84033203125, -1.1494140625, -0.0433349609375, -0.2034912109375, 1.09765625, -1.142578125, -0.283203125, -0.427978515625, 1.0927734375, -0.67529296875, -0.61572265625, 2.517578125, 0.84130859375, 1.8662109375, 0.1748046875, -0.407958984375, -0.029449462890625, -0.27587890625, -0.958984375, -0.10028076171875, 1.248046875, -0.0792236328125, -0.45556640625, 0.7685546875, 1.5556640625, -1.8759765625, -0.131591796875, -1.3583984375, 0.7890625, 0.80810546875, -1.0322265625, -0.53076171875, -0.1484375, -1.7841796875, -1.2470703125, 0.17138671875, -0.04864501953125, -0.80322265625, -0.0933837890625, 0.984375, 0.7001953125, 0.5380859375, 0.2022705078125, -1.1865234375, 0.5439453125, 1.1318359375, 0.79931640625, 0.32666015625, -1.26171875, 0.457763671875, 1.1591796875, -0.34423828125, 0.65771484375, 0.216552734375, 1.19140625, -0.2744140625, -0.020416259765625, -0.86376953125, 0.93017578125, 1.0556640625, 0.69873046875, -0.15087890625, -0.33056640625, 0.8505859375, 0.06890869140625, 0.359375, -0.262939453125, 0.12493896484375, 0.017059326171875, -0.98974609375, 0.5107421875, 0.2408447265625, 0.615234375, -0.62890625, 0.86962890625, -0.07427978515625, 0.85595703125, 0.300537109375, -1.072265625, -1.6064453125, -0.353515625, -0.484130859375, -0.6044921875, -0.455810546875, 0.95849609375, 1.3671875, 0.544921875, 0.560546875, 0.34521484375, -0.6513671875, -0.410400390625, -0.2021484375, -0.1656494140625, 0.073486328125, 0.84716796875, -1.7998046875, -1.0126953125, -0.1324462890625, 0.95849609375, -0.669921875, -0.79052734375, -2.193359375, -0.42529296875, -1.7275390625, -1.04296875, 0.716796875, -0.4423828125, -1.193359375, 0.61572265625, -1.5224609375, 0.62890625, -0.705078125, 0.677734375, -0.213134765625, -1.6748046875, -1.087890625, -0.65185546875, -1.1337890625, 2.314453125, -0.352783203125, -0.27001953125, -2.01953125, -1.2685546875, 0.308837890625, -0.280517578125, -1.3798828125, -1.595703125, 0.642578125, 1.693359375, -0.82470703125, -1.255859375, 0.57373046875, 1.5859375, 1.068359375, -0.876953125, 0.370849609375, 1.220703125, 0.59765625, 0.007602691650390625, 0.09326171875, -0.9521484375, -0.024932861328125, -0.94775390625, -0.299560546875, -0.002536773681640625, 1.41796875, -0.06903076171875, -1.5927734375, 0.353515625, 3.63671875, -0.765625, -1.1142578125, 0.4287109375, -0.86865234375, -0.9267578125, -0.21826171875, -1.10546875, 0.29296875, -0.225830078125, 0.5400390625, -0.45556640625, -0.68701171875, -0.79150390625, -1.0810546875, 0.25439453125, -1.2998046875, -0.494140625, -0.1510009765625, 1.5615234375, -0.4248046875, -0.486572265625, 0.45458984375, 0.047637939453125, -0.11639404296875, 0.057403564453125, 0.130126953125, -0.10125732421875, -0.56201171875, 1.4765625, -1.7451171875, 1.34765625, -0.45703125, 0.873046875, -0.056121826171875, -0.8876953125, -0.986328125, 1.5654296875, 0.49853515625, 0.55859375, -0.2198486328125, 0.62548828125, 0.2734375, -0.63671875, -0.41259765625, -1.2705078125, 0.0665283203125, 1.3369140625, 0.90283203125, -0.77685546875, -1.5, -1.8525390625, -1.314453125, -0.86767578125, -0.331787109375, 0.1590576171875, 0.94775390625, -0.1771240234375, 1.638671875, -2.17578125, 0.58740234375, 0.424560546875, -0.3466796875, 0.642578125, 0.473388671875, 0.96435546875, 1.38671875, -0.91357421875, 1.0361328125, -0.67333984375, 1.5009765625]]]).to(device) - - cond = [[prompt_embeds, {}]] - - return (cond,) - -NODE_CLASS_MAPPINGS = { - "LotusConditioning" : LotusConditioning, -} diff --git a/comfy_extras/nodes_lt.py b/comfy_extras/nodes_lt.py deleted file mode 100644 index b5058667a726cc4b97bd8e63f4792f1e8eb3dcea..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_lt.py +++ /dev/null @@ -1,474 +0,0 @@ -import io -import nodes -import node_helpers -import torch -import comfy.model_management -import comfy.model_sampling -import comfy.utils -import math -import numpy as np -import av -from comfy.ldm.lightricks.symmetric_patchifier import SymmetricPatchifier, latent_to_pixel_coords - -class EmptyLTXVLatentVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": { "width": ("INT", {"default": 768, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}), - "height": ("INT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}), - "length": ("INT", {"default": 97, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 8}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/video/ltxv" - - def generate(self, width, height, length, batch_size=1): - latent = torch.zeros([batch_size, 128, ((length - 1) // 8) + 1, height // 32, width // 32], device=comfy.model_management.intermediate_device()) - return ({"samples": latent}, ) - - -class LTXVImgToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE",), - "image": ("IMAGE",), - "width": ("INT", {"default": 768, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}), - "height": ("INT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}), - "length": ("INT", {"default": 97, "min": 9, "max": nodes.MAX_RESOLUTION, "step": 8}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - CATEGORY = "conditioning/video_models" - FUNCTION = "generate" - - def generate(self, positive, negative, image, vae, width, height, length, batch_size, strength): - pixels = comfy.utils.common_upscale(image.movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - encode_pixels = pixels[:, :, :, :3] - t = vae.encode(encode_pixels) - - latent = torch.zeros([batch_size, 128, ((length - 1) // 8) + 1, height // 32, width // 32], device=comfy.model_management.intermediate_device()) - latent[:, :, :t.shape[2]] = t - - conditioning_latent_frames_mask = torch.ones( - (batch_size, 1, latent.shape[2], 1, 1), - dtype=torch.float32, - device=latent.device, - ) - conditioning_latent_frames_mask[:, :, :t.shape[2]] = 1.0 - strength - - return (positive, negative, {"samples": latent, "noise_mask": conditioning_latent_frames_mask}, ) - - -def conditioning_get_any_value(conditioning, key, default=None): - for t in conditioning: - if key in t[1]: - return t[1][key] - return default - - -def get_noise_mask(latent): - noise_mask = latent.get("noise_mask", None) - latent_image = latent["samples"] - if noise_mask is None: - batch_size, _, latent_length, _, _ = latent_image.shape - noise_mask = torch.ones( - (batch_size, 1, latent_length, 1, 1), - dtype=torch.float32, - device=latent_image.device, - ) - else: - noise_mask = noise_mask.clone() - return noise_mask - -def get_keyframe_idxs(cond): - keyframe_idxs = conditioning_get_any_value(cond, "keyframe_idxs", None) - if keyframe_idxs is None: - return None, 0 - num_keyframes = torch.unique(keyframe_idxs[:, 0]).shape[0] - return keyframe_idxs, num_keyframes - -class LTXVAddGuide: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE",), - "latent": ("LATENT",), - "image": ("IMAGE", {"tooltip": "Image or video to condition the latent video on. Must be 8*n + 1 frames." - "If the video is not 8*n + 1 frames, it will be cropped to the nearest 8*n + 1 frames."}), - "frame_idx": ("INT", {"default": 0, "min": -9999, "max": 9999, - "tooltip": "Frame index to start the conditioning at. For single-frame images or " - "videos with 1-8 frames, any frame_idx value is acceptable. For videos with 9+ " - "frames, frame_idx must be divisible by 8, otherwise it will be rounded down to " - "the nearest multiple of 8. Negative values are counted from the end of the video."}), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - CATEGORY = "conditioning/video_models" - FUNCTION = "generate" - - def __init__(self): - self._num_prefix_frames = 2 - self._patchifier = SymmetricPatchifier(1) - - def encode(self, vae, latent_width, latent_height, images, scale_factors): - time_scale_factor, width_scale_factor, height_scale_factor = scale_factors - images = images[:(images.shape[0] - 1) // time_scale_factor * time_scale_factor + 1] - pixels = comfy.utils.common_upscale(images.movedim(-1, 1), latent_width * width_scale_factor, latent_height * height_scale_factor, "bilinear", crop="disabled").movedim(1, -1) - encode_pixels = pixels[:, :, :, :3] - t = vae.encode(encode_pixels) - return encode_pixels, t - - def get_latent_index(self, cond, latent_length, guide_length, frame_idx, scale_factors): - time_scale_factor, _, _ = scale_factors - _, num_keyframes = get_keyframe_idxs(cond) - latent_count = latent_length - num_keyframes - frame_idx = frame_idx if frame_idx >= 0 else max((latent_count - 1) * time_scale_factor + 1 + frame_idx, 0) - if guide_length > 1 and frame_idx != 0: - frame_idx = (frame_idx - 1) // time_scale_factor * time_scale_factor + 1 # frame index - 1 must be divisible by 8 or frame_idx == 0 - - latent_idx = (frame_idx + time_scale_factor - 1) // time_scale_factor - - return frame_idx, latent_idx - - def add_keyframe_index(self, cond, frame_idx, guiding_latent, scale_factors): - keyframe_idxs, _ = get_keyframe_idxs(cond) - _, latent_coords = self._patchifier.patchify(guiding_latent) - pixel_coords = latent_to_pixel_coords(latent_coords, scale_factors, causal_fix=frame_idx == 0) # we need the causal fix only if we're placing the new latents at index 0 - pixel_coords[:, 0] += frame_idx - if keyframe_idxs is None: - keyframe_idxs = pixel_coords - else: - keyframe_idxs = torch.cat([keyframe_idxs, pixel_coords], dim=2) - return node_helpers.conditioning_set_values(cond, {"keyframe_idxs": keyframe_idxs}) - - def append_keyframe(self, positive, negative, frame_idx, latent_image, noise_mask, guiding_latent, strength, scale_factors): - _, latent_idx = self.get_latent_index( - cond=positive, - latent_length=latent_image.shape[2], - guide_length=guiding_latent.shape[2], - frame_idx=frame_idx, - scale_factors=scale_factors, - ) - noise_mask[:, :, latent_idx:latent_idx + guiding_latent.shape[2]] = 1.0 - - positive = self.add_keyframe_index(positive, frame_idx, guiding_latent, scale_factors) - negative = self.add_keyframe_index(negative, frame_idx, guiding_latent, scale_factors) - - mask = torch.full( - (noise_mask.shape[0], 1, guiding_latent.shape[2], 1, 1), - 1.0 - strength, - dtype=noise_mask.dtype, - device=noise_mask.device, - ) - - latent_image = torch.cat([latent_image, guiding_latent], dim=2) - noise_mask = torch.cat([noise_mask, mask], dim=2) - return positive, negative, latent_image, noise_mask - - def replace_latent_frames(self, latent_image, noise_mask, guiding_latent, latent_idx, strength): - cond_length = guiding_latent.shape[2] - assert latent_image.shape[2] >= latent_idx + cond_length, "Conditioning frames exceed the length of the latent sequence." - - mask = torch.full( - (noise_mask.shape[0], 1, cond_length, 1, 1), - 1.0 - strength, - dtype=noise_mask.dtype, - device=noise_mask.device, - ) - - latent_image = latent_image.clone() - noise_mask = noise_mask.clone() - - latent_image[:, :, latent_idx : latent_idx + cond_length] = guiding_latent - noise_mask[:, :, latent_idx : latent_idx + cond_length] = mask - - return latent_image, noise_mask - - def generate(self, positive, negative, vae, latent, image, frame_idx, strength): - scale_factors = vae.downscale_index_formula - latent_image = latent["samples"] - noise_mask = get_noise_mask(latent) - - _, _, latent_length, latent_height, latent_width = latent_image.shape - image, t = self.encode(vae, latent_width, latent_height, image, scale_factors) - - frame_idx, latent_idx = self.get_latent_index(positive, latent_length, len(image), frame_idx, scale_factors) - assert latent_idx + t.shape[2] <= latent_length, "Conditioning frames exceed the length of the latent sequence." - - num_prefix_frames = min(self._num_prefix_frames, t.shape[2]) - - positive, negative, latent_image, noise_mask = self.append_keyframe( - positive, - negative, - frame_idx, - latent_image, - noise_mask, - t[:, :, :num_prefix_frames], - strength, - scale_factors, - ) - - latent_idx += num_prefix_frames - - t = t[:, :, num_prefix_frames:] - if t.shape[2] == 0: - return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},) - - latent_image, noise_mask = self.replace_latent_frames( - latent_image, - noise_mask, - t, - latent_idx, - strength, - ) - - return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},) - - -class LTXVCropGuides: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "latent": ("LATENT",), - } - } - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - CATEGORY = "conditioning/video_models" - FUNCTION = "crop" - - def __init__(self): - self._patchifier = SymmetricPatchifier(1) - - def crop(self, positive, negative, latent): - latent_image = latent["samples"].clone() - noise_mask = get_noise_mask(latent) - - _, num_keyframes = get_keyframe_idxs(positive) - if num_keyframes == 0: - return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},) - - latent_image = latent_image[:, :, :-num_keyframes] - noise_mask = noise_mask[:, :, :-num_keyframes] - - positive = node_helpers.conditioning_set_values(positive, {"keyframe_idxs": None}) - negative = node_helpers.conditioning_set_values(negative, {"keyframe_idxs": None}) - - return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},) - - -class LTXVConditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "frame_rate": ("FLOAT", {"default": 25.0, "min": 0.0, "max": 1000.0, "step": 0.01}), - }} - RETURN_TYPES = ("CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("positive", "negative") - FUNCTION = "append" - - CATEGORY = "conditioning/video_models" - - def append(self, positive, negative, frame_rate): - positive = node_helpers.conditioning_set_values(positive, {"frame_rate": frame_rate}) - negative = node_helpers.conditioning_set_values(negative, {"frame_rate": frame_rate}) - return (positive, negative) - - -class ModelSamplingLTXV: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "max_shift": ("FLOAT", {"default": 2.05, "min": 0.0, "max": 100.0, "step":0.01}), - "base_shift": ("FLOAT", {"default": 0.95, "min": 0.0, "max": 100.0, "step":0.01}), - }, - "optional": {"latent": ("LATENT",), } - } - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, max_shift, base_shift, latent=None): - m = model.clone() - - if latent is None: - tokens = 4096 - else: - tokens = math.prod(latent["samples"].shape[2:]) - - x1 = 1024 - x2 = 4096 - mm = (max_shift - base_shift) / (x2 - x1) - b = base_shift - mm * x1 - shift = (tokens) * mm + b - - sampling_base = comfy.model_sampling.ModelSamplingFlux - sampling_type = comfy.model_sampling.CONST - - class ModelSamplingAdvanced(sampling_base, sampling_type): - pass - - model_sampling = ModelSamplingAdvanced(model.model.model_config) - model_sampling.set_parameters(shift=shift) - m.add_object_patch("model_sampling", model_sampling) - - return (m, ) - - -class LTXVScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "max_shift": ("FLOAT", {"default": 2.05, "min": 0.0, "max": 100.0, "step":0.01}), - "base_shift": ("FLOAT", {"default": 0.95, "min": 0.0, "max": 100.0, "step":0.01}), - "stretch": ("BOOLEAN", { - "default": True, - "tooltip": "Stretch the sigmas to be in the range [terminal, 1]." - }), - "terminal": ( - "FLOAT", - { - "default": 0.1, "min": 0.0, "max": 0.99, "step": 0.01, - "tooltip": "The terminal value of the sigmas after stretching." - }, - ), - }, - "optional": {"latent": ("LATENT",), } - } - - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, steps, max_shift, base_shift, stretch, terminal, latent=None): - if latent is None: - tokens = 4096 - else: - tokens = math.prod(latent["samples"].shape[2:]) - - sigmas = torch.linspace(1.0, 0.0, steps + 1) - - x1 = 1024 - x2 = 4096 - mm = (max_shift - base_shift) / (x2 - x1) - b = base_shift - mm * x1 - sigma_shift = (tokens) * mm + b - - power = 1 - sigmas = torch.where( - sigmas != 0, - math.exp(sigma_shift) / (math.exp(sigma_shift) + (1 / sigmas - 1) ** power), - 0, - ) - - # Stretch sigmas so that its final value matches the given terminal value. - if stretch: - non_zero_mask = sigmas != 0 - non_zero_sigmas = sigmas[non_zero_mask] - one_minus_z = 1.0 - non_zero_sigmas - scale_factor = one_minus_z[-1] / (1.0 - terminal) - stretched = 1.0 - (one_minus_z / scale_factor) - sigmas[non_zero_mask] = stretched - - return (sigmas,) - -def encode_single_frame(output_file, image_array: np.ndarray, crf): - container = av.open(output_file, "w", format="mp4") - try: - stream = container.add_stream( - "libx264", rate=1, options={"crf": str(crf), "preset": "veryfast"} - ) - stream.height = image_array.shape[0] - stream.width = image_array.shape[1] - av_frame = av.VideoFrame.from_ndarray(image_array, format="rgb24").reformat( - format="yuv420p" - ) - container.mux(stream.encode(av_frame)) - container.mux(stream.encode()) - finally: - container.close() - - -def decode_single_frame(video_file): - container = av.open(video_file) - try: - stream = next(s for s in container.streams if s.type == "video") - frame = next(container.decode(stream)) - finally: - container.close() - return frame.to_ndarray(format="rgb24") - - -def preprocess(image: torch.Tensor, crf=29): - if crf == 0: - return image - - image_array = (image[:(image.shape[0] // 2) * 2, :(image.shape[1] // 2) * 2] * 255.0).byte().cpu().numpy() - with io.BytesIO() as output_file: - encode_single_frame(output_file, image_array, crf) - video_bytes = output_file.getvalue() - with io.BytesIO(video_bytes) as video_file: - image_array = decode_single_frame(video_file) - tensor = torch.tensor(image_array, dtype=image.dtype, device=image.device) / 255.0 - return tensor - - -class LTXVPreprocess: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "img_compression": ( - "INT", - { - "default": 35, - "min": 0, - "max": 100, - "tooltip": "Amount of compression to apply on image.", - }, - ), - } - } - - FUNCTION = "preprocess" - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("output_image",) - CATEGORY = "image" - - def preprocess(self, image, img_compression): - output_images = [] - for i in range(image.shape[0]): - output_images.append(preprocess(image[i], img_compression)) - return (torch.stack(output_images),) - - -NODE_CLASS_MAPPINGS = { - "EmptyLTXVLatentVideo": EmptyLTXVLatentVideo, - "LTXVImgToVideo": LTXVImgToVideo, - "ModelSamplingLTXV": ModelSamplingLTXV, - "LTXVConditioning": LTXVConditioning, - "LTXVScheduler": LTXVScheduler, - "LTXVAddGuide": LTXVAddGuide, - "LTXVPreprocess": LTXVPreprocess, - "LTXVCropGuides": LTXVCropGuides, -} diff --git a/comfy_extras/nodes_lumina2.py b/comfy_extras/nodes_lumina2.py deleted file mode 100644 index 275189785dca4dd6a9c56b58e87a0ab93342d9ec..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_lumina2.py +++ /dev/null @@ -1,104 +0,0 @@ -from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict -import torch - - -class RenormCFG: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "cfg_trunc": ("FLOAT", {"default": 100, "min": 0.0, "max": 100.0, "step": 0.01}), - "renorm_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, cfg_trunc, renorm_cfg): - def renorm_cfg_func(args): - cond_denoised = args["cond_denoised"] - uncond_denoised = args["uncond_denoised"] - cond_scale = args["cond_scale"] - timestep = args["timestep"] - x_orig = args["input"] - in_channels = model.model.diffusion_model.in_channels - - if timestep[0] < cfg_trunc: - cond_eps, uncond_eps = cond_denoised[:, :in_channels], uncond_denoised[:, :in_channels] - cond_rest, _ = cond_denoised[:, in_channels:], uncond_denoised[:, in_channels:] - half_eps = uncond_eps + cond_scale * (cond_eps - uncond_eps) - half_rest = cond_rest - - if float(renorm_cfg) > 0.0: - ori_pos_norm = torch.linalg.vector_norm(cond_eps - , dim=tuple(range(1, len(cond_eps.shape))), keepdim=True - ) - max_new_norm = ori_pos_norm * float(renorm_cfg) - new_pos_norm = torch.linalg.vector_norm( - half_eps, dim=tuple(range(1, len(half_eps.shape))), keepdim=True - ) - if new_pos_norm >= max_new_norm: - half_eps = half_eps * (max_new_norm / new_pos_norm) - else: - cond_eps, uncond_eps = cond_denoised[:, :in_channels], uncond_denoised[:, :in_channels] - cond_rest, _ = cond_denoised[:, in_channels:], uncond_denoised[:, in_channels:] - half_eps = cond_eps - half_rest = cond_rest - - cfg_result = torch.cat([half_eps, half_rest], dim=1) - - # cfg_result = uncond_denoised + (cond_denoised - uncond_denoised) * cond_scale - - return x_orig - cfg_result - - m = model.clone() - m.set_model_sampler_cfg_function(renorm_cfg_func) - return (m, ) - - -class CLIPTextEncodeLumina2(ComfyNodeABC): - SYSTEM_PROMPT = { - "superior": "You are an assistant designed to generate superior images with the superior "\ - "degree of image-text alignment based on textual prompts or user prompts.", - "alignment": "You are an assistant designed to generate high-quality images with the "\ - "highest degree of image-text alignment based on textual prompts." - } - SYSTEM_PROMPT_TIP = "Lumina2 provide two types of system prompts:" \ - "Superior: You are an assistant designed to generate superior images with the superior "\ - "degree of image-text alignment based on textual prompts or user prompts. "\ - "Alignment: You are an assistant designed to generate high-quality images with the highest "\ - "degree of image-text alignment based on textual prompts." - @classmethod - def INPUT_TYPES(s) -> InputTypeDict: - return { - "required": { - "system_prompt": (list(CLIPTextEncodeLumina2.SYSTEM_PROMPT.keys()), {"tooltip": CLIPTextEncodeLumina2.SYSTEM_PROMPT_TIP}), - "user_prompt": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}), - "clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}) - } - } - RETURN_TYPES = (IO.CONDITIONING,) - OUTPUT_TOOLTIPS = ("A conditioning containing the embedded text used to guide the diffusion model.",) - FUNCTION = "encode" - - CATEGORY = "conditioning" - DESCRIPTION = "Encodes a system prompt and a user prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images." - - def encode(self, clip, user_prompt, system_prompt): - if clip is None: - raise RuntimeError("ERROR: clip input is invalid: None\n\nIf the clip is from a checkpoint loader node your checkpoint does not contain a valid clip or text encoder model.") - system_prompt = CLIPTextEncodeLumina2.SYSTEM_PROMPT[system_prompt] - prompt = f'{system_prompt} {user_prompt}' - tokens = clip.tokenize(prompt) - return (clip.encode_from_tokens_scheduled(tokens), ) - - -NODE_CLASS_MAPPINGS = { - "CLIPTextEncodeLumina2": CLIPTextEncodeLumina2, - "RenormCFG": RenormCFG -} - - -NODE_DISPLAY_NAME_MAPPINGS = { - "CLIPTextEncodeLumina2": "CLIP Text Encode for Lumina2", -} diff --git a/comfy_extras/nodes_mahiro.py b/comfy_extras/nodes_mahiro.py deleted file mode 100644 index 8fcdfba759f97515e5f64d3a130b0d892005cff2..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_mahiro.py +++ /dev/null @@ -1,41 +0,0 @@ -import torch -import torch.nn.functional as F - -class Mahiro: - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL",), - }} - RETURN_TYPES = ("MODEL",) - RETURN_NAMES = ("patched_model",) - FUNCTION = "patch" - CATEGORY = "_for_testing" - DESCRIPTION = "Modify the guidance to scale more on the 'direction' of the positive prompt rather than the difference between the negative prompt." - def patch(self, model): - m = model.clone() - def mahiro_normd(args): - scale: float = args['cond_scale'] - cond_p: torch.Tensor = args['cond_denoised'] - uncond_p: torch.Tensor = args['uncond_denoised'] - #naive leap - leap = cond_p * scale - #sim with uncond leap - u_leap = uncond_p * scale - cfg = args["denoised"] - merge = (leap + cfg) / 2 - normu = torch.sqrt(u_leap.abs()) * u_leap.sign() - normm = torch.sqrt(merge.abs()) * merge.sign() - sim = F.cosine_similarity(normu, normm).mean() - simsc = 2 * (sim+1) - wm = (simsc*cfg + (4-simsc)*leap) / 4 - return wm - m.set_model_sampler_post_cfg_function(mahiro_normd) - return (m, ) - -NODE_CLASS_MAPPINGS = { - "Mahiro": Mahiro -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "Mahiro": "Mahiro is so cute that she deserves a better guidance function!! (。・ω・。)", -} diff --git a/comfy_extras/nodes_mask.py b/comfy_extras/nodes_mask.py deleted file mode 100644 index 2b0f8dd5d72b121532ea1c8b595081210fe4779e..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_mask.py +++ /dev/null @@ -1,412 +0,0 @@ -import numpy as np -import scipy.ndimage -import torch -import comfy.utils -import node_helpers -import folder_paths -import random - -import nodes -from nodes import MAX_RESOLUTION - -def composite(destination, source, x, y, mask = None, multiplier = 8, resize_source = False): - source = source.to(destination.device) - if resize_source: - source = torch.nn.functional.interpolate(source, size=(destination.shape[2], destination.shape[3]), mode="bilinear") - - source = comfy.utils.repeat_to_batch_size(source, destination.shape[0]) - - x = max(-source.shape[3] * multiplier, min(x, destination.shape[3] * multiplier)) - y = max(-source.shape[2] * multiplier, min(y, destination.shape[2] * multiplier)) - - left, top = (x // multiplier, y // multiplier) - right, bottom = (left + source.shape[3], top + source.shape[2],) - - if mask is None: - mask = torch.ones_like(source) - else: - mask = mask.to(destination.device, copy=True) - mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(source.shape[2], source.shape[3]), mode="bilinear") - mask = comfy.utils.repeat_to_batch_size(mask, source.shape[0]) - - # calculate the bounds of the source that will be overlapping the destination - # this prevents the source trying to overwrite latent pixels that are out of bounds - # of the destination - visible_width, visible_height = (destination.shape[3] - left + min(0, x), destination.shape[2] - top + min(0, y),) - - mask = mask[:, :, :visible_height, :visible_width] - inverse_mask = torch.ones_like(mask) - mask - - source_portion = mask * source[:, :, :visible_height, :visible_width] - destination_portion = inverse_mask * destination[:, :, top:bottom, left:right] - - destination[:, :, top:bottom, left:right] = source_portion + destination_portion - return destination - -class LatentCompositeMasked: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "destination": ("LATENT",), - "source": ("LATENT",), - "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), - "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), - "resize_source": ("BOOLEAN", {"default": False}), - }, - "optional": { - "mask": ("MASK",), - } - } - RETURN_TYPES = ("LATENT",) - FUNCTION = "composite" - - CATEGORY = "latent" - - def composite(self, destination, source, x, y, resize_source, mask = None): - output = destination.copy() - destination = destination["samples"].clone() - source = source["samples"] - output["samples"] = composite(destination, source, x, y, mask, 8, resize_source) - return (output,) - -class ImageCompositeMasked: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "destination": ("IMAGE",), - "source": ("IMAGE",), - "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "resize_source": ("BOOLEAN", {"default": False}), - }, - "optional": { - "mask": ("MASK",), - } - } - RETURN_TYPES = ("IMAGE",) - FUNCTION = "composite" - - CATEGORY = "image" - - def composite(self, destination, source, x, y, resize_source, mask = None): - destination, source = node_helpers.image_alpha_fix(destination, source) - destination = destination.clone().movedim(-1, 1) - output = composite(destination, source.movedim(-1, 1), x, y, mask, 1, resize_source).movedim(1, -1) - return (output,) - -class MaskToImage: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mask": ("MASK",), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "mask_to_image" - - def mask_to_image(self, mask): - result = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3) - return (result,) - -class ImageToMask: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "channel": (["red", "green", "blue", "alpha"],), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - FUNCTION = "image_to_mask" - - def image_to_mask(self, image, channel): - channels = ["red", "green", "blue", "alpha"] - mask = image[:, :, :, channels.index(channel)] - return (mask,) - -class ImageColorToMask: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "color": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFF, "step": 1, "display": "color"}), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - FUNCTION = "image_to_mask" - - def image_to_mask(self, image, color): - temp = (torch.clamp(image, 0, 1.0) * 255.0).round().to(torch.int) - temp = torch.bitwise_left_shift(temp[:,:,:,0], 16) + torch.bitwise_left_shift(temp[:,:,:,1], 8) + temp[:,:,:,2] - mask = torch.where(temp == color, 1.0, 0).float() - return (mask,) - -class SolidMask: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "value": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}), - "height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - - FUNCTION = "solid" - - def solid(self, value, width, height): - out = torch.full((1, height, width), value, dtype=torch.float32, device="cpu") - return (out,) - -class InvertMask: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "mask": ("MASK",), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - - FUNCTION = "invert" - - def invert(self, mask): - out = 1.0 - mask - return (out,) - -class CropMask: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "mask": ("MASK",), - "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}), - "height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - - FUNCTION = "crop" - - def crop(self, mask, x, y, width, height): - mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1])) - out = mask[:, y:y + height, x:x + width] - return (out,) - -class MaskComposite: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "destination": ("MASK",), - "source": ("MASK",), - "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "operation": (["multiply", "add", "subtract", "and", "or", "xor"],), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - - FUNCTION = "combine" - - def combine(self, destination, source, x, y, operation): - output = destination.reshape((-1, destination.shape[-2], destination.shape[-1])).clone() - source = source.reshape((-1, source.shape[-2], source.shape[-1])) - - left, top = (x, y,) - right, bottom = (min(left + source.shape[-1], destination.shape[-1]), min(top + source.shape[-2], destination.shape[-2])) - visible_width, visible_height = (right - left, bottom - top,) - - source_portion = source[:, :visible_height, :visible_width] - destination_portion = output[:, top:bottom, left:right] - - if operation == "multiply": - output[:, top:bottom, left:right] = destination_portion * source_portion - elif operation == "add": - output[:, top:bottom, left:right] = destination_portion + source_portion - elif operation == "subtract": - output[:, top:bottom, left:right] = destination_portion - source_portion - elif operation == "and": - output[:, top:bottom, left:right] = torch.bitwise_and(destination_portion.round().bool(), source_portion.round().bool()).float() - elif operation == "or": - output[:, top:bottom, left:right] = torch.bitwise_or(destination_portion.round().bool(), source_portion.round().bool()).float() - elif operation == "xor": - output[:, top:bottom, left:right] = torch.bitwise_xor(destination_portion.round().bool(), source_portion.round().bool()).float() - - output = torch.clamp(output, 0.0, 1.0) - - return (output,) - -class FeatherMask: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "mask": ("MASK",), - "left": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "top": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "right": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "bottom": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - - FUNCTION = "feather" - - def feather(self, mask, left, top, right, bottom): - output = mask.reshape((-1, mask.shape[-2], mask.shape[-1])).clone() - - left = min(left, output.shape[-1]) - right = min(right, output.shape[-1]) - top = min(top, output.shape[-2]) - bottom = min(bottom, output.shape[-2]) - - for x in range(left): - feather_rate = (x + 1.0) / left - output[:, :, x] *= feather_rate - - for x in range(right): - feather_rate = (x + 1) / right - output[:, :, -x] *= feather_rate - - for y in range(top): - feather_rate = (y + 1) / top - output[:, y, :] *= feather_rate - - for y in range(bottom): - feather_rate = (y + 1) / bottom - output[:, -y, :] *= feather_rate - - return (output,) - -class GrowMask: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "mask": ("MASK",), - "expand": ("INT", {"default": 0, "min": -MAX_RESOLUTION, "max": MAX_RESOLUTION, "step": 1}), - "tapered_corners": ("BOOLEAN", {"default": True}), - }, - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - - FUNCTION = "expand_mask" - - def expand_mask(self, mask, expand, tapered_corners): - c = 0 if tapered_corners else 1 - kernel = np.array([[c, 1, c], - [1, 1, 1], - [c, 1, c]]) - mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1])) - out = [] - for m in mask: - output = m.numpy() - for _ in range(abs(expand)): - if expand < 0: - output = scipy.ndimage.grey_erosion(output, footprint=kernel) - else: - output = scipy.ndimage.grey_dilation(output, footprint=kernel) - output = torch.from_numpy(output) - out.append(output) - return (torch.stack(out, dim=0),) - -class ThresholdMask: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mask": ("MASK",), - "value": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - - CATEGORY = "mask" - - RETURN_TYPES = ("MASK",) - FUNCTION = "image_to_mask" - - def image_to_mask(self, mask, value): - mask = (mask > value).float() - return (mask,) - -# Mask Preview - original implement from -# https://github.com/cubiq/ComfyUI_essentials/blob/9d9f4bedfc9f0321c19faf71855e228c93bd0dc9/mask.py#L81 -# upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes -class MaskPreview(nodes.SaveImage): - def __init__(self): - self.output_dir = folder_paths.get_temp_directory() - self.type = "temp" - self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) - self.compress_level = 4 - - @classmethod - def INPUT_TYPES(s): - return { - "required": {"mask": ("MASK",), }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - FUNCTION = "execute" - CATEGORY = "mask" - - def execute(self, mask, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None): - preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3) - return self.save_images(preview, filename_prefix, prompt, extra_pnginfo) - - -NODE_CLASS_MAPPINGS = { - "LatentCompositeMasked": LatentCompositeMasked, - "ImageCompositeMasked": ImageCompositeMasked, - "MaskToImage": MaskToImage, - "ImageToMask": ImageToMask, - "ImageColorToMask": ImageColorToMask, - "SolidMask": SolidMask, - "InvertMask": InvertMask, - "CropMask": CropMask, - "MaskComposite": MaskComposite, - "FeatherMask": FeatherMask, - "GrowMask": GrowMask, - "ThresholdMask": ThresholdMask, - "MaskPreview": MaskPreview -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "ImageToMask": "Convert Image to Mask", - "MaskToImage": "Convert Mask to Image", -} diff --git a/comfy_extras/nodes_mochi.py b/comfy_extras/nodes_mochi.py deleted file mode 100644 index 1c474faa94eac8dc48f778460c0d833bee94dcf9..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_mochi.py +++ /dev/null @@ -1,23 +0,0 @@ -import nodes -import torch -import comfy.model_management - -class EmptyMochiLatentVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": { "width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 25, "min": 7, "max": nodes.MAX_RESOLUTION, "step": 6}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/video" - - def generate(self, width, height, length, batch_size=1): - latent = torch.zeros([batch_size, 12, ((length - 1) // 6) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - return ({"samples":latent}, ) - -NODE_CLASS_MAPPINGS = { - "EmptyMochiLatentVideo": EmptyMochiLatentVideo, -} diff --git a/comfy_extras/nodes_model_advanced.py b/comfy_extras/nodes_model_advanced.py deleted file mode 100644 index ae5d2c563183262456fd6a9668445b6b798108ac..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_model_advanced.py +++ /dev/null @@ -1,329 +0,0 @@ -import comfy.sd -import comfy.model_sampling -import comfy.latent_formats -import nodes -import torch -import node_helpers - - -class LCM(comfy.model_sampling.EPS): - def calculate_denoised(self, sigma, model_output, model_input): - timestep = self.timestep(sigma).view(sigma.shape[:1] + (1,) * (model_output.ndim - 1)) - sigma = sigma.view(sigma.shape[:1] + (1,) * (model_output.ndim - 1)) - x0 = model_input - model_output * sigma - - sigma_data = 0.5 - scaled_timestep = timestep * 10.0 #timestep_scaling - - c_skip = sigma_data**2 / (scaled_timestep**2 + sigma_data**2) - c_out = scaled_timestep / (scaled_timestep**2 + sigma_data**2) ** 0.5 - - return c_out * x0 + c_skip * model_input - -class ModelSamplingDiscreteDistilled(comfy.model_sampling.ModelSamplingDiscrete): - original_timesteps = 50 - - def __init__(self, model_config=None, zsnr=None): - super().__init__(model_config, zsnr=zsnr) - - self.skip_steps = self.num_timesteps // self.original_timesteps - - sigmas_valid = torch.zeros((self.original_timesteps), dtype=torch.float32) - for x in range(self.original_timesteps): - sigmas_valid[self.original_timesteps - 1 - x] = self.sigmas[self.num_timesteps - 1 - x * self.skip_steps] - - self.set_sigmas(sigmas_valid) - - def timestep(self, sigma): - log_sigma = sigma.log() - dists = log_sigma.to(self.log_sigmas.device) - self.log_sigmas[:, None] - return (dists.abs().argmin(dim=0).view(sigma.shape) * self.skip_steps + (self.skip_steps - 1)).to(sigma.device) - - def sigma(self, timestep): - t = torch.clamp(((timestep.float().to(self.log_sigmas.device) - (self.skip_steps - 1)) / self.skip_steps).float(), min=0, max=(len(self.sigmas) - 1)) - low_idx = t.floor().long() - high_idx = t.ceil().long() - w = t.frac() - log_sigma = (1 - w) * self.log_sigmas[low_idx] + w * self.log_sigmas[high_idx] - return log_sigma.exp().to(timestep.device) - - -class ModelSamplingDiscrete: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "sampling": (["eps", "v_prediction", "lcm", "x0", "img_to_img"],), - "zsnr": ("BOOLEAN", {"default": False}), - }} - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, sampling, zsnr): - m = model.clone() - - sampling_base = comfy.model_sampling.ModelSamplingDiscrete - if sampling == "eps": - sampling_type = comfy.model_sampling.EPS - elif sampling == "v_prediction": - sampling_type = comfy.model_sampling.V_PREDICTION - elif sampling == "lcm": - sampling_type = LCM - sampling_base = ModelSamplingDiscreteDistilled - elif sampling == "x0": - sampling_type = comfy.model_sampling.X0 - elif sampling == "img_to_img": - sampling_type = comfy.model_sampling.IMG_TO_IMG - - class ModelSamplingAdvanced(sampling_base, sampling_type): - pass - - model_sampling = ModelSamplingAdvanced(model.model.model_config, zsnr=zsnr) - - m.add_object_patch("model_sampling", model_sampling) - return (m, ) - -class ModelSamplingStableCascade: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "shift": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 100.0, "step":0.01}), - }} - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, shift): - m = model.clone() - - sampling_base = comfy.model_sampling.StableCascadeSampling - sampling_type = comfy.model_sampling.EPS - - class ModelSamplingAdvanced(sampling_base, sampling_type): - pass - - model_sampling = ModelSamplingAdvanced(model.model.model_config) - model_sampling.set_parameters(shift) - m.add_object_patch("model_sampling", model_sampling) - return (m, ) - -class ModelSamplingSD3: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "shift": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step":0.01}), - }} - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, shift, multiplier=1000): - m = model.clone() - - sampling_base = comfy.model_sampling.ModelSamplingDiscreteFlow - sampling_type = comfy.model_sampling.CONST - - class ModelSamplingAdvanced(sampling_base, sampling_type): - pass - - model_sampling = ModelSamplingAdvanced(model.model.model_config) - model_sampling.set_parameters(shift=shift, multiplier=multiplier) - m.add_object_patch("model_sampling", model_sampling) - return (m, ) - -class ModelSamplingAuraFlow(ModelSamplingSD3): - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "shift": ("FLOAT", {"default": 1.73, "min": 0.0, "max": 100.0, "step":0.01}), - }} - - FUNCTION = "patch_aura" - - def patch_aura(self, model, shift): - return self.patch(model, shift, multiplier=1.0) - -class ModelSamplingFlux: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "max_shift": ("FLOAT", {"default": 1.15, "min": 0.0, "max": 100.0, "step":0.01}), - "base_shift": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 100.0, "step":0.01}), - "width": ("INT", {"default": 1024, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 1024, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - }} - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, max_shift, base_shift, width, height): - m = model.clone() - - x1 = 256 - x2 = 4096 - mm = (max_shift - base_shift) / (x2 - x1) - b = base_shift - mm * x1 - shift = (width * height / (8 * 8 * 2 * 2)) * mm + b - - sampling_base = comfy.model_sampling.ModelSamplingFlux - sampling_type = comfy.model_sampling.CONST - - class ModelSamplingAdvanced(sampling_base, sampling_type): - pass - - model_sampling = ModelSamplingAdvanced(model.model.model_config) - model_sampling.set_parameters(shift=shift) - m.add_object_patch("model_sampling", model_sampling) - return (m, ) - - -class ModelSamplingContinuousEDM: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "sampling": (["v_prediction", "edm", "edm_playground_v2.5", "eps", "cosmos_rflow"],), - "sigma_max": ("FLOAT", {"default": 120.0, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}), - "sigma_min": ("FLOAT", {"default": 0.002, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}), - }} - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, sampling, sigma_max, sigma_min): - m = model.clone() - - sampling_base = comfy.model_sampling.ModelSamplingContinuousEDM - latent_format = None - sigma_data = 1.0 - if sampling == "eps": - sampling_type = comfy.model_sampling.EPS - elif sampling == "edm": - sampling_type = comfy.model_sampling.EDM - sigma_data = 0.5 - elif sampling == "v_prediction": - sampling_type = comfy.model_sampling.V_PREDICTION - elif sampling == "edm_playground_v2.5": - sampling_type = comfy.model_sampling.EDM - sigma_data = 0.5 - latent_format = comfy.latent_formats.SDXL_Playground_2_5() - elif sampling == "cosmos_rflow": - sampling_type = comfy.model_sampling.COSMOS_RFLOW - sampling_base = comfy.model_sampling.ModelSamplingCosmosRFlow - - class ModelSamplingAdvanced(sampling_base, sampling_type): - pass - - model_sampling = ModelSamplingAdvanced(model.model.model_config) - model_sampling.set_parameters(sigma_min, sigma_max, sigma_data) - m.add_object_patch("model_sampling", model_sampling) - if latent_format is not None: - m.add_object_patch("latent_format", latent_format) - return (m, ) - -class ModelSamplingContinuousV: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "sampling": (["v_prediction"],), - "sigma_max": ("FLOAT", {"default": 500.0, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}), - "sigma_min": ("FLOAT", {"default": 0.03, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}), - }} - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, sampling, sigma_max, sigma_min): - m = model.clone() - - sigma_data = 1.0 - if sampling == "v_prediction": - sampling_type = comfy.model_sampling.V_PREDICTION - - class ModelSamplingAdvanced(comfy.model_sampling.ModelSamplingContinuousV, sampling_type): - pass - - model_sampling = ModelSamplingAdvanced(model.model.model_config) - model_sampling.set_parameters(sigma_min, sigma_max, sigma_data) - m.add_object_patch("model_sampling", model_sampling) - return (m, ) - -class RescaleCFG: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "multiplier": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/model" - - def patch(self, model, multiplier): - def rescale_cfg(args): - cond = args["cond"] - uncond = args["uncond"] - cond_scale = args["cond_scale"] - sigma = args["sigma"] - sigma = sigma.view(sigma.shape[:1] + (1,) * (cond.ndim - 1)) - x_orig = args["input"] - - #rescale cfg has to be done on v-pred model output - x = x_orig / (sigma * sigma + 1.0) - cond = ((x - (x_orig - cond)) * (sigma ** 2 + 1.0) ** 0.5) / (sigma) - uncond = ((x - (x_orig - uncond)) * (sigma ** 2 + 1.0) ** 0.5) / (sigma) - - #rescalecfg - x_cfg = uncond + cond_scale * (cond - uncond) - ro_pos = torch.std(cond, dim=(1,2,3), keepdim=True) - ro_cfg = torch.std(x_cfg, dim=(1,2,3), keepdim=True) - - x_rescaled = x_cfg * (ro_pos / ro_cfg) - x_final = multiplier * x_rescaled + (1.0 - multiplier) * x_cfg - - return x_orig - (x - x_final * sigma / (sigma * sigma + 1.0) ** 0.5) - - m = model.clone() - m.set_model_sampler_cfg_function(rescale_cfg) - return (m, ) - -class ModelComputeDtype: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "dtype": (["default", "fp32", "fp16", "bf16"],), - }} - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "advanced/debug/model" - - def patch(self, model, dtype): - m = model.clone() - m.set_model_compute_dtype(node_helpers.string_to_torch_dtype(dtype)) - return (m, ) - - -NODE_CLASS_MAPPINGS = { - "ModelSamplingDiscrete": ModelSamplingDiscrete, - "ModelSamplingContinuousEDM": ModelSamplingContinuousEDM, - "ModelSamplingContinuousV": ModelSamplingContinuousV, - "ModelSamplingStableCascade": ModelSamplingStableCascade, - "ModelSamplingSD3": ModelSamplingSD3, - "ModelSamplingAuraFlow": ModelSamplingAuraFlow, - "ModelSamplingFlux": ModelSamplingFlux, - "RescaleCFG": RescaleCFG, - "ModelComputeDtype": ModelComputeDtype, -} diff --git a/comfy_extras/nodes_model_downscale.py b/comfy_extras/nodes_model_downscale.py deleted file mode 100644 index 49420dee9260ced4e0d08c196be937354c5e1d4e..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_model_downscale.py +++ /dev/null @@ -1,53 +0,0 @@ -import comfy.utils - -class PatchModelAddDownscale: - upscale_methods = ["bicubic", "nearest-exact", "bilinear", "area", "bislerp"] - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "block_number": ("INT", {"default": 3, "min": 1, "max": 32, "step": 1}), - "downscale_factor": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 9.0, "step": 0.001}), - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_percent": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.001}), - "downscale_after_skip": ("BOOLEAN", {"default": True}), - "downscale_method": (s.upscale_methods,), - "upscale_method": (s.upscale_methods,), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "model_patches/unet" - - def patch(self, model, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method): - model_sampling = model.get_model_object("model_sampling") - sigma_start = model_sampling.percent_to_sigma(start_percent) - sigma_end = model_sampling.percent_to_sigma(end_percent) - - def input_block_patch(h, transformer_options): - if transformer_options["block"][1] == block_number: - sigma = transformer_options["sigmas"][0].item() - if sigma <= sigma_start and sigma >= sigma_end: - h = comfy.utils.common_upscale(h, round(h.shape[-1] * (1.0 / downscale_factor)), round(h.shape[-2] * (1.0 / downscale_factor)), downscale_method, "disabled") - return h - - def output_block_patch(h, hsp, transformer_options): - if h.shape[2] != hsp.shape[2]: - h = comfy.utils.common_upscale(h, hsp.shape[-1], hsp.shape[-2], upscale_method, "disabled") - return h, hsp - - m = model.clone() - if downscale_after_skip: - m.set_model_input_block_patch_after_skip(input_block_patch) - else: - m.set_model_input_block_patch(input_block_patch) - m.set_model_output_block_patch(output_block_patch) - return (m, ) - -NODE_CLASS_MAPPINGS = { - "PatchModelAddDownscale": PatchModelAddDownscale, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - # Sampling - "PatchModelAddDownscale": "PatchModelAddDownscale (Kohya Deep Shrink)", -} diff --git a/comfy_extras/nodes_model_merging.py b/comfy_extras/nodes_model_merging.py deleted file mode 100644 index f20beab7d480391cbd4deb2f855a43c18d19ae33..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_model_merging.py +++ /dev/null @@ -1,374 +0,0 @@ -import comfy.sd -import comfy.utils -import comfy.model_base -import comfy.model_management -import comfy.model_sampling - -import torch -import folder_paths -import json -import os - -from comfy.cli_args import args - -class ModelMergeSimple: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model1": ("MODEL",), - "model2": ("MODEL",), - "ratio": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "merge" - - CATEGORY = "advanced/model_merging" - - def merge(self, model1, model2, ratio): - m = model1.clone() - kp = model2.get_key_patches("diffusion_model.") - for k in kp: - m.add_patches({k: kp[k]}, 1.0 - ratio, ratio) - return (m, ) - -class ModelSubtract: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model1": ("MODEL",), - "model2": ("MODEL",), - "multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "merge" - - CATEGORY = "advanced/model_merging" - - def merge(self, model1, model2, multiplier): - m = model1.clone() - kp = model2.get_key_patches("diffusion_model.") - for k in kp: - m.add_patches({k: kp[k]}, - multiplier, multiplier) - return (m, ) - -class ModelAdd: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model1": ("MODEL",), - "model2": ("MODEL",), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "merge" - - CATEGORY = "advanced/model_merging" - - def merge(self, model1, model2): - m = model1.clone() - kp = model2.get_key_patches("diffusion_model.") - for k in kp: - m.add_patches({k: kp[k]}, 1.0, 1.0) - return (m, ) - - -class CLIPMergeSimple: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip1": ("CLIP",), - "clip2": ("CLIP",), - "ratio": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - }} - RETURN_TYPES = ("CLIP",) - FUNCTION = "merge" - - CATEGORY = "advanced/model_merging" - - def merge(self, clip1, clip2, ratio): - m = clip1.clone() - kp = clip2.get_key_patches() - for k in kp: - if k.endswith(".position_ids") or k.endswith(".logit_scale"): - continue - m.add_patches({k: kp[k]}, 1.0 - ratio, ratio) - return (m, ) - - -class CLIPSubtract: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip1": ("CLIP",), - "clip2": ("CLIP",), - "multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("CLIP",) - FUNCTION = "merge" - - CATEGORY = "advanced/model_merging" - - def merge(self, clip1, clip2, multiplier): - m = clip1.clone() - kp = clip2.get_key_patches() - for k in kp: - if k.endswith(".position_ids") or k.endswith(".logit_scale"): - continue - m.add_patches({k: kp[k]}, - multiplier, multiplier) - return (m, ) - - -class CLIPAdd: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip1": ("CLIP",), - "clip2": ("CLIP",), - }} - RETURN_TYPES = ("CLIP",) - FUNCTION = "merge" - - CATEGORY = "advanced/model_merging" - - def merge(self, clip1, clip2): - m = clip1.clone() - kp = clip2.get_key_patches() - for k in kp: - if k.endswith(".position_ids") or k.endswith(".logit_scale"): - continue - m.add_patches({k: kp[k]}, 1.0, 1.0) - return (m, ) - - -class ModelMergeBlocks: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model1": ("MODEL",), - "model2": ("MODEL",), - "input": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "middle": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "merge" - - CATEGORY = "advanced/model_merging" - - def merge(self, model1, model2, **kwargs): - m = model1.clone() - kp = model2.get_key_patches("diffusion_model.") - default_ratio = next(iter(kwargs.values())) - - for k in kp: - ratio = default_ratio - k_unet = k[len("diffusion_model."):] - - last_arg_size = 0 - for arg in kwargs: - if k_unet.startswith(arg) and last_arg_size < len(arg): - ratio = kwargs[arg] - last_arg_size = len(arg) - - m.add_patches({k: kp[k]}, 1.0 - ratio, ratio) - return (m, ) - -def save_checkpoint(model, clip=None, vae=None, clip_vision=None, filename_prefix=None, output_dir=None, prompt=None, extra_pnginfo=None): - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, output_dir) - prompt_info = "" - if prompt is not None: - prompt_info = json.dumps(prompt) - - metadata = {} - - enable_modelspec = True - if isinstance(model.model, comfy.model_base.SDXL): - if isinstance(model.model, comfy.model_base.SDXL_instructpix2pix): - metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-edit" - else: - metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-base" - elif isinstance(model.model, comfy.model_base.SDXLRefiner): - metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-refiner" - elif isinstance(model.model, comfy.model_base.SVD_img2vid): - metadata["modelspec.architecture"] = "stable-video-diffusion-img2vid-v1" - elif isinstance(model.model, comfy.model_base.SD3): - metadata["modelspec.architecture"] = "stable-diffusion-v3-medium" #TODO: other SD3 variants - else: - enable_modelspec = False - - if enable_modelspec: - metadata["modelspec.sai_model_spec"] = "1.0.0" - metadata["modelspec.implementation"] = "sgm" - metadata["modelspec.title"] = "{} {}".format(filename, counter) - - #TODO: - # "stable-diffusion-v1", "stable-diffusion-v1-inpainting", "stable-diffusion-v2-512", - # "stable-diffusion-v2-768-v", "stable-diffusion-v2-unclip-l", "stable-diffusion-v2-unclip-h", - # "v2-inpainting" - - extra_keys = {} - model_sampling = model.get_model_object("model_sampling") - if isinstance(model_sampling, comfy.model_sampling.ModelSamplingContinuousEDM): - if isinstance(model_sampling, comfy.model_sampling.V_PREDICTION): - extra_keys["edm_vpred.sigma_max"] = torch.tensor(model_sampling.sigma_max).float() - extra_keys["edm_vpred.sigma_min"] = torch.tensor(model_sampling.sigma_min).float() - - if model.model.model_type == comfy.model_base.ModelType.EPS: - metadata["modelspec.predict_key"] = "epsilon" - elif model.model.model_type == comfy.model_base.ModelType.V_PREDICTION: - metadata["modelspec.predict_key"] = "v" - extra_keys["v_pred"] = torch.tensor([]) - if getattr(model_sampling, "zsnr", False): - extra_keys["ztsnr"] = torch.tensor([]) - - if not args.disable_metadata: - metadata["prompt"] = prompt_info - if extra_pnginfo is not None: - for x in extra_pnginfo: - metadata[x] = json.dumps(extra_pnginfo[x]) - - output_checkpoint = f"{filename}_{counter:05}_.safetensors" - output_checkpoint = os.path.join(full_output_folder, output_checkpoint) - - comfy.sd.save_checkpoint(output_checkpoint, model, clip, vae, clip_vision, metadata=metadata, extra_keys=extra_keys) - -class CheckpointSave: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "clip": ("CLIP",), - "vae": ("VAE",), - "filename_prefix": ("STRING", {"default": "checkpoints/ComfyUI"}),}, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},} - RETURN_TYPES = () - FUNCTION = "save" - OUTPUT_NODE = True - - CATEGORY = "advanced/model_merging" - - def save(self, model, clip, vae, filename_prefix, prompt=None, extra_pnginfo=None): - save_checkpoint(model, clip=clip, vae=vae, filename_prefix=filename_prefix, output_dir=self.output_dir, prompt=prompt, extra_pnginfo=extra_pnginfo) - return {} - -class CLIPSave: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip": ("CLIP",), - "filename_prefix": ("STRING", {"default": "clip/ComfyUI"}),}, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},} - RETURN_TYPES = () - FUNCTION = "save" - OUTPUT_NODE = True - - CATEGORY = "advanced/model_merging" - - def save(self, clip, filename_prefix, prompt=None, extra_pnginfo=None): - prompt_info = "" - if prompt is not None: - prompt_info = json.dumps(prompt) - - metadata = {} - if not args.disable_metadata: - metadata["format"] = "pt" - metadata["prompt"] = prompt_info - if extra_pnginfo is not None: - for x in extra_pnginfo: - metadata[x] = json.dumps(extra_pnginfo[x]) - - comfy.model_management.load_models_gpu([clip.load_model()], force_patch_weights=True) - clip_sd = clip.get_sd() - - for prefix in ["clip_l.", "clip_g.", "clip_h.", "t5xxl.", "pile_t5xl.", "mt5xl.", "umt5xxl.", "t5base.", "gemma2_2b.", "llama.", "hydit_clip.", ""]: - k = list(filter(lambda a: a.startswith(prefix), clip_sd.keys())) - current_clip_sd = {} - for x in k: - current_clip_sd[x] = clip_sd.pop(x) - if len(current_clip_sd) == 0: - continue - - p = prefix[:-1] - replace_prefix = {} - filename_prefix_ = filename_prefix - if len(p) > 0: - filename_prefix_ = "{}_{}".format(filename_prefix_, p) - replace_prefix[prefix] = "" - replace_prefix["transformer."] = "" - - full_output_folder, filename, counter, subfolder, filename_prefix_ = folder_paths.get_save_image_path(filename_prefix_, self.output_dir) - - output_checkpoint = f"{filename}_{counter:05}_.safetensors" - output_checkpoint = os.path.join(full_output_folder, output_checkpoint) - - current_clip_sd = comfy.utils.state_dict_prefix_replace(current_clip_sd, replace_prefix) - - comfy.utils.save_torch_file(current_clip_sd, output_checkpoint, metadata=metadata) - return {} - -class VAESave: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - - @classmethod - def INPUT_TYPES(s): - return {"required": { "vae": ("VAE",), - "filename_prefix": ("STRING", {"default": "vae/ComfyUI_vae"}),}, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},} - RETURN_TYPES = () - FUNCTION = "save" - OUTPUT_NODE = True - - CATEGORY = "advanced/model_merging" - - def save(self, vae, filename_prefix, prompt=None, extra_pnginfo=None): - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir) - prompt_info = "" - if prompt is not None: - prompt_info = json.dumps(prompt) - - metadata = {} - if not args.disable_metadata: - metadata["prompt"] = prompt_info - if extra_pnginfo is not None: - for x in extra_pnginfo: - metadata[x] = json.dumps(extra_pnginfo[x]) - - output_checkpoint = f"{filename}_{counter:05}_.safetensors" - output_checkpoint = os.path.join(full_output_folder, output_checkpoint) - - comfy.utils.save_torch_file(vae.get_sd(), output_checkpoint, metadata=metadata) - return {} - -class ModelSave: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "filename_prefix": ("STRING", {"default": "diffusion_models/ComfyUI"}),}, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},} - RETURN_TYPES = () - FUNCTION = "save" - OUTPUT_NODE = True - - CATEGORY = "advanced/model_merging" - - def save(self, model, filename_prefix, prompt=None, extra_pnginfo=None): - save_checkpoint(model, filename_prefix=filename_prefix, output_dir=self.output_dir, prompt=prompt, extra_pnginfo=extra_pnginfo) - return {} - -NODE_CLASS_MAPPINGS = { - "ModelMergeSimple": ModelMergeSimple, - "ModelMergeBlocks": ModelMergeBlocks, - "ModelMergeSubtract": ModelSubtract, - "ModelMergeAdd": ModelAdd, - "CheckpointSave": CheckpointSave, - "CLIPMergeSimple": CLIPMergeSimple, - "CLIPMergeSubtract": CLIPSubtract, - "CLIPMergeAdd": CLIPAdd, - "CLIPSave": CLIPSave, - "VAESave": VAESave, - "ModelSave": ModelSave, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "CheckpointSave": "Save Checkpoint", -} diff --git a/comfy_extras/nodes_model_merging_model_specific.py b/comfy_extras/nodes_model_merging_model_specific.py deleted file mode 100644 index 2c93cd84f8ac459234b8491b1892677a4464a855..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_model_merging_model_specific.py +++ /dev/null @@ -1,332 +0,0 @@ -import comfy_extras.nodes_model_merging - -class ModelMergeSD1(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["time_embed."] = argument - arg_dict["label_emb."] = argument - - for i in range(12): - arg_dict["input_blocks.{}.".format(i)] = argument - - for i in range(3): - arg_dict["middle_block.{}.".format(i)] = argument - - for i in range(12): - arg_dict["output_blocks.{}.".format(i)] = argument - - arg_dict["out."] = argument - - return {"required": arg_dict} - - -class ModelMergeSDXL(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["time_embed."] = argument - arg_dict["label_emb."] = argument - - for i in range(9): - arg_dict["input_blocks.{}".format(i)] = argument - - for i in range(3): - arg_dict["middle_block.{}".format(i)] = argument - - for i in range(9): - arg_dict["output_blocks.{}".format(i)] = argument - - arg_dict["out."] = argument - - return {"required": arg_dict} - -class ModelMergeSD3_2B(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["pos_embed."] = argument - arg_dict["x_embedder."] = argument - arg_dict["context_embedder."] = argument - arg_dict["y_embedder."] = argument - arg_dict["t_embedder."] = argument - - for i in range(24): - arg_dict["joint_blocks.{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - - -class ModelMergeAuraflow(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["init_x_linear."] = argument - arg_dict["positional_encoding"] = argument - arg_dict["cond_seq_linear."] = argument - arg_dict["register_tokens"] = argument - arg_dict["t_embedder."] = argument - - for i in range(4): - arg_dict["double_layers.{}.".format(i)] = argument - - for i in range(32): - arg_dict["single_layers.{}.".format(i)] = argument - - arg_dict["modF."] = argument - arg_dict["final_linear."] = argument - - return {"required": arg_dict} - -class ModelMergeFlux1(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["img_in."] = argument - arg_dict["time_in."] = argument - arg_dict["guidance_in"] = argument - arg_dict["vector_in."] = argument - arg_dict["txt_in."] = argument - - for i in range(19): - arg_dict["double_blocks.{}.".format(i)] = argument - - for i in range(38): - arg_dict["single_blocks.{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - -class ModelMergeSD35_Large(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["pos_embed."] = argument - arg_dict["x_embedder."] = argument - arg_dict["context_embedder."] = argument - arg_dict["y_embedder."] = argument - arg_dict["t_embedder."] = argument - - for i in range(38): - arg_dict["joint_blocks.{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - -class ModelMergeMochiPreview(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["pos_frequencies."] = argument - arg_dict["t_embedder."] = argument - arg_dict["t5_y_embedder."] = argument - arg_dict["t5_yproj."] = argument - - for i in range(48): - arg_dict["blocks.{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - -class ModelMergeLTXV(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["patchify_proj."] = argument - arg_dict["adaln_single."] = argument - arg_dict["caption_projection."] = argument - - for i in range(28): - arg_dict["transformer_blocks.{}.".format(i)] = argument - - arg_dict["scale_shift_table"] = argument - arg_dict["proj_out."] = argument - - return {"required": arg_dict} - -class ModelMergeCosmos7B(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["pos_embedder."] = argument - arg_dict["extra_pos_embedder."] = argument - arg_dict["x_embedder."] = argument - arg_dict["t_embedder."] = argument - arg_dict["affline_norm."] = argument - - - for i in range(28): - arg_dict["blocks.block{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - -class ModelMergeCosmos14B(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["pos_embedder."] = argument - arg_dict["extra_pos_embedder."] = argument - arg_dict["x_embedder."] = argument - arg_dict["t_embedder."] = argument - arg_dict["affline_norm."] = argument - - - for i in range(36): - arg_dict["blocks.block{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - -class ModelMergeWAN2_1(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - DESCRIPTION = "1.3B model has 30 blocks, 14B model has 40 blocks. Image to video model has the extra img_emb." - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["patch_embedding."] = argument - arg_dict["time_embedding."] = argument - arg_dict["time_projection."] = argument - arg_dict["text_embedding."] = argument - arg_dict["img_emb."] = argument - - for i in range(40): - arg_dict["blocks.{}.".format(i)] = argument - - arg_dict["head."] = argument - - return {"required": arg_dict} - -class ModelMergeCosmosPredict2_2B(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["pos_embedder."] = argument - arg_dict["x_embedder."] = argument - arg_dict["t_embedder."] = argument - arg_dict["t_embedding_norm."] = argument - - - for i in range(28): - arg_dict["blocks.{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - -class ModelMergeCosmosPredict2_14B(comfy_extras.nodes_model_merging.ModelMergeBlocks): - CATEGORY = "advanced/model_merging/model_specific" - - @classmethod - def INPUT_TYPES(s): - arg_dict = { "model1": ("MODEL",), - "model2": ("MODEL",)} - - argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}) - - arg_dict["pos_embedder."] = argument - arg_dict["x_embedder."] = argument - arg_dict["t_embedder."] = argument - arg_dict["t_embedding_norm."] = argument - - - for i in range(36): - arg_dict["blocks.{}.".format(i)] = argument - - arg_dict["final_layer."] = argument - - return {"required": arg_dict} - -NODE_CLASS_MAPPINGS = { - "ModelMergeSD1": ModelMergeSD1, - "ModelMergeSD2": ModelMergeSD1, #SD1 and SD2 have the same blocks - "ModelMergeSDXL": ModelMergeSDXL, - "ModelMergeSD3_2B": ModelMergeSD3_2B, - "ModelMergeAuraflow": ModelMergeAuraflow, - "ModelMergeFlux1": ModelMergeFlux1, - "ModelMergeSD35_Large": ModelMergeSD35_Large, - "ModelMergeMochiPreview": ModelMergeMochiPreview, - "ModelMergeLTXV": ModelMergeLTXV, - "ModelMergeCosmos7B": ModelMergeCosmos7B, - "ModelMergeCosmos14B": ModelMergeCosmos14B, - "ModelMergeWAN2_1": ModelMergeWAN2_1, - "ModelMergeCosmosPredict2_2B": ModelMergeCosmosPredict2_2B, - "ModelMergeCosmosPredict2_14B": ModelMergeCosmosPredict2_14B, -} diff --git a/comfy_extras/nodes_morphology.py b/comfy_extras/nodes_morphology.py deleted file mode 100644 index 075b26c4024bccb510b9052f40f51eb88382ab30..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_morphology.py +++ /dev/null @@ -1,87 +0,0 @@ -import torch -import comfy.model_management - -from kornia.morphology import dilation, erosion, opening, closing, gradient, top_hat, bottom_hat -import kornia.color - - -class Morphology: - @classmethod - def INPUT_TYPES(s): - return {"required": {"image": ("IMAGE",), - "operation": (["erode", "dilate", "open", "close", "gradient", "bottom_hat", "top_hat"],), - "kernel_size": ("INT", {"default": 3, "min": 3, "max": 999, "step": 1}), - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "process" - - CATEGORY = "image/postprocessing" - - def process(self, image, operation, kernel_size): - device = comfy.model_management.get_torch_device() - kernel = torch.ones(kernel_size, kernel_size, device=device) - image_k = image.to(device).movedim(-1, 1) - if operation == "erode": - output = erosion(image_k, kernel) - elif operation == "dilate": - output = dilation(image_k, kernel) - elif operation == "open": - output = opening(image_k, kernel) - elif operation == "close": - output = closing(image_k, kernel) - elif operation == "gradient": - output = gradient(image_k, kernel) - elif operation == "top_hat": - output = top_hat(image_k, kernel) - elif operation == "bottom_hat": - output = bottom_hat(image_k, kernel) - else: - raise ValueError(f"Invalid operation {operation} for morphology. Must be one of 'erode', 'dilate', 'open', 'close', 'gradient', 'tophat', 'bottomhat'") - img_out = output.to(comfy.model_management.intermediate_device()).movedim(1, -1) - return (img_out,) - - -class ImageRGBToYUV: - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": ("IMAGE",), - }} - - RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE") - RETURN_NAMES = ("Y", "U", "V") - FUNCTION = "execute" - - CATEGORY = "image/batch" - - def execute(self, image): - out = kornia.color.rgb_to_ycbcr(image.movedim(-1, 1)).movedim(1, -1) - return (out[..., 0:1].expand_as(image), out[..., 1:2].expand_as(image), out[..., 2:3].expand_as(image)) - -class ImageYUVToRGB: - @classmethod - def INPUT_TYPES(s): - return {"required": {"Y": ("IMAGE",), - "U": ("IMAGE",), - "V": ("IMAGE",), - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "execute" - - CATEGORY = "image/batch" - - def execute(self, Y, U, V): - image = torch.cat([torch.mean(Y, dim=-1, keepdim=True), torch.mean(U, dim=-1, keepdim=True), torch.mean(V, dim=-1, keepdim=True)], dim=-1) - out = kornia.color.ycbcr_to_rgb(image.movedim(-1, 1)).movedim(1, -1) - return (out,) - -NODE_CLASS_MAPPINGS = { - "Morphology": Morphology, - "ImageRGBToYUV": ImageRGBToYUV, - "ImageYUVToRGB": ImageYUVToRGB, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "Morphology": "ImageMorphology", -} diff --git a/comfy_extras/nodes_optimalsteps.py b/comfy_extras/nodes_optimalsteps.py deleted file mode 100644 index e7c851ca211c923f48506fd01f5a643f60dfaf62..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_optimalsteps.py +++ /dev/null @@ -1,57 +0,0 @@ -# from https://github.com/bebebe666/OptimalSteps - - -import numpy as np -import torch - -def loglinear_interp(t_steps, num_steps): - """ - Performs log-linear interpolation of a given array of decreasing numbers. - """ - xs = np.linspace(0, 1, len(t_steps)) - ys = np.log(t_steps[::-1]) - - new_xs = np.linspace(0, 1, num_steps) - new_ys = np.interp(new_xs, xs, ys) - - interped_ys = np.exp(new_ys)[::-1].copy() - return interped_ys - - -NOISE_LEVELS = {"FLUX": [0.9968, 0.9886, 0.9819, 0.975, 0.966, 0.9471, 0.9158, 0.8287, 0.5512, 0.2808, 0.001], -"Wan":[1.0, 0.997, 0.995, 0.993, 0.991, 0.989, 0.987, 0.985, 0.98, 0.975, 0.973, 0.968, 0.96, 0.946, 0.927, 0.902, 0.864, 0.776, 0.539, 0.208, 0.001], -"Chroma": [0.992, 0.99, 0.988, 0.985, 0.982, 0.978, 0.973, 0.968, 0.961, 0.953, 0.943, 0.931, 0.917, 0.9, 0.881, 0.858, 0.832, 0.802, 0.769, 0.731, 0.69, 0.646, 0.599, 0.55, 0.501, 0.451, 0.402, 0.355, 0.311, 0.27, 0.232, 0.199, 0.169, 0.143, 0.12, 0.101, 0.084, 0.07, 0.058, 0.048, 0.001], -} - -class OptimalStepsScheduler: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model_type": (["FLUX", "Wan", "Chroma"], ), - "steps": ("INT", {"default": 20, "min": 3, "max": 1000}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - RETURN_TYPES = ("SIGMAS",) - CATEGORY = "sampling/custom_sampling/schedulers" - - FUNCTION = "get_sigmas" - - def get_sigmas(self, model_type, steps, denoise): - total_steps = steps - if denoise < 1.0: - if denoise <= 0.0: - return (torch.FloatTensor([]),) - total_steps = round(steps * denoise) - - sigmas = NOISE_LEVELS[model_type][:] - if (steps + 1) != len(sigmas): - sigmas = loglinear_interp(sigmas, steps + 1) - - sigmas = sigmas[-(total_steps + 1):] - sigmas[-1] = 0 - return (torch.FloatTensor(sigmas), ) - -NODE_CLASS_MAPPINGS = { - "OptimalStepsScheduler": OptimalStepsScheduler, -} diff --git a/comfy_extras/nodes_pag.py b/comfy_extras/nodes_pag.py deleted file mode 100644 index eb28196f41c56fd45fda051a42d0814b96558fb8..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_pag.py +++ /dev/null @@ -1,56 +0,0 @@ -#Modified/simplified version of the node from: https://github.com/pamparamm/sd-perturbed-attention -#If you want the one with more options see the above repo. - -#My modified one here is more basic but has less chances of breaking with ComfyUI updates. - -import comfy.model_patcher -import comfy.samplers - -class PerturbedAttentionGuidance: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": 0.01}), - } - } - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "model_patches/unet" - - def patch(self, model, scale): - unet_block = "middle" - unet_block_id = 0 - m = model.clone() - - def perturbed_attention(q, k, v, extra_options, mask=None): - return v - - def post_cfg_function(args): - model = args["model"] - cond_pred = args["cond_denoised"] - cond = args["cond"] - cfg_result = args["denoised"] - sigma = args["sigma"] - model_options = args["model_options"].copy() - x = args["input"] - - if scale == 0: - return cfg_result - - # Replace Self-attention with PAG - model_options = comfy.model_patcher.set_model_options_patch_replace(model_options, perturbed_attention, "attn1", unet_block, unet_block_id) - (pag,) = comfy.samplers.calc_cond_batch(model, [cond], x, sigma, model_options) - - return cfg_result + (cond_pred - pag) * scale - - m.set_model_sampler_post_cfg_function(post_cfg_function) - - return (m,) - -NODE_CLASS_MAPPINGS = { - "PerturbedAttentionGuidance": PerturbedAttentionGuidance, -} diff --git a/comfy_extras/nodes_perpneg.py b/comfy_extras/nodes_perpneg.py deleted file mode 100644 index 89e5eef905ac5a417b850da7c2f5693915117ec5..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_perpneg.py +++ /dev/null @@ -1,146 +0,0 @@ -import torch -import comfy.model_management -import comfy.sampler_helpers -import comfy.samplers -import comfy.utils -import node_helpers -import math - -def perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_nocond, neg_scale, cond_scale): - pos = noise_pred_pos - noise_pred_nocond - neg = noise_pred_neg - noise_pred_nocond - - perp = neg - ((torch.mul(neg, pos).sum())/(torch.norm(pos)**2)) * pos - perp_neg = perp * neg_scale - cfg_result = noise_pred_nocond + cond_scale*(pos - perp_neg) - return cfg_result - -#TODO: This node should be removed, it has been replaced with PerpNegGuider -class PerpNeg: - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL", ), - "empty_conditioning": ("CONDITIONING", ), - "neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "_for_testing" - DEPRECATED = True - - def patch(self, model, empty_conditioning, neg_scale): - m = model.clone() - nocond = comfy.sampler_helpers.convert_cond(empty_conditioning) - - def cfg_function(args): - model = args["model"] - noise_pred_pos = args["cond_denoised"] - noise_pred_neg = args["uncond_denoised"] - cond_scale = args["cond_scale"] - x = args["input"] - sigma = args["sigma"] - model_options = args["model_options"] - nocond_processed = comfy.samplers.encode_model_conds(model.extra_conds, nocond, x, x.device, "negative") - - (noise_pred_nocond,) = comfy.samplers.calc_cond_batch(model, [nocond_processed], x, sigma, model_options) - - cfg_result = x - perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_nocond, neg_scale, cond_scale) - return cfg_result - - m.set_model_sampler_cfg_function(cfg_function) - - return (m, ) - - -class Guider_PerpNeg(comfy.samplers.CFGGuider): - def set_conds(self, positive, negative, empty_negative_prompt): - empty_negative_prompt = node_helpers.conditioning_set_values(empty_negative_prompt, {"prompt_type": "negative"}) - self.inner_set_conds({"positive": positive, "empty_negative_prompt": empty_negative_prompt, "negative": negative}) - - def set_cfg(self, cfg, neg_scale): - self.cfg = cfg - self.neg_scale = neg_scale - - def predict_noise(self, x, timestep, model_options={}, seed=None): - # in CFGGuider.predict_noise, we call sampling_function(), which uses cfg_function() to compute pos & neg - # but we'd rather do a single batch of sampling pos, neg, and empty, so we call calc_cond_batch([pos,neg,empty]) directly - - positive_cond = self.conds.get("positive", None) - negative_cond = self.conds.get("negative", None) - empty_cond = self.conds.get("empty_negative_prompt", None) - - if model_options.get("disable_cfg1_optimization", False) == False: - if math.isclose(self.neg_scale, 0.0): - negative_cond = None - if math.isclose(self.cfg, 1.0): - empty_cond = None - - conds = [positive_cond, negative_cond, empty_cond] - - out = comfy.samplers.calc_cond_batch(self.inner_model, conds, x, timestep, model_options) - - # Apply pre_cfg_functions since sampling_function() is skipped - for fn in model_options.get("sampler_pre_cfg_function", []): - args = {"conds":conds, "conds_out": out, "cond_scale": self.cfg, "timestep": timestep, - "input": x, "sigma": timestep, "model": self.inner_model, "model_options": model_options} - out = fn(args) - - noise_pred_pos, noise_pred_neg, noise_pred_empty = out - cfg_result = perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_empty, self.neg_scale, self.cfg) - - # normally this would be done in cfg_function, but we skipped - # that for efficiency: we can compute the noise predictions in - # a single call to calc_cond_batch() (rather than two) - # so we replicate the hook here - for fn in model_options.get("sampler_post_cfg_function", []): - args = { - "denoised": cfg_result, - "cond": positive_cond, - "uncond": negative_cond, - "cond_scale": self.cfg, - "model": self.inner_model, - "uncond_denoised": noise_pred_neg, - "cond_denoised": noise_pred_pos, - "sigma": timestep, - "model_options": model_options, - "input": x, - # not in the original call in samplers.py:cfg_function, but made available for future hooks - "empty_cond": empty_cond, - "empty_cond_denoised": noise_pred_empty,} - cfg_result = fn(args) - - return cfg_result - -class PerpNegGuider: - @classmethod - def INPUT_TYPES(s): - return {"required": - {"model": ("MODEL",), - "positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "empty_conditioning": ("CONDITIONING", ), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}), - "neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), - } - } - - RETURN_TYPES = ("GUIDER",) - - FUNCTION = "get_guider" - CATEGORY = "_for_testing" - - def get_guider(self, model, positive, negative, empty_conditioning, cfg, neg_scale): - guider = Guider_PerpNeg(model) - guider.set_conds(positive, negative, empty_conditioning) - guider.set_cfg(cfg, neg_scale) - return (guider,) - -NODE_CLASS_MAPPINGS = { - "PerpNeg": PerpNeg, - "PerpNegGuider": PerpNegGuider, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "PerpNeg": "Perp-Neg (DEPRECATED by PerpNegGuider)", -} diff --git a/comfy_extras/nodes_photomaker.py b/comfy_extras/nodes_photomaker.py deleted file mode 100644 index d358ed6d5b75a37f9195e1f0c663a188eb8aedc3..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_photomaker.py +++ /dev/null @@ -1,188 +0,0 @@ -import torch -import torch.nn as nn -import folder_paths -import comfy.clip_model -import comfy.clip_vision -import comfy.ops - -# code for model from: https://github.com/TencentARC/PhotoMaker/blob/main/photomaker/model.py under Apache License Version 2.0 -VISION_CONFIG_DICT = { - "hidden_size": 1024, - "image_size": 224, - "intermediate_size": 4096, - "num_attention_heads": 16, - "num_channels": 3, - "num_hidden_layers": 24, - "patch_size": 14, - "projection_dim": 768, - "hidden_act": "quick_gelu", - "model_type": "clip_vision_model", -} - -class MLP(nn.Module): - def __init__(self, in_dim, out_dim, hidden_dim, use_residual=True, operations=comfy.ops): - super().__init__() - if use_residual: - assert in_dim == out_dim - self.layernorm = operations.LayerNorm(in_dim) - self.fc1 = operations.Linear(in_dim, hidden_dim) - self.fc2 = operations.Linear(hidden_dim, out_dim) - self.use_residual = use_residual - self.act_fn = nn.GELU() - - def forward(self, x): - residual = x - x = self.layernorm(x) - x = self.fc1(x) - x = self.act_fn(x) - x = self.fc2(x) - if self.use_residual: - x = x + residual - return x - - -class FuseModule(nn.Module): - def __init__(self, embed_dim, operations): - super().__init__() - self.mlp1 = MLP(embed_dim * 2, embed_dim, embed_dim, use_residual=False, operations=operations) - self.mlp2 = MLP(embed_dim, embed_dim, embed_dim, use_residual=True, operations=operations) - self.layer_norm = operations.LayerNorm(embed_dim) - - def fuse_fn(self, prompt_embeds, id_embeds): - stacked_id_embeds = torch.cat([prompt_embeds, id_embeds], dim=-1) - stacked_id_embeds = self.mlp1(stacked_id_embeds) + prompt_embeds - stacked_id_embeds = self.mlp2(stacked_id_embeds) - stacked_id_embeds = self.layer_norm(stacked_id_embeds) - return stacked_id_embeds - - def forward( - self, - prompt_embeds, - id_embeds, - class_tokens_mask, - ) -> torch.Tensor: - # id_embeds shape: [b, max_num_inputs, 1, 2048] - id_embeds = id_embeds.to(prompt_embeds.dtype) - num_inputs = class_tokens_mask.sum().unsqueeze(0) # TODO: check for training case - batch_size, max_num_inputs = id_embeds.shape[:2] - # seq_length: 77 - seq_length = prompt_embeds.shape[1] - # flat_id_embeds shape: [b*max_num_inputs, 1, 2048] - flat_id_embeds = id_embeds.view( - -1, id_embeds.shape[-2], id_embeds.shape[-1] - ) - # valid_id_mask [b*max_num_inputs] - valid_id_mask = ( - torch.arange(max_num_inputs, device=flat_id_embeds.device)[None, :] - < num_inputs[:, None] - ) - valid_id_embeds = flat_id_embeds[valid_id_mask.flatten()] - - prompt_embeds = prompt_embeds.view(-1, prompt_embeds.shape[-1]) - class_tokens_mask = class_tokens_mask.view(-1) - valid_id_embeds = valid_id_embeds.view(-1, valid_id_embeds.shape[-1]) - # slice out the image token embeddings - image_token_embeds = prompt_embeds[class_tokens_mask] - stacked_id_embeds = self.fuse_fn(image_token_embeds, valid_id_embeds) - assert class_tokens_mask.sum() == stacked_id_embeds.shape[0], f"{class_tokens_mask.sum()} != {stacked_id_embeds.shape[0]}" - prompt_embeds.masked_scatter_(class_tokens_mask[:, None], stacked_id_embeds.to(prompt_embeds.dtype)) - updated_prompt_embeds = prompt_embeds.view(batch_size, seq_length, -1) - return updated_prompt_embeds - -class PhotoMakerIDEncoder(comfy.clip_model.CLIPVisionModelProjection): - def __init__(self): - self.load_device = comfy.model_management.text_encoder_device() - offload_device = comfy.model_management.text_encoder_offload_device() - dtype = comfy.model_management.text_encoder_dtype(self.load_device) - - super().__init__(VISION_CONFIG_DICT, dtype, offload_device, comfy.ops.manual_cast) - self.visual_projection_2 = comfy.ops.manual_cast.Linear(1024, 1280, bias=False) - self.fuse_module = FuseModule(2048, comfy.ops.manual_cast) - - def forward(self, id_pixel_values, prompt_embeds, class_tokens_mask): - b, num_inputs, c, h, w = id_pixel_values.shape - id_pixel_values = id_pixel_values.view(b * num_inputs, c, h, w) - - shared_id_embeds = self.vision_model(id_pixel_values)[2] - id_embeds = self.visual_projection(shared_id_embeds) - id_embeds_2 = self.visual_projection_2(shared_id_embeds) - - id_embeds = id_embeds.view(b, num_inputs, 1, -1) - id_embeds_2 = id_embeds_2.view(b, num_inputs, 1, -1) - - id_embeds = torch.cat((id_embeds, id_embeds_2), dim=-1) - updated_prompt_embeds = self.fuse_module(prompt_embeds, id_embeds, class_tokens_mask) - - return updated_prompt_embeds - - -class PhotoMakerLoader: - @classmethod - def INPUT_TYPES(s): - return {"required": { "photomaker_model_name": (folder_paths.get_filename_list("photomaker"), )}} - - RETURN_TYPES = ("PHOTOMAKER",) - FUNCTION = "load_photomaker_model" - - CATEGORY = "_for_testing/photomaker" - - def load_photomaker_model(self, photomaker_model_name): - photomaker_model_path = folder_paths.get_full_path_or_raise("photomaker", photomaker_model_name) - photomaker_model = PhotoMakerIDEncoder() - data = comfy.utils.load_torch_file(photomaker_model_path, safe_load=True) - if "id_encoder" in data: - data = data["id_encoder"] - photomaker_model.load_state_dict(data) - return (photomaker_model,) - - -class PhotoMakerEncode: - @classmethod - def INPUT_TYPES(s): - return {"required": { "photomaker": ("PHOTOMAKER",), - "image": ("IMAGE",), - "clip": ("CLIP", ), - "text": ("STRING", {"multiline": True, "dynamicPrompts": True, "default": "photograph of photomaker"}), - }} - - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "apply_photomaker" - - CATEGORY = "_for_testing/photomaker" - - def apply_photomaker(self, photomaker, image, clip, text): - special_token = "photomaker" - pixel_values = comfy.clip_vision.clip_preprocess(image.to(photomaker.load_device)).float() - try: - index = text.split(" ").index(special_token) + 1 - except ValueError: - index = -1 - tokens = clip.tokenize(text, return_word_ids=True) - out_tokens = {} - for k in tokens: - out_tokens[k] = [] - for t in tokens[k]: - f = list(filter(lambda x: x[2] != index, t)) - while len(f) < len(t): - f.append(t[-1]) - out_tokens[k].append(f) - - cond, pooled = clip.encode_from_tokens(out_tokens, return_pooled=True) - - if index > 0: - token_index = index - 1 - num_id_images = 1 - class_tokens_mask = [True if token_index <= i < token_index+num_id_images else False for i in range(77)] - out = photomaker(id_pixel_values=pixel_values.unsqueeze(0), prompt_embeds=cond.to(photomaker.load_device), - class_tokens_mask=torch.tensor(class_tokens_mask, dtype=torch.bool, device=photomaker.load_device).unsqueeze(0)) - else: - out = cond - - return ([[out, {"pooled_output": pooled}]], ) - - -NODE_CLASS_MAPPINGS = { - "PhotoMakerLoader": PhotoMakerLoader, - "PhotoMakerEncode": PhotoMakerEncode, -} - diff --git a/comfy_extras/nodes_pixart.py b/comfy_extras/nodes_pixart.py deleted file mode 100644 index 8d9276afe4b12c51c818a879cce1cd5453895552..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_pixart.py +++ /dev/null @@ -1,24 +0,0 @@ -from nodes import MAX_RESOLUTION - -class CLIPTextEncodePixArtAlpha: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), - # "aspect_ratio": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", ), - }} - - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - CATEGORY = "advanced/conditioning" - DESCRIPTION = "Encodes text and sets the resolution conditioning for PixArt Alpha. Does not apply to PixArt Sigma." - - def encode(self, clip, width, height, text): - tokens = clip.tokenize(text) - return (clip.encode_from_tokens_scheduled(tokens, add_dict={"width": width, "height": height}),) - -NODE_CLASS_MAPPINGS = { - "CLIPTextEncodePixArtAlpha": CLIPTextEncodePixArtAlpha, -} diff --git a/comfy_extras/nodes_post_processing.py b/comfy_extras/nodes_post_processing.py deleted file mode 100644 index cb1a0d88303eef19fff34ce2b19611cfcb162e91..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_post_processing.py +++ /dev/null @@ -1,281 +0,0 @@ -import numpy as np -import torch -import torch.nn.functional as F -from PIL import Image -import math - -import comfy.utils -import comfy.model_management -import node_helpers - -class Blend: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image1": ("IMAGE",), - "image2": ("IMAGE",), - "blend_factor": ("FLOAT", { - "default": 0.5, - "min": 0.0, - "max": 1.0, - "step": 0.01 - }), - "blend_mode": (["normal", "multiply", "screen", "overlay", "soft_light", "difference"],), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "blend_images" - - CATEGORY = "image/postprocessing" - - def blend_images(self, image1: torch.Tensor, image2: torch.Tensor, blend_factor: float, blend_mode: str): - image1, image2 = node_helpers.image_alpha_fix(image1, image2) - image2 = image2.to(image1.device) - if image1.shape != image2.shape: - image2 = image2.permute(0, 3, 1, 2) - image2 = comfy.utils.common_upscale(image2, image1.shape[2], image1.shape[1], upscale_method='bicubic', crop='center') - image2 = image2.permute(0, 2, 3, 1) - - blended_image = self.blend_mode(image1, image2, blend_mode) - blended_image = image1 * (1 - blend_factor) + blended_image * blend_factor - blended_image = torch.clamp(blended_image, 0, 1) - return (blended_image,) - - def blend_mode(self, img1, img2, mode): - if mode == "normal": - return img2 - elif mode == "multiply": - return img1 * img2 - elif mode == "screen": - return 1 - (1 - img1) * (1 - img2) - elif mode == "overlay": - return torch.where(img1 <= 0.5, 2 * img1 * img2, 1 - 2 * (1 - img1) * (1 - img2)) - elif mode == "soft_light": - return torch.where(img2 <= 0.5, img1 - (1 - 2 * img2) * img1 * (1 - img1), img1 + (2 * img2 - 1) * (self.g(img1) - img1)) - elif mode == "difference": - return img1 - img2 - else: - raise ValueError(f"Unsupported blend mode: {mode}") - - def g(self, x): - return torch.where(x <= 0.25, ((16 * x - 12) * x + 4) * x, torch.sqrt(x)) - -def gaussian_kernel(kernel_size: int, sigma: float, device=None): - x, y = torch.meshgrid(torch.linspace(-1, 1, kernel_size, device=device), torch.linspace(-1, 1, kernel_size, device=device), indexing="ij") - d = torch.sqrt(x * x + y * y) - g = torch.exp(-(d * d) / (2.0 * sigma * sigma)) - return g / g.sum() - -class Blur: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "blur_radius": ("INT", { - "default": 1, - "min": 1, - "max": 31, - "step": 1 - }), - "sigma": ("FLOAT", { - "default": 1.0, - "min": 0.1, - "max": 10.0, - "step": 0.1 - }), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "blur" - - CATEGORY = "image/postprocessing" - - def blur(self, image: torch.Tensor, blur_radius: int, sigma: float): - if blur_radius == 0: - return (image,) - - image = image.to(comfy.model_management.get_torch_device()) - batch_size, height, width, channels = image.shape - - kernel_size = blur_radius * 2 + 1 - kernel = gaussian_kernel(kernel_size, sigma, device=image.device).repeat(channels, 1, 1).unsqueeze(1) - - image = image.permute(0, 3, 1, 2) # Torch wants (B, C, H, W) we use (B, H, W, C) - padded_image = F.pad(image, (blur_radius,blur_radius,blur_radius,blur_radius), 'reflect') - blurred = F.conv2d(padded_image, kernel, padding=kernel_size // 2, groups=channels)[:,:,blur_radius:-blur_radius, blur_radius:-blur_radius] - blurred = blurred.permute(0, 2, 3, 1) - - return (blurred.to(comfy.model_management.intermediate_device()),) - -class Quantize: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "colors": ("INT", { - "default": 256, - "min": 1, - "max": 256, - "step": 1 - }), - "dither": (["none", "floyd-steinberg", "bayer-2", "bayer-4", "bayer-8", "bayer-16"],), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "quantize" - - CATEGORY = "image/postprocessing" - - @staticmethod - def bayer(im, pal_im, order): - def normalized_bayer_matrix(n): - if n == 0: - return np.zeros((1,1), "float32") - else: - q = 4 ** n - m = q * normalized_bayer_matrix(n - 1) - return np.bmat(((m-1.5, m+0.5), (m+1.5, m-0.5))) / q - - num_colors = len(pal_im.getpalette()) // 3 - spread = 2 * 256 / num_colors - bayer_n = int(math.log2(order)) - bayer_matrix = torch.from_numpy(spread * normalized_bayer_matrix(bayer_n) + 0.5) - - result = torch.from_numpy(np.array(im).astype(np.float32)) - tw = math.ceil(result.shape[0] / bayer_matrix.shape[0]) - th = math.ceil(result.shape[1] / bayer_matrix.shape[1]) - tiled_matrix = bayer_matrix.tile(tw, th).unsqueeze(-1) - result.add_(tiled_matrix[:result.shape[0],:result.shape[1]]).clamp_(0, 255) - result = result.to(dtype=torch.uint8) - - im = Image.fromarray(result.cpu().numpy()) - im = im.quantize(palette=pal_im, dither=Image.Dither.NONE) - return im - - def quantize(self, image: torch.Tensor, colors: int, dither: str): - batch_size, height, width, _ = image.shape - result = torch.zeros_like(image) - - for b in range(batch_size): - im = Image.fromarray((image[b] * 255).to(torch.uint8).numpy(), mode='RGB') - - pal_im = im.quantize(colors=colors) # Required as described in https://github.com/python-pillow/Pillow/issues/5836 - - if dither == "none": - quantized_image = im.quantize(palette=pal_im, dither=Image.Dither.NONE) - elif dither == "floyd-steinberg": - quantized_image = im.quantize(palette=pal_im, dither=Image.Dither.FLOYDSTEINBERG) - elif dither.startswith("bayer"): - order = int(dither.split('-')[-1]) - quantized_image = Quantize.bayer(im, pal_im, order) - - quantized_array = torch.tensor(np.array(quantized_image.convert("RGB"))).float() / 255 - result[b] = quantized_array - - return (result,) - -class Sharpen: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "sharpen_radius": ("INT", { - "default": 1, - "min": 1, - "max": 31, - "step": 1 - }), - "sigma": ("FLOAT", { - "default": 1.0, - "min": 0.1, - "max": 10.0, - "step": 0.01 - }), - "alpha": ("FLOAT", { - "default": 1.0, - "min": 0.0, - "max": 5.0, - "step": 0.01 - }), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "sharpen" - - CATEGORY = "image/postprocessing" - - def sharpen(self, image: torch.Tensor, sharpen_radius: int, sigma:float, alpha: float): - if sharpen_radius == 0: - return (image,) - - batch_size, height, width, channels = image.shape - image = image.to(comfy.model_management.get_torch_device()) - - kernel_size = sharpen_radius * 2 + 1 - kernel = gaussian_kernel(kernel_size, sigma, device=image.device) * -(alpha*10) - center = kernel_size // 2 - kernel[center, center] = kernel[center, center] - kernel.sum() + 1.0 - kernel = kernel.repeat(channels, 1, 1).unsqueeze(1) - - tensor_image = image.permute(0, 3, 1, 2) # Torch wants (B, C, H, W) we use (B, H, W, C) - tensor_image = F.pad(tensor_image, (sharpen_radius,sharpen_radius,sharpen_radius,sharpen_radius), 'reflect') - sharpened = F.conv2d(tensor_image, kernel, padding=center, groups=channels)[:,:,sharpen_radius:-sharpen_radius, sharpen_radius:-sharpen_radius] - sharpened = sharpened.permute(0, 2, 3, 1) - - result = torch.clamp(sharpened, 0, 1) - - return (result.to(comfy.model_management.intermediate_device()),) - -class ImageScaleToTotalPixels: - upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"] - crop_methods = ["disabled", "center"] - - @classmethod - def INPUT_TYPES(s): - return {"required": { "image": ("IMAGE",), "upscale_method": (s.upscale_methods,), - "megapixels": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 16.0, "step": 0.01}), - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "upscale" - - CATEGORY = "image/upscaling" - - def upscale(self, image, upscale_method, megapixels): - samples = image.movedim(-1,1) - total = int(megapixels * 1024 * 1024) - - scale_by = math.sqrt(total / (samples.shape[3] * samples.shape[2])) - width = round(samples.shape[3] * scale_by) - height = round(samples.shape[2] * scale_by) - - s = comfy.utils.common_upscale(samples, width, height, upscale_method, "disabled") - s = s.movedim(1,-1) - return (s,) - -NODE_CLASS_MAPPINGS = { - "ImageBlend": Blend, - "ImageBlur": Blur, - "ImageQuantize": Quantize, - "ImageSharpen": Sharpen, - "ImageScaleToTotalPixels": ImageScaleToTotalPixels, -} diff --git a/comfy_extras/nodes_preview_any.py b/comfy_extras/nodes_preview_any.py deleted file mode 100644 index e6805696f302e785f0ea4afe4ca82aa2eeca0495..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_preview_any.py +++ /dev/null @@ -1,43 +0,0 @@ -import json -from comfy.comfy_types.node_typing import IO - -# Preview Any - original implement from -# https://github.com/rgthree/rgthree-comfy/blob/main/py/display_any.py -# upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes -class PreviewAny(): - @classmethod - def INPUT_TYPES(cls): - return { - "required": {"source": (IO.ANY, {})}, - } - - RETURN_TYPES = () - FUNCTION = "main" - OUTPUT_NODE = True - - CATEGORY = "utils" - - def main(self, source=None): - value = 'None' - if isinstance(source, str): - value = source - elif isinstance(source, (int, float, bool)): - value = str(source) - elif source is not None: - try: - value = json.dumps(source) - except Exception: - try: - value = str(source) - except Exception: - value = 'source exists, but could not be serialized.' - - return {"ui": {"text": (value,)}} - -NODE_CLASS_MAPPINGS = { - "PreviewAny": PreviewAny, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "PreviewAny": "Preview Any", -} diff --git a/comfy_extras/nodes_primitive.py b/comfy_extras/nodes_primitive.py deleted file mode 100644 index 1f93f87a79531b3650981a651d355466db518ede..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_primitive.py +++ /dev/null @@ -1,98 +0,0 @@ -# Primitive nodes that are evaluated at backend. -from __future__ import annotations - -import sys - -from comfy.comfy_types.node_typing import ComfyNodeABC, InputTypeDict, IO - - -class String(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": {"value": (IO.STRING, {})}, - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/primitive" - - def execute(self, value: str) -> tuple[str]: - return (value,) - - -class StringMultiline(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": {"value": (IO.STRING, {"multiline": True,},)}, - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/primitive" - - def execute(self, value: str) -> tuple[str]: - return (value,) - - -class Int(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": {"value": (IO.INT, {"min": -sys.maxsize, "max": sys.maxsize, "control_after_generate": True})}, - } - - RETURN_TYPES = (IO.INT,) - FUNCTION = "execute" - CATEGORY = "utils/primitive" - - def execute(self, value: int) -> tuple[int]: - return (value,) - - -class Float(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": {"value": (IO.FLOAT, {"min": -sys.maxsize, "max": sys.maxsize})}, - } - - RETURN_TYPES = (IO.FLOAT,) - FUNCTION = "execute" - CATEGORY = "utils/primitive" - - def execute(self, value: float) -> tuple[float]: - return (value,) - - -class Boolean(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": {"value": (IO.BOOLEAN, {})}, - } - - RETURN_TYPES = (IO.BOOLEAN,) - FUNCTION = "execute" - CATEGORY = "utils/primitive" - - def execute(self, value: bool) -> tuple[bool]: - return (value,) - - -NODE_CLASS_MAPPINGS = { - "PrimitiveString": String, - "PrimitiveStringMultiline": StringMultiline, - "PrimitiveInt": Int, - "PrimitiveFloat": Float, - "PrimitiveBoolean": Boolean, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "PrimitiveString": "String", - "PrimitiveStringMultiline": "String (Multiline)", - "PrimitiveInt": "Int", - "PrimitiveFloat": "Float", - "PrimitiveBoolean": "Boolean", -} diff --git a/comfy_extras/nodes_rebatch.py b/comfy_extras/nodes_rebatch.py deleted file mode 100644 index e29cb9ed10dae329ac2bea313df72f2387bd7ade..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_rebatch.py +++ /dev/null @@ -1,138 +0,0 @@ -import torch - -class LatentRebatch: - @classmethod - def INPUT_TYPES(s): - return {"required": { "latents": ("LATENT",), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }} - RETURN_TYPES = ("LATENT",) - INPUT_IS_LIST = True - OUTPUT_IS_LIST = (True, ) - - FUNCTION = "rebatch" - - CATEGORY = "latent/batch" - - @staticmethod - def get_batch(latents, list_ind, offset): - '''prepare a batch out of the list of latents''' - samples = latents[list_ind]['samples'] - shape = samples.shape - mask = latents[list_ind]['noise_mask'] if 'noise_mask' in latents[list_ind] else torch.ones((shape[0], 1, shape[2]*8, shape[3]*8), device='cpu') - if mask.shape[-1] != shape[-1] * 8 or mask.shape[-2] != shape[-2]: - torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(shape[-2]*8, shape[-1]*8), mode="bilinear") - if mask.shape[0] < samples.shape[0]: - mask = mask.repeat((shape[0] - 1) // mask.shape[0] + 1, 1, 1, 1)[:shape[0]] - if 'batch_index' in latents[list_ind]: - batch_inds = latents[list_ind]['batch_index'] - else: - batch_inds = [x+offset for x in range(shape[0])] - return samples, mask, batch_inds - - @staticmethod - def get_slices(indexable, num, batch_size): - '''divides an indexable object into num slices of length batch_size, and a remainder''' - slices = [] - for i in range(num): - slices.append(indexable[i*batch_size:(i+1)*batch_size]) - if num * batch_size < len(indexable): - return slices, indexable[num * batch_size:] - else: - return slices, None - - @staticmethod - def slice_batch(batch, num, batch_size): - result = [LatentRebatch.get_slices(x, num, batch_size) for x in batch] - return list(zip(*result)) - - @staticmethod - def cat_batch(batch1, batch2): - if batch1[0] is None: - return batch2 - result = [torch.cat((b1, b2)) if torch.is_tensor(b1) else b1 + b2 for b1, b2 in zip(batch1, batch2)] - return result - - def rebatch(self, latents, batch_size): - batch_size = batch_size[0] - - output_list = [] - current_batch = (None, None, None) - processed = 0 - - for i in range(len(latents)): - # fetch new entry of list - #samples, masks, indices = self.get_batch(latents, i) - next_batch = self.get_batch(latents, i, processed) - processed += len(next_batch[2]) - # set to current if current is None - if current_batch[0] is None: - current_batch = next_batch - # add previous to list if dimensions do not match - elif next_batch[0].shape[-1] != current_batch[0].shape[-1] or next_batch[0].shape[-2] != current_batch[0].shape[-2]: - sliced, _ = self.slice_batch(current_batch, 1, batch_size) - output_list.append({'samples': sliced[0][0], 'noise_mask': sliced[1][0], 'batch_index': sliced[2][0]}) - current_batch = next_batch - # cat if everything checks out - else: - current_batch = self.cat_batch(current_batch, next_batch) - - # add to list if dimensions gone above target batch size - if current_batch[0].shape[0] > batch_size: - num = current_batch[0].shape[0] // batch_size - sliced, remainder = self.slice_batch(current_batch, num, batch_size) - - for i in range(num): - output_list.append({'samples': sliced[0][i], 'noise_mask': sliced[1][i], 'batch_index': sliced[2][i]}) - - current_batch = remainder - - #add remainder - if current_batch[0] is not None: - sliced, _ = self.slice_batch(current_batch, 1, batch_size) - output_list.append({'samples': sliced[0][0], 'noise_mask': sliced[1][0], 'batch_index': sliced[2][0]}) - - #get rid of empty masks - for s in output_list: - if s['noise_mask'].mean() == 1.0: - del s['noise_mask'] - - return (output_list,) - -class ImageRebatch: - @classmethod - def INPUT_TYPES(s): - return {"required": { "images": ("IMAGE",), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }} - RETURN_TYPES = ("IMAGE",) - INPUT_IS_LIST = True - OUTPUT_IS_LIST = (True, ) - - FUNCTION = "rebatch" - - CATEGORY = "image/batch" - - def rebatch(self, images, batch_size): - batch_size = batch_size[0] - - output_list = [] - all_images = [] - for img in images: - for i in range(img.shape[0]): - all_images.append(img[i:i+1]) - - for i in range(0, len(all_images), batch_size): - output_list.append(torch.cat(all_images[i:i+batch_size], dim=0)) - - return (output_list,) - -NODE_CLASS_MAPPINGS = { - "RebatchLatents": LatentRebatch, - "RebatchImages": ImageRebatch, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "RebatchLatents": "Rebatch Latents", - "RebatchImages": "Rebatch Images", -} diff --git a/comfy_extras/nodes_sag.py b/comfy_extras/nodes_sag.py deleted file mode 100644 index 1bd8d7364fdde4bd8ce9b11f342dfce71d3a4214..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_sag.py +++ /dev/null @@ -1,181 +0,0 @@ -import torch -from torch import einsum -import torch.nn.functional as F -import math - -from einops import rearrange, repeat -from comfy.ldm.modules.attention import optimized_attention -import comfy.samplers - -# from comfy/ldm/modules/attention.py -# but modified to return attention scores as well as output -def attention_basic_with_sim(q, k, v, heads, mask=None, attn_precision=None): - b, _, dim_head = q.shape - dim_head //= heads - scale = dim_head ** -0.5 - - h = heads - q, k, v = map( - lambda t: t.unsqueeze(3) - .reshape(b, -1, heads, dim_head) - .permute(0, 2, 1, 3) - .reshape(b * heads, -1, dim_head) - .contiguous(), - (q, k, v), - ) - - # force cast to fp32 to avoid overflowing - if attn_precision == torch.float32: - sim = einsum('b i d, b j d -> b i j', q.float(), k.float()) * scale - else: - sim = einsum('b i d, b j d -> b i j', q, k) * scale - - del q, k - - if mask is not None: - mask = rearrange(mask, 'b ... -> b (...)') - max_neg_value = -torch.finfo(sim.dtype).max - mask = repeat(mask, 'b j -> (b h) () j', h=h) - sim.masked_fill_(~mask, max_neg_value) - - # attention, what we cannot get enough of - sim = sim.softmax(dim=-1) - - out = einsum('b i j, b j d -> b i d', sim.to(v.dtype), v) - out = ( - out.unsqueeze(0) - .reshape(b, heads, -1, dim_head) - .permute(0, 2, 1, 3) - .reshape(b, -1, heads * dim_head) - ) - return (out, sim) - -def create_blur_map(x0, attn, sigma=3.0, threshold=1.0): - # reshape and GAP the attention map - _, hw1, hw2 = attn.shape - b, _, lh, lw = x0.shape - attn = attn.reshape(b, -1, hw1, hw2) - # Global Average Pool - mask = attn.mean(1, keepdim=False).sum(1, keepdim=False) > threshold - - total = mask.shape[-1] - x = round(math.sqrt((lh / lw) * total)) - xx = None - for i in range(0, math.floor(math.sqrt(total) / 2)): - for j in [(x + i), max(1, x - i)]: - if total % j == 0: - xx = j - break - if xx is not None: - break - - x = xx - y = total // x - - # Reshape - mask = ( - mask.reshape(b, x, y) - .unsqueeze(1) - .type(attn.dtype) - ) - # Upsample - mask = F.interpolate(mask, (lh, lw)) - - blurred = gaussian_blur_2d(x0, kernel_size=9, sigma=sigma) - blurred = blurred * mask + x0 * (1 - mask) - return blurred - -def gaussian_blur_2d(img, kernel_size, sigma): - ksize_half = (kernel_size - 1) * 0.5 - - x = torch.linspace(-ksize_half, ksize_half, steps=kernel_size) - - pdf = torch.exp(-0.5 * (x / sigma).pow(2)) - - x_kernel = pdf / pdf.sum() - x_kernel = x_kernel.to(device=img.device, dtype=img.dtype) - - kernel2d = torch.mm(x_kernel[:, None], x_kernel[None, :]) - kernel2d = kernel2d.expand(img.shape[-3], 1, kernel2d.shape[0], kernel2d.shape[1]) - - padding = [kernel_size // 2, kernel_size // 2, kernel_size // 2, kernel_size // 2] - - img = F.pad(img, padding, mode="reflect") - img = F.conv2d(img, kernel2d, groups=img.shape[-3]) - return img - -class SelfAttentionGuidance: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "scale": ("FLOAT", {"default": 0.5, "min": -2.0, "max": 5.0, "step": 0.01}), - "blur_sigma": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 10.0, "step": 0.1}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "_for_testing" - - def patch(self, model, scale, blur_sigma): - m = model.clone() - - attn_scores = None - - # TODO: make this work properly with chunked batches - # currently, we can only save the attn from one UNet call - def attn_and_record(q, k, v, extra_options): - nonlocal attn_scores - # if uncond, save the attention scores - heads = extra_options["n_heads"] - cond_or_uncond = extra_options["cond_or_uncond"] - b = q.shape[0] // len(cond_or_uncond) - if 1 in cond_or_uncond: - uncond_index = cond_or_uncond.index(1) - # do the entire attention operation, but save the attention scores to attn_scores - (out, sim) = attention_basic_with_sim(q, k, v, heads=heads, attn_precision=extra_options["attn_precision"]) - # when using a higher batch size, I BELIEVE the result batch dimension is [uc1, ... ucn, c1, ... cn] - n_slices = heads * b - attn_scores = sim[n_slices * uncond_index:n_slices * (uncond_index+1)] - return out - else: - return optimized_attention(q, k, v, heads=heads, attn_precision=extra_options["attn_precision"]) - - def post_cfg_function(args): - nonlocal attn_scores - uncond_attn = attn_scores - - sag_scale = scale - sag_sigma = blur_sigma - sag_threshold = 1.0 - model = args["model"] - uncond_pred = args["uncond_denoised"] - uncond = args["uncond"] - cfg_result = args["denoised"] - sigma = args["sigma"] - model_options = args["model_options"] - x = args["input"] - if min(cfg_result.shape[2:]) <= 4: #skip when too small to add padding - return cfg_result - - # create the adversarially blurred image - degraded = create_blur_map(uncond_pred, uncond_attn, sag_sigma, sag_threshold) - degraded_noised = degraded + x - uncond_pred - # call into the UNet - (sag,) = comfy.samplers.calc_cond_batch(model, [uncond], degraded_noised, sigma, model_options) - return cfg_result + (degraded - sag) * sag_scale - - m.set_model_sampler_post_cfg_function(post_cfg_function, disable_cfg1_optimization=True) - - # from diffusers: - # unet.mid_block.attentions[0].transformer_blocks[0].attn1.patch - m.set_model_attn1_replace(attn_and_record, "middle", 0, 0) - - return (m, ) - -NODE_CLASS_MAPPINGS = { - "SelfAttentionGuidance": SelfAttentionGuidance, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "SelfAttentionGuidance": "Self-Attention Guidance", -} diff --git a/comfy_extras/nodes_sd3.py b/comfy_extras/nodes_sd3.py deleted file mode 100644 index d75b29e606feaf1e0eb125bd63f4abe2bc03b2f5..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_sd3.py +++ /dev/null @@ -1,138 +0,0 @@ -import folder_paths -import comfy.sd -import comfy.model_management -import nodes -import torch -import comfy_extras.nodes_slg - - -class TripleCLIPLoader: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip_name1": (folder_paths.get_filename_list("text_encoders"), ), "clip_name2": (folder_paths.get_filename_list("text_encoders"), ), "clip_name3": (folder_paths.get_filename_list("text_encoders"), ) - }} - RETURN_TYPES = ("CLIP",) - FUNCTION = "load_clip" - - CATEGORY = "advanced/loaders" - - DESCRIPTION = "[Recipes]\n\nsd3: clip-l, clip-g, t5" - - def load_clip(self, clip_name1, clip_name2, clip_name3): - clip_path1 = folder_paths.get_full_path_or_raise("text_encoders", clip_name1) - clip_path2 = folder_paths.get_full_path_or_raise("text_encoders", clip_name2) - clip_path3 = folder_paths.get_full_path_or_raise("text_encoders", clip_name3) - clip = comfy.sd.load_clip(ckpt_paths=[clip_path1, clip_path2, clip_path3], embedding_directory=folder_paths.get_folder_paths("embeddings")) - return (clip,) - - -class EmptySD3LatentImage: - def __init__(self): - self.device = comfy.model_management.intermediate_device() - - @classmethod - def INPUT_TYPES(s): - return {"required": { "width": ("INT", {"default": 1024, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 1024, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "generate" - - CATEGORY = "latent/sd3" - - def generate(self, width, height, batch_size=1): - latent = torch.zeros([batch_size, 16, height // 8, width // 8], device=self.device) - return ({"samples":latent}, ) - - -class CLIPTextEncodeSD3: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "clip": ("CLIP", ), - "clip_l": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "clip_g": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "t5xxl": ("STRING", {"multiline": True, "dynamicPrompts": True}), - "empty_padding": (["none", "empty_prompt"], ) - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "encode" - - CATEGORY = "advanced/conditioning" - - def encode(self, clip, clip_l, clip_g, t5xxl, empty_padding): - no_padding = empty_padding == "none" - - tokens = clip.tokenize(clip_g) - if len(clip_g) == 0 and no_padding: - tokens["g"] = [] - - if len(clip_l) == 0 and no_padding: - tokens["l"] = [] - else: - tokens["l"] = clip.tokenize(clip_l)["l"] - - if len(t5xxl) == 0 and no_padding: - tokens["t5xxl"] = [] - else: - tokens["t5xxl"] = clip.tokenize(t5xxl)["t5xxl"] - if len(tokens["l"]) != len(tokens["g"]): - empty = clip.tokenize("") - while len(tokens["l"]) < len(tokens["g"]): - tokens["l"] += empty["l"] - while len(tokens["l"]) > len(tokens["g"]): - tokens["g"] += empty["g"] - return (clip.encode_from_tokens_scheduled(tokens), ) - - -class ControlNetApplySD3(nodes.ControlNetApplyAdvanced): - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "control_net": ("CONTROL_NET", ), - "vae": ("VAE", ), - "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}) - }} - CATEGORY = "conditioning/controlnet" - DEPRECATED = True - - -class SkipLayerGuidanceSD3(comfy_extras.nodes_slg.SkipLayerGuidanceDiT): - ''' - Enhance guidance towards detailed dtructure by having another set of CFG negative with skipped layers. - Inspired by Perturbed Attention Guidance (https://arxiv.org/abs/2403.17377) - Experimental implementation by Dango233@StabilityAI. - ''' - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL", ), - "layers": ("STRING", {"default": "7, 8, 9", "multiline": False}), - "scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 10.0, "step": 0.1}), - "start_percent": ("FLOAT", {"default": 0.01, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_percent": ("FLOAT", {"default": 0.15, "min": 0.0, "max": 1.0, "step": 0.001}) - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "skip_guidance_sd3" - - CATEGORY = "advanced/guidance" - - def skip_guidance_sd3(self, model, layers, scale, start_percent, end_percent): - return self.skip_guidance(model=model, scale=scale, start_percent=start_percent, end_percent=end_percent, double_layers=layers) - - -NODE_CLASS_MAPPINGS = { - "TripleCLIPLoader": TripleCLIPLoader, - "EmptySD3LatentImage": EmptySD3LatentImage, - "CLIPTextEncodeSD3": CLIPTextEncodeSD3, - "ControlNetApplySD3": ControlNetApplySD3, - "SkipLayerGuidanceSD3": SkipLayerGuidanceSD3, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - # Sampling - "ControlNetApplySD3": "Apply Controlnet with VAE", -} diff --git a/comfy_extras/nodes_sdupscale.py b/comfy_extras/nodes_sdupscale.py deleted file mode 100644 index bba67e8ddff8064a90ec0f8e71e953ca4e56c4c6..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_sdupscale.py +++ /dev/null @@ -1,46 +0,0 @@ -import torch -import comfy.utils - -class SD_4XUpscale_Conditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": { "images": ("IMAGE",), - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "scale_ratio": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "noise_augmentation": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - }} - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - FUNCTION = "encode" - - CATEGORY = "conditioning/upscale_diffusion" - - def encode(self, images, positive, negative, scale_ratio, noise_augmentation): - width = max(1, round(images.shape[-2] * scale_ratio)) - height = max(1, round(images.shape[-3] * scale_ratio)) - - pixels = comfy.utils.common_upscale((images.movedim(-1,1) * 2.0) - 1.0, width // 4, height // 4, "bilinear", "center") - - out_cp = [] - out_cn = [] - - for t in positive: - n = [t[0], t[1].copy()] - n[1]['concat_image'] = pixels - n[1]['noise_augmentation'] = noise_augmentation - out_cp.append(n) - - for t in negative: - n = [t[0], t[1].copy()] - n[1]['concat_image'] = pixels - n[1]['noise_augmentation'] = noise_augmentation - out_cn.append(n) - - latent = torch.zeros([images.shape[0], 4, height // 4, width // 4]) - return (out_cp, out_cn, {"samples":latent}) - -NODE_CLASS_MAPPINGS = { - "SD_4XUpscale_Conditioning": SD_4XUpscale_Conditioning, -} diff --git a/comfy_extras/nodes_slg.py b/comfy_extras/nodes_slg.py deleted file mode 100644 index 7adff202eb2fcea8b8d3b57e51e24725859d551c..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_slg.py +++ /dev/null @@ -1,152 +0,0 @@ -import comfy.model_patcher -import comfy.samplers -import re - - -class SkipLayerGuidanceDiT: - ''' - Enhance guidance towards detailed dtructure by having another set of CFG negative with skipped layers. - Inspired by Perturbed Attention Guidance (https://arxiv.org/abs/2403.17377) - Original experimental implementation for SD3 by Dango233@StabilityAI. - ''' - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL", ), - "double_layers": ("STRING", {"default": "7, 8, 9", "multiline": False}), - "single_layers": ("STRING", {"default": "7, 8, 9", "multiline": False}), - "scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 10.0, "step": 0.1}), - "start_percent": ("FLOAT", {"default": 0.01, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_percent": ("FLOAT", {"default": 0.15, "min": 0.0, "max": 1.0, "step": 0.001}), - "rescaling_scale": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "skip_guidance" - EXPERIMENTAL = True - - DESCRIPTION = "Generic version of SkipLayerGuidance node that can be used on every DiT model." - - CATEGORY = "advanced/guidance" - - def skip_guidance(self, model, scale, start_percent, end_percent, double_layers="", single_layers="", rescaling_scale=0): - # check if layer is comma separated integers - def skip(args, extra_args): - return args - - model_sampling = model.get_model_object("model_sampling") - sigma_start = model_sampling.percent_to_sigma(start_percent) - sigma_end = model_sampling.percent_to_sigma(end_percent) - - double_layers = re.findall(r'\d+', double_layers) - double_layers = [int(i) for i in double_layers] - - single_layers = re.findall(r'\d+', single_layers) - single_layers = [int(i) for i in single_layers] - - if len(double_layers) == 0 and len(single_layers) == 0: - return (model, ) - - def post_cfg_function(args): - model = args["model"] - cond_pred = args["cond_denoised"] - cond = args["cond"] - cfg_result = args["denoised"] - sigma = args["sigma"] - x = args["input"] - model_options = args["model_options"].copy() - - for layer in double_layers: - model_options = comfy.model_patcher.set_model_options_patch_replace(model_options, skip, "dit", "double_block", layer) - - for layer in single_layers: - model_options = comfy.model_patcher.set_model_options_patch_replace(model_options, skip, "dit", "single_block", layer) - - model_sampling.percent_to_sigma(start_percent) - - sigma_ = sigma[0].item() - if scale > 0 and sigma_ >= sigma_end and sigma_ <= sigma_start: - (slg,) = comfy.samplers.calc_cond_batch(model, [cond], x, sigma, model_options) - cfg_result = cfg_result + (cond_pred - slg) * scale - if rescaling_scale != 0: - factor = cond_pred.std() / cfg_result.std() - factor = rescaling_scale * factor + (1 - rescaling_scale) - cfg_result *= factor - - return cfg_result - - m = model.clone() - m.set_model_sampler_post_cfg_function(post_cfg_function) - - return (m, ) - -class SkipLayerGuidanceDiTSimple: - ''' - Simple version of the SkipLayerGuidanceDiT node that only modifies the uncond pass. - ''' - @classmethod - def INPUT_TYPES(s): - return {"required": {"model": ("MODEL", ), - "double_layers": ("STRING", {"default": "7, 8, 9", "multiline": False}), - "single_layers": ("STRING", {"default": "7, 8, 9", "multiline": False}), - "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}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "skip_guidance" - EXPERIMENTAL = True - - DESCRIPTION = "Simple version of the SkipLayerGuidanceDiT node that only modifies the uncond pass." - - CATEGORY = "advanced/guidance" - - def skip_guidance(self, model, start_percent, end_percent, double_layers="", single_layers=""): - def skip(args, extra_args): - return args - - model_sampling = model.get_model_object("model_sampling") - sigma_start = model_sampling.percent_to_sigma(start_percent) - sigma_end = model_sampling.percent_to_sigma(end_percent) - - double_layers = re.findall(r'\d+', double_layers) - double_layers = [int(i) for i in double_layers] - - single_layers = re.findall(r'\d+', single_layers) - single_layers = [int(i) for i in single_layers] - - if len(double_layers) == 0 and len(single_layers) == 0: - return (model, ) - - def calc_cond_batch_function(args): - x = args["input"] - model = args["model"] - conds = args["conds"] - sigma = args["sigma"] - - model_options = args["model_options"] - slg_model_options = model_options.copy() - - for layer in double_layers: - slg_model_options = comfy.model_patcher.set_model_options_patch_replace(slg_model_options, skip, "dit", "double_block", layer) - - for layer in single_layers: - slg_model_options = comfy.model_patcher.set_model_options_patch_replace(slg_model_options, skip, "dit", "single_block", layer) - - cond, uncond = conds - sigma_ = sigma[0].item() - if sigma_ >= sigma_end and sigma_ <= sigma_start and uncond is not None: - cond_out, _ = comfy.samplers.calc_cond_batch(model, [cond, None], x, sigma, model_options) - _, uncond_out = comfy.samplers.calc_cond_batch(model, [None, uncond], x, sigma, slg_model_options) - out = [cond_out, uncond_out] - else: - out = comfy.samplers.calc_cond_batch(model, conds, x, sigma, model_options) - - return out - - m = model.clone() - m.set_model_sampler_calc_cond_batch_function(calc_cond_batch_function) - - return (m, ) - -NODE_CLASS_MAPPINGS = { - "SkipLayerGuidanceDiT": SkipLayerGuidanceDiT, - "SkipLayerGuidanceDiTSimple": SkipLayerGuidanceDiTSimple, -} diff --git a/comfy_extras/nodes_stable3d.py b/comfy_extras/nodes_stable3d.py deleted file mode 100644 index be2e34c28f49f160a21703c313305193ed00546f..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_stable3d.py +++ /dev/null @@ -1,143 +0,0 @@ -import torch -import nodes -import comfy.utils - -def camera_embeddings(elevation, azimuth): - elevation = torch.as_tensor([elevation]) - azimuth = torch.as_tensor([azimuth]) - embeddings = torch.stack( - [ - torch.deg2rad( - (90 - elevation) - (90) - ), # Zero123 polar is 90-elevation - torch.sin(torch.deg2rad(azimuth)), - torch.cos(torch.deg2rad(azimuth)), - torch.deg2rad( - 90 - torch.full_like(elevation, 0) - ), - ], dim=-1).unsqueeze(1) - - return embeddings - - -class StableZero123_Conditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip_vision": ("CLIP_VISION",), - "init_image": ("IMAGE",), - "vae": ("VAE",), - "width": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - "elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}), - "azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}), - }} - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - FUNCTION = "encode" - - CATEGORY = "conditioning/3d_models" - - def encode(self, clip_vision, init_image, vae, width, height, batch_size, elevation, azimuth): - output = clip_vision.encode_image(init_image) - pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) - encode_pixels = pixels[:,:,:,:3] - t = vae.encode(encode_pixels) - cam_embeds = camera_embeddings(elevation, azimuth) - cond = torch.cat([pooled, cam_embeds.to(pooled.device).repeat((pooled.shape[0], 1, 1))], dim=-1) - - positive = [[cond, {"concat_latent_image": t}]] - negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t)}]] - latent = torch.zeros([batch_size, 4, height // 8, width // 8]) - return (positive, negative, {"samples":latent}) - -class StableZero123_Conditioning_Batched: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip_vision": ("CLIP_VISION",), - "init_image": ("IMAGE",), - "vae": ("VAE",), - "width": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - "elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}), - "azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}), - "elevation_batch_increment": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}), - "azimuth_batch_increment": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}), - }} - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - FUNCTION = "encode" - - CATEGORY = "conditioning/3d_models" - - def encode(self, clip_vision, init_image, vae, width, height, batch_size, elevation, azimuth, elevation_batch_increment, azimuth_batch_increment): - output = clip_vision.encode_image(init_image) - pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) - encode_pixels = pixels[:,:,:,:3] - t = vae.encode(encode_pixels) - - cam_embeds = [] - for i in range(batch_size): - cam_embeds.append(camera_embeddings(elevation, azimuth)) - elevation += elevation_batch_increment - azimuth += azimuth_batch_increment - - cam_embeds = torch.cat(cam_embeds, dim=0) - cond = torch.cat([comfy.utils.repeat_to_batch_size(pooled, batch_size), cam_embeds], dim=-1) - - positive = [[cond, {"concat_latent_image": t}]] - negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t)}]] - latent = torch.zeros([batch_size, 4, height // 8, width // 8]) - return (positive, negative, {"samples":latent, "batch_index": [0] * batch_size}) - -class SV3D_Conditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip_vision": ("CLIP_VISION",), - "init_image": ("IMAGE",), - "vae": ("VAE",), - "width": ("INT", {"default": 576, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 576, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "video_frames": ("INT", {"default": 21, "min": 1, "max": 4096}), - "elevation": ("FLOAT", {"default": 0.0, "min": -90.0, "max": 90.0, "step": 0.1, "round": False}), - }} - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - FUNCTION = "encode" - - CATEGORY = "conditioning/3d_models" - - def encode(self, clip_vision, init_image, vae, width, height, video_frames, elevation): - output = clip_vision.encode_image(init_image) - pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) - encode_pixels = pixels[:,:,:,:3] - t = vae.encode(encode_pixels) - - azimuth = 0 - azimuth_increment = 360 / (max(video_frames, 2) - 1) - - elevations = [] - azimuths = [] - for i in range(video_frames): - elevations.append(elevation) - azimuths.append(azimuth) - azimuth += azimuth_increment - - positive = [[pooled, {"concat_latent_image": t, "elevation": elevations, "azimuth": azimuths}]] - negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t), "elevation": elevations, "azimuth": azimuths}]] - latent = torch.zeros([video_frames, 4, height // 8, width // 8]) - return (positive, negative, {"samples":latent}) - - -NODE_CLASS_MAPPINGS = { - "StableZero123_Conditioning": StableZero123_Conditioning, - "StableZero123_Conditioning_Batched": StableZero123_Conditioning_Batched, - "SV3D_Conditioning": SV3D_Conditioning, -} diff --git a/comfy_extras/nodes_stable_cascade.py b/comfy_extras/nodes_stable_cascade.py deleted file mode 100644 index 0034032150e6eac48d18bb6bd35819114a01fe8d..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_stable_cascade.py +++ /dev/null @@ -1,141 +0,0 @@ -""" - This file is part of ComfyUI. - Copyright (C) 2024 Stability AI - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import torch -import nodes -import comfy.utils - - -class StableCascade_EmptyLatentImage: - def __init__(self, device="cpu"): - self.device = device - - @classmethod - def INPUT_TYPES(s): - return {"required": { - "width": ("INT", {"default": 1024, "min": 256, "max": nodes.MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 1024, "min": 256, "max": nodes.MAX_RESOLUTION, "step": 8}), - "compression": ("INT", {"default": 42, "min": 4, "max": 128, "step": 1}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}) - }} - RETURN_TYPES = ("LATENT", "LATENT") - RETURN_NAMES = ("stage_c", "stage_b") - FUNCTION = "generate" - - CATEGORY = "latent/stable_cascade" - - def generate(self, width, height, compression, batch_size=1): - c_latent = torch.zeros([batch_size, 16, height // compression, width // compression]) - b_latent = torch.zeros([batch_size, 4, height // 4, width // 4]) - return ({ - "samples": c_latent, - }, { - "samples": b_latent, - }) - -class StableCascade_StageC_VAEEncode: - def __init__(self, device="cpu"): - self.device = device - - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image": ("IMAGE",), - "vae": ("VAE", ), - "compression": ("INT", {"default": 42, "min": 4, "max": 128, "step": 1}), - }} - RETURN_TYPES = ("LATENT", "LATENT") - RETURN_NAMES = ("stage_c", "stage_b") - FUNCTION = "generate" - - CATEGORY = "latent/stable_cascade" - - def generate(self, image, vae, compression): - width = image.shape[-2] - height = image.shape[-3] - out_width = (width // compression) * vae.downscale_ratio - out_height = (height // compression) * vae.downscale_ratio - - s = comfy.utils.common_upscale(image.movedim(-1,1), out_width, out_height, "bicubic", "center").movedim(1,-1) - - c_latent = vae.encode(s[:,:,:,:3]) - b_latent = torch.zeros([c_latent.shape[0], 4, (height // 8) * 2, (width // 8) * 2]) - return ({ - "samples": c_latent, - }, { - "samples": b_latent, - }) - -class StableCascade_StageB_Conditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": { "conditioning": ("CONDITIONING",), - "stage_c": ("LATENT",), - }} - RETURN_TYPES = ("CONDITIONING",) - - FUNCTION = "set_prior" - - CATEGORY = "conditioning/stable_cascade" - - def set_prior(self, conditioning, stage_c): - c = [] - for t in conditioning: - d = t[1].copy() - d['stable_cascade_prior'] = stage_c['samples'] - n = [t[0], d] - c.append(n) - return (c, ) - -class StableCascade_SuperResolutionControlnet: - def __init__(self, device="cpu"): - self.device = device - - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image": ("IMAGE",), - "vae": ("VAE", ), - }} - RETURN_TYPES = ("IMAGE", "LATENT", "LATENT") - RETURN_NAMES = ("controlnet_input", "stage_c", "stage_b") - FUNCTION = "generate" - - EXPERIMENTAL = True - CATEGORY = "_for_testing/stable_cascade" - - def generate(self, image, vae): - width = image.shape[-2] - height = image.shape[-3] - batch_size = image.shape[0] - controlnet_input = vae.encode(image[:,:,:,:3]).movedim(1, -1) - - c_latent = torch.zeros([batch_size, 16, height // 16, width // 16]) - b_latent = torch.zeros([batch_size, 4, height // 2, width // 2]) - return (controlnet_input, { - "samples": c_latent, - }, { - "samples": b_latent, - }) - -NODE_CLASS_MAPPINGS = { - "StableCascade_EmptyLatentImage": StableCascade_EmptyLatentImage, - "StableCascade_StageB_Conditioning": StableCascade_StageB_Conditioning, - "StableCascade_StageC_VAEEncode": StableCascade_StageC_VAEEncode, - "StableCascade_SuperResolutionControlnet": StableCascade_SuperResolutionControlnet, -} diff --git a/comfy_extras/nodes_string.py b/comfy_extras/nodes_string.py deleted file mode 100644 index b1a8ceef0fd25c47b9238749e59e06f20f6d346d..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_string.py +++ /dev/null @@ -1,360 +0,0 @@ -import re - -from comfy.comfy_types.node_typing import IO - -class StringConcatenate(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string_a": (IO.STRING, {"multiline": True}), - "string_b": (IO.STRING, {"multiline": True}), - "delimiter": (IO.STRING, {"multiline": False, "default": ""}) - } - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string_a, string_b, delimiter, **kwargs): - return delimiter.join((string_a, string_b)), - -class StringSubstring(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "start": (IO.INT, {}), - "end": (IO.INT, {}), - } - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, start, end, **kwargs): - return string[start:end], - -class StringLength(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}) - } - } - - RETURN_TYPES = (IO.INT,) - RETURN_NAMES = ("length",) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, **kwargs): - length = len(string) - - return length, - -class CaseConverter(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "mode": (IO.COMBO, {"options": ["UPPERCASE", "lowercase", "Capitalize", "Title Case"]}) - } - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, mode, **kwargs): - if mode == "UPPERCASE": - result = string.upper() - elif mode == "lowercase": - result = string.lower() - elif mode == "Capitalize": - result = string.capitalize() - elif mode == "Title Case": - result = string.title() - else: - result = string - - return result, - - -class StringTrim(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "mode": (IO.COMBO, {"options": ["Both", "Left", "Right"]}) - } - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, mode, **kwargs): - if mode == "Both": - result = string.strip() - elif mode == "Left": - result = string.lstrip() - elif mode == "Right": - result = string.rstrip() - else: - result = string - - return result, - -class StringReplace(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "find": (IO.STRING, {"multiline": True}), - "replace": (IO.STRING, {"multiline": True}) - } - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, find, replace, **kwargs): - result = string.replace(find, replace) - return result, - - -class StringContains(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "substring": (IO.STRING, {"multiline": True}), - "case_sensitive": (IO.BOOLEAN, {"default": True}) - } - } - - RETURN_TYPES = (IO.BOOLEAN,) - RETURN_NAMES = ("contains",) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, substring, case_sensitive, **kwargs): - if case_sensitive: - contains = substring in string - else: - contains = substring.lower() in string.lower() - - return contains, - - -class StringCompare(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string_a": (IO.STRING, {"multiline": True}), - "string_b": (IO.STRING, {"multiline": True}), - "mode": (IO.COMBO, {"options": ["Starts With", "Ends With", "Equal"]}), - "case_sensitive": (IO.BOOLEAN, {"default": True}) - } - } - - RETURN_TYPES = (IO.BOOLEAN,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string_a, string_b, mode, case_sensitive, **kwargs): - if case_sensitive: - a = string_a - b = string_b - else: - a = string_a.lower() - b = string_b.lower() - - if mode == "Equal": - return a == b, - elif mode == "Starts With": - return a.startswith(b), - elif mode == "Ends With": - return a.endswith(b), - -class RegexMatch(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "regex_pattern": (IO.STRING, {"multiline": True}), - "case_insensitive": (IO.BOOLEAN, {"default": True}), - "multiline": (IO.BOOLEAN, {"default": False}), - "dotall": (IO.BOOLEAN, {"default": False}) - } - } - - RETURN_TYPES = (IO.BOOLEAN,) - RETURN_NAMES = ("matches",) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, regex_pattern, case_insensitive, multiline, dotall, **kwargs): - flags = 0 - - if case_insensitive: - flags |= re.IGNORECASE - if multiline: - flags |= re.MULTILINE - if dotall: - flags |= re.DOTALL - - try: - match = re.search(regex_pattern, string, flags) - result = match is not None - - except re.error: - result = False - - return result, - - -class RegexExtract(): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "regex_pattern": (IO.STRING, {"multiline": True}), - "mode": (IO.COMBO, {"options": ["First Match", "All Matches", "First Group", "All Groups"]}), - "case_insensitive": (IO.BOOLEAN, {"default": True}), - "multiline": (IO.BOOLEAN, {"default": False}), - "dotall": (IO.BOOLEAN, {"default": False}), - "group_index": (IO.INT, {"default": 1, "min": 0, "max": 100}) - } - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, regex_pattern, mode, case_insensitive, multiline, dotall, group_index, **kwargs): - join_delimiter = "\n" - - flags = 0 - if case_insensitive: - flags |= re.IGNORECASE - if multiline: - flags |= re.MULTILINE - if dotall: - flags |= re.DOTALL - - try: - if mode == "First Match": - match = re.search(regex_pattern, string, flags) - if match: - result = match.group(0) - else: - result = "" - - elif mode == "All Matches": - matches = re.findall(regex_pattern, string, flags) - if matches: - if isinstance(matches[0], tuple): - result = join_delimiter.join([m[0] for m in matches]) - else: - result = join_delimiter.join(matches) - else: - result = "" - - elif mode == "First Group": - match = re.search(regex_pattern, string, flags) - if match and len(match.groups()) >= group_index: - result = match.group(group_index) - else: - result = "" - - elif mode == "All Groups": - matches = re.finditer(regex_pattern, string, flags) - results = [] - for match in matches: - if match.groups() and len(match.groups()) >= group_index: - results.append(match.group(group_index)) - result = join_delimiter.join(results) - else: - result = "" - - except re.error: - result = "" - - return result, - - -class RegexReplace(): - DESCRIPTION = "Find and replace text using regex patterns." - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "string": (IO.STRING, {"multiline": True}), - "regex_pattern": (IO.STRING, {"multiline": True}), - "replace": (IO.STRING, {"multiline": True}), - }, - "optional": { - "case_insensitive": (IO.BOOLEAN, {"default": True}), - "multiline": (IO.BOOLEAN, {"default": False}), - "dotall": (IO.BOOLEAN, {"default": False, "tooltip": "When enabled, the dot (.) character will match any character including newline characters. When disabled, dots won't match newlines."}), - "count": (IO.INT, {"default": 0, "min": 0, "max": 100, "tooltip": "Maximum number of replacements to make. Set to 0 to replace all occurrences (default). Set to 1 to replace only the first match, 2 for the first two matches, etc."}), - } - } - - RETURN_TYPES = (IO.STRING,) - FUNCTION = "execute" - CATEGORY = "utils/string" - - def execute(self, string, regex_pattern, replace, case_insensitive=True, multiline=False, dotall=False, count=0, **kwargs): - flags = 0 - - if case_insensitive: - flags |= re.IGNORECASE - if multiline: - flags |= re.MULTILINE - if dotall: - flags |= re.DOTALL - result = re.sub(regex_pattern, replace, string, count=count, flags=flags) - return result, - -NODE_CLASS_MAPPINGS = { - "StringConcatenate": StringConcatenate, - "StringSubstring": StringSubstring, - "StringLength": StringLength, - "CaseConverter": CaseConverter, - "StringTrim": StringTrim, - "StringReplace": StringReplace, - "StringContains": StringContains, - "StringCompare": StringCompare, - "RegexMatch": RegexMatch, - "RegexExtract": RegexExtract, - "RegexReplace": RegexReplace, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "StringConcatenate": "Concatenate", - "StringSubstring": "Substring", - "StringLength": "Length", - "CaseConverter": "Case Converter", - "StringTrim": "Trim", - "StringReplace": "Replace", - "StringContains": "Contains", - "StringCompare": "Compare", - "RegexMatch": "Regex Match", - "RegexExtract": "Regex Extract", - "RegexReplace": "Regex Replace", -} diff --git a/comfy_extras/nodes_tcfg.py b/comfy_extras/nodes_tcfg.py deleted file mode 100644 index 35b89a73f7fbab0d4ad5e922997a88f3ef04ec7f..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_tcfg.py +++ /dev/null @@ -1,71 +0,0 @@ -# TCFG: Tangential Damping Classifier-free Guidance - (arXiv: https://arxiv.org/abs/2503.18137) - -import torch - -from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict - - -def score_tangential_damping(cond_score: torch.Tensor, uncond_score: torch.Tensor) -> torch.Tensor: - """Drop tangential components from uncond score to align with cond score.""" - # (B, 1, ...) - batch_num = cond_score.shape[0] - cond_score_flat = cond_score.reshape(batch_num, 1, -1).float() - uncond_score_flat = uncond_score.reshape(batch_num, 1, -1).float() - - # Score matrix A (B, 2, ...) - score_matrix = torch.cat((uncond_score_flat, cond_score_flat), dim=1) - try: - _, _, Vh = torch.linalg.svd(score_matrix, full_matrices=False) - except RuntimeError: - # Fallback to CPU - _, _, Vh = torch.linalg.svd(score_matrix.cpu(), full_matrices=False) - - # Drop the tangential components - v1 = Vh[:, 0:1, :].to(uncond_score_flat.device) # (B, 1, ...) - uncond_score_td = (uncond_score_flat @ v1.transpose(-2, -1)) * v1 - return uncond_score_td.reshape_as(uncond_score).to(uncond_score.dtype) - - -class TCFG(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls) -> InputTypeDict: - return { - "required": { - "model": (IO.MODEL, {}), - } - } - - RETURN_TYPES = (IO.MODEL,) - RETURN_NAMES = ("patched_model",) - FUNCTION = "patch" - - CATEGORY = "advanced/guidance" - DESCRIPTION = "TCFG – Tangential Damping CFG (2503.18137)\n\nRefine the uncond (negative) to align with the cond (positive) for improving quality." - - def patch(self, model): - m = model.clone() - - def tangential_damping_cfg(args): - # Assume [cond, uncond, ...] - x = args["input"] - conds_out = args["conds_out"] - if len(conds_out) <= 1 or None in args["conds"][:2]: - # Skip when either cond or uncond is None - return conds_out - cond_pred = conds_out[0] - uncond_pred = conds_out[1] - uncond_td = score_tangential_damping(x - cond_pred, x - uncond_pred) - uncond_pred_td = x - uncond_td - return [cond_pred, uncond_pred_td] + conds_out[2:] - - m.set_model_sampler_pre_cfg_function(tangential_damping_cfg) - return (m,) - - -NODE_CLASS_MAPPINGS = { - "TCFG": TCFG, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "TCFG": "Tangential Damping CFG", -} diff --git a/comfy_extras/nodes_tomesd.py b/comfy_extras/nodes_tomesd.py deleted file mode 100644 index 9f77c06fcb12a2dafbb891cbceb50ba8addaa81a..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_tomesd.py +++ /dev/null @@ -1,176 +0,0 @@ -#Taken from: https://github.com/dbolya/tomesd - -import torch -from typing import Tuple, Callable -import math - -def do_nothing(x: torch.Tensor, mode:str=None): - return x - - -def mps_gather_workaround(input, dim, index): - if input.shape[-1] == 1: - return torch.gather( - input.unsqueeze(-1), - dim - 1 if dim < 0 else dim, - index.unsqueeze(-1) - ).squeeze(-1) - else: - return torch.gather(input, dim, index) - - -def bipartite_soft_matching_random2d(metric: torch.Tensor, - w: int, h: int, sx: int, sy: int, r: int, - no_rand: bool = False) -> Tuple[Callable, Callable]: - """ - Partitions the tokens into src and dst and merges r tokens from src to dst. - Dst tokens are partitioned by choosing one randomy in each (sx, sy) region. - Args: - - metric [B, N, C]: metric to use for similarity - - w: image width in tokens - - h: image height in tokens - - sx: stride in the x dimension for dst, must divide w - - sy: stride in the y dimension for dst, must divide h - - r: number of tokens to remove (by merging) - - no_rand: if true, disable randomness (use top left corner only) - """ - B, N, _ = metric.shape - - if r <= 0 or w == 1 or h == 1: - return do_nothing, do_nothing - - gather = mps_gather_workaround if metric.device.type == "mps" else torch.gather - - with torch.no_grad(): - hsy, wsx = h // sy, w // sx - - # For each sy by sx kernel, randomly assign one token to be dst and the rest src - if no_rand: - rand_idx = torch.zeros(hsy, wsx, 1, device=metric.device, dtype=torch.int64) - else: - rand_idx = torch.randint(sy*sx, size=(hsy, wsx, 1), device=metric.device) - - # The image might not divide sx and sy, so we need to work on a view of the top left if the idx buffer instead - idx_buffer_view = torch.zeros(hsy, wsx, sy*sx, device=metric.device, dtype=torch.int64) - idx_buffer_view.scatter_(dim=2, index=rand_idx, src=-torch.ones_like(rand_idx, dtype=rand_idx.dtype)) - idx_buffer_view = idx_buffer_view.view(hsy, wsx, sy, sx).transpose(1, 2).reshape(hsy * sy, wsx * sx) - - # Image is not divisible by sx or sy so we need to move it into a new buffer - if (hsy * sy) < h or (wsx * sx) < w: - idx_buffer = torch.zeros(h, w, device=metric.device, dtype=torch.int64) - idx_buffer[:(hsy * sy), :(wsx * sx)] = idx_buffer_view - else: - idx_buffer = idx_buffer_view - - # We set dst tokens to be -1 and src to be 0, so an argsort gives us dst|src indices - rand_idx = idx_buffer.reshape(1, -1, 1).argsort(dim=1) - - # We're finished with these - del idx_buffer, idx_buffer_view - - # rand_idx is currently dst|src, so split them - num_dst = hsy * wsx - a_idx = rand_idx[:, num_dst:, :] # src - b_idx = rand_idx[:, :num_dst, :] # dst - - def split(x): - C = x.shape[-1] - src = gather(x, dim=1, index=a_idx.expand(B, N - num_dst, C)) - dst = gather(x, dim=1, index=b_idx.expand(B, num_dst, C)) - return src, dst - - # Cosine similarity between A and B - metric = metric / metric.norm(dim=-1, keepdim=True) - a, b = split(metric) - scores = a @ b.transpose(-1, -2) - - # Can't reduce more than the # tokens in src - r = min(a.shape[1], r) - - # Find the most similar greedily - node_max, node_idx = scores.max(dim=-1) - edge_idx = node_max.argsort(dim=-1, descending=True)[..., None] - - unm_idx = edge_idx[..., r:, :] # Unmerged Tokens - src_idx = edge_idx[..., :r, :] # Merged Tokens - dst_idx = gather(node_idx[..., None], dim=-2, index=src_idx) - - def merge(x: torch.Tensor, mode="mean") -> torch.Tensor: - src, dst = split(x) - n, t1, c = src.shape - - unm = gather(src, dim=-2, index=unm_idx.expand(n, t1 - r, c)) - src = gather(src, dim=-2, index=src_idx.expand(n, r, c)) - dst = dst.scatter_reduce(-2, dst_idx.expand(n, r, c), src, reduce=mode) - - return torch.cat([unm, dst], dim=1) - - def unmerge(x: torch.Tensor) -> torch.Tensor: - unm_len = unm_idx.shape[1] - unm, dst = x[..., :unm_len, :], x[..., unm_len:, :] - _, _, c = unm.shape - - src = gather(dst, dim=-2, index=dst_idx.expand(B, r, c)) - - # Combine back to the original shape - out = torch.zeros(B, N, c, device=x.device, dtype=x.dtype) - out.scatter_(dim=-2, index=b_idx.expand(B, num_dst, c), src=dst) - out.scatter_(dim=-2, index=gather(a_idx.expand(B, a_idx.shape[1], 1), dim=1, index=unm_idx).expand(B, unm_len, c), src=unm) - out.scatter_(dim=-2, index=gather(a_idx.expand(B, a_idx.shape[1], 1), dim=1, index=src_idx).expand(B, r, c), src=src) - - return out - - return merge, unmerge - - -def get_functions(x, ratio, original_shape): - b, c, original_h, original_w = original_shape - original_tokens = original_h * original_w - downsample = int(math.ceil(math.sqrt(original_tokens // x.shape[1]))) - stride_x = 2 - stride_y = 2 - max_downsample = 1 - - if downsample <= max_downsample: - w = int(math.ceil(original_w / downsample)) - h = int(math.ceil(original_h / downsample)) - r = int(x.shape[1] * ratio) - no_rand = False - m, u = bipartite_soft_matching_random2d(x, w, h, stride_x, stride_y, r, no_rand) - return m, u - - nothing = lambda y: y - return nothing, nothing - - - -class TomePatchModel: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "ratio": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "model_patches/unet" - - def patch(self, model, ratio): - self.u = None - def tomesd_m(q, k, v, extra_options): - #NOTE: In the reference code get_functions takes x (input of the transformer block) as the argument instead of q - #however from my basic testing it seems that using q instead gives better results - m, self.u = get_functions(q, ratio, extra_options["original_shape"]) - return m(q), k, v - def tomesd_u(n, extra_options): - return self.u(n) - - m = model.clone() - m.set_model_attn1_patch(tomesd_m) - m.set_model_attn1_output_patch(tomesd_u) - return (m, ) - - -NODE_CLASS_MAPPINGS = { - "TomePatchModel": TomePatchModel, -} diff --git a/comfy_extras/nodes_torch_compile.py b/comfy_extras/nodes_torch_compile.py deleted file mode 100644 index 6055366784d0d46de863d8278b0bb2979e904d2a..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_torch_compile.py +++ /dev/null @@ -1,23 +0,0 @@ -from comfy_api.torch_helpers import set_torch_compile_wrapper - - -class TorchCompileModel: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "backend": (["inductor", "cudagraphs"],), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "_for_testing" - EXPERIMENTAL = True - - def patch(self, model, backend): - m = model.clone() - set_torch_compile_wrapper(model=m, backend=backend) - return (m, ) - -NODE_CLASS_MAPPINGS = { - "TorchCompileModel": TorchCompileModel, -} diff --git a/comfy_extras/nodes_train.py b/comfy_extras/nodes_train.py deleted file mode 100644 index 3d05fdab5d7f069991c07457cb64b244f1d7a78e..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_train.py +++ /dev/null @@ -1,852 +0,0 @@ -import datetime -import json -import logging -import os - -import numpy as np -import safetensors -import torch -from PIL import Image, ImageDraw, ImageFont -from PIL.PngImagePlugin import PngInfo -import torch.utils.checkpoint -import tqdm - -import comfy.samplers -import comfy.sd -import comfy.utils -import comfy.model_management -import comfy_extras.nodes_custom_sampler -import folder_paths -import node_helpers -from comfy.cli_args import args -from comfy.comfy_types.node_typing import IO -from comfy.weight_adapter import adapters - - -def make_batch_extra_option_dict(d, indicies, full_size=None): - new_dict = {} - for k, v in d.items(): - newv = v - if isinstance(v, dict): - newv = make_batch_extra_option_dict(v, indicies, full_size=full_size) - elif isinstance(v, torch.Tensor): - if full_size is None or v.size(0) == full_size: - newv = v[indicies] - elif isinstance(v, (list, tuple)) and len(v) == full_size: - newv = [v[i] for i in indicies] - new_dict[k] = newv - return new_dict - - -class TrainSampler(comfy.samplers.Sampler): - - def __init__(self, loss_fn, optimizer, loss_callback=None, batch_size=1, total_steps=1, seed=0, training_dtype=torch.bfloat16): - self.loss_fn = loss_fn - self.optimizer = optimizer - self.loss_callback = loss_callback - self.batch_size = batch_size - self.total_steps = total_steps - self.seed = seed - self.training_dtype = training_dtype - - def sample(self, model_wrap, sigmas, extra_args, callback, noise, latent_image=None, denoise_mask=None, disable_pbar=False): - cond = model_wrap.conds["positive"] - dataset_size = sigmas.size(0) - torch.cuda.empty_cache() - for i in (pbar:=tqdm.trange(self.total_steps, desc="Training LoRA", smoothing=0.01, disable=not comfy.utils.PROGRESS_BAR_ENABLED)): - noisegen = comfy_extras.nodes_custom_sampler.Noise_RandomNoise(self.seed + i * 1000) - indicies = torch.randperm(dataset_size)[:self.batch_size].tolist() - - batch_latent = torch.stack([latent_image[i] for i in indicies]) - batch_noise = noisegen.generate_noise({"samples": batch_latent}).to(batch_latent.device) - batch_sigmas = [ - model_wrap.inner_model.model_sampling.percent_to_sigma( - torch.rand((1,)).item() - ) for _ in range(min(self.batch_size, dataset_size)) - ] - batch_sigmas = torch.tensor(batch_sigmas).to(batch_latent.device) - - xt = model_wrap.inner_model.model_sampling.noise_scaling( - batch_sigmas, - batch_noise, - batch_latent, - False - ) - x0 = model_wrap.inner_model.model_sampling.noise_scaling( - torch.zeros_like(batch_sigmas), - torch.zeros_like(batch_noise), - batch_latent, - False - ) - - model_wrap.conds["positive"] = [ - cond[i] for i in indicies - ] - batch_extra_args = make_batch_extra_option_dict(extra_args, indicies, full_size=dataset_size) - - with torch.autocast(xt.device.type, dtype=self.training_dtype): - x0_pred = model_wrap(xt, batch_sigmas, **batch_extra_args) - loss = self.loss_fn(x0_pred, x0) - loss.backward() - if self.loss_callback: - self.loss_callback(loss.item()) - pbar.set_postfix({"loss": f"{loss.item():.4f}"}) - - self.optimizer.step() - self.optimizer.zero_grad() - torch.cuda.empty_cache() - return torch.zeros_like(latent_image) - - -class BiasDiff(torch.nn.Module): - def __init__(self, bias): - super().__init__() - self.bias = bias - - def __call__(self, b): - org_dtype = b.dtype - return (b.to(self.bias) + self.bias).to(org_dtype) - - def passive_memory_usage(self): - return self.bias.nelement() * self.bias.element_size() - - def move_to(self, device): - self.to(device=device) - return self.passive_memory_usage() - - -def load_and_process_images(image_files, input_dir, resize_method="None", w=None, h=None): - """Utility function to load and process a list of images. - - Args: - image_files: List of image filenames - input_dir: Base directory containing the images - resize_method: How to handle images of different sizes ("None", "Stretch", "Crop", "Pad") - - Returns: - torch.Tensor: Batch of processed images - """ - if not image_files: - raise ValueError("No valid images found in input") - - output_images = [] - - for file in image_files: - image_path = os.path.join(input_dir, file) - img = node_helpers.pillow(Image.open, image_path) - - if img.mode == "I": - img = img.point(lambda i: i * (1 / 255)) - img = img.convert("RGB") - - if w is None and h is None: - w, h = img.size[0], img.size[1] - - # Resize image to first image - if img.size[0] != w or img.size[1] != h: - if resize_method == "Stretch": - img = img.resize((w, h), Image.Resampling.LANCZOS) - elif resize_method == "Crop": - img = img.crop((0, 0, w, h)) - elif resize_method == "Pad": - img = img.resize((w, h), Image.Resampling.LANCZOS) - elif resize_method == "None": - raise ValueError( - "Your input image size does not match the first image in the dataset. Either select a valid resize method or use the same size for all images." - ) - - img_array = np.array(img).astype(np.float32) / 255.0 - img_tensor = torch.from_numpy(img_array)[None,] - output_images.append(img_tensor) - - return torch.cat(output_images, dim=0) - - -class LoadImageSetNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ( - [ - f - for f in os.listdir(folder_paths.get_input_directory()) - if f.endswith((".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif", ".jpe", ".apng", ".tif", ".tiff")) - ], - {"image_upload": True, "allow_batch": True}, - ) - }, - "optional": { - "resize_method": ( - ["None", "Stretch", "Crop", "Pad"], - {"default": "None"}, - ), - }, - } - - INPUT_IS_LIST = True - RETURN_TYPES = ("IMAGE",) - FUNCTION = "load_images" - CATEGORY = "loaders" - EXPERIMENTAL = True - DESCRIPTION = "Loads a batch of images from a directory for training." - - @classmethod - def VALIDATE_INPUTS(s, images, resize_method): - filenames = images[0] if isinstance(images[0], list) else images - - for image in filenames: - if not folder_paths.exists_annotated_filepath(image): - return "Invalid image file: {}".format(image) - return True - - def load_images(self, input_files, resize_method): - input_dir = folder_paths.get_input_directory() - valid_extensions = [".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif", ".jpe", ".apng", ".tif", ".tiff"] - image_files = [ - f - for f in input_files - if any(f.lower().endswith(ext) for ext in valid_extensions) - ] - output_tensor = load_and_process_images(image_files, input_dir, resize_method) - return (output_tensor,) - - -class LoadImageSetFromFolderNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "folder": (folder_paths.get_input_subfolders(), {"tooltip": "The folder to load images from."}) - }, - "optional": { - "resize_method": ( - ["None", "Stretch", "Crop", "Pad"], - {"default": "None"}, - ), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "load_images" - CATEGORY = "loaders" - EXPERIMENTAL = True - DESCRIPTION = "Loads a batch of images from a directory for training." - - def load_images(self, folder, resize_method): - sub_input_dir = os.path.join(folder_paths.get_input_directory(), folder) - valid_extensions = [".png", ".jpg", ".jpeg", ".webp"] - image_files = [ - f - for f in os.listdir(sub_input_dir) - if any(f.lower().endswith(ext) for ext in valid_extensions) - ] - output_tensor = load_and_process_images(image_files, sub_input_dir, resize_method) - return (output_tensor,) - - -class LoadImageTextSetFromFolderNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "folder": (folder_paths.get_input_subfolders(), {"tooltip": "The folder to load images from."}), - "clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}), - }, - "optional": { - "resize_method": ( - ["None", "Stretch", "Crop", "Pad"], - {"default": "None"}, - ), - "width": ( - IO.INT, - { - "default": -1, - "min": -1, - "max": 10000, - "step": 1, - "tooltip": "The width to resize the images to. -1 means use the original width.", - }, - ), - "height": ( - IO.INT, - { - "default": -1, - "min": -1, - "max": 10000, - "step": 1, - "tooltip": "The height to resize the images to. -1 means use the original height.", - }, - ) - }, - } - - RETURN_TYPES = ("IMAGE", IO.CONDITIONING,) - FUNCTION = "load_images" - CATEGORY = "loaders" - EXPERIMENTAL = True - DESCRIPTION = "Loads a batch of images and caption from a directory for training." - - def load_images(self, folder, clip, resize_method, width=None, height=None): - if clip is None: - raise RuntimeError("ERROR: clip input is invalid: None\n\nIf the clip is from a checkpoint loader node your checkpoint does not contain a valid clip or text encoder model.") - - logging.info(f"Loading images from folder: {folder}") - - sub_input_dir = os.path.join(folder_paths.get_input_directory(), folder) - valid_extensions = [".png", ".jpg", ".jpeg", ".webp"] - - image_files = [] - for item in os.listdir(sub_input_dir): - path = os.path.join(sub_input_dir, item) - if any(item.lower().endswith(ext) for ext in valid_extensions): - image_files.append(path) - elif os.path.isdir(path): - # Support kohya-ss/sd-scripts folder structure - repeat = 1 - if item.split("_")[0].isdigit(): - repeat = int(item.split("_")[0]) - image_files.extend([ - os.path.join(path, f) for f in os.listdir(path) if any(f.lower().endswith(ext) for ext in valid_extensions) - ] * repeat) - - caption_file_path = [ - f.replace(os.path.splitext(f)[1], ".txt") - for f in image_files - ] - captions = [] - for caption_file in caption_file_path: - caption_path = os.path.join(sub_input_dir, caption_file) - if os.path.exists(caption_path): - with open(caption_path, "r", encoding="utf-8") as f: - caption = f.read().strip() - captions.append(caption) - else: - captions.append("") - - width = width if width != -1 else None - height = height if height != -1 else None - output_tensor = load_and_process_images(image_files, sub_input_dir, resize_method, width, height) - - logging.info(f"Loaded {len(output_tensor)} images from {sub_input_dir}.") - - logging.info(f"Encoding captions from {sub_input_dir}.") - conditions = [] - empty_cond = clip.encode_from_tokens_scheduled(clip.tokenize("")) - for text in captions: - if text == "": - conditions.append(empty_cond) - tokens = clip.tokenize(text) - conditions.extend(clip.encode_from_tokens_scheduled(tokens)) - logging.info(f"Encoded {len(conditions)} captions from {sub_input_dir}.") - return (output_tensor, conditions) - - -def draw_loss_graph(loss_map, steps): - width, height = 500, 300 - img = Image.new("RGB", (width, height), "white") - draw = ImageDraw.Draw(img) - - min_loss, max_loss = min(loss_map.values()), max(loss_map.values()) - scaled_loss = [(l - min_loss) / (max_loss - min_loss) for l in loss_map.values()] - - prev_point = (0, height - int(scaled_loss[0] * height)) - for i, l in enumerate(scaled_loss[1:], start=1): - x = int(i / (steps - 1) * width) - y = height - int(l * height) - draw.line([prev_point, (x, y)], fill="blue", width=2) - prev_point = (x, y) - - return img - - -def find_all_highest_child_module_with_forward(model: torch.nn.Module, result = None, name = None): - if result is None: - result = [] - elif hasattr(model, "forward") and not isinstance(model, (torch.nn.ModuleList, torch.nn.Sequential, torch.nn.ModuleDict)): - result.append(model) - logging.debug(f"Found module with forward: {name} ({model.__class__.__name__})") - return result - name = name or "root" - for next_name, child in model.named_children(): - find_all_highest_child_module_with_forward(child, result, f"{name}.{next_name}") - return result - - -def patch(m): - if not hasattr(m, "forward"): - return - org_forward = m.forward - def fwd(args, kwargs): - return org_forward(*args, **kwargs) - def checkpointing_fwd(*args, **kwargs): - return torch.utils.checkpoint.checkpoint( - fwd, args, kwargs, use_reentrant=False - ) - m.org_forward = org_forward - m.forward = checkpointing_fwd - - -def unpatch(m): - if hasattr(m, "org_forward"): - m.forward = m.org_forward - del m.org_forward - - -class TrainLoraNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": (IO.MODEL, {"tooltip": "The model to train the LoRA on."}), - "latents": ( - "LATENT", - { - "tooltip": "The Latents to use for training, serve as dataset/input of the model." - }, - ), - "positive": ( - IO.CONDITIONING, - {"tooltip": "The positive conditioning to use for training."}, - ), - "batch_size": ( - IO.INT, - { - "default": 1, - "min": 1, - "max": 10000, - "step": 1, - "tooltip": "The batch size to use for training.", - }, - ), - "steps": ( - IO.INT, - { - "default": 16, - "min": 1, - "max": 100000, - "tooltip": "The number of steps to train the LoRA for.", - }, - ), - "learning_rate": ( - IO.FLOAT, - { - "default": 0.0005, - "min": 0.0000001, - "max": 1.0, - "step": 0.000001, - "tooltip": "The learning rate to use for training.", - }, - ), - "rank": ( - IO.INT, - { - "default": 8, - "min": 1, - "max": 128, - "tooltip": "The rank of the LoRA layers.", - }, - ), - "optimizer": ( - ["AdamW", "Adam", "SGD", "RMSprop"], - { - "default": "AdamW", - "tooltip": "The optimizer to use for training.", - }, - ), - "loss_function": ( - ["MSE", "L1", "Huber", "SmoothL1"], - { - "default": "MSE", - "tooltip": "The loss function to use for training.", - }, - ), - "seed": ( - IO.INT, - { - "default": 0, - "min": 0, - "max": 0xFFFFFFFFFFFFFFFF, - "tooltip": "The seed to use for training (used in generator for LoRA weight initialization and noise sampling)", - }, - ), - "training_dtype": ( - ["bf16", "fp32"], - {"default": "bf16", "tooltip": "The dtype to use for training."}, - ), - "lora_dtype": ( - ["bf16", "fp32"], - {"default": "bf16", "tooltip": "The dtype to use for lora."}, - ), - "existing_lora": ( - folder_paths.get_filename_list("loras") + ["[None]"], - { - "default": "[None]", - "tooltip": "The existing LoRA to append to. Set to None for new LoRA.", - }, - ), - }, - } - - RETURN_TYPES = (IO.MODEL, IO.LORA_MODEL, IO.LOSS_MAP, IO.INT) - RETURN_NAMES = ("model_with_lora", "lora", "loss", "steps") - FUNCTION = "train" - CATEGORY = "training" - EXPERIMENTAL = True - - def train( - self, - model, - latents, - positive, - batch_size, - steps, - learning_rate, - rank, - optimizer, - loss_function, - seed, - training_dtype, - lora_dtype, - existing_lora, - ): - mp = model.clone() - dtype = node_helpers.string_to_torch_dtype(training_dtype) - lora_dtype = node_helpers.string_to_torch_dtype(lora_dtype) - mp.set_model_compute_dtype(dtype) - - latents = latents["samples"].to(dtype) - num_images = latents.shape[0] - logging.info(f"Total Images: {num_images}, Total Captions: {len(positive)}") - if len(positive) == 1 and num_images > 1: - positive = positive * num_images - elif len(positive) != num_images: - raise ValueError( - f"Number of positive conditions ({len(positive)}) does not match number of images ({num_images})." - ) - - with torch.inference_mode(False): - lora_sd = {} - generator = torch.Generator() - generator.manual_seed(seed) - - # Load existing LoRA weights if provided - existing_weights = {} - existing_steps = 0 - if existing_lora != "[None]": - lora_path = folder_paths.get_full_path_or_raise("loras", existing_lora) - # Extract steps from filename like "trained_lora_10_steps_20250225_203716" - existing_steps = int(existing_lora.split("_steps_")[0].split("_")[-1]) - if lora_path: - existing_weights = comfy.utils.load_torch_file(lora_path) - - all_weight_adapters = [] - for n, m in mp.model.named_modules(): - if hasattr(m, "weight_function"): - if m.weight is not None: - key = "{}.weight".format(n) - shape = m.weight.shape - if len(shape) >= 2: - alpha = float(existing_weights.get(f"{key}.alpha", 1.0)) - dora_scale = existing_weights.get( - f"{key}.dora_scale", None - ) - for adapter_cls in adapters: - existing_adapter = adapter_cls.load( - n, existing_weights, alpha, dora_scale - ) - if existing_adapter is not None: - break - else: - # If no existing adapter found, use LoRA - # We will add algo option in the future - existing_adapter = None - adapter_cls = adapters[0] - - if existing_adapter is not None: - train_adapter = existing_adapter.to_train().to(lora_dtype) - else: - # Use LoRA with alpha=1.0 by default - train_adapter = adapter_cls.create_train( - m.weight, rank=rank, alpha=1.0 - ).to(lora_dtype) - for name, parameter in train_adapter.named_parameters(): - lora_sd[f"{n}.{name}"] = parameter - - mp.add_weight_wrapper(key, train_adapter) - all_weight_adapters.append(train_adapter) - else: - diff = torch.nn.Parameter( - torch.zeros( - m.weight.shape, dtype=lora_dtype, requires_grad=True - ) - ) - diff_module = BiasDiff(diff) - mp.add_weight_wrapper(key, BiasDiff(diff)) - all_weight_adapters.append(diff_module) - lora_sd["{}.diff".format(n)] = diff - if hasattr(m, "bias") and m.bias is not None: - key = "{}.bias".format(n) - bias = torch.nn.Parameter( - torch.zeros(m.bias.shape, dtype=lora_dtype, requires_grad=True) - ) - bias_module = BiasDiff(bias) - lora_sd["{}.diff_b".format(n)] = bias - mp.add_weight_wrapper(key, BiasDiff(bias)) - all_weight_adapters.append(bias_module) - - if optimizer == "Adam": - optimizer = torch.optim.Adam(lora_sd.values(), lr=learning_rate) - elif optimizer == "AdamW": - optimizer = torch.optim.AdamW(lora_sd.values(), lr=learning_rate) - elif optimizer == "SGD": - optimizer = torch.optim.SGD(lora_sd.values(), lr=learning_rate) - elif optimizer == "RMSprop": - optimizer = torch.optim.RMSprop(lora_sd.values(), lr=learning_rate) - - # Setup loss function based on selection - if loss_function == "MSE": - criterion = torch.nn.MSELoss() - elif loss_function == "L1": - criterion = torch.nn.L1Loss() - elif loss_function == "Huber": - criterion = torch.nn.HuberLoss() - elif loss_function == "SmoothL1": - criterion = torch.nn.SmoothL1Loss() - - # setup models - for m in find_all_highest_child_module_with_forward(mp.model.diffusion_model): - patch(m) - mp.model.requires_grad_(False) - comfy.model_management.load_models_gpu([mp], memory_required=1e20, force_full_load=True) - - # Setup sampler and guider like in test script - loss_map = {"loss": []} - def loss_callback(loss): - loss_map["loss"].append(loss) - train_sampler = TrainSampler( - criterion, - optimizer, - loss_callback=loss_callback, - batch_size=batch_size, - total_steps=steps, - seed=seed, - training_dtype=dtype - ) - guider = comfy_extras.nodes_custom_sampler.Guider_Basic(mp) - guider.set_conds(positive) # Set conditioning from input - - # Training loop - try: - # Generate dummy sigmas and noise - sigmas = torch.tensor(range(num_images)) - noise = comfy_extras.nodes_custom_sampler.Noise_RandomNoise(seed) - guider.sample( - noise.generate_noise({"samples": latents}), - latents, - train_sampler, - sigmas, - seed=noise.seed - ) - finally: - for m in mp.model.modules(): - unpatch(m) - del train_sampler, optimizer - - for adapter in all_weight_adapters: - adapter.requires_grad_(False) - - for param in lora_sd: - lora_sd[param] = lora_sd[param].to(lora_dtype) - - return (mp, lora_sd, loss_map, steps + existing_steps) - - -class LoraModelLoader: - def __init__(self): - self.loaded_lora = None - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}), - "lora": (IO.LORA_MODEL, {"tooltip": "The LoRA model to apply to the diffusion model."}), - "strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."}), - } - } - - RETURN_TYPES = ("MODEL",) - OUTPUT_TOOLTIPS = ("The modified diffusion model.",) - FUNCTION = "load_lora_model" - - CATEGORY = "loaders" - DESCRIPTION = "Load Trained LoRA weights from Train LoRA node." - EXPERIMENTAL = True - - def load_lora_model(self, model, lora, strength_model): - if strength_model == 0: - return (model, ) - - model_lora, _ = comfy.sd.load_lora_for_models(model, None, lora, strength_model, 0) - return (model_lora, ) - - -class SaveLoRA: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "lora": ( - IO.LORA_MODEL, - { - "tooltip": "The LoRA model to save. Do not use the model with LoRA layers." - }, - ), - "prefix": ( - "STRING", - { - "default": "loras/ComfyUI_trained_lora", - "tooltip": "The prefix to use for the saved LoRA file.", - }, - ), - }, - "optional": { - "steps": ( - IO.INT, - { - "forceInput": True, - "tooltip": "Optional: The number of steps to LoRA has been trained for, used to name the saved file.", - }, - ), - }, - } - - RETURN_TYPES = () - FUNCTION = "save" - CATEGORY = "loaders" - EXPERIMENTAL = True - OUTPUT_NODE = True - - def save(self, lora, prefix, steps=None): - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(prefix, self.output_dir) - if steps is None: - output_checkpoint = f"{filename}_{counter:05}_.safetensors" - else: - output_checkpoint = f"{filename}_{steps}_steps_{counter:05}_.safetensors" - output_checkpoint = os.path.join(full_output_folder, output_checkpoint) - safetensors.torch.save_file(lora, output_checkpoint) - return {} - - -class LossGraphNode: - def __init__(self): - self.output_dir = folder_paths.get_temp_directory() - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "loss": (IO.LOSS_MAP, {"default": {}}), - "filename_prefix": (IO.STRING, {"default": "loss_graph"}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "plot_loss" - OUTPUT_NODE = True - CATEGORY = "training" - EXPERIMENTAL = True - DESCRIPTION = "Plots the loss graph and saves it to the output directory." - - def plot_loss(self, loss, filename_prefix, prompt=None, extra_pnginfo=None): - loss_values = loss["loss"] - width, height = 800, 480 - margin = 40 - - img = Image.new( - "RGB", (width + margin, height + margin), "white" - ) # Extend canvas - draw = ImageDraw.Draw(img) - - min_loss, max_loss = min(loss_values), max(loss_values) - scaled_loss = [(l - min_loss) / (max_loss - min_loss) for l in loss_values] - - steps = len(loss_values) - - prev_point = (margin, height - int(scaled_loss[0] * height)) - for i, l in enumerate(scaled_loss[1:], start=1): - x = margin + int(i / steps * width) # Scale X properly - y = height - int(l * height) - draw.line([prev_point, (x, y)], fill="blue", width=2) - prev_point = (x, y) - - draw.line([(margin, 0), (margin, height)], fill="black", width=2) # Y-axis - draw.line( - [(margin, height), (width + margin, height)], fill="black", width=2 - ) # X-axis - - font = None - try: - font = ImageFont.truetype("arial.ttf", 12) - except IOError: - font = ImageFont.load_default() - - # Add axis labels - draw.text((5, height // 2), "Loss", font=font, fill="black") - draw.text((width // 2, height + 10), "Steps", font=font, fill="black") - - # Add min/max loss values - draw.text((margin - 30, 0), f"{max_loss:.2f}", font=font, fill="black") - draw.text( - (margin - 30, height - 10), f"{min_loss:.2f}", font=font, fill="black" - ) - - metadata = None - if not args.disable_metadata: - metadata = PngInfo() - if prompt is not None: - metadata.add_text("prompt", json.dumps(prompt)) - if extra_pnginfo is not None: - for x in extra_pnginfo: - metadata.add_text(x, json.dumps(extra_pnginfo[x])) - - date = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - img.save( - os.path.join(self.output_dir, f"{filename_prefix}_{date}.png"), - pnginfo=metadata, - ) - return { - "ui": { - "images": [ - { - "filename": f"{filename_prefix}_{date}.png", - "subfolder": "", - "type": "temp", - } - ] - } - } - - -NODE_CLASS_MAPPINGS = { - "TrainLoraNode": TrainLoraNode, - "SaveLoRANode": SaveLoRA, - "LoraModelLoader": LoraModelLoader, - "LoadImageSetFromFolderNode": LoadImageSetFromFolderNode, - "LoadImageTextSetFromFolderNode": LoadImageTextSetFromFolderNode, - "LossGraphNode": LossGraphNode, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "TrainLoraNode": "Train LoRA", - "SaveLoRANode": "Save LoRA Weights", - "LoraModelLoader": "Load LoRA Model", - "LoadImageSetFromFolderNode": "Load Image Dataset from Folder", - "LoadImageTextSetFromFolderNode": "Load Image and Text Dataset from Folder", - "LossGraphNode": "Plot Loss Graph", -} diff --git a/comfy_extras/nodes_upscale_model.py b/comfy_extras/nodes_upscale_model.py deleted file mode 100644 index 04c948341296dfa7e385141498973977c17c906a..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_upscale_model.py +++ /dev/null @@ -1,83 +0,0 @@ -import logging -from spandrel import ModelLoader, ImageModelDescriptor -from comfy import model_management -import torch -import comfy.utils -import folder_paths - -try: - from spandrel_extra_arches import EXTRA_REGISTRY - from spandrel import MAIN_REGISTRY - MAIN_REGISTRY.add(*EXTRA_REGISTRY) - logging.info("Successfully imported spandrel_extra_arches: support for non commercial upscale models.") -except: - pass - -class UpscaleModelLoader: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model_name": (folder_paths.get_filename_list("upscale_models"), ), - }} - RETURN_TYPES = ("UPSCALE_MODEL",) - FUNCTION = "load_model" - - CATEGORY = "loaders" - - def load_model(self, model_name): - model_path = folder_paths.get_full_path_or_raise("upscale_models", model_name) - sd = comfy.utils.load_torch_file(model_path, safe_load=True) - if "module.layers.0.residual_group.blocks.0.norm1.weight" in sd: - sd = comfy.utils.state_dict_prefix_replace(sd, {"module.":""}) - out = ModelLoader().load_from_state_dict(sd).eval() - - if not isinstance(out, ImageModelDescriptor): - raise Exception("Upscale model must be a single-image model.") - - return (out, ) - - -class ImageUpscaleWithModel: - @classmethod - def INPUT_TYPES(s): - return {"required": { "upscale_model": ("UPSCALE_MODEL",), - "image": ("IMAGE",), - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "upscale" - - CATEGORY = "image/upscaling" - - def upscale(self, upscale_model, image): - device = model_management.get_torch_device() - - memory_required = model_management.module_size(upscale_model.model) - memory_required += (512 * 512 * 3) * image.element_size() * max(upscale_model.scale, 1.0) * 384.0 #The 384.0 is an estimate of how much some of these models take, TODO: make it more accurate - memory_required += image.nelement() * image.element_size() - model_management.free_memory(memory_required, device) - - upscale_model.to(device) - in_img = image.movedim(-1,-3).to(device) - - tile = 512 - overlap = 32 - - oom = True - while oom: - try: - steps = in_img.shape[0] * comfy.utils.get_tiled_scale_steps(in_img.shape[3], in_img.shape[2], tile_x=tile, tile_y=tile, overlap=overlap) - pbar = comfy.utils.ProgressBar(steps) - s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=tile, tile_y=tile, overlap=overlap, upscale_amount=upscale_model.scale, pbar=pbar) - oom = False - except model_management.OOM_EXCEPTION as e: - tile //= 2 - if tile < 128: - raise e - - upscale_model.to("cpu") - s = torch.clamp(s.movedim(-3,-1), min=0, max=1.0) - return (s,) - -NODE_CLASS_MAPPINGS = { - "UpscaleModelLoader": UpscaleModelLoader, - "ImageUpscaleWithModel": ImageUpscaleWithModel -} diff --git a/comfy_extras/nodes_video.py b/comfy_extras/nodes_video.py deleted file mode 100644 index 61f7171b210ea441c93ca36dfa0d8181b081b14d..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_video.py +++ /dev/null @@ -1,241 +0,0 @@ -from __future__ import annotations - -import os -import av -import torch -import folder_paths -import json -from typing import Optional, Literal -from fractions import Fraction -from comfy.comfy_types import IO, FileLocator, ComfyNodeABC -from comfy_api.input import ImageInput, AudioInput, VideoInput -from comfy_api.util import VideoContainer, VideoCodec, VideoComponents -from comfy_api.input_impl import VideoFromFile, VideoFromComponents -from comfy.cli_args import args - -class SaveWEBM: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - @classmethod - def INPUT_TYPES(s): - return {"required": - {"images": ("IMAGE", ), - "filename_prefix": ("STRING", {"default": "ComfyUI"}), - "codec": (["vp9", "av1"],), - "fps": ("FLOAT", {"default": 24.0, "min": 0.01, "max": 1000.0, "step": 0.01}), - "crf": ("FLOAT", {"default": 32.0, "min": 0, "max": 63.0, "step": 1, "tooltip": "Higher crf means lower quality with a smaller file size, lower crf means higher quality higher filesize."}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "save_images" - - OUTPUT_NODE = True - - CATEGORY = "image/video" - - EXPERIMENTAL = True - - def save_images(self, images, codec, fps, filename_prefix, crf, prompt=None, extra_pnginfo=None): - filename_prefix += self.prefix_append - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) - - file = f"{filename}_{counter:05}_.webm" - container = av.open(os.path.join(full_output_folder, file), mode="w") - - if prompt is not None: - container.metadata["prompt"] = json.dumps(prompt) - - if extra_pnginfo is not None: - for x in extra_pnginfo: - container.metadata[x] = json.dumps(extra_pnginfo[x]) - - codec_map = {"vp9": "libvpx-vp9", "av1": "libsvtav1"} - stream = container.add_stream(codec_map[codec], rate=Fraction(round(fps * 1000), 1000)) - stream.width = images.shape[-2] - stream.height = images.shape[-3] - stream.pix_fmt = "yuv420p10le" if codec == "av1" else "yuv420p" - stream.bit_rate = 0 - stream.options = {'crf': str(crf)} - if codec == "av1": - stream.options["preset"] = "6" - - for frame in images: - frame = av.VideoFrame.from_ndarray(torch.clamp(frame[..., :3] * 255, min=0, max=255).to(device=torch.device("cpu"), dtype=torch.uint8).numpy(), format="rgb24") - for packet in stream.encode(frame): - container.mux(packet) - container.mux(stream.encode()) - container.close() - - results: list[FileLocator] = [{ - "filename": file, - "subfolder": subfolder, - "type": self.type - }] - - return {"ui": {"images": results, "animated": (True,)}} # TODO: frontend side - -class SaveVideo(ComfyNodeABC): - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type: Literal["output"] = "output" - self.prefix_append = "" - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "video": (IO.VIDEO, {"tooltip": "The video to save."}), - "filename_prefix": ("STRING", {"default": "video/ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."}), - "format": (VideoContainer.as_input(), {"default": "auto", "tooltip": "The format to save the video as."}), - "codec": (VideoCodec.as_input(), {"default": "auto", "tooltip": "The codec to use for the video."}), - }, - "hidden": { - "prompt": "PROMPT", - "extra_pnginfo": "EXTRA_PNGINFO" - }, - } - - RETURN_TYPES = () - FUNCTION = "save_video" - - OUTPUT_NODE = True - - CATEGORY = "image/video" - DESCRIPTION = "Saves the input images to your ComfyUI output directory." - - def save_video(self, video: VideoInput, filename_prefix, format, codec, prompt=None, extra_pnginfo=None): - filename_prefix += self.prefix_append - width, height = video.get_dimensions() - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path( - filename_prefix, - self.output_dir, - width, - height - ) - results: list[FileLocator] = list() - saved_metadata = None - if not args.disable_metadata: - metadata = {} - if extra_pnginfo is not None: - metadata.update(extra_pnginfo) - if prompt is not None: - metadata["prompt"] = prompt - if len(metadata) > 0: - saved_metadata = metadata - file = f"{filename}_{counter:05}_.{VideoContainer.get_extension(format)}" - video.save_to( - os.path.join(full_output_folder, file), - format=format, - codec=codec, - metadata=saved_metadata - ) - - results.append({ - "filename": file, - "subfolder": subfolder, - "type": self.type - }) - counter += 1 - - return { "ui": { "images": results, "animated": (True,) } } - -class CreateVideo(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "images": (IO.IMAGE, {"tooltip": "The images to create a video from."}), - "fps": ("FLOAT", {"default": 30.0, "min": 1.0, "max": 120.0, "step": 1.0}), - }, - "optional": { - "audio": (IO.AUDIO, {"tooltip": "The audio to add to the video."}), - } - } - - RETURN_TYPES = (IO.VIDEO,) - FUNCTION = "create_video" - - CATEGORY = "image/video" - DESCRIPTION = "Create a video from images." - - def create_video(self, images: ImageInput, fps: float, audio: Optional[AudioInput] = None): - return (VideoFromComponents( - VideoComponents( - images=images, - audio=audio, - frame_rate=Fraction(fps), - ) - ),) - -class GetVideoComponents(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "video": (IO.VIDEO, {"tooltip": "The video to extract components from."}), - } - } - RETURN_TYPES = (IO.IMAGE, IO.AUDIO, IO.FLOAT) - RETURN_NAMES = ("images", "audio", "fps") - FUNCTION = "get_components" - - CATEGORY = "image/video" - DESCRIPTION = "Extracts all components from a video: frames, audio, and framerate." - - def get_components(self, video: VideoInput): - components = video.get_components() - - return (components.images, components.audio, float(components.frame_rate)) - -class LoadVideo(ComfyNodeABC): - @classmethod - def INPUT_TYPES(cls): - input_dir = folder_paths.get_input_directory() - files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] - files = folder_paths.filter_files_content_types(files, ["video"]) - return {"required": - {"file": (sorted(files), {"video_upload": True})}, - } - - CATEGORY = "image/video" - - RETURN_TYPES = (IO.VIDEO,) - FUNCTION = "load_video" - def load_video(self, file): - video_path = folder_paths.get_annotated_filepath(file) - return (VideoFromFile(video_path),) - - @classmethod - def IS_CHANGED(cls, file): - video_path = folder_paths.get_annotated_filepath(file) - mod_time = os.path.getmtime(video_path) - # Instead of hashing the file, we can just use the modification time to avoid - # rehashing large files. - return mod_time - - @classmethod - def VALIDATE_INPUTS(cls, file): - if not folder_paths.exists_annotated_filepath(file): - return "Invalid video file: {}".format(file) - - return True - -NODE_CLASS_MAPPINGS = { - "SaveWEBM": SaveWEBM, - "SaveVideo": SaveVideo, - "CreateVideo": CreateVideo, - "GetVideoComponents": GetVideoComponents, - "LoadVideo": LoadVideo, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "SaveVideo": "Save Video", - "CreateVideo": "Create Video", - "GetVideoComponents": "Get Video Components", - "LoadVideo": "Load Video", -} diff --git a/comfy_extras/nodes_video_model.py b/comfy_extras/nodes_video_model.py deleted file mode 100644 index 0f760aa26627f7dcc9e0d4483e05dae1326a4c66..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_video_model.py +++ /dev/null @@ -1,161 +0,0 @@ -import nodes -import torch -import comfy.utils -import comfy.sd -import folder_paths -import comfy_extras.nodes_model_merging -import node_helpers - - -class ImageOnlyCheckpointLoader: - @classmethod - def INPUT_TYPES(s): - return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ), - }} - RETURN_TYPES = ("MODEL", "CLIP_VISION", "VAE") - FUNCTION = "load_checkpoint" - - CATEGORY = "loaders/video_models" - - def load_checkpoint(self, ckpt_name, output_vae=True, output_clip=True): - ckpt_path = folder_paths.get_full_path_or_raise("checkpoints", ckpt_name) - out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=False, output_clipvision=True, embedding_directory=folder_paths.get_folder_paths("embeddings")) - return (out[0], out[3], out[2]) - - -class SVD_img2vid_Conditioning: - @classmethod - def INPUT_TYPES(s): - return {"required": { "clip_vision": ("CLIP_VISION",), - "init_image": ("IMAGE",), - "vae": ("VAE",), - "width": ("INT", {"default": 1024, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 576, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}), - "video_frames": ("INT", {"default": 14, "min": 1, "max": 4096}), - "motion_bucket_id": ("INT", {"default": 127, "min": 1, "max": 1023}), - "fps": ("INT", {"default": 6, "min": 1, "max": 1024}), - "augmentation_level": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01}) - }} - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, clip_vision, init_image, vae, width, height, video_frames, motion_bucket_id, fps, augmentation_level): - output = clip_vision.encode_image(init_image) - pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) - encode_pixels = pixels[:,:,:,:3] - if augmentation_level > 0: - encode_pixels += torch.randn_like(pixels) * augmentation_level - t = vae.encode(encode_pixels) - positive = [[pooled, {"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level, "concat_latent_image": t}]] - negative = [[torch.zeros_like(pooled), {"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level, "concat_latent_image": torch.zeros_like(t)}]] - latent = torch.zeros([video_frames, 4, height // 8, width // 8]) - return (positive, negative, {"samples":latent}) - -class VideoLinearCFGGuidance: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "min_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "sampling/video_models" - - def patch(self, model, min_cfg): - def linear_cfg(args): - cond = args["cond"] - uncond = args["uncond"] - cond_scale = args["cond_scale"] - - scale = torch.linspace(min_cfg, cond_scale, cond.shape[0], device=cond.device).reshape((cond.shape[0], 1, 1, 1)) - return uncond + scale * (cond - uncond) - - m = model.clone() - m.set_model_sampler_cfg_function(linear_cfg) - return (m, ) - -class VideoTriangleCFGGuidance: - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "min_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}), - }} - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - - CATEGORY = "sampling/video_models" - - def patch(self, model, min_cfg): - def linear_cfg(args): - cond = args["cond"] - uncond = args["uncond"] - cond_scale = args["cond_scale"] - period = 1.0 - values = torch.linspace(0, 1, cond.shape[0], device=cond.device) - values = 2 * (values / period - torch.floor(values / period + 0.5)).abs() - scale = (values * (cond_scale - min_cfg) + min_cfg).reshape((cond.shape[0], 1, 1, 1)) - - return uncond + scale * (cond - uncond) - - m = model.clone() - m.set_model_sampler_cfg_function(linear_cfg) - return (m, ) - -class ImageOnlyCheckpointSave(comfy_extras.nodes_model_merging.CheckpointSave): - CATEGORY = "advanced/model_merging" - - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "clip_vision": ("CLIP_VISION",), - "vae": ("VAE",), - "filename_prefix": ("STRING", {"default": "checkpoints/ComfyUI"}),}, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},} - - def save(self, model, clip_vision, vae, filename_prefix, prompt=None, extra_pnginfo=None): - comfy_extras.nodes_model_merging.save_checkpoint(model, clip_vision=clip_vision, vae=vae, filename_prefix=filename_prefix, output_dir=self.output_dir, prompt=prompt, extra_pnginfo=extra_pnginfo) - return {} - - -class ConditioningSetAreaPercentageVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"conditioning": ("CONDITIONING", ), - "width": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}), - "height": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}), - "temporal": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}), - "x": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}), - "y": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}), - "z": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - }} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "append" - - CATEGORY = "conditioning" - - def append(self, conditioning, width, height, temporal, x, y, z, strength): - c = node_helpers.conditioning_set_values(conditioning, {"area": ("percentage", temporal, height, width, z, y, x), - "strength": strength, - "set_area_to_bounds": False}) - return (c, ) - - -NODE_CLASS_MAPPINGS = { - "ImageOnlyCheckpointLoader": ImageOnlyCheckpointLoader, - "SVD_img2vid_Conditioning": SVD_img2vid_Conditioning, - "VideoLinearCFGGuidance": VideoLinearCFGGuidance, - "VideoTriangleCFGGuidance": VideoTriangleCFGGuidance, - "ImageOnlyCheckpointSave": ImageOnlyCheckpointSave, - "ConditioningSetAreaPercentageVideo": ConditioningSetAreaPercentageVideo, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "ImageOnlyCheckpointLoader": "Image Only Checkpoint Loader (img2vid model)", -} diff --git a/comfy_extras/nodes_wan.py b/comfy_extras/nodes_wan.py deleted file mode 100644 index d6097a10448f3adaab4449ac2bfc7e5096347b60..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_wan.py +++ /dev/null @@ -1,395 +0,0 @@ -import nodes -import node_helpers -import torch -import comfy.model_management -import comfy.utils -import comfy.latent_formats -import comfy.clip_vision - - -class WanImageToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ), - "start_image": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, clip_vision_output=None): - latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - if start_image is not None: - start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - image = torch.ones((length, height, width, start_image.shape[-1]), device=start_image.device, dtype=start_image.dtype) * 0.5 - image[:start_image.shape[0]] = start_image - - concat_latent_image = vae.encode(image[:, :, :, :3]) - mask = torch.ones((1, 1, latent.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), device=start_image.device, dtype=start_image.dtype) - mask[:, :, :((start_image.shape[0] - 1) // 4) + 1] = 0.0 - - positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask}) - negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask}) - - if clip_vision_output is not None: - positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output}) - negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output}) - - out_latent = {} - out_latent["samples"] = latent - return (positive, negative, out_latent) - - -class WanFunControlToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ), - "start_image": ("IMAGE", ), - "control_video": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, clip_vision_output=None, control_video=None): - latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - concat_latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - concat_latent = comfy.latent_formats.Wan21().process_out(concat_latent) - concat_latent = concat_latent.repeat(1, 2, 1, 1, 1) - - if start_image is not None: - start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - concat_latent_image = vae.encode(start_image[:, :, :, :3]) - concat_latent[:,16:,:concat_latent_image.shape[2]] = concat_latent_image[:,:,:concat_latent.shape[2]] - - if control_video is not None: - control_video = comfy.utils.common_upscale(control_video[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - concat_latent_image = vae.encode(control_video[:, :, :, :3]) - concat_latent[:,:16,:concat_latent_image.shape[2]] = concat_latent_image[:,:,:concat_latent.shape[2]] - - positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent}) - negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent}) - - if clip_vision_output is not None: - positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output}) - negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output}) - - out_latent = {} - out_latent["samples"] = latent - return (positive, negative, out_latent) - -class WanFirstLastFrameToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"clip_vision_start_image": ("CLIP_VISION_OUTPUT", ), - "clip_vision_end_image": ("CLIP_VISION_OUTPUT", ), - "start_image": ("IMAGE", ), - "end_image": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, end_image=None, clip_vision_start_image=None, clip_vision_end_image=None): - latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - if start_image is not None: - start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - if end_image is not None: - end_image = comfy.utils.common_upscale(end_image[-length:].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - - image = torch.ones((length, height, width, 3)) * 0.5 - mask = torch.ones((1, 1, latent.shape[2] * 4, latent.shape[-2], latent.shape[-1])) - - if start_image is not None: - image[:start_image.shape[0]] = start_image - mask[:, :, :start_image.shape[0] + 3] = 0.0 - - if end_image is not None: - image[-end_image.shape[0]:] = end_image - mask[:, :, -end_image.shape[0]:] = 0.0 - - concat_latent_image = vae.encode(image[:, :, :, :3]) - mask = mask.view(1, mask.shape[2] // 4, 4, mask.shape[3], mask.shape[4]).transpose(1, 2) - positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask}) - negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask}) - - if clip_vision_start_image is not None: - clip_vision_output = clip_vision_start_image - - if clip_vision_end_image is not None: - if clip_vision_output is not None: - states = torch.cat([clip_vision_output.penultimate_hidden_states, clip_vision_end_image.penultimate_hidden_states], dim=-2) - clip_vision_output = comfy.clip_vision.Output() - clip_vision_output.penultimate_hidden_states = states - else: - clip_vision_output = clip_vision_end_image - - if clip_vision_output is not None: - positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output}) - negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output}) - - out_latent = {} - out_latent["samples"] = latent - return (positive, negative, out_latent) - - -class WanFunInpaintToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ), - "start_image": ("IMAGE", ), - "end_image": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, end_image=None, clip_vision_output=None): - flfv = WanFirstLastFrameToVideo() - return flfv.encode(positive, negative, vae, width, height, length, batch_size, start_image=start_image, end_image=end_image, clip_vision_start_image=clip_vision_output) - - -class WanVaceToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1000.0, "step": 0.01}), - }, - "optional": {"control_video": ("IMAGE", ), - "control_masks": ("MASK", ), - "reference_image": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT", "INT") - RETURN_NAMES = ("positive", "negative", "latent", "trim_latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - EXPERIMENTAL = True - - def encode(self, positive, negative, vae, width, height, length, batch_size, strength, control_video=None, control_masks=None, reference_image=None): - latent_length = ((length - 1) // 4) + 1 - if control_video is not None: - control_video = comfy.utils.common_upscale(control_video[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - if control_video.shape[0] < length: - control_video = torch.nn.functional.pad(control_video, (0, 0, 0, 0, 0, 0, 0, length - control_video.shape[0]), value=0.5) - else: - control_video = torch.ones((length, height, width, 3)) * 0.5 - - if reference_image is not None: - reference_image = comfy.utils.common_upscale(reference_image[:1].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - reference_image = vae.encode(reference_image[:, :, :, :3]) - reference_image = torch.cat([reference_image, comfy.latent_formats.Wan21().process_out(torch.zeros_like(reference_image))], dim=1) - - if control_masks is None: - mask = torch.ones((length, height, width, 1)) - else: - mask = control_masks - if mask.ndim == 3: - mask = mask.unsqueeze(1) - mask = comfy.utils.common_upscale(mask[:length], width, height, "bilinear", "center").movedim(1, -1) - if mask.shape[0] < length: - mask = torch.nn.functional.pad(mask, (0, 0, 0, 0, 0, 0, 0, length - mask.shape[0]), value=1.0) - - control_video = control_video - 0.5 - inactive = (control_video * (1 - mask)) + 0.5 - reactive = (control_video * mask) + 0.5 - - inactive = vae.encode(inactive[:, :, :, :3]) - reactive = vae.encode(reactive[:, :, :, :3]) - control_video_latent = torch.cat((inactive, reactive), dim=1) - if reference_image is not None: - control_video_latent = torch.cat((reference_image, control_video_latent), dim=2) - - vae_stride = 8 - height_mask = height // vae_stride - width_mask = width // vae_stride - mask = mask.view(length, height_mask, vae_stride, width_mask, vae_stride) - mask = mask.permute(2, 4, 0, 1, 3) - mask = mask.reshape(vae_stride * vae_stride, length, height_mask, width_mask) - mask = torch.nn.functional.interpolate(mask.unsqueeze(0), size=(latent_length, height_mask, width_mask), mode='nearest-exact').squeeze(0) - - trim_latent = 0 - if reference_image is not None: - mask_pad = torch.zeros_like(mask[:, :reference_image.shape[2], :, :]) - mask = torch.cat((mask_pad, mask), dim=1) - latent_length += reference_image.shape[2] - trim_latent = reference_image.shape[2] - - mask = mask.unsqueeze(0) - - positive = node_helpers.conditioning_set_values(positive, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True) - negative = node_helpers.conditioning_set_values(negative, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True) - - latent = torch.zeros([batch_size, 16, latent_length, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - out_latent = {} - out_latent["samples"] = latent - return (positive, negative, out_latent, trim_latent) - -class TrimVideoLatent: - @classmethod - def INPUT_TYPES(s): - return {"required": { "samples": ("LATENT",), - "trim_amount": ("INT", {"default": 0, "min": 0, "max": 99999}), - }} - - RETURN_TYPES = ("LATENT",) - FUNCTION = "op" - - CATEGORY = "latent/video" - - EXPERIMENTAL = True - - def op(self, samples, trim_amount): - samples_out = samples.copy() - - s1 = samples["samples"] - samples_out["samples"] = s1[:, :, trim_amount:] - return (samples_out,) - -class WanCameraImageToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ), - "start_image": ("IMAGE", ), - "camera_conditions": ("WAN_CAMERA_EMBEDDING", ), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, clip_vision_output=None, camera_conditions=None): - latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - concat_latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - concat_latent = comfy.latent_formats.Wan21().process_out(concat_latent) - - if start_image is not None: - start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - concat_latent_image = vae.encode(start_image[:, :, :, :3]) - concat_latent[:,:,:concat_latent_image.shape[2]] = concat_latent_image[:,:,:concat_latent.shape[2]] - - positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent}) - negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent}) - - if camera_conditions is not None: - positive = node_helpers.conditioning_set_values(positive, {'camera_conditions': camera_conditions}) - negative = node_helpers.conditioning_set_values(negative, {'camera_conditions': camera_conditions}) - - if clip_vision_output is not None: - positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output}) - negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output}) - - out_latent = {} - out_latent["samples"] = latent - return (positive, negative, out_latent) - -class WanPhantomSubjectToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": {"positive": ("CONDITIONING", ), - "negative": ("CONDITIONING", ), - "vae": ("VAE", ), - "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": {"images": ("IMAGE", ), - }} - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative_text", "negative_img_text", "latent") - FUNCTION = "encode" - - CATEGORY = "conditioning/video_models" - - def encode(self, positive, negative, vae, width, height, length, batch_size, images): - latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) - cond2 = negative - if images is not None: - images = comfy.utils.common_upscale(images[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - latent_images = [] - for i in images: - latent_images += [vae.encode(i.unsqueeze(0)[:, :, :, :3])] - concat_latent_image = torch.cat(latent_images, dim=2) - - positive = node_helpers.conditioning_set_values(positive, {"time_dim_concat": concat_latent_image}) - cond2 = node_helpers.conditioning_set_values(negative, {"time_dim_concat": concat_latent_image}) - negative = node_helpers.conditioning_set_values(negative, {"time_dim_concat": comfy.latent_formats.Wan21().process_out(torch.zeros_like(concat_latent_image))}) - - out_latent = {} - out_latent["samples"] = latent - return (positive, cond2, negative, out_latent) - -NODE_CLASS_MAPPINGS = { - "WanImageToVideo": WanImageToVideo, - "WanFunControlToVideo": WanFunControlToVideo, - "WanFunInpaintToVideo": WanFunInpaintToVideo, - "WanFirstLastFrameToVideo": WanFirstLastFrameToVideo, - "WanVaceToVideo": WanVaceToVideo, - "TrimVideoLatent": TrimVideoLatent, - "WanCameraImageToVideo": WanCameraImageToVideo, - "WanPhantomSubjectToVideo": WanPhantomSubjectToVideo, -} diff --git a/comfy_extras/nodes_webcam.py b/comfy_extras/nodes_webcam.py deleted file mode 100644 index 5bf80b4c6e055f70ed277ec0a9bbbeb180151a3d..0000000000000000000000000000000000000000 --- a/comfy_extras/nodes_webcam.py +++ /dev/null @@ -1,37 +0,0 @@ -import nodes -import folder_paths - -MAX_RESOLUTION = nodes.MAX_RESOLUTION - - -class WebcamCapture(nodes.LoadImage): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("WEBCAM", {}), - "width": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "height": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - "capture_on_queue": ("BOOLEAN", {"default": True}), - } - } - RETURN_TYPES = ("IMAGE",) - FUNCTION = "load_capture" - - CATEGORY = "image" - - def load_capture(self, image, **kwargs): - return super().load_image(folder_paths.get_annotated_filepath(image)) - - @classmethod - def IS_CHANGED(cls, image, width, height, capture_on_queue): - return super().IS_CHANGED(image) - - -NODE_CLASS_MAPPINGS = { - "WebcamCapture": WebcamCapture, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "WebcamCapture": "Webcam Capture", -}