mihaiciorobitca commited on
Commit
9a64566
·
verified ·
1 Parent(s): 8c3b946

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. ComfyUI/comfy_extras/nodes_attention_multiply.py +120 -0
  3. ComfyUI/comfy_extras/nodes_clip_sdxl.py +54 -0
  4. ComfyUI/comfy_extras/nodes_controlnet.py +60 -0
  5. ComfyUI/comfy_extras/nodes_differential_diffusion.py +42 -0
  6. ComfyUI/comfy_extras/nodes_freelunch.py +113 -0
  7. ComfyUI/comfy_extras/nodes_fresca.py +103 -0
  8. ComfyUI/comfy_extras/nodes_gits.py +369 -0
  9. ComfyUI/comfy_extras/nodes_hidream.py +55 -0
  10. ComfyUI/comfy_extras/nodes_hooks.py +745 -0
  11. ComfyUI/comfy_extras/nodes_hunyuan.py +123 -0
  12. ComfyUI/comfy_extras/nodes_hunyuan3d.py +634 -0
  13. ComfyUI/comfy_extras/nodes_hypernetwork.py +120 -0
  14. ComfyUI/comfy_extras/nodes_hypertile.py +81 -0
  15. ComfyUI/comfy_extras/nodes_images.py +642 -0
  16. ComfyUI/comfy_extras/nodes_ip2p.py +45 -0
  17. ComfyUI/comfy_extras/nodes_latent.py +288 -0
  18. ComfyUI/comfy_extras/nodes_load_3d.py +182 -0
  19. ComfyUI/comfy_extras/nodes_lora_extract.py +119 -0
  20. ComfyUI/comfy_extras/nodes_lotus.py +29 -0
  21. ComfyUI/comfy_extras/nodes_lt.py +474 -0
  22. ComfyUI/comfy_extras/nodes_lumina2.py +104 -0
  23. ComfyUI/comfy_extras/nodes_mahiro.py +41 -0
  24. ComfyUI/comfy_extras/nodes_mask.py +412 -0
  25. ComfyUI/comfy_extras/nodes_mochi.py +23 -0
  26. ComfyUI/comfy_extras/nodes_model_advanced.py +329 -0
  27. ComfyUI/comfy_extras/nodes_pag.py +56 -0
  28. ComfyUI/comfy_extras/nodes_perpneg.py +146 -0
  29. ComfyUI/comfy_extras/nodes_preview_any.py +43 -0
  30. ComfyUI/comfy_extras/nodes_tcfg.py +71 -0
  31. ComfyUI/comfy_extras/nodes_tomesd.py +176 -0
  32. ComfyUI/comfy_extras/nodes_torch_compile.py +23 -0
  33. ComfyUI/comfy_extras/nodes_train.py +877 -0
  34. ComfyUI/comfy_extras/nodes_video.py +241 -0
  35. ComfyUI/comfy_extras/nodes_wan.py +742 -0
  36. ComfyUI/custom_nodes/comfyui-kjnodes/web/js/jsnodes.js +413 -0
  37. ComfyUI/models/configs/anything_v3.yaml +73 -0
  38. ComfyUI/models/configs/v1-inference.yaml +70 -0
  39. ComfyUI/models/configs/v1-inference_clip_skip_2.yaml +73 -0
  40. ComfyUI/models/configs/v1-inference_clip_skip_2_fp16.yaml +74 -0
  41. ComfyUI/models/configs/v1-inference_fp16.yaml +71 -0
  42. ComfyUI/models/configs/v1-inpainting-inference.yaml +71 -0
  43. ComfyUI/models/configs/v2-inference-v.yaml +68 -0
  44. ComfyUI/models/configs/v2-inference-v_fp32.yaml +68 -0
  45. ComfyUI/models/configs/v2-inference.yaml +67 -0
  46. ComfyUI/models/configs/v2-inference_fp32.yaml +67 -0
  47. ComfyUI/models/configs/v2-inpainting-inference.yaml +158 -0
  48. ComfyUI/models/controlnet/put_controlnets_and_t2i_here +0 -0
  49. ComfyUI/models/diffusers/put_diffusers_models_here +0 -0
  50. ComfyUI/models/diffusion_models/put_diffusion_model_files_here.safetensors +0 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.whl filter=lfs diff=lfs merge=lfs -text
ComfyUI/comfy_extras/nodes_attention_multiply.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def attention_multiply(attn, model, q, k, v, out):
3
+ m = model.clone()
4
+ sd = model.model_state_dict()
5
+
6
+ for key in sd:
7
+ if key.endswith("{}.to_q.bias".format(attn)) or key.endswith("{}.to_q.weight".format(attn)):
8
+ m.add_patches({key: (None,)}, 0.0, q)
9
+ if key.endswith("{}.to_k.bias".format(attn)) or key.endswith("{}.to_k.weight".format(attn)):
10
+ m.add_patches({key: (None,)}, 0.0, k)
11
+ if key.endswith("{}.to_v.bias".format(attn)) or key.endswith("{}.to_v.weight".format(attn)):
12
+ m.add_patches({key: (None,)}, 0.0, v)
13
+ if key.endswith("{}.to_out.0.bias".format(attn)) or key.endswith("{}.to_out.0.weight".format(attn)):
14
+ m.add_patches({key: (None,)}, 0.0, out)
15
+
16
+ return m
17
+
18
+
19
+ class UNetSelfAttentionMultiply:
20
+ @classmethod
21
+ def INPUT_TYPES(s):
22
+ return {"required": { "model": ("MODEL",),
23
+ "q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
24
+ "k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
25
+ "v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
26
+ "out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
27
+ }}
28
+ RETURN_TYPES = ("MODEL",)
29
+ FUNCTION = "patch"
30
+
31
+ CATEGORY = "_for_testing/attention_experiments"
32
+
33
+ def patch(self, model, q, k, v, out):
34
+ m = attention_multiply("attn1", model, q, k, v, out)
35
+ return (m, )
36
+
37
+ class UNetCrossAttentionMultiply:
38
+ @classmethod
39
+ def INPUT_TYPES(s):
40
+ return {"required": { "model": ("MODEL",),
41
+ "q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
42
+ "k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
43
+ "v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
44
+ "out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
45
+ }}
46
+ RETURN_TYPES = ("MODEL",)
47
+ FUNCTION = "patch"
48
+
49
+ CATEGORY = "_for_testing/attention_experiments"
50
+
51
+ def patch(self, model, q, k, v, out):
52
+ m = attention_multiply("attn2", model, q, k, v, out)
53
+ return (m, )
54
+
55
+ class CLIPAttentionMultiply:
56
+ @classmethod
57
+ def INPUT_TYPES(s):
58
+ return {"required": { "clip": ("CLIP",),
59
+ "q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
60
+ "k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
61
+ "v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
62
+ "out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
63
+ }}
64
+ RETURN_TYPES = ("CLIP",)
65
+ FUNCTION = "patch"
66
+
67
+ CATEGORY = "_for_testing/attention_experiments"
68
+
69
+ def patch(self, clip, q, k, v, out):
70
+ m = clip.clone()
71
+ sd = m.patcher.model_state_dict()
72
+
73
+ for key in sd:
74
+ if key.endswith("self_attn.q_proj.weight") or key.endswith("self_attn.q_proj.bias"):
75
+ m.add_patches({key: (None,)}, 0.0, q)
76
+ if key.endswith("self_attn.k_proj.weight") or key.endswith("self_attn.k_proj.bias"):
77
+ m.add_patches({key: (None,)}, 0.0, k)
78
+ if key.endswith("self_attn.v_proj.weight") or key.endswith("self_attn.v_proj.bias"):
79
+ m.add_patches({key: (None,)}, 0.0, v)
80
+ if key.endswith("self_attn.out_proj.weight") or key.endswith("self_attn.out_proj.bias"):
81
+ m.add_patches({key: (None,)}, 0.0, out)
82
+ return (m, )
83
+
84
+ class UNetTemporalAttentionMultiply:
85
+ @classmethod
86
+ def INPUT_TYPES(s):
87
+ return {"required": { "model": ("MODEL",),
88
+ "self_structural": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
89
+ "self_temporal": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
90
+ "cross_structural": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
91
+ "cross_temporal": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
92
+ }}
93
+ RETURN_TYPES = ("MODEL",)
94
+ FUNCTION = "patch"
95
+
96
+ CATEGORY = "_for_testing/attention_experiments"
97
+
98
+ def patch(self, model, self_structural, self_temporal, cross_structural, cross_temporal):
99
+ m = model.clone()
100
+ sd = model.model_state_dict()
101
+
102
+ for k in sd:
103
+ if (k.endswith("attn1.to_out.0.bias") or k.endswith("attn1.to_out.0.weight")):
104
+ if '.time_stack.' in k:
105
+ m.add_patches({k: (None,)}, 0.0, self_temporal)
106
+ else:
107
+ m.add_patches({k: (None,)}, 0.0, self_structural)
108
+ elif (k.endswith("attn2.to_out.0.bias") or k.endswith("attn2.to_out.0.weight")):
109
+ if '.time_stack.' in k:
110
+ m.add_patches({k: (None,)}, 0.0, cross_temporal)
111
+ else:
112
+ m.add_patches({k: (None,)}, 0.0, cross_structural)
113
+ return (m, )
114
+
115
+ NODE_CLASS_MAPPINGS = {
116
+ "UNetSelfAttentionMultiply": UNetSelfAttentionMultiply,
117
+ "UNetCrossAttentionMultiply": UNetCrossAttentionMultiply,
118
+ "CLIPAttentionMultiply": CLIPAttentionMultiply,
119
+ "UNetTemporalAttentionMultiply": UNetTemporalAttentionMultiply,
120
+ }
ComfyUI/comfy_extras/nodes_clip_sdxl.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from nodes import MAX_RESOLUTION
2
+
3
+ class CLIPTextEncodeSDXLRefiner:
4
+ @classmethod
5
+ def INPUT_TYPES(s):
6
+ return {"required": {
7
+ "ascore": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
8
+ "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
9
+ "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
10
+ "text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", ),
11
+ }}
12
+ RETURN_TYPES = ("CONDITIONING",)
13
+ FUNCTION = "encode"
14
+
15
+ CATEGORY = "advanced/conditioning"
16
+
17
+ def encode(self, clip, ascore, width, height, text):
18
+ tokens = clip.tokenize(text)
19
+ return (clip.encode_from_tokens_scheduled(tokens, add_dict={"aesthetic_score": ascore, "width": width, "height": height}), )
20
+
21
+ class CLIPTextEncodeSDXL:
22
+ @classmethod
23
+ def INPUT_TYPES(s):
24
+ return {"required": {
25
+ "clip": ("CLIP", ),
26
+ "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
27
+ "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
28
+ "crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
29
+ "crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
30
+ "target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
31
+ "target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
32
+ "text_g": ("STRING", {"multiline": True, "dynamicPrompts": True}),
33
+ "text_l": ("STRING", {"multiline": True, "dynamicPrompts": True}),
34
+ }}
35
+ RETURN_TYPES = ("CONDITIONING",)
36
+ FUNCTION = "encode"
37
+
38
+ CATEGORY = "advanced/conditioning"
39
+
40
+ def encode(self, clip, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l):
41
+ tokens = clip.tokenize(text_g)
42
+ tokens["l"] = clip.tokenize(text_l)["l"]
43
+ if len(tokens["l"]) != len(tokens["g"]):
44
+ empty = clip.tokenize("")
45
+ while len(tokens["l"]) < len(tokens["g"]):
46
+ tokens["l"] += empty["l"]
47
+ while len(tokens["l"]) > len(tokens["g"]):
48
+ tokens["g"] += empty["g"]
49
+ 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}), )
50
+
51
+ NODE_CLASS_MAPPINGS = {
52
+ "CLIPTextEncodeSDXLRefiner": CLIPTextEncodeSDXLRefiner,
53
+ "CLIPTextEncodeSDXL": CLIPTextEncodeSDXL,
54
+ }
ComfyUI/comfy_extras/nodes_controlnet.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from comfy.cldm.control_types import UNION_CONTROLNET_TYPES
2
+ import nodes
3
+ import comfy.utils
4
+
5
+ class SetUnionControlNetType:
6
+ @classmethod
7
+ def INPUT_TYPES(s):
8
+ return {"required": {"control_net": ("CONTROL_NET", ),
9
+ "type": (["auto"] + list(UNION_CONTROLNET_TYPES.keys()),)
10
+ }}
11
+
12
+ CATEGORY = "conditioning/controlnet"
13
+ RETURN_TYPES = ("CONTROL_NET",)
14
+
15
+ FUNCTION = "set_controlnet_type"
16
+
17
+ def set_controlnet_type(self, control_net, type):
18
+ control_net = control_net.copy()
19
+ type_number = UNION_CONTROLNET_TYPES.get(type, -1)
20
+ if type_number >= 0:
21
+ control_net.set_extra_arg("control_type", [type_number])
22
+ else:
23
+ control_net.set_extra_arg("control_type", [])
24
+
25
+ return (control_net,)
26
+
27
+ class ControlNetInpaintingAliMamaApply(nodes.ControlNetApplyAdvanced):
28
+ @classmethod
29
+ def INPUT_TYPES(s):
30
+ return {"required": {"positive": ("CONDITIONING", ),
31
+ "negative": ("CONDITIONING", ),
32
+ "control_net": ("CONTROL_NET", ),
33
+ "vae": ("VAE", ),
34
+ "image": ("IMAGE", ),
35
+ "mask": ("MASK", ),
36
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
37
+ "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
38
+ "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001})
39
+ }}
40
+
41
+ FUNCTION = "apply_inpaint_controlnet"
42
+
43
+ CATEGORY = "conditioning/controlnet"
44
+
45
+ def apply_inpaint_controlnet(self, positive, negative, control_net, vae, image, mask, strength, start_percent, end_percent):
46
+ extra_concat = []
47
+ if control_net.concat_mask:
48
+ mask = 1.0 - mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1]))
49
+ mask_apply = comfy.utils.common_upscale(mask, image.shape[2], image.shape[1], "bilinear", "center").round()
50
+ image = image * mask_apply.movedim(1, -1).repeat(1, 1, 1, image.shape[3])
51
+ extra_concat = [mask]
52
+
53
+ return self.apply_controlnet(positive, negative, control_net, image, strength, start_percent, end_percent, vae=vae, extra_concat=extra_concat)
54
+
55
+
56
+
57
+ NODE_CLASS_MAPPINGS = {
58
+ "SetUnionControlNetType": SetUnionControlNetType,
59
+ "ControlNetInpaintingAliMamaApply": ControlNetInpaintingAliMamaApply,
60
+ }
ComfyUI/comfy_extras/nodes_differential_diffusion.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # code adapted from https://github.com/exx8/differential-diffusion
2
+
3
+ import torch
4
+
5
+ class DifferentialDiffusion():
6
+ @classmethod
7
+ def INPUT_TYPES(s):
8
+ return {"required": {"model": ("MODEL", ),
9
+ }}
10
+ RETURN_TYPES = ("MODEL",)
11
+ FUNCTION = "apply"
12
+ CATEGORY = "_for_testing"
13
+ INIT = False
14
+
15
+ def apply(self, model):
16
+ model = model.clone()
17
+ model.set_model_denoise_mask_function(self.forward)
18
+ return (model,)
19
+
20
+ def forward(self, sigma: torch.Tensor, denoise_mask: torch.Tensor, extra_options: dict):
21
+ model = extra_options["model"]
22
+ step_sigmas = extra_options["sigmas"]
23
+ sigma_to = model.inner_model.model_sampling.sigma_min
24
+ if step_sigmas[-1] > sigma_to:
25
+ sigma_to = step_sigmas[-1]
26
+ sigma_from = step_sigmas[0]
27
+
28
+ ts_from = model.inner_model.model_sampling.timestep(sigma_from)
29
+ ts_to = model.inner_model.model_sampling.timestep(sigma_to)
30
+ current_ts = model.inner_model.model_sampling.timestep(sigma[0])
31
+
32
+ threshold = (current_ts - ts_to) / (ts_from - ts_to)
33
+
34
+ return (denoise_mask >= threshold).to(denoise_mask.dtype)
35
+
36
+
37
+ NODE_CLASS_MAPPINGS = {
38
+ "DifferentialDiffusion": DifferentialDiffusion,
39
+ }
40
+ NODE_DISPLAY_NAME_MAPPINGS = {
41
+ "DifferentialDiffusion": "Differential Diffusion",
42
+ }
ComfyUI/comfy_extras/nodes_freelunch.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #code originally taken from: https://github.com/ChenyangSi/FreeU (under MIT License)
2
+
3
+ import torch
4
+ import logging
5
+
6
+ def Fourier_filter(x, threshold, scale):
7
+ # FFT
8
+ x_freq = torch.fft.fftn(x.float(), dim=(-2, -1))
9
+ x_freq = torch.fft.fftshift(x_freq, dim=(-2, -1))
10
+
11
+ B, C, H, W = x_freq.shape
12
+ mask = torch.ones((B, C, H, W), device=x.device)
13
+
14
+ crow, ccol = H // 2, W //2
15
+ mask[..., crow - threshold:crow + threshold, ccol - threshold:ccol + threshold] = scale
16
+ x_freq = x_freq * mask
17
+
18
+ # IFFT
19
+ x_freq = torch.fft.ifftshift(x_freq, dim=(-2, -1))
20
+ x_filtered = torch.fft.ifftn(x_freq, dim=(-2, -1)).real
21
+
22
+ return x_filtered.to(x.dtype)
23
+
24
+
25
+ class FreeU:
26
+ @classmethod
27
+ def INPUT_TYPES(s):
28
+ return {"required": { "model": ("MODEL",),
29
+ "b1": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 10.0, "step": 0.01}),
30
+ "b2": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 10.0, "step": 0.01}),
31
+ "s1": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 10.0, "step": 0.01}),
32
+ "s2": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 10.0, "step": 0.01}),
33
+ }}
34
+ RETURN_TYPES = ("MODEL",)
35
+ FUNCTION = "patch"
36
+
37
+ CATEGORY = "model_patches/unet"
38
+
39
+ def patch(self, model, b1, b2, s1, s2):
40
+ model_channels = model.model.model_config.unet_config["model_channels"]
41
+ scale_dict = {model_channels * 4: (b1, s1), model_channels * 2: (b2, s2)}
42
+ on_cpu_devices = {}
43
+
44
+ def output_block_patch(h, hsp, transformer_options):
45
+ scale = scale_dict.get(int(h.shape[1]), None)
46
+ if scale is not None:
47
+ h[:,:h.shape[1] // 2] = h[:,:h.shape[1] // 2] * scale[0]
48
+ if hsp.device not in on_cpu_devices:
49
+ try:
50
+ hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
51
+ except:
52
+ logging.warning("Device {} does not support the torch.fft functions used in the FreeU node, switching to CPU.".format(hsp.device))
53
+ on_cpu_devices[hsp.device] = True
54
+ hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
55
+ else:
56
+ hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
57
+
58
+ return h, hsp
59
+
60
+ m = model.clone()
61
+ m.set_model_output_block_patch(output_block_patch)
62
+ return (m, )
63
+
64
+ class FreeU_V2:
65
+ @classmethod
66
+ def INPUT_TYPES(s):
67
+ return {"required": { "model": ("MODEL",),
68
+ "b1": ("FLOAT", {"default": 1.3, "min": 0.0, "max": 10.0, "step": 0.01}),
69
+ "b2": ("FLOAT", {"default": 1.4, "min": 0.0, "max": 10.0, "step": 0.01}),
70
+ "s1": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 10.0, "step": 0.01}),
71
+ "s2": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 10.0, "step": 0.01}),
72
+ }}
73
+ RETURN_TYPES = ("MODEL",)
74
+ FUNCTION = "patch"
75
+
76
+ CATEGORY = "model_patches/unet"
77
+
78
+ def patch(self, model, b1, b2, s1, s2):
79
+ model_channels = model.model.model_config.unet_config["model_channels"]
80
+ scale_dict = {model_channels * 4: (b1, s1), model_channels * 2: (b2, s2)}
81
+ on_cpu_devices = {}
82
+
83
+ def output_block_patch(h, hsp, transformer_options):
84
+ scale = scale_dict.get(int(h.shape[1]), None)
85
+ if scale is not None:
86
+ hidden_mean = h.mean(1).unsqueeze(1)
87
+ B = hidden_mean.shape[0]
88
+ hidden_max, _ = torch.max(hidden_mean.view(B, -1), dim=-1, keepdim=True)
89
+ hidden_min, _ = torch.min(hidden_mean.view(B, -1), dim=-1, keepdim=True)
90
+ hidden_mean = (hidden_mean - hidden_min.unsqueeze(2).unsqueeze(3)) / (hidden_max - hidden_min).unsqueeze(2).unsqueeze(3)
91
+
92
+ h[:,:h.shape[1] // 2] = h[:,:h.shape[1] // 2] * ((scale[0] - 1 ) * hidden_mean + 1)
93
+
94
+ if hsp.device not in on_cpu_devices:
95
+ try:
96
+ hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
97
+ except:
98
+ logging.warning("Device {} does not support the torch.fft functions used in the FreeU node, switching to CPU.".format(hsp.device))
99
+ on_cpu_devices[hsp.device] = True
100
+ hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
101
+ else:
102
+ hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
103
+
104
+ return h, hsp
105
+
106
+ m = model.clone()
107
+ m.set_model_output_block_patch(output_block_patch)
108
+ return (m, )
109
+
110
+ NODE_CLASS_MAPPINGS = {
111
+ "FreeU": FreeU,
112
+ "FreeU_V2": FreeU_V2,
113
+ }
ComfyUI/comfy_extras/nodes_fresca.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Code based on https://github.com/WikiChao/FreSca (MIT License)
2
+ import torch
3
+ import torch.fft as fft
4
+
5
+
6
+ def Fourier_filter(x, scale_low=1.0, scale_high=1.5, freq_cutoff=20):
7
+ """
8
+ Apply frequency-dependent scaling to an image tensor using Fourier transforms.
9
+
10
+ Parameters:
11
+ x: Input tensor of shape (B, C, H, W)
12
+ scale_low: Scaling factor for low-frequency components (default: 1.0)
13
+ scale_high: Scaling factor for high-frequency components (default: 1.5)
14
+ freq_cutoff: Number of frequency indices around center to consider as low-frequency (default: 20)
15
+
16
+ Returns:
17
+ x_filtered: Filtered version of x in spatial domain with frequency-specific scaling applied.
18
+ """
19
+ # Preserve input dtype and device
20
+ dtype, device = x.dtype, x.device
21
+
22
+ # Convert to float32 for FFT computations
23
+ x = x.to(torch.float32)
24
+
25
+ # 1) Apply FFT and shift low frequencies to center
26
+ x_freq = fft.fftn(x, dim=(-2, -1))
27
+ x_freq = fft.fftshift(x_freq, dim=(-2, -1))
28
+
29
+ # Initialize mask with high-frequency scaling factor
30
+ mask = torch.ones(x_freq.shape, device=device) * scale_high
31
+ m = mask
32
+ for d in range(len(x_freq.shape) - 2):
33
+ dim = d + 2
34
+ cc = x_freq.shape[dim] // 2
35
+ f_c = min(freq_cutoff, cc)
36
+ m = m.narrow(dim, cc - f_c, f_c * 2)
37
+
38
+ # Apply low-frequency scaling factor to center region
39
+ m[:] = scale_low
40
+
41
+ # 3) Apply frequency-specific scaling
42
+ x_freq = x_freq * mask
43
+
44
+ # 4) Convert back to spatial domain
45
+ x_freq = fft.ifftshift(x_freq, dim=(-2, -1))
46
+ x_filtered = fft.ifftn(x_freq, dim=(-2, -1)).real
47
+
48
+ # 5) Restore original dtype
49
+ x_filtered = x_filtered.to(dtype)
50
+
51
+ return x_filtered
52
+
53
+
54
+ class FreSca:
55
+ @classmethod
56
+ def INPUT_TYPES(s):
57
+ return {
58
+ "required": {
59
+ "model": ("MODEL",),
60
+ "scale_low": ("FLOAT", {"default": 1.0, "min": 0, "max": 10, "step": 0.01,
61
+ "tooltip": "Scaling factor for low-frequency components"}),
62
+ "scale_high": ("FLOAT", {"default": 1.25, "min": 0, "max": 10, "step": 0.01,
63
+ "tooltip": "Scaling factor for high-frequency components"}),
64
+ "freq_cutoff": ("INT", {"default": 20, "min": 1, "max": 10000, "step": 1,
65
+ "tooltip": "Number of frequency indices around center to consider as low-frequency"}),
66
+ }
67
+ }
68
+ RETURN_TYPES = ("MODEL",)
69
+ FUNCTION = "patch"
70
+ CATEGORY = "_for_testing"
71
+ DESCRIPTION = "Applies frequency-dependent scaling to the guidance"
72
+ def patch(self, model, scale_low, scale_high, freq_cutoff):
73
+ def custom_cfg_function(args):
74
+ conds_out = args["conds_out"]
75
+ if len(conds_out) <= 1 or None in args["conds"][:2]:
76
+ return conds_out
77
+ cond = conds_out[0]
78
+ uncond = conds_out[1]
79
+
80
+ guidance = cond - uncond
81
+ filtered_guidance = Fourier_filter(
82
+ guidance,
83
+ scale_low=scale_low,
84
+ scale_high=scale_high,
85
+ freq_cutoff=freq_cutoff,
86
+ )
87
+ filtered_cond = filtered_guidance + uncond
88
+
89
+ return [filtered_cond, uncond] + conds_out[2:]
90
+
91
+ m = model.clone()
92
+ m.set_model_sampler_pre_cfg_function(custom_cfg_function)
93
+
94
+ return (m,)
95
+
96
+
97
+ NODE_CLASS_MAPPINGS = {
98
+ "FreSca": FreSca,
99
+ }
100
+
101
+ NODE_DISPLAY_NAME_MAPPINGS = {
102
+ "FreSca": "FreSca",
103
+ }
ComfyUI/comfy_extras/nodes_gits.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # from https://github.com/zju-pi/diff-sampler/tree/main/gits-main
2
+ import numpy as np
3
+ import torch
4
+
5
+ def loglinear_interp(t_steps, num_steps):
6
+ """
7
+ Performs log-linear interpolation of a given array of decreasing numbers.
8
+ """
9
+ xs = np.linspace(0, 1, len(t_steps))
10
+ ys = np.log(t_steps[::-1])
11
+
12
+ new_xs = np.linspace(0, 1, num_steps)
13
+ new_ys = np.interp(new_xs, xs, ys)
14
+
15
+ interped_ys = np.exp(new_ys)[::-1].copy()
16
+ return interped_ys
17
+
18
+ NOISE_LEVELS = {
19
+ 0.80: [
20
+ [14.61464119, 7.49001646, 0.02916753],
21
+ [14.61464119, 11.54541874, 6.77309084, 0.02916753],
22
+ [14.61464119, 11.54541874, 7.49001646, 3.07277966, 0.02916753],
23
+ [14.61464119, 11.54541874, 7.49001646, 5.85520077, 2.05039096, 0.02916753],
24
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 2.05039096, 0.02916753],
25
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753],
26
+ [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753],
27
+ [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753],
28
+ [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753],
29
+ [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753],
30
+ [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],
31
+ [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],
32
+ [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],
33
+ [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],
34
+ [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],
35
+ [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],
36
+ [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],
37
+ [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],
38
+ [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],
39
+ ],
40
+ 0.85: [
41
+ [14.61464119, 7.49001646, 0.02916753],
42
+ [14.61464119, 7.49001646, 1.84880662, 0.02916753],
43
+ [14.61464119, 11.54541874, 6.77309084, 1.56271636, 0.02916753],
44
+ [14.61464119, 11.54541874, 7.11996698, 3.07277966, 1.24153244, 0.02916753],
45
+ [14.61464119, 11.54541874, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753],
46
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753],
47
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, 0.02916753],
48
+ [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, 0.02916753],
49
+ [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753],
50
+ [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753],
51
+ [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],
52
+ [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],
53
+ [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],
54
+ [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],
55
+ [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],
56
+ [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],
57
+ [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],
58
+ [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],
59
+ [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],
60
+ ],
61
+ 0.90: [
62
+ [14.61464119, 6.77309084, 0.02916753],
63
+ [14.61464119, 7.49001646, 1.56271636, 0.02916753],
64
+ [14.61464119, 7.49001646, 3.07277966, 0.95350921, 0.02916753],
65
+ [14.61464119, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753],
66
+ [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753],
67
+ [14.61464119, 11.54541874, 7.49001646, 5.09240818, 3.07277966, 1.61558151, 0.69515091, 0.02916753],
68
+ [14.61464119, 12.2308979, 8.75849152, 7.11996698, 4.86714602, 3.07277966, 1.61558151, 0.69515091, 0.02916753],
69
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 2.95596409, 1.61558151, 0.69515091, 0.02916753],
70
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.24153244, 0.57119018, 0.02916753],
71
+ [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.24153244, 0.57119018, 0.02916753],
72
+ [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],
73
+ [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],
74
+ [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],
75
+ [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],
76
+ [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],
77
+ [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],
78
+ [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],
79
+ [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],
80
+ [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],
81
+ ],
82
+ 0.95: [
83
+ [14.61464119, 6.77309084, 0.02916753],
84
+ [14.61464119, 6.77309084, 1.56271636, 0.02916753],
85
+ [14.61464119, 7.49001646, 2.84484982, 0.89115214, 0.02916753],
86
+ [14.61464119, 7.49001646, 4.86714602, 2.36326075, 0.803307, 0.02916753],
87
+ [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753],
88
+ [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753],
89
+ [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.91321158, 1.08895338, 0.50118381, 0.02916753],
90
+ [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, 1.08895338, 0.50118381, 0.02916753],
91
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, 1.08895338, 0.50118381, 0.02916753],
92
+ [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.41535246, 0.803307, 0.38853383, 0.02916753],
93
+ [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],
94
+ [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],
95
+ [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],
96
+ [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],
97
+ [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],
98
+ [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],
99
+ [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],
100
+ [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],
101
+ [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],
102
+ ],
103
+ 1.00: [
104
+ [14.61464119, 1.56271636, 0.02916753],
105
+ [14.61464119, 6.77309084, 0.95350921, 0.02916753],
106
+ [14.61464119, 6.77309084, 2.36326075, 0.803307, 0.02916753],
107
+ [14.61464119, 7.11996698, 3.07277966, 1.56271636, 0.59516323, 0.02916753],
108
+ [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.41535246, 0.57119018, 0.02916753],
109
+ [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, 0.02916753],
110
+ [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, 0.02916753],
111
+ [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.72133851, 0.34370604, 0.02916753],
112
+ [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.98035145, 1.24153244, 0.72133851, 0.34370604, 0.02916753],
113
+ [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.27973175, 1.51179266, 0.95350921, 0.54755926, 0.25053367, 0.02916753],
114
+ [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],
115
+ [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],
116
+ [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],
117
+ [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],
118
+ [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],
119
+ [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],
120
+ [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],
121
+ [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],
122
+ [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],
123
+ ],
124
+ 1.05: [
125
+ [14.61464119, 0.95350921, 0.02916753],
126
+ [14.61464119, 6.77309084, 0.89115214, 0.02916753],
127
+ [14.61464119, 6.77309084, 2.05039096, 0.72133851, 0.02916753],
128
+ [14.61464119, 6.77309084, 2.84484982, 1.28281462, 0.52423614, 0.02916753],
129
+ [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.803307, 0.34370604, 0.02916753],
130
+ [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.56271636, 0.803307, 0.34370604, 0.02916753],
131
+ [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.52423614, 0.22545385, 0.02916753],
132
+ [14.61464119, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.74807048, 0.41087446, 0.17026083, 0.02916753],
133
+ [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.27973175, 1.51179266, 0.95350921, 0.59516323, 0.34370604, 0.13792117, 0.02916753],
134
+ [14.61464119, 7.49001646, 5.09240818, 3.46139455, 2.45070267, 1.61558151, 1.08895338, 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753],
135
+ [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],
136
+ [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],
137
+ [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],
138
+ [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],
139
+ [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],
140
+ [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],
141
+ [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],
142
+ [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],
143
+ [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],
144
+ ],
145
+ 1.10: [
146
+ [14.61464119, 0.89115214, 0.02916753],
147
+ [14.61464119, 2.36326075, 0.72133851, 0.02916753],
148
+ [14.61464119, 5.85520077, 1.61558151, 0.57119018, 0.02916753],
149
+ [14.61464119, 6.77309084, 2.45070267, 1.08895338, 0.45573691, 0.02916753],
150
+ [14.61464119, 6.77309084, 2.95596409, 1.56271636, 0.803307, 0.34370604, 0.02916753],
151
+ [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.89115214, 0.4783645, 0.19894916, 0.02916753],
152
+ [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.08895338, 0.64427125, 0.34370604, 0.13792117, 0.02916753],
153
+ [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.54755926, 0.27464288, 0.09824532, 0.02916753],
154
+ [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.4783645, 0.25053367, 0.09824532, 0.02916753],
155
+ [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.05039096, 1.41535246, 0.95350921, 0.64427125, 0.41087446, 0.22545385, 0.09824532, 0.02916753],
156
+ [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],
157
+ [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],
158
+ [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],
159
+ [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],
160
+ [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],
161
+ [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],
162
+ [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],
163
+ [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],
164
+ [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],
165
+ ],
166
+ 1.15: [
167
+ [14.61464119, 0.83188516, 0.02916753],
168
+ [14.61464119, 1.84880662, 0.59516323, 0.02916753],
169
+ [14.61464119, 5.85520077, 1.56271636, 0.52423614, 0.02916753],
170
+ [14.61464119, 5.85520077, 1.91321158, 0.83188516, 0.34370604, 0.02916753],
171
+ [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.59516323, 0.25053367, 0.02916753],
172
+ [14.61464119, 5.85520077, 2.84484982, 1.51179266, 0.803307, 0.41087446, 0.17026083, 0.02916753],
173
+ [14.61464119, 5.85520077, 2.84484982, 1.56271636, 0.89115214, 0.50118381, 0.25053367, 0.09824532, 0.02916753],
174
+ [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.12534678, 0.72133851, 0.43325692, 0.22545385, 0.09824532, 0.02916753],
175
+ [14.61464119, 6.77309084, 3.07277966, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, 0.19894916, 0.09824532, 0.02916753],
176
+ [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, 0.19894916, 0.09824532, 0.02916753],
177
+ [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],
178
+ [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],
179
+ [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],
180
+ [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],
181
+ [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],
182
+ [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],
183
+ [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],
184
+ [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],
185
+ [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],
186
+ ],
187
+ 1.20: [
188
+ [14.61464119, 0.803307, 0.02916753],
189
+ [14.61464119, 1.56271636, 0.52423614, 0.02916753],
190
+ [14.61464119, 2.36326075, 0.92192322, 0.36617002, 0.02916753],
191
+ [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.25053367, 0.02916753],
192
+ [14.61464119, 5.85520077, 2.05039096, 0.95350921, 0.45573691, 0.17026083, 0.02916753],
193
+ [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.64427125, 0.29807833, 0.09824532, 0.02916753],
194
+ [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.803307, 0.45573691, 0.25053367, 0.09824532, 0.02916753],
195
+ [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.95350921, 0.59516323, 0.36617002, 0.19894916, 0.09824532, 0.02916753],
196
+ [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.50118381, 0.32104823, 0.19894916, 0.09824532, 0.02916753],
197
+ [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.24153244, 0.83188516, 0.59516323, 0.41087446, 0.27464288, 0.17026083, 0.09824532, 0.02916753],
198
+ [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],
199
+ [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],
200
+ [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],
201
+ [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],
202
+ [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],
203
+ [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],
204
+ [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],
205
+ [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],
206
+ [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],
207
+ ],
208
+ 1.25: [
209
+ [14.61464119, 0.72133851, 0.02916753],
210
+ [14.61464119, 1.56271636, 0.50118381, 0.02916753],
211
+ [14.61464119, 2.05039096, 0.803307, 0.32104823, 0.02916753],
212
+ [14.61464119, 2.36326075, 0.95350921, 0.43325692, 0.17026083, 0.02916753],
213
+ [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.27464288, 0.09824532, 0.02916753],
214
+ [14.61464119, 3.07277966, 1.51179266, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753],
215
+ [14.61464119, 5.85520077, 2.36326075, 1.24153244, 0.72133851, 0.41087446, 0.22545385, 0.09824532, 0.02916753],
216
+ [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.52423614, 0.34370604, 0.19894916, 0.09824532, 0.02916753],
217
+ [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.98595673, 0.64427125, 0.43325692, 0.27464288, 0.17026083, 0.09824532, 0.02916753],
218
+ [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.52423614, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753],
219
+ [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],
220
+ [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],
221
+ [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],
222
+ [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],
223
+ [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],
224
+ [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],
225
+ [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],
226
+ [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],
227
+ [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],
228
+ ],
229
+ 1.30: [
230
+ [14.61464119, 0.72133851, 0.02916753],
231
+ [14.61464119, 1.24153244, 0.43325692, 0.02916753],
232
+ [14.61464119, 1.56271636, 0.59516323, 0.22545385, 0.02916753],
233
+ [14.61464119, 1.84880662, 0.803307, 0.36617002, 0.13792117, 0.02916753],
234
+ [14.61464119, 2.36326075, 1.01931262, 0.52423614, 0.25053367, 0.09824532, 0.02916753],
235
+ [14.61464119, 2.84484982, 1.36964464, 0.74807048, 0.41087446, 0.22545385, 0.09824532, 0.02916753],
236
+ [14.61464119, 3.07277966, 1.56271636, 0.89115214, 0.54755926, 0.34370604, 0.19894916, 0.09824532, 0.02916753],
237
+ [14.61464119, 3.07277966, 1.61558151, 0.95350921, 0.61951244, 0.41087446, 0.27464288, 0.17026083, 0.09824532, 0.02916753],
238
+ [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.54755926, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753],
239
+ [14.61464119, 5.85520077, 2.45070267, 1.41535246, 0.92192322, 0.64427125, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753],
240
+ [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],
241
+ [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],
242
+ [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],
243
+ [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],
244
+ [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],
245
+ [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],
246
+ [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],
247
+ [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],
248
+ [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],
249
+ ],
250
+ 1.35: [
251
+ [14.61464119, 0.69515091, 0.02916753],
252
+ [14.61464119, 0.95350921, 0.34370604, 0.02916753],
253
+ [14.61464119, 1.56271636, 0.57119018, 0.19894916, 0.02916753],
254
+ [14.61464119, 1.61558151, 0.69515091, 0.29807833, 0.09824532, 0.02916753],
255
+ [14.61464119, 1.84880662, 0.83188516, 0.43325692, 0.22545385, 0.09824532, 0.02916753],
256
+ [14.61464119, 2.45070267, 1.162866, 0.64427125, 0.36617002, 0.19894916, 0.09824532, 0.02916753],
257
+ [14.61464119, 2.84484982, 1.36964464, 0.803307, 0.50118381, 0.32104823, 0.19894916, 0.09824532, 0.02916753],
258
+ [14.61464119, 2.84484982, 1.41535246, 0.83188516, 0.54755926, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753],
259
+ [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.32104823, 0.22545385, 0.17026083, 0.09824532, 0.02916753],
260
+ [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.34370604, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753],
261
+ [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],
262
+ [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],
263
+ [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],
264
+ [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],
265
+ [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],
266
+ [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],
267
+ [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],
268
+ [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],
269
+ [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],
270
+ ],
271
+ 1.40: [
272
+ [14.61464119, 0.59516323, 0.02916753],
273
+ [14.61464119, 0.95350921, 0.34370604, 0.02916753],
274
+ [14.61464119, 1.08895338, 0.43325692, 0.13792117, 0.02916753],
275
+ [14.61464119, 1.56271636, 0.64427125, 0.27464288, 0.09824532, 0.02916753],
276
+ [14.61464119, 1.61558151, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753],
277
+ [14.61464119, 2.05039096, 0.95350921, 0.54755926, 0.34370604, 0.19894916, 0.09824532, 0.02916753],
278
+ [14.61464119, 2.45070267, 1.24153244, 0.72133851, 0.43325692, 0.27464288, 0.17026083, 0.09824532, 0.02916753],
279
+ [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753],
280
+ [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.52423614, 0.36617002, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753],
281
+ [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.54755926, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753],
282
+ [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],
283
+ [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],
284
+ [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],
285
+ [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],
286
+ [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],
287
+ [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],
288
+ [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],
289
+ [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],
290
+ [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],
291
+ ],
292
+ 1.45: [
293
+ [14.61464119, 0.59516323, 0.02916753],
294
+ [14.61464119, 0.803307, 0.25053367, 0.02916753],
295
+ [14.61464119, 0.95350921, 0.34370604, 0.09824532, 0.02916753],
296
+ [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753],
297
+ [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753],
298
+ [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753],
299
+ [14.61464119, 1.91321158, 0.95350921, 0.57119018, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753],
300
+ [14.61464119, 2.19988537, 1.08895338, 0.64427125, 0.41087446, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753],
301
+ [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753],
302
+ [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.36617002, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753],
303
+ [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],
304
+ [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],
305
+ [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],
306
+ [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],
307
+ [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],
308
+ [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],
309
+ [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],
310
+ [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],
311
+ [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],
312
+ ],
313
+ 1.50: [
314
+ [14.61464119, 0.54755926, 0.02916753],
315
+ [14.61464119, 0.803307, 0.25053367, 0.02916753],
316
+ [14.61464119, 0.86115354, 0.32104823, 0.09824532, 0.02916753],
317
+ [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753],
318
+ [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753],
319
+ [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753],
320
+ [14.61464119, 1.61558151, 0.83188516, 0.52423614, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753],
321
+ [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.38853383, 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753],
322
+ [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.41087446, 0.29807833, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753],
323
+ [14.61464119, 1.84880662, 0.95350921, 0.61951244, 0.43325692, 0.32104823, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753],
324
+ [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],
325
+ [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],
326
+ [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],
327
+ [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],
328
+ [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],
329
+ [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],
330
+ [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],
331
+ [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],
332
+ [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],
333
+ ],
334
+ }
335
+
336
+ class GITSScheduler:
337
+ @classmethod
338
+ def INPUT_TYPES(s):
339
+ return {"required":
340
+ {"coeff": ("FLOAT", {"default": 1.20, "min": 0.80, "max": 1.50, "step": 0.05}),
341
+ "steps": ("INT", {"default": 10, "min": 2, "max": 1000}),
342
+ "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
343
+ }
344
+ }
345
+ RETURN_TYPES = ("SIGMAS",)
346
+ CATEGORY = "sampling/custom_sampling/schedulers"
347
+
348
+ FUNCTION = "get_sigmas"
349
+
350
+ def get_sigmas(self, coeff, steps, denoise):
351
+ total_steps = steps
352
+ if denoise < 1.0:
353
+ if denoise <= 0.0:
354
+ return (torch.FloatTensor([]),)
355
+ total_steps = round(steps * denoise)
356
+
357
+ if steps <= 20:
358
+ sigmas = NOISE_LEVELS[round(coeff, 2)][steps-2][:]
359
+ else:
360
+ sigmas = NOISE_LEVELS[round(coeff, 2)][-1][:]
361
+ sigmas = loglinear_interp(sigmas, steps + 1)
362
+
363
+ sigmas = sigmas[-(total_steps + 1):]
364
+ sigmas[-1] = 0
365
+ return (torch.FloatTensor(sigmas), )
366
+
367
+ NODE_CLASS_MAPPINGS = {
368
+ "GITSScheduler": GITSScheduler,
369
+ }
ComfyUI/comfy_extras/nodes_hidream.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import folder_paths
2
+ import comfy.sd
3
+ import comfy.model_management
4
+
5
+
6
+ class QuadrupleCLIPLoader:
7
+ @classmethod
8
+ def INPUT_TYPES(s):
9
+ return {"required": { "clip_name1": (folder_paths.get_filename_list("text_encoders"), ),
10
+ "clip_name2": (folder_paths.get_filename_list("text_encoders"), ),
11
+ "clip_name3": (folder_paths.get_filename_list("text_encoders"), ),
12
+ "clip_name4": (folder_paths.get_filename_list("text_encoders"), )
13
+ }}
14
+ RETURN_TYPES = ("CLIP",)
15
+ FUNCTION = "load_clip"
16
+
17
+ CATEGORY = "advanced/loaders"
18
+
19
+ DESCRIPTION = "[Recipes]\n\nhidream: long clip-l, long clip-g, t5xxl, llama_8b_3.1_instruct"
20
+
21
+ def load_clip(self, clip_name1, clip_name2, clip_name3, clip_name4):
22
+ clip_path1 = folder_paths.get_full_path_or_raise("text_encoders", clip_name1)
23
+ clip_path2 = folder_paths.get_full_path_or_raise("text_encoders", clip_name2)
24
+ clip_path3 = folder_paths.get_full_path_or_raise("text_encoders", clip_name3)
25
+ clip_path4 = folder_paths.get_full_path_or_raise("text_encoders", clip_name4)
26
+ clip = comfy.sd.load_clip(ckpt_paths=[clip_path1, clip_path2, clip_path3, clip_path4], embedding_directory=folder_paths.get_folder_paths("embeddings"))
27
+ return (clip,)
28
+
29
+ class CLIPTextEncodeHiDream:
30
+ @classmethod
31
+ def INPUT_TYPES(s):
32
+ return {"required": {
33
+ "clip": ("CLIP", ),
34
+ "clip_l": ("STRING", {"multiline": True, "dynamicPrompts": True}),
35
+ "clip_g": ("STRING", {"multiline": True, "dynamicPrompts": True}),
36
+ "t5xxl": ("STRING", {"multiline": True, "dynamicPrompts": True}),
37
+ "llama": ("STRING", {"multiline": True, "dynamicPrompts": True})
38
+ }}
39
+ RETURN_TYPES = ("CONDITIONING",)
40
+ FUNCTION = "encode"
41
+
42
+ CATEGORY = "advanced/conditioning"
43
+
44
+ def encode(self, clip, clip_l, clip_g, t5xxl, llama):
45
+
46
+ tokens = clip.tokenize(clip_g)
47
+ tokens["l"] = clip.tokenize(clip_l)["l"]
48
+ tokens["t5xxl"] = clip.tokenize(t5xxl)["t5xxl"]
49
+ tokens["llama"] = clip.tokenize(llama)["llama"]
50
+ return (clip.encode_from_tokens_scheduled(tokens), )
51
+
52
+ NODE_CLASS_MAPPINGS = {
53
+ "QuadrupleCLIPLoader": QuadrupleCLIPLoader,
54
+ "CLIPTextEncodeHiDream": CLIPTextEncodeHiDream,
55
+ }
ComfyUI/comfy_extras/nodes_hooks.py ADDED
@@ -0,0 +1,745 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Union
3
+ import logging
4
+ import torch
5
+ from collections.abc import Iterable
6
+
7
+ if TYPE_CHECKING:
8
+ from comfy.sd import CLIP
9
+
10
+ import comfy.hooks
11
+ import comfy.sd
12
+ import comfy.utils
13
+ import folder_paths
14
+
15
+ ###########################################
16
+ # Mask, Combine, and Hook Conditioning
17
+ #------------------------------------------
18
+ class PairConditioningSetProperties:
19
+ NodeId = 'PairConditioningSetProperties'
20
+ NodeName = 'Cond Pair Set Props'
21
+ @classmethod
22
+ def INPUT_TYPES(s):
23
+ return {
24
+ "required": {
25
+ "positive_NEW": ("CONDITIONING", ),
26
+ "negative_NEW": ("CONDITIONING", ),
27
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
28
+ "set_cond_area": (["default", "mask bounds"],),
29
+ },
30
+ "optional": {
31
+ "mask": ("MASK", ),
32
+ "hooks": ("HOOKS",),
33
+ "timesteps": ("TIMESTEPS_RANGE",),
34
+ }
35
+ }
36
+
37
+ EXPERIMENTAL = True
38
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
39
+ RETURN_NAMES = ("positive", "negative")
40
+ CATEGORY = "advanced/hooks/cond pair"
41
+ FUNCTION = "set_properties"
42
+
43
+ def set_properties(self, positive_NEW, negative_NEW,
44
+ strength: float, set_cond_area: str,
45
+ mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None):
46
+ final_positive, final_negative = comfy.hooks.set_conds_props(conds=[positive_NEW, negative_NEW],
47
+ strength=strength, set_cond_area=set_cond_area,
48
+ mask=mask, hooks=hooks, timesteps_range=timesteps)
49
+ return (final_positive, final_negative)
50
+
51
+ class PairConditioningSetPropertiesAndCombine:
52
+ NodeId = 'PairConditioningSetPropertiesAndCombine'
53
+ NodeName = 'Cond Pair Set Props Combine'
54
+ @classmethod
55
+ def INPUT_TYPES(s):
56
+ return {
57
+ "required": {
58
+ "positive": ("CONDITIONING", ),
59
+ "negative": ("CONDITIONING", ),
60
+ "positive_NEW": ("CONDITIONING", ),
61
+ "negative_NEW": ("CONDITIONING", ),
62
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
63
+ "set_cond_area": (["default", "mask bounds"],),
64
+ },
65
+ "optional": {
66
+ "mask": ("MASK", ),
67
+ "hooks": ("HOOKS",),
68
+ "timesteps": ("TIMESTEPS_RANGE",),
69
+ }
70
+ }
71
+
72
+ EXPERIMENTAL = True
73
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
74
+ RETURN_NAMES = ("positive", "negative")
75
+ CATEGORY = "advanced/hooks/cond pair"
76
+ FUNCTION = "set_properties"
77
+
78
+ def set_properties(self, positive, negative, positive_NEW, negative_NEW,
79
+ strength: float, set_cond_area: str,
80
+ mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None):
81
+ final_positive, final_negative = comfy.hooks.set_conds_props_and_combine(conds=[positive, negative], new_conds=[positive_NEW, negative_NEW],
82
+ strength=strength, set_cond_area=set_cond_area,
83
+ mask=mask, hooks=hooks, timesteps_range=timesteps)
84
+ return (final_positive, final_negative)
85
+
86
+ class ConditioningSetProperties:
87
+ NodeId = 'ConditioningSetProperties'
88
+ NodeName = 'Cond Set Props'
89
+ @classmethod
90
+ def INPUT_TYPES(s):
91
+ return {
92
+ "required": {
93
+ "cond_NEW": ("CONDITIONING", ),
94
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
95
+ "set_cond_area": (["default", "mask bounds"],),
96
+ },
97
+ "optional": {
98
+ "mask": ("MASK", ),
99
+ "hooks": ("HOOKS",),
100
+ "timesteps": ("TIMESTEPS_RANGE",),
101
+ }
102
+ }
103
+
104
+ EXPERIMENTAL = True
105
+ RETURN_TYPES = ("CONDITIONING",)
106
+ CATEGORY = "advanced/hooks/cond single"
107
+ FUNCTION = "set_properties"
108
+
109
+ def set_properties(self, cond_NEW,
110
+ strength: float, set_cond_area: str,
111
+ mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None):
112
+ (final_cond,) = comfy.hooks.set_conds_props(conds=[cond_NEW],
113
+ strength=strength, set_cond_area=set_cond_area,
114
+ mask=mask, hooks=hooks, timesteps_range=timesteps)
115
+ return (final_cond,)
116
+
117
+ class ConditioningSetPropertiesAndCombine:
118
+ NodeId = 'ConditioningSetPropertiesAndCombine'
119
+ NodeName = 'Cond Set Props Combine'
120
+ @classmethod
121
+ def INPUT_TYPES(s):
122
+ return {
123
+ "required": {
124
+ "cond": ("CONDITIONING", ),
125
+ "cond_NEW": ("CONDITIONING", ),
126
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
127
+ "set_cond_area": (["default", "mask bounds"],),
128
+ },
129
+ "optional": {
130
+ "mask": ("MASK", ),
131
+ "hooks": ("HOOKS",),
132
+ "timesteps": ("TIMESTEPS_RANGE",),
133
+ }
134
+ }
135
+
136
+ EXPERIMENTAL = True
137
+ RETURN_TYPES = ("CONDITIONING",)
138
+ CATEGORY = "advanced/hooks/cond single"
139
+ FUNCTION = "set_properties"
140
+
141
+ def set_properties(self, cond, cond_NEW,
142
+ strength: float, set_cond_area: str,
143
+ mask: torch.Tensor=None, hooks: comfy.hooks.HookGroup=None, timesteps: tuple=None):
144
+ (final_cond,) = comfy.hooks.set_conds_props_and_combine(conds=[cond], new_conds=[cond_NEW],
145
+ strength=strength, set_cond_area=set_cond_area,
146
+ mask=mask, hooks=hooks, timesteps_range=timesteps)
147
+ return (final_cond,)
148
+
149
+ class PairConditioningCombine:
150
+ NodeId = 'PairConditioningCombine'
151
+ NodeName = 'Cond Pair Combine'
152
+ @classmethod
153
+ def INPUT_TYPES(s):
154
+ return {
155
+ "required": {
156
+ "positive_A": ("CONDITIONING",),
157
+ "negative_A": ("CONDITIONING",),
158
+ "positive_B": ("CONDITIONING",),
159
+ "negative_B": ("CONDITIONING",),
160
+ },
161
+ }
162
+
163
+ EXPERIMENTAL = True
164
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
165
+ RETURN_NAMES = ("positive", "negative")
166
+ CATEGORY = "advanced/hooks/cond pair"
167
+ FUNCTION = "combine"
168
+
169
+ def combine(self, positive_A, negative_A, positive_B, negative_B):
170
+ final_positive, final_negative = comfy.hooks.set_conds_props_and_combine(conds=[positive_A, negative_A], new_conds=[positive_B, negative_B],)
171
+ return (final_positive, final_negative,)
172
+
173
+ class PairConditioningSetDefaultAndCombine:
174
+ NodeId = 'PairConditioningSetDefaultCombine'
175
+ NodeName = 'Cond Pair Set Default Combine'
176
+ @classmethod
177
+ def INPUT_TYPES(s):
178
+ return {
179
+ "required": {
180
+ "positive": ("CONDITIONING",),
181
+ "negative": ("CONDITIONING",),
182
+ "positive_DEFAULT": ("CONDITIONING",),
183
+ "negative_DEFAULT": ("CONDITIONING",),
184
+ },
185
+ "optional": {
186
+ "hooks": ("HOOKS",),
187
+ }
188
+ }
189
+
190
+ EXPERIMENTAL = True
191
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
192
+ RETURN_NAMES = ("positive", "negative")
193
+ CATEGORY = "advanced/hooks/cond pair"
194
+ FUNCTION = "set_default_and_combine"
195
+
196
+ def set_default_and_combine(self, positive, negative, positive_DEFAULT, negative_DEFAULT,
197
+ hooks: comfy.hooks.HookGroup=None):
198
+ final_positive, final_negative = comfy.hooks.set_default_conds_and_combine(conds=[positive, negative], new_conds=[positive_DEFAULT, negative_DEFAULT],
199
+ hooks=hooks)
200
+ return (final_positive, final_negative)
201
+
202
+ class ConditioningSetDefaultAndCombine:
203
+ NodeId = 'ConditioningSetDefaultCombine'
204
+ NodeName = 'Cond Set Default Combine'
205
+ @classmethod
206
+ def INPUT_TYPES(s):
207
+ return {
208
+ "required": {
209
+ "cond": ("CONDITIONING",),
210
+ "cond_DEFAULT": ("CONDITIONING",),
211
+ },
212
+ "optional": {
213
+ "hooks": ("HOOKS",),
214
+ }
215
+ }
216
+
217
+ EXPERIMENTAL = True
218
+ RETURN_TYPES = ("CONDITIONING",)
219
+ CATEGORY = "advanced/hooks/cond single"
220
+ FUNCTION = "set_default_and_combine"
221
+
222
+ def set_default_and_combine(self, cond, cond_DEFAULT,
223
+ hooks: comfy.hooks.HookGroup=None):
224
+ (final_conditioning,) = comfy.hooks.set_default_conds_and_combine(conds=[cond], new_conds=[cond_DEFAULT],
225
+ hooks=hooks)
226
+ return (final_conditioning,)
227
+
228
+ class SetClipHooks:
229
+ NodeId = 'SetClipHooks'
230
+ NodeName = 'Set CLIP Hooks'
231
+ @classmethod
232
+ def INPUT_TYPES(s):
233
+ return {
234
+ "required": {
235
+ "clip": ("CLIP",),
236
+ "apply_to_conds": ("BOOLEAN", {"default": True}),
237
+ "schedule_clip": ("BOOLEAN", {"default": False})
238
+ },
239
+ "optional": {
240
+ "hooks": ("HOOKS",)
241
+ }
242
+ }
243
+
244
+ EXPERIMENTAL = True
245
+ RETURN_TYPES = ("CLIP",)
246
+ CATEGORY = "advanced/hooks/clip"
247
+ FUNCTION = "apply_hooks"
248
+
249
+ def apply_hooks(self, clip: CLIP, schedule_clip: bool, apply_to_conds: bool, hooks: comfy.hooks.HookGroup=None):
250
+ if hooks is not None:
251
+ clip = clip.clone()
252
+ if apply_to_conds:
253
+ clip.apply_hooks_to_conds = hooks
254
+ clip.patcher.forced_hooks = hooks.clone()
255
+ clip.use_clip_schedule = schedule_clip
256
+ if not clip.use_clip_schedule:
257
+ clip.patcher.forced_hooks.set_keyframes_on_hooks(None)
258
+ clip.patcher.register_all_hook_patches(hooks, comfy.hooks.create_target_dict(comfy.hooks.EnumWeightTarget.Clip))
259
+ return (clip,)
260
+
261
+ class ConditioningTimestepsRange:
262
+ NodeId = 'ConditioningTimestepsRange'
263
+ NodeName = 'Timesteps Range'
264
+ @classmethod
265
+ def INPUT_TYPES(s):
266
+ return {
267
+ "required": {
268
+ "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
269
+ "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001})
270
+ },
271
+ }
272
+
273
+ EXPERIMENTAL = True
274
+ RETURN_TYPES = ("TIMESTEPS_RANGE", "TIMESTEPS_RANGE", "TIMESTEPS_RANGE")
275
+ RETURN_NAMES = ("TIMESTEPS_RANGE", "BEFORE_RANGE", "AFTER_RANGE")
276
+ CATEGORY = "advanced/hooks"
277
+ FUNCTION = "create_range"
278
+
279
+ def create_range(self, start_percent: float, end_percent: float):
280
+ return ((start_percent, end_percent), (0.0, start_percent), (end_percent, 1.0))
281
+ #------------------------------------------
282
+ ###########################################
283
+
284
+
285
+ ###########################################
286
+ # Create Hooks
287
+ #------------------------------------------
288
+ class CreateHookLora:
289
+ NodeId = 'CreateHookLora'
290
+ NodeName = 'Create Hook LoRA'
291
+ def __init__(self):
292
+ self.loaded_lora = None
293
+
294
+ @classmethod
295
+ def INPUT_TYPES(s):
296
+ return {
297
+ "required": {
298
+ "lora_name": (folder_paths.get_filename_list("loras"), ),
299
+ "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
300
+ "strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
301
+ },
302
+ "optional": {
303
+ "prev_hooks": ("HOOKS",)
304
+ }
305
+ }
306
+
307
+ EXPERIMENTAL = True
308
+ RETURN_TYPES = ("HOOKS",)
309
+ CATEGORY = "advanced/hooks/create"
310
+ FUNCTION = "create_hook"
311
+
312
+ def create_hook(self, lora_name: str, strength_model: float, strength_clip: float, prev_hooks: comfy.hooks.HookGroup=None):
313
+ if prev_hooks is None:
314
+ prev_hooks = comfy.hooks.HookGroup()
315
+ prev_hooks.clone()
316
+
317
+ if strength_model == 0 and strength_clip == 0:
318
+ return (prev_hooks,)
319
+
320
+ lora_path = folder_paths.get_full_path("loras", lora_name)
321
+ lora = None
322
+ if self.loaded_lora is not None:
323
+ if self.loaded_lora[0] == lora_path:
324
+ lora = self.loaded_lora[1]
325
+ else:
326
+ temp = self.loaded_lora
327
+ self.loaded_lora = None
328
+ del temp
329
+
330
+ if lora is None:
331
+ lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
332
+ self.loaded_lora = (lora_path, lora)
333
+
334
+ hooks = comfy.hooks.create_hook_lora(lora=lora, strength_model=strength_model, strength_clip=strength_clip)
335
+ return (prev_hooks.clone_and_combine(hooks),)
336
+
337
+ class CreateHookLoraModelOnly(CreateHookLora):
338
+ NodeId = 'CreateHookLoraModelOnly'
339
+ NodeName = 'Create Hook LoRA (MO)'
340
+ @classmethod
341
+ def INPUT_TYPES(s):
342
+ return {
343
+ "required": {
344
+ "lora_name": (folder_paths.get_filename_list("loras"), ),
345
+ "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
346
+ },
347
+ "optional": {
348
+ "prev_hooks": ("HOOKS",)
349
+ }
350
+ }
351
+
352
+ EXPERIMENTAL = True
353
+ RETURN_TYPES = ("HOOKS",)
354
+ CATEGORY = "advanced/hooks/create"
355
+ FUNCTION = "create_hook_model_only"
356
+
357
+ def create_hook_model_only(self, lora_name: str, strength_model: float, prev_hooks: comfy.hooks.HookGroup=None):
358
+ return self.create_hook(lora_name=lora_name, strength_model=strength_model, strength_clip=0, prev_hooks=prev_hooks)
359
+
360
+ class CreateHookModelAsLora:
361
+ NodeId = 'CreateHookModelAsLora'
362
+ NodeName = 'Create Hook Model as LoRA'
363
+
364
+ def __init__(self):
365
+ # when not None, will be in following format:
366
+ # (ckpt_path: str, weights_model: dict, weights_clip: dict)
367
+ self.loaded_weights = None
368
+
369
+ @classmethod
370
+ def INPUT_TYPES(s):
371
+ return {
372
+ "required": {
373
+ "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
374
+ "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
375
+ "strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
376
+ },
377
+ "optional": {
378
+ "prev_hooks": ("HOOKS",)
379
+ }
380
+ }
381
+
382
+ EXPERIMENTAL = True
383
+ RETURN_TYPES = ("HOOKS",)
384
+ CATEGORY = "advanced/hooks/create"
385
+ FUNCTION = "create_hook"
386
+
387
+ def create_hook(self, ckpt_name: str, strength_model: float, strength_clip: float,
388
+ prev_hooks: comfy.hooks.HookGroup=None):
389
+ if prev_hooks is None:
390
+ prev_hooks = comfy.hooks.HookGroup()
391
+ prev_hooks.clone()
392
+
393
+ ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
394
+ weights_model = None
395
+ weights_clip = None
396
+ if self.loaded_weights is not None:
397
+ if self.loaded_weights[0] == ckpt_path:
398
+ weights_model = self.loaded_weights[1]
399
+ weights_clip = self.loaded_weights[2]
400
+ else:
401
+ temp = self.loaded_weights
402
+ self.loaded_weights = None
403
+ del temp
404
+
405
+ if weights_model is None:
406
+ out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
407
+ weights_model = comfy.hooks.get_patch_weights_from_model(out[0])
408
+ weights_clip = comfy.hooks.get_patch_weights_from_model(out[1].patcher if out[1] else out[1])
409
+ self.loaded_weights = (ckpt_path, weights_model, weights_clip)
410
+
411
+ hooks = comfy.hooks.create_hook_model_as_lora(weights_model=weights_model, weights_clip=weights_clip,
412
+ strength_model=strength_model, strength_clip=strength_clip)
413
+ return (prev_hooks.clone_and_combine(hooks),)
414
+
415
+ class CreateHookModelAsLoraModelOnly(CreateHookModelAsLora):
416
+ NodeId = 'CreateHookModelAsLoraModelOnly'
417
+ NodeName = 'Create Hook Model as LoRA (MO)'
418
+ @classmethod
419
+ def INPUT_TYPES(s):
420
+ return {
421
+ "required": {
422
+ "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
423
+ "strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
424
+ },
425
+ "optional": {
426
+ "prev_hooks": ("HOOKS",)
427
+ }
428
+ }
429
+
430
+ EXPERIMENTAL = True
431
+ RETURN_TYPES = ("HOOKS",)
432
+ CATEGORY = "advanced/hooks/create"
433
+ FUNCTION = "create_hook_model_only"
434
+
435
+ def create_hook_model_only(self, ckpt_name: str, strength_model: float,
436
+ prev_hooks: comfy.hooks.HookGroup=None):
437
+ return self.create_hook(ckpt_name=ckpt_name, strength_model=strength_model, strength_clip=0.0, prev_hooks=prev_hooks)
438
+ #------------------------------------------
439
+ ###########################################
440
+
441
+
442
+ ###########################################
443
+ # Schedule Hooks
444
+ #------------------------------------------
445
+ class SetHookKeyframes:
446
+ NodeId = 'SetHookKeyframes'
447
+ NodeName = 'Set Hook Keyframes'
448
+ @classmethod
449
+ def INPUT_TYPES(s):
450
+ return {
451
+ "required": {
452
+ "hooks": ("HOOKS",),
453
+ },
454
+ "optional": {
455
+ "hook_kf": ("HOOK_KEYFRAMES",),
456
+ }
457
+ }
458
+
459
+ EXPERIMENTAL = True
460
+ RETURN_TYPES = ("HOOKS",)
461
+ CATEGORY = "advanced/hooks/scheduling"
462
+ FUNCTION = "set_hook_keyframes"
463
+
464
+ def set_hook_keyframes(self, hooks: comfy.hooks.HookGroup, hook_kf: comfy.hooks.HookKeyframeGroup=None):
465
+ if hook_kf is not None:
466
+ hooks = hooks.clone()
467
+ hooks.set_keyframes_on_hooks(hook_kf=hook_kf)
468
+ return (hooks,)
469
+
470
+ class CreateHookKeyframe:
471
+ NodeId = 'CreateHookKeyframe'
472
+ NodeName = 'Create Hook Keyframe'
473
+ @classmethod
474
+ def INPUT_TYPES(s):
475
+ return {
476
+ "required": {
477
+ "strength_mult": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
478
+ "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
479
+ },
480
+ "optional": {
481
+ "prev_hook_kf": ("HOOK_KEYFRAMES",),
482
+ }
483
+ }
484
+
485
+ EXPERIMENTAL = True
486
+ RETURN_TYPES = ("HOOK_KEYFRAMES",)
487
+ RETURN_NAMES = ("HOOK_KF",)
488
+ CATEGORY = "advanced/hooks/scheduling"
489
+ FUNCTION = "create_hook_keyframe"
490
+
491
+ def create_hook_keyframe(self, strength_mult: float, start_percent: float, prev_hook_kf: comfy.hooks.HookKeyframeGroup=None):
492
+ if prev_hook_kf is None:
493
+ prev_hook_kf = comfy.hooks.HookKeyframeGroup()
494
+ prev_hook_kf = prev_hook_kf.clone()
495
+ keyframe = comfy.hooks.HookKeyframe(strength=strength_mult, start_percent=start_percent)
496
+ prev_hook_kf.add(keyframe)
497
+ return (prev_hook_kf,)
498
+
499
+ class CreateHookKeyframesInterpolated:
500
+ NodeId = 'CreateHookKeyframesInterpolated'
501
+ NodeName = 'Create Hook Keyframes Interp.'
502
+ @classmethod
503
+ def INPUT_TYPES(s):
504
+ return {
505
+ "required": {
506
+ "strength_start": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ),
507
+ "strength_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ),
508
+ "interpolation": (comfy.hooks.InterpolationMethod._LIST, ),
509
+ "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
510
+ "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
511
+ "keyframes_count": ("INT", {"default": 5, "min": 2, "max": 100, "step": 1}),
512
+ "print_keyframes": ("BOOLEAN", {"default": False}),
513
+ },
514
+ "optional": {
515
+ "prev_hook_kf": ("HOOK_KEYFRAMES",),
516
+ },
517
+ }
518
+
519
+ EXPERIMENTAL = True
520
+ RETURN_TYPES = ("HOOK_KEYFRAMES",)
521
+ RETURN_NAMES = ("HOOK_KF",)
522
+ CATEGORY = "advanced/hooks/scheduling"
523
+ FUNCTION = "create_hook_keyframes"
524
+
525
+ def create_hook_keyframes(self, strength_start: float, strength_end: float, interpolation: str,
526
+ start_percent: float, end_percent: float, keyframes_count: int,
527
+ print_keyframes=False, prev_hook_kf: comfy.hooks.HookKeyframeGroup=None):
528
+ if prev_hook_kf is None:
529
+ prev_hook_kf = comfy.hooks.HookKeyframeGroup()
530
+ prev_hook_kf = prev_hook_kf.clone()
531
+ percents = comfy.hooks.InterpolationMethod.get_weights(num_from=start_percent, num_to=end_percent, length=keyframes_count,
532
+ method=comfy.hooks.InterpolationMethod.LINEAR)
533
+ strengths = comfy.hooks.InterpolationMethod.get_weights(num_from=strength_start, num_to=strength_end, length=keyframes_count, method=interpolation)
534
+
535
+ is_first = True
536
+ for percent, strength in zip(percents, strengths):
537
+ guarantee_steps = 0
538
+ if is_first:
539
+ guarantee_steps = 1
540
+ is_first = False
541
+ prev_hook_kf.add(comfy.hooks.HookKeyframe(strength=strength, start_percent=percent, guarantee_steps=guarantee_steps))
542
+ if print_keyframes:
543
+ logging.info(f"Hook Keyframe - start_percent:{percent} = {strength}")
544
+ return (prev_hook_kf,)
545
+
546
+ class CreateHookKeyframesFromFloats:
547
+ NodeId = 'CreateHookKeyframesFromFloats'
548
+ NodeName = 'Create Hook Keyframes From Floats'
549
+ @classmethod
550
+ def INPUT_TYPES(s):
551
+ return {
552
+ "required": {
553
+ "floats_strength": ("FLOATS", {"default": -1, "min": -1, "step": 0.001, "forceInput": True}),
554
+ "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
555
+ "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
556
+ "print_keyframes": ("BOOLEAN", {"default": False}),
557
+ },
558
+ "optional": {
559
+ "prev_hook_kf": ("HOOK_KEYFRAMES",),
560
+ }
561
+ }
562
+
563
+ EXPERIMENTAL = True
564
+ RETURN_TYPES = ("HOOK_KEYFRAMES",)
565
+ RETURN_NAMES = ("HOOK_KF",)
566
+ CATEGORY = "advanced/hooks/scheduling"
567
+ FUNCTION = "create_hook_keyframes"
568
+
569
+ def create_hook_keyframes(self, floats_strength: Union[float, list[float]],
570
+ start_percent: float, end_percent: float,
571
+ prev_hook_kf: comfy.hooks.HookKeyframeGroup=None, print_keyframes=False):
572
+ if prev_hook_kf is None:
573
+ prev_hook_kf = comfy.hooks.HookKeyframeGroup()
574
+ prev_hook_kf = prev_hook_kf.clone()
575
+ if type(floats_strength) in (float, int):
576
+ floats_strength = [float(floats_strength)]
577
+ elif isinstance(floats_strength, Iterable):
578
+ pass
579
+ else:
580
+ raise Exception(f"floats_strength must be either an iterable input or a float, but was{type(floats_strength).__repr__}.")
581
+ percents = comfy.hooks.InterpolationMethod.get_weights(num_from=start_percent, num_to=end_percent, length=len(floats_strength),
582
+ method=comfy.hooks.InterpolationMethod.LINEAR)
583
+
584
+ is_first = True
585
+ for percent, strength in zip(percents, floats_strength):
586
+ guarantee_steps = 0
587
+ if is_first:
588
+ guarantee_steps = 1
589
+ is_first = False
590
+ prev_hook_kf.add(comfy.hooks.HookKeyframe(strength=strength, start_percent=percent, guarantee_steps=guarantee_steps))
591
+ if print_keyframes:
592
+ logging.info(f"Hook Keyframe - start_percent:{percent} = {strength}")
593
+ return (prev_hook_kf,)
594
+ #------------------------------------------
595
+ ###########################################
596
+
597
+
598
+ class SetModelHooksOnCond:
599
+ @classmethod
600
+ def INPUT_TYPES(s):
601
+ return {
602
+ "required": {
603
+ "conditioning": ("CONDITIONING",),
604
+ "hooks": ("HOOKS",),
605
+ },
606
+ }
607
+
608
+ EXPERIMENTAL = True
609
+ RETURN_TYPES = ("CONDITIONING",)
610
+ CATEGORY = "advanced/hooks/manual"
611
+ FUNCTION = "attach_hook"
612
+
613
+ def attach_hook(self, conditioning, hooks: comfy.hooks.HookGroup):
614
+ return (comfy.hooks.set_hooks_for_conditioning(conditioning, hooks),)
615
+
616
+
617
+ ###########################################
618
+ # Combine Hooks
619
+ #------------------------------------------
620
+ class CombineHooks:
621
+ NodeId = 'CombineHooks2'
622
+ NodeName = 'Combine Hooks [2]'
623
+ @classmethod
624
+ def INPUT_TYPES(s):
625
+ return {
626
+ "required": {
627
+ },
628
+ "optional": {
629
+ "hooks_A": ("HOOKS",),
630
+ "hooks_B": ("HOOKS",),
631
+ }
632
+ }
633
+
634
+ EXPERIMENTAL = True
635
+ RETURN_TYPES = ("HOOKS",)
636
+ CATEGORY = "advanced/hooks/combine"
637
+ FUNCTION = "combine_hooks"
638
+
639
+ def combine_hooks(self,
640
+ hooks_A: comfy.hooks.HookGroup=None,
641
+ hooks_B: comfy.hooks.HookGroup=None):
642
+ candidates = [hooks_A, hooks_B]
643
+ return (comfy.hooks.HookGroup.combine_all_hooks(candidates),)
644
+
645
+ class CombineHooksFour:
646
+ NodeId = 'CombineHooks4'
647
+ NodeName = 'Combine Hooks [4]'
648
+ @classmethod
649
+ def INPUT_TYPES(s):
650
+ return {
651
+ "required": {
652
+ },
653
+ "optional": {
654
+ "hooks_A": ("HOOKS",),
655
+ "hooks_B": ("HOOKS",),
656
+ "hooks_C": ("HOOKS",),
657
+ "hooks_D": ("HOOKS",),
658
+ }
659
+ }
660
+
661
+ EXPERIMENTAL = True
662
+ RETURN_TYPES = ("HOOKS",)
663
+ CATEGORY = "advanced/hooks/combine"
664
+ FUNCTION = "combine_hooks"
665
+
666
+ def combine_hooks(self,
667
+ hooks_A: comfy.hooks.HookGroup=None,
668
+ hooks_B: comfy.hooks.HookGroup=None,
669
+ hooks_C: comfy.hooks.HookGroup=None,
670
+ hooks_D: comfy.hooks.HookGroup=None):
671
+ candidates = [hooks_A, hooks_B, hooks_C, hooks_D]
672
+ return (comfy.hooks.HookGroup.combine_all_hooks(candidates),)
673
+
674
+ class CombineHooksEight:
675
+ NodeId = 'CombineHooks8'
676
+ NodeName = 'Combine Hooks [8]'
677
+ @classmethod
678
+ def INPUT_TYPES(s):
679
+ return {
680
+ "required": {
681
+ },
682
+ "optional": {
683
+ "hooks_A": ("HOOKS",),
684
+ "hooks_B": ("HOOKS",),
685
+ "hooks_C": ("HOOKS",),
686
+ "hooks_D": ("HOOKS",),
687
+ "hooks_E": ("HOOKS",),
688
+ "hooks_F": ("HOOKS",),
689
+ "hooks_G": ("HOOKS",),
690
+ "hooks_H": ("HOOKS",),
691
+ }
692
+ }
693
+
694
+ EXPERIMENTAL = True
695
+ RETURN_TYPES = ("HOOKS",)
696
+ CATEGORY = "advanced/hooks/combine"
697
+ FUNCTION = "combine_hooks"
698
+
699
+ def combine_hooks(self,
700
+ hooks_A: comfy.hooks.HookGroup=None,
701
+ hooks_B: comfy.hooks.HookGroup=None,
702
+ hooks_C: comfy.hooks.HookGroup=None,
703
+ hooks_D: comfy.hooks.HookGroup=None,
704
+ hooks_E: comfy.hooks.HookGroup=None,
705
+ hooks_F: comfy.hooks.HookGroup=None,
706
+ hooks_G: comfy.hooks.HookGroup=None,
707
+ hooks_H: comfy.hooks.HookGroup=None):
708
+ candidates = [hooks_A, hooks_B, hooks_C, hooks_D, hooks_E, hooks_F, hooks_G, hooks_H]
709
+ return (comfy.hooks.HookGroup.combine_all_hooks(candidates),)
710
+ #------------------------------------------
711
+ ###########################################
712
+
713
+ node_list = [
714
+ # Create
715
+ CreateHookLora,
716
+ CreateHookLoraModelOnly,
717
+ CreateHookModelAsLora,
718
+ CreateHookModelAsLoraModelOnly,
719
+ # Scheduling
720
+ SetHookKeyframes,
721
+ CreateHookKeyframe,
722
+ CreateHookKeyframesInterpolated,
723
+ CreateHookKeyframesFromFloats,
724
+ # Combine
725
+ CombineHooks,
726
+ CombineHooksFour,
727
+ CombineHooksEight,
728
+ # Attach
729
+ ConditioningSetProperties,
730
+ ConditioningSetPropertiesAndCombine,
731
+ PairConditioningSetProperties,
732
+ PairConditioningSetPropertiesAndCombine,
733
+ ConditioningSetDefaultAndCombine,
734
+ PairConditioningSetDefaultAndCombine,
735
+ PairConditioningCombine,
736
+ SetClipHooks,
737
+ # Other
738
+ ConditioningTimestepsRange,
739
+ ]
740
+ NODE_CLASS_MAPPINGS = {}
741
+ NODE_DISPLAY_NAME_MAPPINGS = {}
742
+
743
+ for node in node_list:
744
+ NODE_CLASS_MAPPINGS[node.NodeId] = node
745
+ NODE_DISPLAY_NAME_MAPPINGS[node.NodeId] = node.NodeName
ComfyUI/comfy_extras/nodes_hunyuan.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import nodes
2
+ import node_helpers
3
+ import torch
4
+ import comfy.model_management
5
+
6
+
7
+ class CLIPTextEncodeHunyuanDiT:
8
+ @classmethod
9
+ def INPUT_TYPES(s):
10
+ return {"required": {
11
+ "clip": ("CLIP", ),
12
+ "bert": ("STRING", {"multiline": True, "dynamicPrompts": True}),
13
+ "mt5xl": ("STRING", {"multiline": True, "dynamicPrompts": True}),
14
+ }}
15
+ RETURN_TYPES = ("CONDITIONING",)
16
+ FUNCTION = "encode"
17
+
18
+ CATEGORY = "advanced/conditioning"
19
+
20
+ def encode(self, clip, bert, mt5xl):
21
+ tokens = clip.tokenize(bert)
22
+ tokens["mt5xl"] = clip.tokenize(mt5xl)["mt5xl"]
23
+
24
+ return (clip.encode_from_tokens_scheduled(tokens), )
25
+
26
+ class EmptyHunyuanLatentVideo:
27
+ @classmethod
28
+ def INPUT_TYPES(s):
29
+ return {"required": { "width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
30
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
31
+ "length": ("INT", {"default": 25, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
32
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}}
33
+ RETURN_TYPES = ("LATENT",)
34
+ FUNCTION = "generate"
35
+
36
+ CATEGORY = "latent/video"
37
+
38
+ def generate(self, width, height, length, batch_size=1):
39
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
40
+ return ({"samples":latent}, )
41
+
42
+ PROMPT_TEMPLATE_ENCODE_VIDEO_I2V = (
43
+ "<|start_header_id|>system<|end_header_id|>\n\n<image>\nDescribe the video by detailing the following aspects according to the reference image: "
44
+ "1. The main content and theme of the video."
45
+ "2. The color, shape, size, texture, quantity, text, and spatial relationships of the objects."
46
+ "3. Actions, events, behaviors temporal relationships, physical movement changes of the objects."
47
+ "4. background environment, light, style and atmosphere."
48
+ "5. camera angles, movements, and transitions used in the video:<|eot_id|>\n\n"
49
+ "<|start_header_id|>user<|end_header_id|>\n\n{}<|eot_id|>"
50
+ "<|start_header_id|>assistant<|end_header_id|>\n\n"
51
+ )
52
+
53
+ class TextEncodeHunyuanVideo_ImageToVideo:
54
+ @classmethod
55
+ def INPUT_TYPES(s):
56
+ return {"required": {
57
+ "clip": ("CLIP", ),
58
+ "clip_vision_output": ("CLIP_VISION_OUTPUT", ),
59
+ "prompt": ("STRING", {"multiline": True, "dynamicPrompts": True}),
60
+ "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."}),
61
+ }}
62
+ RETURN_TYPES = ("CONDITIONING",)
63
+ FUNCTION = "encode"
64
+
65
+ CATEGORY = "advanced/conditioning"
66
+
67
+ def encode(self, clip, clip_vision_output, prompt, image_interleave):
68
+ tokens = clip.tokenize(prompt, llama_template=PROMPT_TEMPLATE_ENCODE_VIDEO_I2V, image_embeds=clip_vision_output.mm_projected, image_interleave=image_interleave)
69
+ return (clip.encode_from_tokens_scheduled(tokens), )
70
+
71
+ class HunyuanImageToVideo:
72
+ @classmethod
73
+ def INPUT_TYPES(s):
74
+ return {"required": {"positive": ("CONDITIONING", ),
75
+ "vae": ("VAE", ),
76
+ "width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
77
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
78
+ "length": ("INT", {"default": 53, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
79
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
80
+ "guidance_type": (["v1 (concat)", "v2 (replace)", "custom"], )
81
+ },
82
+ "optional": {"start_image": ("IMAGE", ),
83
+ }}
84
+
85
+ RETURN_TYPES = ("CONDITIONING", "LATENT")
86
+ RETURN_NAMES = ("positive", "latent")
87
+ FUNCTION = "encode"
88
+
89
+ CATEGORY = "conditioning/video_models"
90
+
91
+ def encode(self, positive, vae, width, height, length, batch_size, guidance_type, start_image=None):
92
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
93
+ out_latent = {}
94
+
95
+ if start_image is not None:
96
+ start_image = comfy.utils.common_upscale(start_image[:length, :, :, :3].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
97
+
98
+ concat_latent_image = vae.encode(start_image)
99
+ 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)
100
+ mask[:, :, :((start_image.shape[0] - 1) // 4) + 1] = 0.0
101
+
102
+ if guidance_type == "v1 (concat)":
103
+ cond = {"concat_latent_image": concat_latent_image, "concat_mask": mask}
104
+ elif guidance_type == "v2 (replace)":
105
+ cond = {'guiding_frame_index': 0}
106
+ latent[:, :, :concat_latent_image.shape[2]] = concat_latent_image
107
+ out_latent["noise_mask"] = mask
108
+ elif guidance_type == "custom":
109
+ cond = {"ref_latent": concat_latent_image}
110
+
111
+ positive = node_helpers.conditioning_set_values(positive, cond)
112
+
113
+ out_latent["samples"] = latent
114
+ return (positive, out_latent)
115
+
116
+
117
+
118
+ NODE_CLASS_MAPPINGS = {
119
+ "CLIPTextEncodeHunyuanDiT": CLIPTextEncodeHunyuanDiT,
120
+ "TextEncodeHunyuanVideo_ImageToVideo": TextEncodeHunyuanVideo_ImageToVideo,
121
+ "EmptyHunyuanLatentVideo": EmptyHunyuanLatentVideo,
122
+ "HunyuanImageToVideo": HunyuanImageToVideo,
123
+ }
ComfyUI/comfy_extras/nodes_hunyuan3d.py ADDED
@@ -0,0 +1,634 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import os
3
+ import json
4
+ import struct
5
+ import numpy as np
6
+ from comfy.ldm.modules.diffusionmodules.mmdit import get_1d_sincos_pos_embed_from_grid_torch
7
+ import folder_paths
8
+ import comfy.model_management
9
+ from comfy.cli_args import args
10
+
11
+
12
+ class EmptyLatentHunyuan3Dv2:
13
+ @classmethod
14
+ def INPUT_TYPES(s):
15
+ return {"required": {"resolution": ("INT", {"default": 3072, "min": 1, "max": 8192}),
16
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}),
17
+ }}
18
+ RETURN_TYPES = ("LATENT",)
19
+ FUNCTION = "generate"
20
+
21
+ CATEGORY = "latent/3d"
22
+
23
+ def generate(self, resolution, batch_size):
24
+ latent = torch.zeros([batch_size, 64, resolution], device=comfy.model_management.intermediate_device())
25
+ return ({"samples": latent, "type": "hunyuan3dv2"}, )
26
+
27
+
28
+ class Hunyuan3Dv2Conditioning:
29
+ @classmethod
30
+ def INPUT_TYPES(s):
31
+ return {"required": {"clip_vision_output": ("CLIP_VISION_OUTPUT",),
32
+ }}
33
+
34
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
35
+ RETURN_NAMES = ("positive", "negative")
36
+
37
+ FUNCTION = "encode"
38
+
39
+ CATEGORY = "conditioning/video_models"
40
+
41
+ def encode(self, clip_vision_output):
42
+ embeds = clip_vision_output.last_hidden_state
43
+ positive = [[embeds, {}]]
44
+ negative = [[torch.zeros_like(embeds), {}]]
45
+ return (positive, negative)
46
+
47
+
48
+ class Hunyuan3Dv2ConditioningMultiView:
49
+ @classmethod
50
+ def INPUT_TYPES(s):
51
+ return {"required": {},
52
+ "optional": {"front": ("CLIP_VISION_OUTPUT",),
53
+ "left": ("CLIP_VISION_OUTPUT",),
54
+ "back": ("CLIP_VISION_OUTPUT",),
55
+ "right": ("CLIP_VISION_OUTPUT",), }}
56
+
57
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
58
+ RETURN_NAMES = ("positive", "negative")
59
+
60
+ FUNCTION = "encode"
61
+
62
+ CATEGORY = "conditioning/video_models"
63
+
64
+ def encode(self, front=None, left=None, back=None, right=None):
65
+ all_embeds = [front, left, back, right]
66
+ out = []
67
+ pos_embeds = None
68
+ for i, e in enumerate(all_embeds):
69
+ if e is not None:
70
+ if pos_embeds is None:
71
+ pos_embeds = get_1d_sincos_pos_embed_from_grid_torch(e.last_hidden_state.shape[-1], torch.arange(4))
72
+ out.append(e.last_hidden_state + pos_embeds[i].reshape(1, 1, -1))
73
+
74
+ embeds = torch.cat(out, dim=1)
75
+ positive = [[embeds, {}]]
76
+ negative = [[torch.zeros_like(embeds), {}]]
77
+ return (positive, negative)
78
+
79
+
80
+ class VOXEL:
81
+ def __init__(self, data):
82
+ self.data = data
83
+
84
+
85
+ class VAEDecodeHunyuan3D:
86
+ @classmethod
87
+ def INPUT_TYPES(s):
88
+ return {"required": {"samples": ("LATENT", ),
89
+ "vae": ("VAE", ),
90
+ "num_chunks": ("INT", {"default": 8000, "min": 1000, "max": 500000}),
91
+ "octree_resolution": ("INT", {"default": 256, "min": 16, "max": 512}),
92
+ }}
93
+ RETURN_TYPES = ("VOXEL",)
94
+ FUNCTION = "decode"
95
+
96
+ CATEGORY = "latent/3d"
97
+
98
+ def decode(self, vae, samples, num_chunks, octree_resolution):
99
+ voxels = VOXEL(vae.decode(samples["samples"], vae_options={"num_chunks": num_chunks, "octree_resolution": octree_resolution}))
100
+ return (voxels, )
101
+
102
+
103
+ def voxel_to_mesh(voxels, threshold=0.5, device=None):
104
+ if device is None:
105
+ device = torch.device("cpu")
106
+ voxels = voxels.to(device)
107
+
108
+ binary = (voxels > threshold).float()
109
+ padded = torch.nn.functional.pad(binary, (1, 1, 1, 1, 1, 1), 'constant', 0)
110
+
111
+ D, H, W = binary.shape
112
+
113
+ neighbors = torch.tensor([
114
+ [0, 0, 1],
115
+ [0, 0, -1],
116
+ [0, 1, 0],
117
+ [0, -1, 0],
118
+ [1, 0, 0],
119
+ [-1, 0, 0]
120
+ ], device=device)
121
+
122
+ z, y, x = torch.meshgrid(
123
+ torch.arange(D, device=device),
124
+ torch.arange(H, device=device),
125
+ torch.arange(W, device=device),
126
+ indexing='ij'
127
+ )
128
+ voxel_indices = torch.stack([z.flatten(), y.flatten(), x.flatten()], dim=1)
129
+
130
+ solid_mask = binary.flatten() > 0
131
+ solid_indices = voxel_indices[solid_mask]
132
+
133
+ corner_offsets = [
134
+ torch.tensor([
135
+ [0, 0, 1], [0, 1, 1], [1, 1, 1], [1, 0, 1]
136
+ ], device=device),
137
+ torch.tensor([
138
+ [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]
139
+ ], device=device),
140
+ torch.tensor([
141
+ [0, 1, 0], [1, 1, 0], [1, 1, 1], [0, 1, 1]
142
+ ], device=device),
143
+ torch.tensor([
144
+ [0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]
145
+ ], device=device),
146
+ torch.tensor([
147
+ [1, 0, 1], [1, 1, 1], [1, 1, 0], [1, 0, 0]
148
+ ], device=device),
149
+ torch.tensor([
150
+ [0, 1, 0], [0, 1, 1], [0, 0, 1], [0, 0, 0]
151
+ ], device=device)
152
+ ]
153
+
154
+ all_vertices = []
155
+ all_indices = []
156
+
157
+ vertex_count = 0
158
+
159
+ for face_idx, offset in enumerate(neighbors):
160
+ neighbor_indices = solid_indices + offset
161
+
162
+ padded_indices = neighbor_indices + 1
163
+
164
+ is_exposed = padded[
165
+ padded_indices[:, 0],
166
+ padded_indices[:, 1],
167
+ padded_indices[:, 2]
168
+ ] == 0
169
+
170
+ if not is_exposed.any():
171
+ continue
172
+
173
+ exposed_indices = solid_indices[is_exposed]
174
+
175
+ corners = corner_offsets[face_idx].unsqueeze(0)
176
+
177
+ face_vertices = exposed_indices.unsqueeze(1) + corners
178
+
179
+ all_vertices.append(face_vertices.reshape(-1, 3))
180
+
181
+ num_faces = exposed_indices.shape[0]
182
+ face_indices = torch.arange(
183
+ vertex_count,
184
+ vertex_count + 4 * num_faces,
185
+ device=device
186
+ ).reshape(-1, 4)
187
+
188
+ all_indices.append(torch.stack([face_indices[:, 0], face_indices[:, 1], face_indices[:, 2]], dim=1))
189
+ all_indices.append(torch.stack([face_indices[:, 0], face_indices[:, 2], face_indices[:, 3]], dim=1))
190
+
191
+ vertex_count += 4 * num_faces
192
+
193
+ if len(all_vertices) > 0:
194
+ vertices = torch.cat(all_vertices, dim=0)
195
+ faces = torch.cat(all_indices, dim=0)
196
+ else:
197
+ vertices = torch.zeros((1, 3))
198
+ faces = torch.zeros((1, 3))
199
+
200
+ v_min = 0
201
+ v_max = max(voxels.shape)
202
+
203
+ vertices = vertices - (v_min + v_max) / 2
204
+
205
+ scale = (v_max - v_min) / 2
206
+ if scale > 0:
207
+ vertices = vertices / scale
208
+
209
+ vertices = torch.fliplr(vertices)
210
+ return vertices, faces
211
+
212
+ def voxel_to_mesh_surfnet(voxels, threshold=0.5, device=None):
213
+ if device is None:
214
+ device = torch.device("cpu")
215
+ voxels = voxels.to(device)
216
+
217
+ D, H, W = voxels.shape
218
+
219
+ padded = torch.nn.functional.pad(voxels, (1, 1, 1, 1, 1, 1), 'constant', 0)
220
+ z, y, x = torch.meshgrid(
221
+ torch.arange(D, device=device),
222
+ torch.arange(H, device=device),
223
+ torch.arange(W, device=device),
224
+ indexing='ij'
225
+ )
226
+ cell_positions = torch.stack([z.flatten(), y.flatten(), x.flatten()], dim=1)
227
+
228
+ corner_offsets = torch.tensor([
229
+ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0],
230
+ [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]
231
+ ], device=device)
232
+
233
+ corner_values = torch.zeros((cell_positions.shape[0], 8), device=device)
234
+ for c, (dz, dy, dx) in enumerate(corner_offsets):
235
+ corner_values[:, c] = padded[
236
+ cell_positions[:, 0] + dz,
237
+ cell_positions[:, 1] + dy,
238
+ cell_positions[:, 2] + dx
239
+ ]
240
+
241
+ corner_signs = corner_values > threshold
242
+ has_inside = torch.any(corner_signs, dim=1)
243
+ has_outside = torch.any(~corner_signs, dim=1)
244
+ contains_surface = has_inside & has_outside
245
+
246
+ active_cells = cell_positions[contains_surface]
247
+ active_signs = corner_signs[contains_surface]
248
+ active_values = corner_values[contains_surface]
249
+
250
+ if active_cells.shape[0] == 0:
251
+ return torch.zeros((0, 3), device=device), torch.zeros((0, 3), dtype=torch.long, device=device)
252
+
253
+ edges = torch.tensor([
254
+ [0, 1], [0, 2], [0, 4], [1, 3],
255
+ [1, 5], [2, 3], [2, 6], [3, 7],
256
+ [4, 5], [4, 6], [5, 7], [6, 7]
257
+ ], device=device)
258
+
259
+ cell_vertices = {}
260
+ progress = comfy.utils.ProgressBar(100)
261
+
262
+ for edge_idx, (e1, e2) in enumerate(edges):
263
+ progress.update(1)
264
+ crossing = active_signs[:, e1] != active_signs[:, e2]
265
+ if not crossing.any():
266
+ continue
267
+
268
+ cell_indices = torch.nonzero(crossing, as_tuple=True)[0]
269
+
270
+ v1 = active_values[cell_indices, e1]
271
+ v2 = active_values[cell_indices, e2]
272
+
273
+ t = torch.zeros_like(v1, device=device)
274
+ denom = v2 - v1
275
+ valid = denom != 0
276
+ t[valid] = (threshold - v1[valid]) / denom[valid]
277
+ t[~valid] = 0.5
278
+
279
+ p1 = corner_offsets[e1].float()
280
+ p2 = corner_offsets[e2].float()
281
+
282
+ intersection = p1.unsqueeze(0) + t.unsqueeze(1) * (p2.unsqueeze(0) - p1.unsqueeze(0))
283
+
284
+ for i, point in zip(cell_indices.tolist(), intersection):
285
+ if i not in cell_vertices:
286
+ cell_vertices[i] = []
287
+ cell_vertices[i].append(point)
288
+
289
+ # Calculate the final vertices as the average of intersection points for each cell
290
+ vertices = []
291
+ vertex_lookup = {}
292
+
293
+ vert_progress_mod = round(len(cell_vertices)/50)
294
+
295
+ for i, points in cell_vertices.items():
296
+ if not i % vert_progress_mod:
297
+ progress.update(1)
298
+
299
+ if points:
300
+ vertex = torch.stack(points).mean(dim=0)
301
+ vertex = vertex + active_cells[i].float()
302
+ vertex_lookup[tuple(active_cells[i].tolist())] = len(vertices)
303
+ vertices.append(vertex)
304
+
305
+ if not vertices:
306
+ return torch.zeros((0, 3), device=device), torch.zeros((0, 3), dtype=torch.long, device=device)
307
+
308
+ final_vertices = torch.stack(vertices)
309
+
310
+ inside_corners_mask = active_signs
311
+ outside_corners_mask = ~active_signs
312
+
313
+ inside_counts = inside_corners_mask.sum(dim=1, keepdim=True).float()
314
+ outside_counts = outside_corners_mask.sum(dim=1, keepdim=True).float()
315
+
316
+ inside_pos = torch.zeros((active_cells.shape[0], 3), device=device)
317
+ outside_pos = torch.zeros((active_cells.shape[0], 3), device=device)
318
+
319
+ for i in range(8):
320
+ mask_inside = inside_corners_mask[:, i].unsqueeze(1)
321
+ mask_outside = outside_corners_mask[:, i].unsqueeze(1)
322
+ inside_pos += corner_offsets[i].float().unsqueeze(0) * mask_inside
323
+ outside_pos += corner_offsets[i].float().unsqueeze(0) * mask_outside
324
+
325
+ inside_pos /= inside_counts
326
+ outside_pos /= outside_counts
327
+ gradients = inside_pos - outside_pos
328
+
329
+ pos_dirs = torch.tensor([
330
+ [1, 0, 0],
331
+ [0, 1, 0],
332
+ [0, 0, 1]
333
+ ], device=device)
334
+
335
+ cross_products = [
336
+ torch.linalg.cross(pos_dirs[i].float(), pos_dirs[j].float())
337
+ for i in range(3) for j in range(i+1, 3)
338
+ ]
339
+
340
+ faces = []
341
+ all_keys = set(vertex_lookup.keys())
342
+
343
+ face_progress_mod = round(len(active_cells)/38*3)
344
+
345
+ for pair_idx, (i, j) in enumerate([(0,1), (0,2), (1,2)]):
346
+ dir_i = pos_dirs[i]
347
+ dir_j = pos_dirs[j]
348
+ cross_product = cross_products[pair_idx]
349
+
350
+ ni_positions = active_cells + dir_i
351
+ nj_positions = active_cells + dir_j
352
+ diag_positions = active_cells + dir_i + dir_j
353
+
354
+ alignments = torch.matmul(gradients, cross_product)
355
+
356
+ valid_quads = []
357
+ quad_indices = []
358
+
359
+ for idx, active_cell in enumerate(active_cells):
360
+ if not idx % face_progress_mod:
361
+ progress.update(1)
362
+ cell_key = tuple(active_cell.tolist())
363
+ ni_key = tuple(ni_positions[idx].tolist())
364
+ nj_key = tuple(nj_positions[idx].tolist())
365
+ diag_key = tuple(diag_positions[idx].tolist())
366
+
367
+ if cell_key in all_keys and ni_key in all_keys and nj_key in all_keys and diag_key in all_keys:
368
+ v0 = vertex_lookup[cell_key]
369
+ v1 = vertex_lookup[ni_key]
370
+ v2 = vertex_lookup[nj_key]
371
+ v3 = vertex_lookup[diag_key]
372
+
373
+ valid_quads.append((v0, v1, v2, v3))
374
+ quad_indices.append(idx)
375
+
376
+ for q_idx, (v0, v1, v2, v3) in enumerate(valid_quads):
377
+ cell_idx = quad_indices[q_idx]
378
+ if alignments[cell_idx] > 0:
379
+ faces.append(torch.tensor([v0, v1, v3], device=device, dtype=torch.long))
380
+ faces.append(torch.tensor([v0, v3, v2], device=device, dtype=torch.long))
381
+ else:
382
+ faces.append(torch.tensor([v0, v3, v1], device=device, dtype=torch.long))
383
+ faces.append(torch.tensor([v0, v2, v3], device=device, dtype=torch.long))
384
+
385
+ if faces:
386
+ faces = torch.stack(faces)
387
+ else:
388
+ faces = torch.zeros((0, 3), dtype=torch.long, device=device)
389
+
390
+ v_min = 0
391
+ v_max = max(D, H, W)
392
+
393
+ final_vertices = final_vertices - (v_min + v_max) / 2
394
+
395
+ scale = (v_max - v_min) / 2
396
+ if scale > 0:
397
+ final_vertices = final_vertices / scale
398
+
399
+ final_vertices = torch.fliplr(final_vertices)
400
+
401
+ return final_vertices, faces
402
+
403
+ class MESH:
404
+ def __init__(self, vertices, faces):
405
+ self.vertices = vertices
406
+ self.faces = faces
407
+
408
+
409
+ class VoxelToMeshBasic:
410
+ @classmethod
411
+ def INPUT_TYPES(s):
412
+ return {"required": {"voxel": ("VOXEL", ),
413
+ "threshold": ("FLOAT", {"default": 0.6, "min": -1.0, "max": 1.0, "step": 0.01}),
414
+ }}
415
+ RETURN_TYPES = ("MESH",)
416
+ FUNCTION = "decode"
417
+
418
+ CATEGORY = "3d"
419
+
420
+ def decode(self, voxel, threshold):
421
+ vertices = []
422
+ faces = []
423
+ for x in voxel.data:
424
+ v, f = voxel_to_mesh(x, threshold=threshold, device=None)
425
+ vertices.append(v)
426
+ faces.append(f)
427
+
428
+ return (MESH(torch.stack(vertices), torch.stack(faces)), )
429
+
430
+ class VoxelToMesh:
431
+ @classmethod
432
+ def INPUT_TYPES(s):
433
+ return {"required": {"voxel": ("VOXEL", ),
434
+ "algorithm": (["surface net", "basic"], ),
435
+ "threshold": ("FLOAT", {"default": 0.6, "min": -1.0, "max": 1.0, "step": 0.01}),
436
+ }}
437
+ RETURN_TYPES = ("MESH",)
438
+ FUNCTION = "decode"
439
+
440
+ CATEGORY = "3d"
441
+
442
+ def decode(self, voxel, algorithm, threshold):
443
+ vertices = []
444
+ faces = []
445
+
446
+ if algorithm == "basic":
447
+ mesh_function = voxel_to_mesh
448
+ elif algorithm == "surface net":
449
+ mesh_function = voxel_to_mesh_surfnet
450
+
451
+ for x in voxel.data:
452
+ v, f = mesh_function(x, threshold=threshold, device=None)
453
+ vertices.append(v)
454
+ faces.append(f)
455
+
456
+ return (MESH(torch.stack(vertices), torch.stack(faces)), )
457
+
458
+
459
+ def save_glb(vertices, faces, filepath, metadata=None):
460
+ """
461
+ Save PyTorch tensor vertices and faces as a GLB file without external dependencies.
462
+
463
+ Parameters:
464
+ vertices: torch.Tensor of shape (N, 3) - The vertex coordinates
465
+ faces: torch.Tensor of shape (M, 3) - The face indices (triangle faces)
466
+ filepath: str - Output filepath (should end with .glb)
467
+ """
468
+
469
+ # Convert tensors to numpy arrays
470
+ vertices_np = vertices.cpu().numpy().astype(np.float32)
471
+ faces_np = faces.cpu().numpy().astype(np.uint32)
472
+
473
+ vertices_buffer = vertices_np.tobytes()
474
+ indices_buffer = faces_np.tobytes()
475
+
476
+ def pad_to_4_bytes(buffer):
477
+ padding_length = (4 - (len(buffer) % 4)) % 4
478
+ return buffer + b'\x00' * padding_length
479
+
480
+ vertices_buffer_padded = pad_to_4_bytes(vertices_buffer)
481
+ indices_buffer_padded = pad_to_4_bytes(indices_buffer)
482
+
483
+ buffer_data = vertices_buffer_padded + indices_buffer_padded
484
+
485
+ vertices_byte_length = len(vertices_buffer)
486
+ vertices_byte_offset = 0
487
+ indices_byte_length = len(indices_buffer)
488
+ indices_byte_offset = len(vertices_buffer_padded)
489
+
490
+ gltf = {
491
+ "asset": {"version": "2.0", "generator": "ComfyUI"},
492
+ "buffers": [
493
+ {
494
+ "byteLength": len(buffer_data)
495
+ }
496
+ ],
497
+ "bufferViews": [
498
+ {
499
+ "buffer": 0,
500
+ "byteOffset": vertices_byte_offset,
501
+ "byteLength": vertices_byte_length,
502
+ "target": 34962 # ARRAY_BUFFER
503
+ },
504
+ {
505
+ "buffer": 0,
506
+ "byteOffset": indices_byte_offset,
507
+ "byteLength": indices_byte_length,
508
+ "target": 34963 # ELEMENT_ARRAY_BUFFER
509
+ }
510
+ ],
511
+ "accessors": [
512
+ {
513
+ "bufferView": 0,
514
+ "byteOffset": 0,
515
+ "componentType": 5126, # FLOAT
516
+ "count": len(vertices_np),
517
+ "type": "VEC3",
518
+ "max": vertices_np.max(axis=0).tolist(),
519
+ "min": vertices_np.min(axis=0).tolist()
520
+ },
521
+ {
522
+ "bufferView": 1,
523
+ "byteOffset": 0,
524
+ "componentType": 5125, # UNSIGNED_INT
525
+ "count": faces_np.size,
526
+ "type": "SCALAR"
527
+ }
528
+ ],
529
+ "meshes": [
530
+ {
531
+ "primitives": [
532
+ {
533
+ "attributes": {
534
+ "POSITION": 0
535
+ },
536
+ "indices": 1,
537
+ "mode": 4 # TRIANGLES
538
+ }
539
+ ]
540
+ }
541
+ ],
542
+ "nodes": [
543
+ {
544
+ "mesh": 0
545
+ }
546
+ ],
547
+ "scenes": [
548
+ {
549
+ "nodes": [0]
550
+ }
551
+ ],
552
+ "scene": 0
553
+ }
554
+
555
+ if metadata is not None:
556
+ gltf["asset"]["extras"] = metadata
557
+
558
+ # Convert the JSON to bytes
559
+ gltf_json = json.dumps(gltf).encode('utf8')
560
+
561
+ def pad_json_to_4_bytes(buffer):
562
+ padding_length = (4 - (len(buffer) % 4)) % 4
563
+ return buffer + b' ' * padding_length
564
+
565
+ gltf_json_padded = pad_json_to_4_bytes(gltf_json)
566
+
567
+ # Create the GLB header
568
+ # Magic glTF
569
+ glb_header = struct.pack('<4sII', b'glTF', 2, 12 + 8 + len(gltf_json_padded) + 8 + len(buffer_data))
570
+
571
+ # Create JSON chunk header (chunk type 0)
572
+ json_chunk_header = struct.pack('<II', len(gltf_json_padded), 0x4E4F534A) # "JSON" in little endian
573
+
574
+ # Create BIN chunk header (chunk type 1)
575
+ bin_chunk_header = struct.pack('<II', len(buffer_data), 0x004E4942) # "BIN\0" in little endian
576
+
577
+ # Write the GLB file
578
+ with open(filepath, 'wb') as f:
579
+ f.write(glb_header)
580
+ f.write(json_chunk_header)
581
+ f.write(gltf_json_padded)
582
+ f.write(bin_chunk_header)
583
+ f.write(buffer_data)
584
+
585
+ return filepath
586
+
587
+
588
+ class SaveGLB:
589
+ @classmethod
590
+ def INPUT_TYPES(s):
591
+ return {"required": {"mesh": ("MESH", ),
592
+ "filename_prefix": ("STRING", {"default": "mesh/ComfyUI"}), },
593
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, }
594
+
595
+ RETURN_TYPES = ()
596
+ FUNCTION = "save"
597
+
598
+ OUTPUT_NODE = True
599
+
600
+ CATEGORY = "3d"
601
+
602
+ def save(self, mesh, filename_prefix, prompt=None, extra_pnginfo=None):
603
+ full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, folder_paths.get_output_directory())
604
+ results = []
605
+
606
+ metadata = {}
607
+ if not args.disable_metadata:
608
+ if prompt is not None:
609
+ metadata["prompt"] = json.dumps(prompt)
610
+ if extra_pnginfo is not None:
611
+ for x in extra_pnginfo:
612
+ metadata[x] = json.dumps(extra_pnginfo[x])
613
+
614
+ for i in range(mesh.vertices.shape[0]):
615
+ f = f"{filename}_{counter:05}_.glb"
616
+ save_glb(mesh.vertices[i], mesh.faces[i], os.path.join(full_output_folder, f), metadata)
617
+ results.append({
618
+ "filename": f,
619
+ "subfolder": subfolder,
620
+ "type": "output"
621
+ })
622
+ counter += 1
623
+ return {"ui": {"3d": results}}
624
+
625
+
626
+ NODE_CLASS_MAPPINGS = {
627
+ "EmptyLatentHunyuan3Dv2": EmptyLatentHunyuan3Dv2,
628
+ "Hunyuan3Dv2Conditioning": Hunyuan3Dv2Conditioning,
629
+ "Hunyuan3Dv2ConditioningMultiView": Hunyuan3Dv2ConditioningMultiView,
630
+ "VAEDecodeHunyuan3D": VAEDecodeHunyuan3D,
631
+ "VoxelToMeshBasic": VoxelToMeshBasic,
632
+ "VoxelToMesh": VoxelToMesh,
633
+ "SaveGLB": SaveGLB,
634
+ }
ComfyUI/comfy_extras/nodes_hypernetwork.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import comfy.utils
2
+ import folder_paths
3
+ import torch
4
+ import logging
5
+
6
+ def load_hypernetwork_patch(path, strength):
7
+ sd = comfy.utils.load_torch_file(path, safe_load=True)
8
+ activation_func = sd.get('activation_func', 'linear')
9
+ is_layer_norm = sd.get('is_layer_norm', False)
10
+ use_dropout = sd.get('use_dropout', False)
11
+ activate_output = sd.get('activate_output', False)
12
+ last_layer_dropout = sd.get('last_layer_dropout', False)
13
+
14
+ valid_activation = {
15
+ "linear": torch.nn.Identity,
16
+ "relu": torch.nn.ReLU,
17
+ "leakyrelu": torch.nn.LeakyReLU,
18
+ "elu": torch.nn.ELU,
19
+ "swish": torch.nn.Hardswish,
20
+ "tanh": torch.nn.Tanh,
21
+ "sigmoid": torch.nn.Sigmoid,
22
+ "softsign": torch.nn.Softsign,
23
+ "mish": torch.nn.Mish,
24
+ }
25
+
26
+ if activation_func not in valid_activation:
27
+ logging.error("Unsupported Hypernetwork format, if you report it I might implement it. {} {} {} {} {} {}".format(path, activation_func, is_layer_norm, use_dropout, activate_output, last_layer_dropout))
28
+ return None
29
+
30
+ out = {}
31
+
32
+ for d in sd:
33
+ try:
34
+ dim = int(d)
35
+ except:
36
+ continue
37
+
38
+ output = []
39
+ for index in [0, 1]:
40
+ attn_weights = sd[dim][index]
41
+ keys = attn_weights.keys()
42
+
43
+ linears = filter(lambda a: a.endswith(".weight"), keys)
44
+ linears = list(map(lambda a: a[:-len(".weight")], linears))
45
+ layers = []
46
+
47
+ i = 0
48
+ while i < len(linears):
49
+ lin_name = linears[i]
50
+ last_layer = (i == (len(linears) - 1))
51
+ penultimate_layer = (i == (len(linears) - 2))
52
+
53
+ lin_weight = attn_weights['{}.weight'.format(lin_name)]
54
+ lin_bias = attn_weights['{}.bias'.format(lin_name)]
55
+ layer = torch.nn.Linear(lin_weight.shape[1], lin_weight.shape[0])
56
+ layer.load_state_dict({"weight": lin_weight, "bias": lin_bias})
57
+ layers.append(layer)
58
+ if activation_func != "linear":
59
+ if (not last_layer) or (activate_output):
60
+ layers.append(valid_activation[activation_func]())
61
+ if is_layer_norm:
62
+ i += 1
63
+ ln_name = linears[i]
64
+ ln_weight = attn_weights['{}.weight'.format(ln_name)]
65
+ ln_bias = attn_weights['{}.bias'.format(ln_name)]
66
+ ln = torch.nn.LayerNorm(ln_weight.shape[0])
67
+ ln.load_state_dict({"weight": ln_weight, "bias": ln_bias})
68
+ layers.append(ln)
69
+ if use_dropout:
70
+ if (not last_layer) and (not penultimate_layer or last_layer_dropout):
71
+ layers.append(torch.nn.Dropout(p=0.3))
72
+ i += 1
73
+
74
+ output.append(torch.nn.Sequential(*layers))
75
+ out[dim] = torch.nn.ModuleList(output)
76
+
77
+ class hypernetwork_patch:
78
+ def __init__(self, hypernet, strength):
79
+ self.hypernet = hypernet
80
+ self.strength = strength
81
+ def __call__(self, q, k, v, extra_options):
82
+ dim = k.shape[-1]
83
+ if dim in self.hypernet:
84
+ hn = self.hypernet[dim]
85
+ k = k + hn[0](k) * self.strength
86
+ v = v + hn[1](v) * self.strength
87
+
88
+ return q, k, v
89
+
90
+ def to(self, device):
91
+ for d in self.hypernet.keys():
92
+ self.hypernet[d] = self.hypernet[d].to(device)
93
+ return self
94
+
95
+ return hypernetwork_patch(out, strength)
96
+
97
+ class HypernetworkLoader:
98
+ @classmethod
99
+ def INPUT_TYPES(s):
100
+ return {"required": { "model": ("MODEL",),
101
+ "hypernetwork_name": (folder_paths.get_filename_list("hypernetworks"), ),
102
+ "strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
103
+ }}
104
+ RETURN_TYPES = ("MODEL",)
105
+ FUNCTION = "load_hypernetwork"
106
+
107
+ CATEGORY = "loaders"
108
+
109
+ def load_hypernetwork(self, model, hypernetwork_name, strength):
110
+ hypernetwork_path = folder_paths.get_full_path_or_raise("hypernetworks", hypernetwork_name)
111
+ model_hypernetwork = model.clone()
112
+ patch = load_hypernetwork_patch(hypernetwork_path, strength)
113
+ if patch is not None:
114
+ model_hypernetwork.set_model_attn1_patch(patch)
115
+ model_hypernetwork.set_model_attn2_patch(patch)
116
+ return (model_hypernetwork,)
117
+
118
+ NODE_CLASS_MAPPINGS = {
119
+ "HypernetworkLoader": HypernetworkLoader
120
+ }
ComfyUI/comfy_extras/nodes_hypertile.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Taken from: https://github.com/tfernd/HyperTile/
2
+
3
+ import math
4
+ from einops import rearrange
5
+ # Use torch rng for consistency across generations
6
+ from torch import randint
7
+
8
+ def random_divisor(value: int, min_value: int, /, max_options: int = 1) -> int:
9
+ min_value = min(min_value, value)
10
+
11
+ # All big divisors of value (inclusive)
12
+ divisors = [i for i in range(min_value, value + 1) if value % i == 0]
13
+
14
+ ns = [value // i for i in divisors[:max_options]] # has at least 1 element
15
+
16
+ if len(ns) - 1 > 0:
17
+ idx = randint(low=0, high=len(ns) - 1, size=(1,)).item()
18
+ else:
19
+ idx = 0
20
+
21
+ return ns[idx]
22
+
23
+ class HyperTile:
24
+ @classmethod
25
+ def INPUT_TYPES(s):
26
+ return {"required": { "model": ("MODEL",),
27
+ "tile_size": ("INT", {"default": 256, "min": 1, "max": 2048}),
28
+ "swap_size": ("INT", {"default": 2, "min": 1, "max": 128}),
29
+ "max_depth": ("INT", {"default": 0, "min": 0, "max": 10}),
30
+ "scale_depth": ("BOOLEAN", {"default": False}),
31
+ }}
32
+ RETURN_TYPES = ("MODEL",)
33
+ FUNCTION = "patch"
34
+
35
+ CATEGORY = "model_patches/unet"
36
+
37
+ def patch(self, model, tile_size, swap_size, max_depth, scale_depth):
38
+ latent_tile_size = max(32, tile_size) // 8
39
+ self.temp = None
40
+
41
+ def hypertile_in(q, k, v, extra_options):
42
+ model_chans = q.shape[-2]
43
+ orig_shape = extra_options['original_shape']
44
+ apply_to = []
45
+ for i in range(max_depth + 1):
46
+ apply_to.append((orig_shape[-2] / (2 ** i)) * (orig_shape[-1] / (2 ** i)))
47
+
48
+ if model_chans in apply_to:
49
+ shape = extra_options["original_shape"]
50
+ aspect_ratio = shape[-1] / shape[-2]
51
+
52
+ hw = q.size(1)
53
+ h, w = round(math.sqrt(hw * aspect_ratio)), round(math.sqrt(hw / aspect_ratio))
54
+
55
+ factor = (2 ** apply_to.index(model_chans)) if scale_depth else 1
56
+ nh = random_divisor(h, latent_tile_size * factor, swap_size)
57
+ nw = random_divisor(w, latent_tile_size * factor, swap_size)
58
+
59
+ if nh * nw > 1:
60
+ 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)
61
+ self.temp = (nh, nw, h, w)
62
+ return q, k, v
63
+
64
+ return q, k, v
65
+ def hypertile_out(out, extra_options):
66
+ if self.temp is not None:
67
+ nh, nw, h, w = self.temp
68
+ self.temp = None
69
+ out = rearrange(out, "(b nh nw) hw c -> b nh nw hw c", nh=nh, nw=nw)
70
+ out = rearrange(out, "b nh nw (h w) c -> b (nh h nw w) c", h=h // nh, w=w // nw)
71
+ return out
72
+
73
+
74
+ m = model.clone()
75
+ m.set_model_attn1_patch(hypertile_in)
76
+ m.set_model_attn1_output_patch(hypertile_out)
77
+ return (m, )
78
+
79
+ NODE_CLASS_MAPPINGS = {
80
+ "HyperTile": HyperTile,
81
+ }
ComfyUI/comfy_extras/nodes_images.py ADDED
@@ -0,0 +1,642 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import nodes
4
+ import folder_paths
5
+ from comfy.cli_args import args
6
+
7
+ from PIL import Image
8
+ from PIL.PngImagePlugin import PngInfo
9
+
10
+ import numpy as np
11
+ import json
12
+ import os
13
+ import re
14
+ from io import BytesIO
15
+ from inspect import cleandoc
16
+ import torch
17
+ import comfy.utils
18
+
19
+ from comfy.comfy_types import FileLocator, IO
20
+ from server import PromptServer
21
+
22
+ MAX_RESOLUTION = nodes.MAX_RESOLUTION
23
+
24
+ class ImageCrop:
25
+ @classmethod
26
+ def INPUT_TYPES(s):
27
+ return {"required": { "image": ("IMAGE",),
28
+ "width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
29
+ "height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
30
+ "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
31
+ "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
32
+ }}
33
+ RETURN_TYPES = ("IMAGE",)
34
+ FUNCTION = "crop"
35
+
36
+ CATEGORY = "image/transform"
37
+
38
+ def crop(self, image, width, height, x, y):
39
+ x = min(x, image.shape[2] - 1)
40
+ y = min(y, image.shape[1] - 1)
41
+ to_x = width + x
42
+ to_y = height + y
43
+ img = image[:,y:to_y, x:to_x, :]
44
+ return (img,)
45
+
46
+ class RepeatImageBatch:
47
+ @classmethod
48
+ def INPUT_TYPES(s):
49
+ return {"required": { "image": ("IMAGE",),
50
+ "amount": ("INT", {"default": 1, "min": 1, "max": 4096}),
51
+ }}
52
+ RETURN_TYPES = ("IMAGE",)
53
+ FUNCTION = "repeat"
54
+
55
+ CATEGORY = "image/batch"
56
+
57
+ def repeat(self, image, amount):
58
+ s = image.repeat((amount, 1,1,1))
59
+ return (s,)
60
+
61
+ class ImageFromBatch:
62
+ @classmethod
63
+ def INPUT_TYPES(s):
64
+ return {"required": { "image": ("IMAGE",),
65
+ "batch_index": ("INT", {"default": 0, "min": 0, "max": 4095}),
66
+ "length": ("INT", {"default": 1, "min": 1, "max": 4096}),
67
+ }}
68
+ RETURN_TYPES = ("IMAGE",)
69
+ FUNCTION = "frombatch"
70
+
71
+ CATEGORY = "image/batch"
72
+
73
+ def frombatch(self, image, batch_index, length):
74
+ s_in = image
75
+ batch_index = min(s_in.shape[0] - 1, batch_index)
76
+ length = min(s_in.shape[0] - batch_index, length)
77
+ s = s_in[batch_index:batch_index + length].clone()
78
+ return (s,)
79
+
80
+
81
+ class ImageAddNoise:
82
+ @classmethod
83
+ def INPUT_TYPES(s):
84
+ return {"required": { "image": ("IMAGE",),
85
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True, "tooltip": "The random seed used for creating the noise."}),
86
+ "strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
87
+ }}
88
+ RETURN_TYPES = ("IMAGE",)
89
+ FUNCTION = "repeat"
90
+
91
+ CATEGORY = "image"
92
+
93
+ def repeat(self, image, seed, strength):
94
+ generator = torch.manual_seed(seed)
95
+ s = torch.clip((image + strength * torch.randn(image.size(), generator=generator, device="cpu").to(image)), min=0.0, max=1.0)
96
+ return (s,)
97
+
98
+ class SaveAnimatedWEBP:
99
+ def __init__(self):
100
+ self.output_dir = folder_paths.get_output_directory()
101
+ self.type = "output"
102
+ self.prefix_append = ""
103
+
104
+ methods = {"default": 4, "fastest": 0, "slowest": 6}
105
+ @classmethod
106
+ def INPUT_TYPES(s):
107
+ return {"required":
108
+ {"images": ("IMAGE", ),
109
+ "filename_prefix": ("STRING", {"default": "ComfyUI"}),
110
+ "fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
111
+ "lossless": ("BOOLEAN", {"default": True}),
112
+ "quality": ("INT", {"default": 80, "min": 0, "max": 100}),
113
+ "method": (list(s.methods.keys()),),
114
+ # "num_frames": ("INT", {"default": 0, "min": 0, "max": 8192}),
115
+ },
116
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
117
+ }
118
+
119
+ RETURN_TYPES = ()
120
+ FUNCTION = "save_images"
121
+
122
+ OUTPUT_NODE = True
123
+
124
+ CATEGORY = "image/animation"
125
+
126
+ def save_images(self, images, fps, filename_prefix, lossless, quality, method, num_frames=0, prompt=None, extra_pnginfo=None):
127
+ method = self.methods.get(method)
128
+ filename_prefix += self.prefix_append
129
+ 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])
130
+ results: list[FileLocator] = []
131
+ pil_images = []
132
+ for image in images:
133
+ i = 255. * image.cpu().numpy()
134
+ img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
135
+ pil_images.append(img)
136
+
137
+ metadata = pil_images[0].getexif()
138
+ if not args.disable_metadata:
139
+ if prompt is not None:
140
+ metadata[0x0110] = "prompt:{}".format(json.dumps(prompt))
141
+ if extra_pnginfo is not None:
142
+ inital_exif = 0x010f
143
+ for x in extra_pnginfo:
144
+ metadata[inital_exif] = "{}:{}".format(x, json.dumps(extra_pnginfo[x]))
145
+ inital_exif -= 1
146
+
147
+ if num_frames == 0:
148
+ num_frames = len(pil_images)
149
+
150
+ c = len(pil_images)
151
+ for i in range(0, c, num_frames):
152
+ file = f"{filename}_{counter:05}_.webp"
153
+ 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)
154
+ results.append({
155
+ "filename": file,
156
+ "subfolder": subfolder,
157
+ "type": self.type
158
+ })
159
+ counter += 1
160
+
161
+ animated = num_frames != 1
162
+ return { "ui": { "images": results, "animated": (animated,) } }
163
+
164
+ class SaveAnimatedPNG:
165
+ def __init__(self):
166
+ self.output_dir = folder_paths.get_output_directory()
167
+ self.type = "output"
168
+ self.prefix_append = ""
169
+
170
+ @classmethod
171
+ def INPUT_TYPES(s):
172
+ return {"required":
173
+ {"images": ("IMAGE", ),
174
+ "filename_prefix": ("STRING", {"default": "ComfyUI"}),
175
+ "fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
176
+ "compress_level": ("INT", {"default": 4, "min": 0, "max": 9})
177
+ },
178
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
179
+ }
180
+
181
+ RETURN_TYPES = ()
182
+ FUNCTION = "save_images"
183
+
184
+ OUTPUT_NODE = True
185
+
186
+ CATEGORY = "image/animation"
187
+
188
+ def save_images(self, images, fps, compress_level, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
189
+ filename_prefix += self.prefix_append
190
+ 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])
191
+ results = list()
192
+ pil_images = []
193
+ for image in images:
194
+ i = 255. * image.cpu().numpy()
195
+ img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
196
+ pil_images.append(img)
197
+
198
+ metadata = None
199
+ if not args.disable_metadata:
200
+ metadata = PngInfo()
201
+ if prompt is not None:
202
+ metadata.add(b"comf", "prompt".encode("latin-1", "strict") + b"\0" + json.dumps(prompt).encode("latin-1", "strict"), after_idat=True)
203
+ if extra_pnginfo is not None:
204
+ for x in extra_pnginfo:
205
+ metadata.add(b"comf", x.encode("latin-1", "strict") + b"\0" + json.dumps(extra_pnginfo[x]).encode("latin-1", "strict"), after_idat=True)
206
+
207
+ file = f"{filename}_{counter:05}_.png"
208
+ 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:])
209
+ results.append({
210
+ "filename": file,
211
+ "subfolder": subfolder,
212
+ "type": self.type
213
+ })
214
+
215
+ return { "ui": { "images": results, "animated": (True,)} }
216
+
217
+ class SVG:
218
+ """
219
+ Stores SVG representations via a list of BytesIO objects.
220
+ """
221
+ def __init__(self, data: list[BytesIO]):
222
+ self.data = data
223
+
224
+ def combine(self, other: 'SVG') -> 'SVG':
225
+ return SVG(self.data + other.data)
226
+
227
+ @staticmethod
228
+ def combine_all(svgs: list['SVG']) -> 'SVG':
229
+ all_svgs_list: list[BytesIO] = []
230
+ for svg_item in svgs:
231
+ all_svgs_list.extend(svg_item.data)
232
+ return SVG(all_svgs_list)
233
+
234
+
235
+ class ImageStitch:
236
+ """Upstreamed from https://github.com/kijai/ComfyUI-KJNodes"""
237
+
238
+ @classmethod
239
+ def INPUT_TYPES(s):
240
+ return {
241
+ "required": {
242
+ "image1": ("IMAGE",),
243
+ "direction": (["right", "down", "left", "up"], {"default": "right"}),
244
+ "match_image_size": ("BOOLEAN", {"default": True}),
245
+ "spacing_width": (
246
+ "INT",
247
+ {"default": 0, "min": 0, "max": 1024, "step": 2},
248
+ ),
249
+ "spacing_color": (
250
+ ["white", "black", "red", "green", "blue"],
251
+ {"default": "white"},
252
+ ),
253
+ },
254
+ "optional": {
255
+ "image2": ("IMAGE",),
256
+ },
257
+ }
258
+
259
+ RETURN_TYPES = ("IMAGE",)
260
+ FUNCTION = "stitch"
261
+ CATEGORY = "image/transform"
262
+ DESCRIPTION = """
263
+ Stitches image2 to image1 in the specified direction.
264
+ If image2 is not provided, returns image1 unchanged.
265
+ Optional spacing can be added between images.
266
+ """
267
+
268
+ def stitch(
269
+ self,
270
+ image1,
271
+ direction,
272
+ match_image_size,
273
+ spacing_width,
274
+ spacing_color,
275
+ image2=None,
276
+ ):
277
+ if image2 is None:
278
+ return (image1,)
279
+
280
+ # Handle batch size differences
281
+ if image1.shape[0] != image2.shape[0]:
282
+ max_batch = max(image1.shape[0], image2.shape[0])
283
+ if image1.shape[0] < max_batch:
284
+ image1 = torch.cat(
285
+ [image1, image1[-1:].repeat(max_batch - image1.shape[0], 1, 1, 1)]
286
+ )
287
+ if image2.shape[0] < max_batch:
288
+ image2 = torch.cat(
289
+ [image2, image2[-1:].repeat(max_batch - image2.shape[0], 1, 1, 1)]
290
+ )
291
+
292
+ # Match image sizes if requested
293
+ if match_image_size:
294
+ h1, w1 = image1.shape[1:3]
295
+ h2, w2 = image2.shape[1:3]
296
+ aspect_ratio = w2 / h2
297
+
298
+ if direction in ["left", "right"]:
299
+ target_h, target_w = h1, int(h1 * aspect_ratio)
300
+ else: # up, down
301
+ target_w, target_h = w1, int(w1 / aspect_ratio)
302
+
303
+ image2 = comfy.utils.common_upscale(
304
+ image2.movedim(-1, 1), target_w, target_h, "lanczos", "disabled"
305
+ ).movedim(1, -1)
306
+
307
+ color_map = {
308
+ "white": 1.0,
309
+ "black": 0.0,
310
+ "red": (1.0, 0.0, 0.0),
311
+ "green": (0.0, 1.0, 0.0),
312
+ "blue": (0.0, 0.0, 1.0),
313
+ }
314
+
315
+ color_val = color_map[spacing_color]
316
+
317
+ # When not matching sizes, pad to align non-concat dimensions
318
+ if not match_image_size:
319
+ h1, w1 = image1.shape[1:3]
320
+ h2, w2 = image2.shape[1:3]
321
+ pad_value = 0.0
322
+ if not isinstance(color_val, tuple):
323
+ pad_value = color_val
324
+
325
+ if direction in ["left", "right"]:
326
+ # For horizontal concat, pad heights to match
327
+ if h1 != h2:
328
+ target_h = max(h1, h2)
329
+ if h1 < target_h:
330
+ pad_h = target_h - h1
331
+ pad_top, pad_bottom = pad_h // 2, pad_h - pad_h // 2
332
+ image1 = torch.nn.functional.pad(image1, (0, 0, 0, 0, pad_top, pad_bottom), mode='constant', value=pad_value)
333
+ if h2 < target_h:
334
+ pad_h = target_h - h2
335
+ pad_top, pad_bottom = pad_h // 2, pad_h - pad_h // 2
336
+ image2 = torch.nn.functional.pad(image2, (0, 0, 0, 0, pad_top, pad_bottom), mode='constant', value=pad_value)
337
+ else: # up, down
338
+ # For vertical concat, pad widths to match
339
+ if w1 != w2:
340
+ target_w = max(w1, w2)
341
+ if w1 < target_w:
342
+ pad_w = target_w - w1
343
+ pad_left, pad_right = pad_w // 2, pad_w - pad_w // 2
344
+ image1 = torch.nn.functional.pad(image1, (0, 0, pad_left, pad_right), mode='constant', value=pad_value)
345
+ if w2 < target_w:
346
+ pad_w = target_w - w2
347
+ pad_left, pad_right = pad_w // 2, pad_w - pad_w // 2
348
+ image2 = torch.nn.functional.pad(image2, (0, 0, pad_left, pad_right), mode='constant', value=pad_value)
349
+
350
+ # Ensure same number of channels
351
+ if image1.shape[-1] != image2.shape[-1]:
352
+ max_channels = max(image1.shape[-1], image2.shape[-1])
353
+ if image1.shape[-1] < max_channels:
354
+ image1 = torch.cat(
355
+ [
356
+ image1,
357
+ torch.ones(
358
+ *image1.shape[:-1],
359
+ max_channels - image1.shape[-1],
360
+ device=image1.device,
361
+ ),
362
+ ],
363
+ dim=-1,
364
+ )
365
+ if image2.shape[-1] < max_channels:
366
+ image2 = torch.cat(
367
+ [
368
+ image2,
369
+ torch.ones(
370
+ *image2.shape[:-1],
371
+ max_channels - image2.shape[-1],
372
+ device=image2.device,
373
+ ),
374
+ ],
375
+ dim=-1,
376
+ )
377
+
378
+ # Add spacing if specified
379
+ if spacing_width > 0:
380
+ spacing_width = spacing_width + (spacing_width % 2) # Ensure even
381
+
382
+ if direction in ["left", "right"]:
383
+ spacing_shape = (
384
+ image1.shape[0],
385
+ max(image1.shape[1], image2.shape[1]),
386
+ spacing_width,
387
+ image1.shape[-1],
388
+ )
389
+ else:
390
+ spacing_shape = (
391
+ image1.shape[0],
392
+ spacing_width,
393
+ max(image1.shape[2], image2.shape[2]),
394
+ image1.shape[-1],
395
+ )
396
+
397
+ spacing = torch.full(spacing_shape, 0.0, device=image1.device)
398
+ if isinstance(color_val, tuple):
399
+ for i, c in enumerate(color_val):
400
+ if i < spacing.shape[-1]:
401
+ spacing[..., i] = c
402
+ if spacing.shape[-1] == 4: # Add alpha
403
+ spacing[..., 3] = 1.0
404
+ else:
405
+ spacing[..., : min(3, spacing.shape[-1])] = color_val
406
+ if spacing.shape[-1] == 4:
407
+ spacing[..., 3] = 1.0
408
+
409
+ # Concatenate images
410
+ images = [image2, image1] if direction in ["left", "up"] else [image1, image2]
411
+ if spacing_width > 0:
412
+ images.insert(1, spacing)
413
+
414
+ concat_dim = 2 if direction in ["left", "right"] else 1
415
+ return (torch.cat(images, dim=concat_dim),)
416
+
417
+ class ResizeAndPadImage:
418
+ @classmethod
419
+ def INPUT_TYPES(cls):
420
+ return {
421
+ "required": {
422
+ "image": ("IMAGE",),
423
+ "target_width": ("INT", {
424
+ "default": 512,
425
+ "min": 1,
426
+ "max": MAX_RESOLUTION,
427
+ "step": 1
428
+ }),
429
+ "target_height": ("INT", {
430
+ "default": 512,
431
+ "min": 1,
432
+ "max": MAX_RESOLUTION,
433
+ "step": 1
434
+ }),
435
+ "padding_color": (["white", "black"],),
436
+ "interpolation": (["area", "bicubic", "nearest-exact", "bilinear", "lanczos"],),
437
+ }
438
+ }
439
+
440
+ RETURN_TYPES = ("IMAGE",)
441
+ FUNCTION = "resize_and_pad"
442
+ CATEGORY = "image/transform"
443
+
444
+ def resize_and_pad(self, image, target_width, target_height, padding_color, interpolation):
445
+ batch_size, orig_height, orig_width, channels = image.shape
446
+
447
+ scale_w = target_width / orig_width
448
+ scale_h = target_height / orig_height
449
+ scale = min(scale_w, scale_h)
450
+
451
+ new_width = int(orig_width * scale)
452
+ new_height = int(orig_height * scale)
453
+
454
+ image_permuted = image.permute(0, 3, 1, 2)
455
+
456
+ resized = comfy.utils.common_upscale(image_permuted, new_width, new_height, interpolation, "disabled")
457
+
458
+ pad_value = 0.0 if padding_color == "black" else 1.0
459
+ padded = torch.full(
460
+ (batch_size, channels, target_height, target_width),
461
+ pad_value,
462
+ dtype=image.dtype,
463
+ device=image.device
464
+ )
465
+
466
+ y_offset = (target_height - new_height) // 2
467
+ x_offset = (target_width - new_width) // 2
468
+
469
+ padded[:, :, y_offset:y_offset + new_height, x_offset:x_offset + new_width] = resized
470
+
471
+ output = padded.permute(0, 2, 3, 1)
472
+ return (output,)
473
+
474
+ class SaveSVGNode:
475
+ """
476
+ Save SVG files on disk.
477
+ """
478
+
479
+ def __init__(self):
480
+ self.output_dir = folder_paths.get_output_directory()
481
+ self.type = "output"
482
+ self.prefix_append = ""
483
+
484
+ RETURN_TYPES = ()
485
+ DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value
486
+ FUNCTION = "save_svg"
487
+ CATEGORY = "image/save" # Changed
488
+ OUTPUT_NODE = True
489
+
490
+ @classmethod
491
+ def INPUT_TYPES(s):
492
+ return {
493
+ "required": {
494
+ "svg": ("SVG",), # Changed
495
+ "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."})
496
+ },
497
+ "hidden": {
498
+ "prompt": "PROMPT",
499
+ "extra_pnginfo": "EXTRA_PNGINFO"
500
+ }
501
+ }
502
+
503
+ def save_svg(self, svg: SVG, filename_prefix="svg/ComfyUI", prompt=None, extra_pnginfo=None):
504
+ filename_prefix += self.prefix_append
505
+ full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
506
+ results = list()
507
+
508
+ # Prepare metadata JSON
509
+ metadata_dict = {}
510
+ if prompt is not None:
511
+ metadata_dict["prompt"] = prompt
512
+ if extra_pnginfo is not None:
513
+ metadata_dict.update(extra_pnginfo)
514
+
515
+ # Convert metadata to JSON string
516
+ metadata_json = json.dumps(metadata_dict, indent=2) if metadata_dict else None
517
+
518
+ for batch_number, svg_bytes in enumerate(svg.data):
519
+ filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
520
+ file = f"{filename_with_batch_num}_{counter:05}_.svg"
521
+
522
+ # Read SVG content
523
+ svg_bytes.seek(0)
524
+ svg_content = svg_bytes.read().decode('utf-8')
525
+
526
+ # Inject metadata if available
527
+ if metadata_json:
528
+ # Create metadata element with CDATA section
529
+ metadata_element = f""" <metadata>
530
+ <![CDATA[
531
+ {metadata_json}
532
+ ]]>
533
+ </metadata>
534
+ """
535
+ # Insert metadata after opening svg tag using regex with a replacement function
536
+ def replacement(match):
537
+ # match.group(1) contains the captured <svg> tag
538
+ return match.group(1) + '\n' + metadata_element
539
+
540
+ # Apply the substitution
541
+ svg_content = re.sub(r'(<svg[^>]*>)', replacement, svg_content, flags=re.UNICODE)
542
+
543
+ # Write the modified SVG to file
544
+ with open(os.path.join(full_output_folder, file), 'wb') as svg_file:
545
+ svg_file.write(svg_content.encode('utf-8'))
546
+
547
+ results.append({
548
+ "filename": file,
549
+ "subfolder": subfolder,
550
+ "type": self.type
551
+ })
552
+ counter += 1
553
+ return { "ui": { "images": results } }
554
+
555
+ class GetImageSize:
556
+
557
+ @classmethod
558
+ def INPUT_TYPES(s):
559
+ return {
560
+ "required": {
561
+ "image": (IO.IMAGE,),
562
+ },
563
+ "hidden": {
564
+ "unique_id": "UNIQUE_ID",
565
+ }
566
+ }
567
+
568
+ RETURN_TYPES = (IO.INT, IO.INT, IO.INT)
569
+ RETURN_NAMES = ("width", "height", "batch_size")
570
+ FUNCTION = "get_size"
571
+
572
+ CATEGORY = "image"
573
+ DESCRIPTION = """Returns width and height of the image, and passes it through unchanged."""
574
+
575
+ def get_size(self, image, unique_id=None) -> tuple[int, int]:
576
+ height = image.shape[1]
577
+ width = image.shape[2]
578
+ batch_size = image.shape[0]
579
+
580
+ # Send progress text to display size on the node
581
+ if unique_id:
582
+ PromptServer.instance.send_progress_text(f"width: {width}, height: {height}\n batch size: {batch_size}", unique_id)
583
+
584
+ return width, height, batch_size
585
+
586
+ class ImageRotate:
587
+ @classmethod
588
+ def INPUT_TYPES(s):
589
+ return {"required": { "image": (IO.IMAGE,),
590
+ "rotation": (["none", "90 degrees", "180 degrees", "270 degrees"],),
591
+ }}
592
+ RETURN_TYPES = (IO.IMAGE,)
593
+ FUNCTION = "rotate"
594
+
595
+ CATEGORY = "image/transform"
596
+
597
+ def rotate(self, image, rotation):
598
+ rotate_by = 0
599
+ if rotation.startswith("90"):
600
+ rotate_by = 1
601
+ elif rotation.startswith("180"):
602
+ rotate_by = 2
603
+ elif rotation.startswith("270"):
604
+ rotate_by = 3
605
+
606
+ image = torch.rot90(image, k=rotate_by, dims=[2, 1])
607
+ return (image,)
608
+
609
+ class ImageFlip:
610
+ @classmethod
611
+ def INPUT_TYPES(s):
612
+ return {"required": { "image": (IO.IMAGE,),
613
+ "flip_method": (["x-axis: vertically", "y-axis: horizontally"],),
614
+ }}
615
+ RETURN_TYPES = (IO.IMAGE,)
616
+ FUNCTION = "flip"
617
+
618
+ CATEGORY = "image/transform"
619
+
620
+ def flip(self, image, flip_method):
621
+ if flip_method.startswith("x"):
622
+ image = torch.flip(image, dims=[1])
623
+ elif flip_method.startswith("y"):
624
+ image = torch.flip(image, dims=[2])
625
+
626
+ return (image,)
627
+
628
+
629
+ NODE_CLASS_MAPPINGS = {
630
+ "ImageCrop": ImageCrop,
631
+ "RepeatImageBatch": RepeatImageBatch,
632
+ "ImageFromBatch": ImageFromBatch,
633
+ "ImageAddNoise": ImageAddNoise,
634
+ "SaveAnimatedWEBP": SaveAnimatedWEBP,
635
+ "SaveAnimatedPNG": SaveAnimatedPNG,
636
+ "SaveSVGNode": SaveSVGNode,
637
+ "ImageStitch": ImageStitch,
638
+ "ResizeAndPadImage": ResizeAndPadImage,
639
+ "GetImageSize": GetImageSize,
640
+ "ImageRotate": ImageRotate,
641
+ "ImageFlip": ImageFlip,
642
+ }
ComfyUI/comfy_extras/nodes_ip2p.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ class InstructPixToPixConditioning:
4
+ @classmethod
5
+ def INPUT_TYPES(s):
6
+ return {"required": {"positive": ("CONDITIONING", ),
7
+ "negative": ("CONDITIONING", ),
8
+ "vae": ("VAE", ),
9
+ "pixels": ("IMAGE", ),
10
+ }}
11
+
12
+ RETURN_TYPES = ("CONDITIONING","CONDITIONING","LATENT")
13
+ RETURN_NAMES = ("positive", "negative", "latent")
14
+ FUNCTION = "encode"
15
+
16
+ CATEGORY = "conditioning/instructpix2pix"
17
+
18
+ def encode(self, positive, negative, pixels, vae):
19
+ x = (pixels.shape[1] // 8) * 8
20
+ y = (pixels.shape[2] // 8) * 8
21
+
22
+ if pixels.shape[1] != x or pixels.shape[2] != y:
23
+ x_offset = (pixels.shape[1] % 8) // 2
24
+ y_offset = (pixels.shape[2] % 8) // 2
25
+ pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:]
26
+
27
+ concat_latent = vae.encode(pixels)
28
+
29
+ out_latent = {}
30
+ out_latent["samples"] = torch.zeros_like(concat_latent)
31
+
32
+ out = []
33
+ for conditioning in [positive, negative]:
34
+ c = []
35
+ for t in conditioning:
36
+ d = t[1].copy()
37
+ d["concat_latent_image"] = concat_latent
38
+ n = [t[0], d]
39
+ c.append(n)
40
+ out.append(c)
41
+ return (out[0], out[1], out_latent)
42
+
43
+ NODE_CLASS_MAPPINGS = {
44
+ "InstructPixToPixConditioning": InstructPixToPixConditioning,
45
+ }
ComfyUI/comfy_extras/nodes_latent.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import comfy.utils
2
+ import comfy_extras.nodes_post_processing
3
+ import torch
4
+
5
+
6
+ def reshape_latent_to(target_shape, latent, repeat_batch=True):
7
+ if latent.shape[1:] != target_shape[1:]:
8
+ latent = comfy.utils.common_upscale(latent, target_shape[-1], target_shape[-2], "bilinear", "center")
9
+ if repeat_batch:
10
+ return comfy.utils.repeat_to_batch_size(latent, target_shape[0])
11
+ else:
12
+ return latent
13
+
14
+
15
+ class LatentAdd:
16
+ @classmethod
17
+ def INPUT_TYPES(s):
18
+ return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}}
19
+
20
+ RETURN_TYPES = ("LATENT",)
21
+ FUNCTION = "op"
22
+
23
+ CATEGORY = "latent/advanced"
24
+
25
+ def op(self, samples1, samples2):
26
+ samples_out = samples1.copy()
27
+
28
+ s1 = samples1["samples"]
29
+ s2 = samples2["samples"]
30
+
31
+ s2 = reshape_latent_to(s1.shape, s2)
32
+ samples_out["samples"] = s1 + s2
33
+ return (samples_out,)
34
+
35
+ class LatentSubtract:
36
+ @classmethod
37
+ def INPUT_TYPES(s):
38
+ return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}}
39
+
40
+ RETURN_TYPES = ("LATENT",)
41
+ FUNCTION = "op"
42
+
43
+ CATEGORY = "latent/advanced"
44
+
45
+ def op(self, samples1, samples2):
46
+ samples_out = samples1.copy()
47
+
48
+ s1 = samples1["samples"]
49
+ s2 = samples2["samples"]
50
+
51
+ s2 = reshape_latent_to(s1.shape, s2)
52
+ samples_out["samples"] = s1 - s2
53
+ return (samples_out,)
54
+
55
+ class LatentMultiply:
56
+ @classmethod
57
+ def INPUT_TYPES(s):
58
+ return {"required": { "samples": ("LATENT",),
59
+ "multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
60
+ }}
61
+
62
+ RETURN_TYPES = ("LATENT",)
63
+ FUNCTION = "op"
64
+
65
+ CATEGORY = "latent/advanced"
66
+
67
+ def op(self, samples, multiplier):
68
+ samples_out = samples.copy()
69
+
70
+ s1 = samples["samples"]
71
+ samples_out["samples"] = s1 * multiplier
72
+ return (samples_out,)
73
+
74
+ class LatentInterpolate:
75
+ @classmethod
76
+ def INPUT_TYPES(s):
77
+ return {"required": { "samples1": ("LATENT",),
78
+ "samples2": ("LATENT",),
79
+ "ratio": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
80
+ }}
81
+
82
+ RETURN_TYPES = ("LATENT",)
83
+ FUNCTION = "op"
84
+
85
+ CATEGORY = "latent/advanced"
86
+
87
+ def op(self, samples1, samples2, ratio):
88
+ samples_out = samples1.copy()
89
+
90
+ s1 = samples1["samples"]
91
+ s2 = samples2["samples"]
92
+
93
+ s2 = reshape_latent_to(s1.shape, s2)
94
+
95
+ m1 = torch.linalg.vector_norm(s1, dim=(1))
96
+ m2 = torch.linalg.vector_norm(s2, dim=(1))
97
+
98
+ s1 = torch.nan_to_num(s1 / m1)
99
+ s2 = torch.nan_to_num(s2 / m2)
100
+
101
+ t = (s1 * ratio + s2 * (1.0 - ratio))
102
+ mt = torch.linalg.vector_norm(t, dim=(1))
103
+ st = torch.nan_to_num(t / mt)
104
+
105
+ samples_out["samples"] = st * (m1 * ratio + m2 * (1.0 - ratio))
106
+ return (samples_out,)
107
+
108
+ class LatentBatch:
109
+ @classmethod
110
+ def INPUT_TYPES(s):
111
+ return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}}
112
+
113
+ RETURN_TYPES = ("LATENT",)
114
+ FUNCTION = "batch"
115
+
116
+ CATEGORY = "latent/batch"
117
+
118
+ def batch(self, samples1, samples2):
119
+ samples_out = samples1.copy()
120
+ s1 = samples1["samples"]
121
+ s2 = samples2["samples"]
122
+
123
+ s2 = reshape_latent_to(s1.shape, s2, repeat_batch=False)
124
+ s = torch.cat((s1, s2), dim=0)
125
+ samples_out["samples"] = s
126
+ 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])])
127
+ return (samples_out,)
128
+
129
+ class LatentBatchSeedBehavior:
130
+ @classmethod
131
+ def INPUT_TYPES(s):
132
+ return {"required": { "samples": ("LATENT",),
133
+ "seed_behavior": (["random", "fixed"],{"default": "fixed"}),}}
134
+
135
+ RETURN_TYPES = ("LATENT",)
136
+ FUNCTION = "op"
137
+
138
+ CATEGORY = "latent/advanced"
139
+
140
+ def op(self, samples, seed_behavior):
141
+ samples_out = samples.copy()
142
+ latent = samples["samples"]
143
+ if seed_behavior == "random":
144
+ if 'batch_index' in samples_out:
145
+ samples_out.pop('batch_index')
146
+ elif seed_behavior == "fixed":
147
+ batch_number = samples_out.get("batch_index", [0])[0]
148
+ samples_out["batch_index"] = [batch_number] * latent.shape[0]
149
+
150
+ return (samples_out,)
151
+
152
+ class LatentApplyOperation:
153
+ @classmethod
154
+ def INPUT_TYPES(s):
155
+ return {"required": { "samples": ("LATENT",),
156
+ "operation": ("LATENT_OPERATION",),
157
+ }}
158
+
159
+ RETURN_TYPES = ("LATENT",)
160
+ FUNCTION = "op"
161
+
162
+ CATEGORY = "latent/advanced/operations"
163
+ EXPERIMENTAL = True
164
+
165
+ def op(self, samples, operation):
166
+ samples_out = samples.copy()
167
+
168
+ s1 = samples["samples"]
169
+ samples_out["samples"] = operation(latent=s1)
170
+ return (samples_out,)
171
+
172
+ class LatentApplyOperationCFG:
173
+ @classmethod
174
+ def INPUT_TYPES(s):
175
+ return {"required": { "model": ("MODEL",),
176
+ "operation": ("LATENT_OPERATION",),
177
+ }}
178
+ RETURN_TYPES = ("MODEL",)
179
+ FUNCTION = "patch"
180
+
181
+ CATEGORY = "latent/advanced/operations"
182
+ EXPERIMENTAL = True
183
+
184
+ def patch(self, model, operation):
185
+ m = model.clone()
186
+
187
+ def pre_cfg_function(args):
188
+ conds_out = args["conds_out"]
189
+ if len(conds_out) == 2:
190
+ conds_out[0] = operation(latent=(conds_out[0] - conds_out[1])) + conds_out[1]
191
+ else:
192
+ conds_out[0] = operation(latent=conds_out[0])
193
+ return conds_out
194
+
195
+ m.set_model_sampler_pre_cfg_function(pre_cfg_function)
196
+ return (m, )
197
+
198
+ class LatentOperationTonemapReinhard:
199
+ @classmethod
200
+ def INPUT_TYPES(s):
201
+ return {"required": { "multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
202
+ }}
203
+
204
+ RETURN_TYPES = ("LATENT_OPERATION",)
205
+ FUNCTION = "op"
206
+
207
+ CATEGORY = "latent/advanced/operations"
208
+ EXPERIMENTAL = True
209
+
210
+ def op(self, multiplier):
211
+ def tonemap_reinhard(latent, **kwargs):
212
+ latent_vector_magnitude = (torch.linalg.vector_norm(latent, dim=(1)) + 0.0000000001)[:,None]
213
+ normalized_latent = latent / latent_vector_magnitude
214
+
215
+ mean = torch.mean(latent_vector_magnitude, dim=(1,2,3), keepdim=True)
216
+ std = torch.std(latent_vector_magnitude, dim=(1,2,3), keepdim=True)
217
+
218
+ top = (std * 5 + mean) * multiplier
219
+
220
+ #reinhard
221
+ latent_vector_magnitude *= (1.0 / top)
222
+ new_magnitude = latent_vector_magnitude / (latent_vector_magnitude + 1.0)
223
+ new_magnitude *= top
224
+
225
+ return normalized_latent * new_magnitude
226
+ return (tonemap_reinhard,)
227
+
228
+ class LatentOperationSharpen:
229
+ @classmethod
230
+ def INPUT_TYPES(s):
231
+ return {"required": {
232
+ "sharpen_radius": ("INT", {
233
+ "default": 9,
234
+ "min": 1,
235
+ "max": 31,
236
+ "step": 1
237
+ }),
238
+ "sigma": ("FLOAT", {
239
+ "default": 1.0,
240
+ "min": 0.1,
241
+ "max": 10.0,
242
+ "step": 0.1
243
+ }),
244
+ "alpha": ("FLOAT", {
245
+ "default": 0.1,
246
+ "min": 0.0,
247
+ "max": 5.0,
248
+ "step": 0.01
249
+ }),
250
+ }}
251
+
252
+ RETURN_TYPES = ("LATENT_OPERATION",)
253
+ FUNCTION = "op"
254
+
255
+ CATEGORY = "latent/advanced/operations"
256
+ EXPERIMENTAL = True
257
+
258
+ def op(self, sharpen_radius, sigma, alpha):
259
+ def sharpen(latent, **kwargs):
260
+ luminance = (torch.linalg.vector_norm(latent, dim=(1)) + 1e-6)[:,None]
261
+ normalized_latent = latent / luminance
262
+ channels = latent.shape[1]
263
+
264
+ kernel_size = sharpen_radius * 2 + 1
265
+ kernel = comfy_extras.nodes_post_processing.gaussian_kernel(kernel_size, sigma, device=luminance.device)
266
+ center = kernel_size // 2
267
+
268
+ kernel *= alpha * -10
269
+ kernel[center, center] = kernel[center, center] - kernel.sum() + 1.0
270
+
271
+ padded_image = torch.nn.functional.pad(normalized_latent, (sharpen_radius,sharpen_radius,sharpen_radius,sharpen_radius), 'reflect')
272
+ 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]
273
+
274
+ return luminance * sharpened
275
+ return (sharpen,)
276
+
277
+ NODE_CLASS_MAPPINGS = {
278
+ "LatentAdd": LatentAdd,
279
+ "LatentSubtract": LatentSubtract,
280
+ "LatentMultiply": LatentMultiply,
281
+ "LatentInterpolate": LatentInterpolate,
282
+ "LatentBatch": LatentBatch,
283
+ "LatentBatchSeedBehavior": LatentBatchSeedBehavior,
284
+ "LatentApplyOperation": LatentApplyOperation,
285
+ "LatentApplyOperationCFG": LatentApplyOperationCFG,
286
+ "LatentOperationTonemapReinhard": LatentOperationTonemapReinhard,
287
+ "LatentOperationSharpen": LatentOperationSharpen,
288
+ }
ComfyUI/comfy_extras/nodes_load_3d.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import nodes
2
+ import folder_paths
3
+ import os
4
+
5
+ from comfy.comfy_types import IO
6
+ from comfy_api.input_impl import VideoFromFile
7
+
8
+ from pathlib import Path
9
+
10
+
11
+ def normalize_path(path):
12
+ return path.replace('\\', '/')
13
+
14
+ class Load3D():
15
+ @classmethod
16
+ def INPUT_TYPES(s):
17
+ input_dir = os.path.join(folder_paths.get_input_directory(), "3d")
18
+
19
+ os.makedirs(input_dir, exist_ok=True)
20
+
21
+ input_path = Path(input_dir)
22
+ base_path = Path(folder_paths.get_input_directory())
23
+
24
+ files = [
25
+ normalize_path(str(file_path.relative_to(base_path)))
26
+ for file_path in input_path.rglob("*")
27
+ if file_path.suffix.lower() in {'.gltf', '.glb', '.obj', '.fbx', '.stl'}
28
+ ]
29
+
30
+ return {"required": {
31
+ "model_file": (sorted(files), {"file_upload": True}),
32
+ "image": ("LOAD_3D", {}),
33
+ "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}),
34
+ "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}),
35
+ }}
36
+
37
+ RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO)
38
+ RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "lineart", "camera_info", "recording_video")
39
+
40
+ FUNCTION = "process"
41
+ EXPERIMENTAL = True
42
+
43
+ CATEGORY = "3d"
44
+
45
+ def process(self, model_file, image, **kwargs):
46
+ image_path = folder_paths.get_annotated_filepath(image['image'])
47
+ mask_path = folder_paths.get_annotated_filepath(image['mask'])
48
+ normal_path = folder_paths.get_annotated_filepath(image['normal'])
49
+ lineart_path = folder_paths.get_annotated_filepath(image['lineart'])
50
+
51
+ load_image_node = nodes.LoadImage()
52
+ output_image, ignore_mask = load_image_node.load_image(image=image_path)
53
+ ignore_image, output_mask = load_image_node.load_image(image=mask_path)
54
+ normal_image, ignore_mask2 = load_image_node.load_image(image=normal_path)
55
+ lineart_image, ignore_mask3 = load_image_node.load_image(image=lineart_path)
56
+
57
+ video = None
58
+
59
+ if image['recording'] != "":
60
+ recording_video_path = folder_paths.get_annotated_filepath(image['recording'])
61
+
62
+ video = VideoFromFile(recording_video_path)
63
+
64
+ return output_image, output_mask, model_file, normal_image, lineart_image, image['camera_info'], video
65
+
66
+ class Load3DAnimation():
67
+ @classmethod
68
+ def INPUT_TYPES(s):
69
+ input_dir = os.path.join(folder_paths.get_input_directory(), "3d")
70
+
71
+ os.makedirs(input_dir, exist_ok=True)
72
+
73
+ input_path = Path(input_dir)
74
+ base_path = Path(folder_paths.get_input_directory())
75
+
76
+ files = [
77
+ normalize_path(str(file_path.relative_to(base_path)))
78
+ for file_path in input_path.rglob("*")
79
+ if file_path.suffix.lower() in {'.gltf', '.glb', '.fbx'}
80
+ ]
81
+
82
+ return {"required": {
83
+ "model_file": (sorted(files), {"file_upload": True}),
84
+ "image": ("LOAD_3D_ANIMATION", {}),
85
+ "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}),
86
+ "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}),
87
+ }}
88
+
89
+ RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO)
90
+ RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "camera_info", "recording_video")
91
+
92
+ FUNCTION = "process"
93
+ EXPERIMENTAL = True
94
+
95
+ CATEGORY = "3d"
96
+
97
+ def process(self, model_file, image, **kwargs):
98
+ image_path = folder_paths.get_annotated_filepath(image['image'])
99
+ mask_path = folder_paths.get_annotated_filepath(image['mask'])
100
+ normal_path = folder_paths.get_annotated_filepath(image['normal'])
101
+
102
+ load_image_node = nodes.LoadImage()
103
+ output_image, ignore_mask = load_image_node.load_image(image=image_path)
104
+ ignore_image, output_mask = load_image_node.load_image(image=mask_path)
105
+ normal_image, ignore_mask2 = load_image_node.load_image(image=normal_path)
106
+
107
+ video = None
108
+
109
+ if image['recording'] != "":
110
+ recording_video_path = folder_paths.get_annotated_filepath(image['recording'])
111
+
112
+ video = VideoFromFile(recording_video_path)
113
+
114
+ return output_image, output_mask, model_file, normal_image, image['camera_info'], video
115
+
116
+ class Preview3D():
117
+ @classmethod
118
+ def INPUT_TYPES(s):
119
+ return {"required": {
120
+ "model_file": ("STRING", {"default": "", "multiline": False}),
121
+ },
122
+ "optional": {
123
+ "camera_info": ("LOAD3D_CAMERA", {})
124
+ }}
125
+
126
+ OUTPUT_NODE = True
127
+ RETURN_TYPES = ()
128
+
129
+ CATEGORY = "3d"
130
+
131
+ FUNCTION = "process"
132
+ EXPERIMENTAL = True
133
+
134
+ def process(self, model_file, **kwargs):
135
+ camera_info = kwargs.get("camera_info", None)
136
+
137
+ return {
138
+ "ui": {
139
+ "result": [model_file, camera_info]
140
+ }
141
+ }
142
+
143
+ class Preview3DAnimation():
144
+ @classmethod
145
+ def INPUT_TYPES(s):
146
+ return {"required": {
147
+ "model_file": ("STRING", {"default": "", "multiline": False}),
148
+ },
149
+ "optional": {
150
+ "camera_info": ("LOAD3D_CAMERA", {})
151
+ }}
152
+
153
+ OUTPUT_NODE = True
154
+ RETURN_TYPES = ()
155
+
156
+ CATEGORY = "3d"
157
+
158
+ FUNCTION = "process"
159
+ EXPERIMENTAL = True
160
+
161
+ def process(self, model_file, **kwargs):
162
+ camera_info = kwargs.get("camera_info", None)
163
+
164
+ return {
165
+ "ui": {
166
+ "result": [model_file, camera_info]
167
+ }
168
+ }
169
+
170
+ NODE_CLASS_MAPPINGS = {
171
+ "Load3D": Load3D,
172
+ "Load3DAnimation": Load3DAnimation,
173
+ "Preview3D": Preview3D,
174
+ "Preview3DAnimation": Preview3DAnimation
175
+ }
176
+
177
+ NODE_DISPLAY_NAME_MAPPINGS = {
178
+ "Load3D": "Load 3D",
179
+ "Load3DAnimation": "Load 3D - Animation",
180
+ "Preview3D": "Preview 3D",
181
+ "Preview3DAnimation": "Preview 3D - Animation"
182
+ }
ComfyUI/comfy_extras/nodes_lora_extract.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import comfy.model_management
3
+ import comfy.utils
4
+ import folder_paths
5
+ import os
6
+ import logging
7
+ from enum import Enum
8
+
9
+ CLAMP_QUANTILE = 0.99
10
+
11
+ def extract_lora(diff, rank):
12
+ conv2d = (len(diff.shape) == 4)
13
+ kernel_size = None if not conv2d else diff.size()[2:4]
14
+ conv2d_3x3 = conv2d and kernel_size != (1, 1)
15
+ out_dim, in_dim = diff.size()[0:2]
16
+ rank = min(rank, in_dim, out_dim)
17
+
18
+ if conv2d:
19
+ if conv2d_3x3:
20
+ diff = diff.flatten(start_dim=1)
21
+ else:
22
+ diff = diff.squeeze()
23
+
24
+
25
+ U, S, Vh = torch.linalg.svd(diff.float())
26
+ U = U[:, :rank]
27
+ S = S[:rank]
28
+ U = U @ torch.diag(S)
29
+ Vh = Vh[:rank, :]
30
+
31
+ dist = torch.cat([U.flatten(), Vh.flatten()])
32
+ hi_val = torch.quantile(dist, CLAMP_QUANTILE)
33
+ low_val = -hi_val
34
+
35
+ U = U.clamp(low_val, hi_val)
36
+ Vh = Vh.clamp(low_val, hi_val)
37
+ if conv2d:
38
+ U = U.reshape(out_dim, rank, 1, 1)
39
+ Vh = Vh.reshape(rank, in_dim, kernel_size[0], kernel_size[1])
40
+ return (U, Vh)
41
+
42
+ class LORAType(Enum):
43
+ STANDARD = 0
44
+ FULL_DIFF = 1
45
+
46
+ LORA_TYPES = {"standard": LORAType.STANDARD,
47
+ "full_diff": LORAType.FULL_DIFF}
48
+
49
+ def calc_lora_model(model_diff, rank, prefix_model, prefix_lora, output_sd, lora_type, bias_diff=False):
50
+ comfy.model_management.load_models_gpu([model_diff], force_patch_weights=True)
51
+ sd = model_diff.model_state_dict(filter_prefix=prefix_model)
52
+
53
+ for k in sd:
54
+ if k.endswith(".weight"):
55
+ weight_diff = sd[k]
56
+ if lora_type == LORAType.STANDARD:
57
+ if weight_diff.ndim < 2:
58
+ if bias_diff:
59
+ output_sd["{}{}.diff".format(prefix_lora, k[len(prefix_model):-7])] = weight_diff.contiguous().half().cpu()
60
+ continue
61
+ try:
62
+ out = extract_lora(weight_diff, rank)
63
+ output_sd["{}{}.lora_up.weight".format(prefix_lora, k[len(prefix_model):-7])] = out[0].contiguous().half().cpu()
64
+ output_sd["{}{}.lora_down.weight".format(prefix_lora, k[len(prefix_model):-7])] = out[1].contiguous().half().cpu()
65
+ except:
66
+ logging.warning("Could not generate lora weights for key {}, is the weight difference a zero?".format(k))
67
+ elif lora_type == LORAType.FULL_DIFF:
68
+ output_sd["{}{}.diff".format(prefix_lora, k[len(prefix_model):-7])] = weight_diff.contiguous().half().cpu()
69
+
70
+ elif bias_diff and k.endswith(".bias"):
71
+ output_sd["{}{}.diff_b".format(prefix_lora, k[len(prefix_model):-5])] = sd[k].contiguous().half().cpu()
72
+ return output_sd
73
+
74
+ class LoraSave:
75
+ def __init__(self):
76
+ self.output_dir = folder_paths.get_output_directory()
77
+
78
+ @classmethod
79
+ def INPUT_TYPES(s):
80
+ return {"required": {"filename_prefix": ("STRING", {"default": "loras/ComfyUI_extracted_lora"}),
81
+ "rank": ("INT", {"default": 8, "min": 1, "max": 4096, "step": 1}),
82
+ "lora_type": (tuple(LORA_TYPES.keys()),),
83
+ "bias_diff": ("BOOLEAN", {"default": True}),
84
+ },
85
+ "optional": {"model_diff": ("MODEL", {"tooltip": "The ModelSubtract output to be converted to a lora."}),
86
+ "text_encoder_diff": ("CLIP", {"tooltip": "The CLIPSubtract output to be converted to a lora."})},
87
+ }
88
+ RETURN_TYPES = ()
89
+ FUNCTION = "save"
90
+ OUTPUT_NODE = True
91
+
92
+ CATEGORY = "_for_testing"
93
+
94
+ def save(self, filename_prefix, rank, lora_type, bias_diff, model_diff=None, text_encoder_diff=None):
95
+ if model_diff is None and text_encoder_diff is None:
96
+ return {}
97
+
98
+ lora_type = LORA_TYPES.get(lora_type)
99
+ full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
100
+
101
+ output_sd = {}
102
+ if model_diff is not None:
103
+ output_sd = calc_lora_model(model_diff, rank, "diffusion_model.", "diffusion_model.", output_sd, lora_type, bias_diff=bias_diff)
104
+ if text_encoder_diff is not None:
105
+ output_sd = calc_lora_model(text_encoder_diff.patcher, rank, "", "text_encoders.", output_sd, lora_type, bias_diff=bias_diff)
106
+
107
+ output_checkpoint = f"{filename}_{counter:05}_.safetensors"
108
+ output_checkpoint = os.path.join(full_output_folder, output_checkpoint)
109
+
110
+ comfy.utils.save_torch_file(output_sd, output_checkpoint, metadata=None)
111
+ return {}
112
+
113
+ NODE_CLASS_MAPPINGS = {
114
+ "LoraSave": LoraSave
115
+ }
116
+
117
+ NODE_DISPLAY_NAME_MAPPINGS = {
118
+ "LoraSave": "Extract and Save Lora"
119
+ }
ComfyUI/comfy_extras/nodes_lotus.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import comfy.model_management as mm
3
+
4
+ class LotusConditioning:
5
+ @classmethod
6
+ def INPUT_TYPES(s):
7
+ return {
8
+ "required": {
9
+ },
10
+ }
11
+
12
+ RETURN_TYPES = ("CONDITIONING",)
13
+ RETURN_NAMES = ("conditioning",)
14
+ FUNCTION = "conditioning"
15
+ CATEGORY = "conditioning/lotus"
16
+
17
+ def conditioning(self):
18
+ device = mm.get_torch_device()
19
+ #lotus uses a frozen encoder and null conditioning, i'm just inlining the results of that operation since it doesn't change
20
+ #and getting parity with the reference implementation would otherwise require inference and 800mb of tensors
21
+ 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)
22
+
23
+ cond = [[prompt_embeds, {}]]
24
+
25
+ return (cond,)
26
+
27
+ NODE_CLASS_MAPPINGS = {
28
+ "LotusConditioning" : LotusConditioning,
29
+ }
ComfyUI/comfy_extras/nodes_lt.py ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import nodes
3
+ import node_helpers
4
+ import torch
5
+ import comfy.model_management
6
+ import comfy.model_sampling
7
+ import comfy.utils
8
+ import math
9
+ import numpy as np
10
+ import av
11
+ from comfy.ldm.lightricks.symmetric_patchifier import SymmetricPatchifier, latent_to_pixel_coords
12
+
13
+ class EmptyLTXVLatentVideo:
14
+ @classmethod
15
+ def INPUT_TYPES(s):
16
+ return {"required": { "width": ("INT", {"default": 768, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}),
17
+ "height": ("INT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}),
18
+ "length": ("INT", {"default": 97, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 8}),
19
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}}
20
+ RETURN_TYPES = ("LATENT",)
21
+ FUNCTION = "generate"
22
+
23
+ CATEGORY = "latent/video/ltxv"
24
+
25
+ def generate(self, width, height, length, batch_size=1):
26
+ latent = torch.zeros([batch_size, 128, ((length - 1) // 8) + 1, height // 32, width // 32], device=comfy.model_management.intermediate_device())
27
+ return ({"samples": latent}, )
28
+
29
+
30
+ class LTXVImgToVideo:
31
+ @classmethod
32
+ def INPUT_TYPES(s):
33
+ return {"required": {"positive": ("CONDITIONING", ),
34
+ "negative": ("CONDITIONING", ),
35
+ "vae": ("VAE",),
36
+ "image": ("IMAGE",),
37
+ "width": ("INT", {"default": 768, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}),
38
+ "height": ("INT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 32}),
39
+ "length": ("INT", {"default": 97, "min": 9, "max": nodes.MAX_RESOLUTION, "step": 8}),
40
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
41
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}),
42
+ }}
43
+
44
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
45
+ RETURN_NAMES = ("positive", "negative", "latent")
46
+
47
+ CATEGORY = "conditioning/video_models"
48
+ FUNCTION = "generate"
49
+
50
+ def generate(self, positive, negative, image, vae, width, height, length, batch_size, strength):
51
+ pixels = comfy.utils.common_upscale(image.movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
52
+ encode_pixels = pixels[:, :, :, :3]
53
+ t = vae.encode(encode_pixels)
54
+
55
+ latent = torch.zeros([batch_size, 128, ((length - 1) // 8) + 1, height // 32, width // 32], device=comfy.model_management.intermediate_device())
56
+ latent[:, :, :t.shape[2]] = t
57
+
58
+ conditioning_latent_frames_mask = torch.ones(
59
+ (batch_size, 1, latent.shape[2], 1, 1),
60
+ dtype=torch.float32,
61
+ device=latent.device,
62
+ )
63
+ conditioning_latent_frames_mask[:, :, :t.shape[2]] = 1.0 - strength
64
+
65
+ return (positive, negative, {"samples": latent, "noise_mask": conditioning_latent_frames_mask}, )
66
+
67
+
68
+ def conditioning_get_any_value(conditioning, key, default=None):
69
+ for t in conditioning:
70
+ if key in t[1]:
71
+ return t[1][key]
72
+ return default
73
+
74
+
75
+ def get_noise_mask(latent):
76
+ noise_mask = latent.get("noise_mask", None)
77
+ latent_image = latent["samples"]
78
+ if noise_mask is None:
79
+ batch_size, _, latent_length, _, _ = latent_image.shape
80
+ noise_mask = torch.ones(
81
+ (batch_size, 1, latent_length, 1, 1),
82
+ dtype=torch.float32,
83
+ device=latent_image.device,
84
+ )
85
+ else:
86
+ noise_mask = noise_mask.clone()
87
+ return noise_mask
88
+
89
+ def get_keyframe_idxs(cond):
90
+ keyframe_idxs = conditioning_get_any_value(cond, "keyframe_idxs", None)
91
+ if keyframe_idxs is None:
92
+ return None, 0
93
+ num_keyframes = torch.unique(keyframe_idxs[:, 0]).shape[0]
94
+ return keyframe_idxs, num_keyframes
95
+
96
+ class LTXVAddGuide:
97
+ @classmethod
98
+ def INPUT_TYPES(s):
99
+ return {"required": {"positive": ("CONDITIONING", ),
100
+ "negative": ("CONDITIONING", ),
101
+ "vae": ("VAE",),
102
+ "latent": ("LATENT",),
103
+ "image": ("IMAGE", {"tooltip": "Image or video to condition the latent video on. Must be 8*n + 1 frames."
104
+ "If the video is not 8*n + 1 frames, it will be cropped to the nearest 8*n + 1 frames."}),
105
+ "frame_idx": ("INT", {"default": 0, "min": -9999, "max": 9999,
106
+ "tooltip": "Frame index to start the conditioning at. For single-frame images or "
107
+ "videos with 1-8 frames, any frame_idx value is acceptable. For videos with 9+ "
108
+ "frames, frame_idx must be divisible by 8, otherwise it will be rounded down to "
109
+ "the nearest multiple of 8. Negative values are counted from the end of the video."}),
110
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
111
+ }
112
+ }
113
+
114
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
115
+ RETURN_NAMES = ("positive", "negative", "latent")
116
+
117
+ CATEGORY = "conditioning/video_models"
118
+ FUNCTION = "generate"
119
+
120
+ def __init__(self):
121
+ self._num_prefix_frames = 2
122
+ self._patchifier = SymmetricPatchifier(1)
123
+
124
+ def encode(self, vae, latent_width, latent_height, images, scale_factors):
125
+ time_scale_factor, width_scale_factor, height_scale_factor = scale_factors
126
+ images = images[:(images.shape[0] - 1) // time_scale_factor * time_scale_factor + 1]
127
+ 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)
128
+ encode_pixels = pixels[:, :, :, :3]
129
+ t = vae.encode(encode_pixels)
130
+ return encode_pixels, t
131
+
132
+ def get_latent_index(self, cond, latent_length, guide_length, frame_idx, scale_factors):
133
+ time_scale_factor, _, _ = scale_factors
134
+ _, num_keyframes = get_keyframe_idxs(cond)
135
+ latent_count = latent_length - num_keyframes
136
+ frame_idx = frame_idx if frame_idx >= 0 else max((latent_count - 1) * time_scale_factor + 1 + frame_idx, 0)
137
+ if guide_length > 1 and frame_idx != 0:
138
+ frame_idx = (frame_idx - 1) // time_scale_factor * time_scale_factor + 1 # frame index - 1 must be divisible by 8 or frame_idx == 0
139
+
140
+ latent_idx = (frame_idx + time_scale_factor - 1) // time_scale_factor
141
+
142
+ return frame_idx, latent_idx
143
+
144
+ def add_keyframe_index(self, cond, frame_idx, guiding_latent, scale_factors):
145
+ keyframe_idxs, _ = get_keyframe_idxs(cond)
146
+ _, latent_coords = self._patchifier.patchify(guiding_latent)
147
+ 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
148
+ pixel_coords[:, 0] += frame_idx
149
+ if keyframe_idxs is None:
150
+ keyframe_idxs = pixel_coords
151
+ else:
152
+ keyframe_idxs = torch.cat([keyframe_idxs, pixel_coords], dim=2)
153
+ return node_helpers.conditioning_set_values(cond, {"keyframe_idxs": keyframe_idxs})
154
+
155
+ def append_keyframe(self, positive, negative, frame_idx, latent_image, noise_mask, guiding_latent, strength, scale_factors):
156
+ _, latent_idx = self.get_latent_index(
157
+ cond=positive,
158
+ latent_length=latent_image.shape[2],
159
+ guide_length=guiding_latent.shape[2],
160
+ frame_idx=frame_idx,
161
+ scale_factors=scale_factors,
162
+ )
163
+ noise_mask[:, :, latent_idx:latent_idx + guiding_latent.shape[2]] = 1.0
164
+
165
+ positive = self.add_keyframe_index(positive, frame_idx, guiding_latent, scale_factors)
166
+ negative = self.add_keyframe_index(negative, frame_idx, guiding_latent, scale_factors)
167
+
168
+ mask = torch.full(
169
+ (noise_mask.shape[0], 1, guiding_latent.shape[2], 1, 1),
170
+ 1.0 - strength,
171
+ dtype=noise_mask.dtype,
172
+ device=noise_mask.device,
173
+ )
174
+
175
+ latent_image = torch.cat([latent_image, guiding_latent], dim=2)
176
+ noise_mask = torch.cat([noise_mask, mask], dim=2)
177
+ return positive, negative, latent_image, noise_mask
178
+
179
+ def replace_latent_frames(self, latent_image, noise_mask, guiding_latent, latent_idx, strength):
180
+ cond_length = guiding_latent.shape[2]
181
+ assert latent_image.shape[2] >= latent_idx + cond_length, "Conditioning frames exceed the length of the latent sequence."
182
+
183
+ mask = torch.full(
184
+ (noise_mask.shape[0], 1, cond_length, 1, 1),
185
+ 1.0 - strength,
186
+ dtype=noise_mask.dtype,
187
+ device=noise_mask.device,
188
+ )
189
+
190
+ latent_image = latent_image.clone()
191
+ noise_mask = noise_mask.clone()
192
+
193
+ latent_image[:, :, latent_idx : latent_idx + cond_length] = guiding_latent
194
+ noise_mask[:, :, latent_idx : latent_idx + cond_length] = mask
195
+
196
+ return latent_image, noise_mask
197
+
198
+ def generate(self, positive, negative, vae, latent, image, frame_idx, strength):
199
+ scale_factors = vae.downscale_index_formula
200
+ latent_image = latent["samples"]
201
+ noise_mask = get_noise_mask(latent)
202
+
203
+ _, _, latent_length, latent_height, latent_width = latent_image.shape
204
+ image, t = self.encode(vae, latent_width, latent_height, image, scale_factors)
205
+
206
+ frame_idx, latent_idx = self.get_latent_index(positive, latent_length, len(image), frame_idx, scale_factors)
207
+ assert latent_idx + t.shape[2] <= latent_length, "Conditioning frames exceed the length of the latent sequence."
208
+
209
+ num_prefix_frames = min(self._num_prefix_frames, t.shape[2])
210
+
211
+ positive, negative, latent_image, noise_mask = self.append_keyframe(
212
+ positive,
213
+ negative,
214
+ frame_idx,
215
+ latent_image,
216
+ noise_mask,
217
+ t[:, :, :num_prefix_frames],
218
+ strength,
219
+ scale_factors,
220
+ )
221
+
222
+ latent_idx += num_prefix_frames
223
+
224
+ t = t[:, :, num_prefix_frames:]
225
+ if t.shape[2] == 0:
226
+ return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},)
227
+
228
+ latent_image, noise_mask = self.replace_latent_frames(
229
+ latent_image,
230
+ noise_mask,
231
+ t,
232
+ latent_idx,
233
+ strength,
234
+ )
235
+
236
+ return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},)
237
+
238
+
239
+ class LTXVCropGuides:
240
+ @classmethod
241
+ def INPUT_TYPES(s):
242
+ return {"required": {"positive": ("CONDITIONING", ),
243
+ "negative": ("CONDITIONING", ),
244
+ "latent": ("LATENT",),
245
+ }
246
+ }
247
+
248
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
249
+ RETURN_NAMES = ("positive", "negative", "latent")
250
+
251
+ CATEGORY = "conditioning/video_models"
252
+ FUNCTION = "crop"
253
+
254
+ def __init__(self):
255
+ self._patchifier = SymmetricPatchifier(1)
256
+
257
+ def crop(self, positive, negative, latent):
258
+ latent_image = latent["samples"].clone()
259
+ noise_mask = get_noise_mask(latent)
260
+
261
+ _, num_keyframes = get_keyframe_idxs(positive)
262
+ if num_keyframes == 0:
263
+ return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},)
264
+
265
+ latent_image = latent_image[:, :, :-num_keyframes]
266
+ noise_mask = noise_mask[:, :, :-num_keyframes]
267
+
268
+ positive = node_helpers.conditioning_set_values(positive, {"keyframe_idxs": None})
269
+ negative = node_helpers.conditioning_set_values(negative, {"keyframe_idxs": None})
270
+
271
+ return (positive, negative, {"samples": latent_image, "noise_mask": noise_mask},)
272
+
273
+
274
+ class LTXVConditioning:
275
+ @classmethod
276
+ def INPUT_TYPES(s):
277
+ return {"required": {"positive": ("CONDITIONING", ),
278
+ "negative": ("CONDITIONING", ),
279
+ "frame_rate": ("FLOAT", {"default": 25.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
280
+ }}
281
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
282
+ RETURN_NAMES = ("positive", "negative")
283
+ FUNCTION = "append"
284
+
285
+ CATEGORY = "conditioning/video_models"
286
+
287
+ def append(self, positive, negative, frame_rate):
288
+ positive = node_helpers.conditioning_set_values(positive, {"frame_rate": frame_rate})
289
+ negative = node_helpers.conditioning_set_values(negative, {"frame_rate": frame_rate})
290
+ return (positive, negative)
291
+
292
+
293
+ class ModelSamplingLTXV:
294
+ @classmethod
295
+ def INPUT_TYPES(s):
296
+ return {"required": { "model": ("MODEL",),
297
+ "max_shift": ("FLOAT", {"default": 2.05, "min": 0.0, "max": 100.0, "step":0.01}),
298
+ "base_shift": ("FLOAT", {"default": 0.95, "min": 0.0, "max": 100.0, "step":0.01}),
299
+ },
300
+ "optional": {"latent": ("LATENT",), }
301
+ }
302
+
303
+ RETURN_TYPES = ("MODEL",)
304
+ FUNCTION = "patch"
305
+
306
+ CATEGORY = "advanced/model"
307
+
308
+ def patch(self, model, max_shift, base_shift, latent=None):
309
+ m = model.clone()
310
+
311
+ if latent is None:
312
+ tokens = 4096
313
+ else:
314
+ tokens = math.prod(latent["samples"].shape[2:])
315
+
316
+ x1 = 1024
317
+ x2 = 4096
318
+ mm = (max_shift - base_shift) / (x2 - x1)
319
+ b = base_shift - mm * x1
320
+ shift = (tokens) * mm + b
321
+
322
+ sampling_base = comfy.model_sampling.ModelSamplingFlux
323
+ sampling_type = comfy.model_sampling.CONST
324
+
325
+ class ModelSamplingAdvanced(sampling_base, sampling_type):
326
+ pass
327
+
328
+ model_sampling = ModelSamplingAdvanced(model.model.model_config)
329
+ model_sampling.set_parameters(shift=shift)
330
+ m.add_object_patch("model_sampling", model_sampling)
331
+
332
+ return (m, )
333
+
334
+
335
+ class LTXVScheduler:
336
+ @classmethod
337
+ def INPUT_TYPES(s):
338
+ return {"required":
339
+ {"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
340
+ "max_shift": ("FLOAT", {"default": 2.05, "min": 0.0, "max": 100.0, "step":0.01}),
341
+ "base_shift": ("FLOAT", {"default": 0.95, "min": 0.0, "max": 100.0, "step":0.01}),
342
+ "stretch": ("BOOLEAN", {
343
+ "default": True,
344
+ "tooltip": "Stretch the sigmas to be in the range [terminal, 1]."
345
+ }),
346
+ "terminal": (
347
+ "FLOAT",
348
+ {
349
+ "default": 0.1, "min": 0.0, "max": 0.99, "step": 0.01,
350
+ "tooltip": "The terminal value of the sigmas after stretching."
351
+ },
352
+ ),
353
+ },
354
+ "optional": {"latent": ("LATENT",), }
355
+ }
356
+
357
+ RETURN_TYPES = ("SIGMAS",)
358
+ CATEGORY = "sampling/custom_sampling/schedulers"
359
+
360
+ FUNCTION = "get_sigmas"
361
+
362
+ def get_sigmas(self, steps, max_shift, base_shift, stretch, terminal, latent=None):
363
+ if latent is None:
364
+ tokens = 4096
365
+ else:
366
+ tokens = math.prod(latent["samples"].shape[2:])
367
+
368
+ sigmas = torch.linspace(1.0, 0.0, steps + 1)
369
+
370
+ x1 = 1024
371
+ x2 = 4096
372
+ mm = (max_shift - base_shift) / (x2 - x1)
373
+ b = base_shift - mm * x1
374
+ sigma_shift = (tokens) * mm + b
375
+
376
+ power = 1
377
+ sigmas = torch.where(
378
+ sigmas != 0,
379
+ math.exp(sigma_shift) / (math.exp(sigma_shift) + (1 / sigmas - 1) ** power),
380
+ 0,
381
+ )
382
+
383
+ # Stretch sigmas so that its final value matches the given terminal value.
384
+ if stretch:
385
+ non_zero_mask = sigmas != 0
386
+ non_zero_sigmas = sigmas[non_zero_mask]
387
+ one_minus_z = 1.0 - non_zero_sigmas
388
+ scale_factor = one_minus_z[-1] / (1.0 - terminal)
389
+ stretched = 1.0 - (one_minus_z / scale_factor)
390
+ sigmas[non_zero_mask] = stretched
391
+
392
+ return (sigmas,)
393
+
394
+ def encode_single_frame(output_file, image_array: np.ndarray, crf):
395
+ container = av.open(output_file, "w", format="mp4")
396
+ try:
397
+ stream = container.add_stream(
398
+ "libx264", rate=1, options={"crf": str(crf), "preset": "veryfast"}
399
+ )
400
+ stream.height = image_array.shape[0]
401
+ stream.width = image_array.shape[1]
402
+ av_frame = av.VideoFrame.from_ndarray(image_array, format="rgb24").reformat(
403
+ format="yuv420p"
404
+ )
405
+ container.mux(stream.encode(av_frame))
406
+ container.mux(stream.encode())
407
+ finally:
408
+ container.close()
409
+
410
+
411
+ def decode_single_frame(video_file):
412
+ container = av.open(video_file)
413
+ try:
414
+ stream = next(s for s in container.streams if s.type == "video")
415
+ frame = next(container.decode(stream))
416
+ finally:
417
+ container.close()
418
+ return frame.to_ndarray(format="rgb24")
419
+
420
+
421
+ def preprocess(image: torch.Tensor, crf=29):
422
+ if crf == 0:
423
+ return image
424
+
425
+ image_array = (image[:(image.shape[0] // 2) * 2, :(image.shape[1] // 2) * 2] * 255.0).byte().cpu().numpy()
426
+ with io.BytesIO() as output_file:
427
+ encode_single_frame(output_file, image_array, crf)
428
+ video_bytes = output_file.getvalue()
429
+ with io.BytesIO(video_bytes) as video_file:
430
+ image_array = decode_single_frame(video_file)
431
+ tensor = torch.tensor(image_array, dtype=image.dtype, device=image.device) / 255.0
432
+ return tensor
433
+
434
+
435
+ class LTXVPreprocess:
436
+ @classmethod
437
+ def INPUT_TYPES(s):
438
+ return {
439
+ "required": {
440
+ "image": ("IMAGE",),
441
+ "img_compression": (
442
+ "INT",
443
+ {
444
+ "default": 35,
445
+ "min": 0,
446
+ "max": 100,
447
+ "tooltip": "Amount of compression to apply on image.",
448
+ },
449
+ ),
450
+ }
451
+ }
452
+
453
+ FUNCTION = "preprocess"
454
+ RETURN_TYPES = ("IMAGE",)
455
+ RETURN_NAMES = ("output_image",)
456
+ CATEGORY = "image"
457
+
458
+ def preprocess(self, image, img_compression):
459
+ output_images = []
460
+ for i in range(image.shape[0]):
461
+ output_images.append(preprocess(image[i], img_compression))
462
+ return (torch.stack(output_images),)
463
+
464
+
465
+ NODE_CLASS_MAPPINGS = {
466
+ "EmptyLTXVLatentVideo": EmptyLTXVLatentVideo,
467
+ "LTXVImgToVideo": LTXVImgToVideo,
468
+ "ModelSamplingLTXV": ModelSamplingLTXV,
469
+ "LTXVConditioning": LTXVConditioning,
470
+ "LTXVScheduler": LTXVScheduler,
471
+ "LTXVAddGuide": LTXVAddGuide,
472
+ "LTXVPreprocess": LTXVPreprocess,
473
+ "LTXVCropGuides": LTXVCropGuides,
474
+ }
ComfyUI/comfy_extras/nodes_lumina2.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict
2
+ import torch
3
+
4
+
5
+ class RenormCFG:
6
+ @classmethod
7
+ def INPUT_TYPES(s):
8
+ return {"required": { "model": ("MODEL",),
9
+ "cfg_trunc": ("FLOAT", {"default": 100, "min": 0.0, "max": 100.0, "step": 0.01}),
10
+ "renorm_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
11
+ }}
12
+ RETURN_TYPES = ("MODEL",)
13
+ FUNCTION = "patch"
14
+
15
+ CATEGORY = "advanced/model"
16
+
17
+ def patch(self, model, cfg_trunc, renorm_cfg):
18
+ def renorm_cfg_func(args):
19
+ cond_denoised = args["cond_denoised"]
20
+ uncond_denoised = args["uncond_denoised"]
21
+ cond_scale = args["cond_scale"]
22
+ timestep = args["timestep"]
23
+ x_orig = args["input"]
24
+ in_channels = model.model.diffusion_model.in_channels
25
+
26
+ if timestep[0] < cfg_trunc:
27
+ cond_eps, uncond_eps = cond_denoised[:, :in_channels], uncond_denoised[:, :in_channels]
28
+ cond_rest, _ = cond_denoised[:, in_channels:], uncond_denoised[:, in_channels:]
29
+ half_eps = uncond_eps + cond_scale * (cond_eps - uncond_eps)
30
+ half_rest = cond_rest
31
+
32
+ if float(renorm_cfg) > 0.0:
33
+ ori_pos_norm = torch.linalg.vector_norm(cond_eps
34
+ , dim=tuple(range(1, len(cond_eps.shape))), keepdim=True
35
+ )
36
+ max_new_norm = ori_pos_norm * float(renorm_cfg)
37
+ new_pos_norm = torch.linalg.vector_norm(
38
+ half_eps, dim=tuple(range(1, len(half_eps.shape))), keepdim=True
39
+ )
40
+ if new_pos_norm >= max_new_norm:
41
+ half_eps = half_eps * (max_new_norm / new_pos_norm)
42
+ else:
43
+ cond_eps, uncond_eps = cond_denoised[:, :in_channels], uncond_denoised[:, :in_channels]
44
+ cond_rest, _ = cond_denoised[:, in_channels:], uncond_denoised[:, in_channels:]
45
+ half_eps = cond_eps
46
+ half_rest = cond_rest
47
+
48
+ cfg_result = torch.cat([half_eps, half_rest], dim=1)
49
+
50
+ # cfg_result = uncond_denoised + (cond_denoised - uncond_denoised) * cond_scale
51
+
52
+ return x_orig - cfg_result
53
+
54
+ m = model.clone()
55
+ m.set_model_sampler_cfg_function(renorm_cfg_func)
56
+ return (m, )
57
+
58
+
59
+ class CLIPTextEncodeLumina2(ComfyNodeABC):
60
+ SYSTEM_PROMPT = {
61
+ "superior": "You are an assistant designed to generate superior images with the superior "\
62
+ "degree of image-text alignment based on textual prompts or user prompts.",
63
+ "alignment": "You are an assistant designed to generate high-quality images with the "\
64
+ "highest degree of image-text alignment based on textual prompts."
65
+ }
66
+ SYSTEM_PROMPT_TIP = "Lumina2 provide two types of system prompts:" \
67
+ "Superior: You are an assistant designed to generate superior images with the superior "\
68
+ "degree of image-text alignment based on textual prompts or user prompts. "\
69
+ "Alignment: You are an assistant designed to generate high-quality images with the highest "\
70
+ "degree of image-text alignment based on textual prompts."
71
+ @classmethod
72
+ def INPUT_TYPES(s) -> InputTypeDict:
73
+ return {
74
+ "required": {
75
+ "system_prompt": (list(CLIPTextEncodeLumina2.SYSTEM_PROMPT.keys()), {"tooltip": CLIPTextEncodeLumina2.SYSTEM_PROMPT_TIP}),
76
+ "user_prompt": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
77
+ "clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."})
78
+ }
79
+ }
80
+ RETURN_TYPES = (IO.CONDITIONING,)
81
+ OUTPUT_TOOLTIPS = ("A conditioning containing the embedded text used to guide the diffusion model.",)
82
+ FUNCTION = "encode"
83
+
84
+ CATEGORY = "conditioning"
85
+ 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."
86
+
87
+ def encode(self, clip, user_prompt, system_prompt):
88
+ if clip is None:
89
+ 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.")
90
+ system_prompt = CLIPTextEncodeLumina2.SYSTEM_PROMPT[system_prompt]
91
+ prompt = f'{system_prompt} <Prompt Start> {user_prompt}'
92
+ tokens = clip.tokenize(prompt)
93
+ return (clip.encode_from_tokens_scheduled(tokens), )
94
+
95
+
96
+ NODE_CLASS_MAPPINGS = {
97
+ "CLIPTextEncodeLumina2": CLIPTextEncodeLumina2,
98
+ "RenormCFG": RenormCFG
99
+ }
100
+
101
+
102
+ NODE_DISPLAY_NAME_MAPPINGS = {
103
+ "CLIPTextEncodeLumina2": "CLIP Text Encode for Lumina2",
104
+ }
ComfyUI/comfy_extras/nodes_mahiro.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn.functional as F
3
+
4
+ class Mahiro:
5
+ @classmethod
6
+ def INPUT_TYPES(s):
7
+ return {"required": {"model": ("MODEL",),
8
+ }}
9
+ RETURN_TYPES = ("MODEL",)
10
+ RETURN_NAMES = ("patched_model",)
11
+ FUNCTION = "patch"
12
+ CATEGORY = "_for_testing"
13
+ DESCRIPTION = "Modify the guidance to scale more on the 'direction' of the positive prompt rather than the difference between the negative prompt."
14
+ def patch(self, model):
15
+ m = model.clone()
16
+ def mahiro_normd(args):
17
+ scale: float = args['cond_scale']
18
+ cond_p: torch.Tensor = args['cond_denoised']
19
+ uncond_p: torch.Tensor = args['uncond_denoised']
20
+ #naive leap
21
+ leap = cond_p * scale
22
+ #sim with uncond leap
23
+ u_leap = uncond_p * scale
24
+ cfg = args["denoised"]
25
+ merge = (leap + cfg) / 2
26
+ normu = torch.sqrt(u_leap.abs()) * u_leap.sign()
27
+ normm = torch.sqrt(merge.abs()) * merge.sign()
28
+ sim = F.cosine_similarity(normu, normm).mean()
29
+ simsc = 2 * (sim+1)
30
+ wm = (simsc*cfg + (4-simsc)*leap) / 4
31
+ return wm
32
+ m.set_model_sampler_post_cfg_function(mahiro_normd)
33
+ return (m, )
34
+
35
+ NODE_CLASS_MAPPINGS = {
36
+ "Mahiro": Mahiro
37
+ }
38
+
39
+ NODE_DISPLAY_NAME_MAPPINGS = {
40
+ "Mahiro": "Mahiro is so cute that she deserves a better guidance function!! (。・ω・。)",
41
+ }
ComfyUI/comfy_extras/nodes_mask.py ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import scipy.ndimage
3
+ import torch
4
+ import comfy.utils
5
+ import node_helpers
6
+ import folder_paths
7
+ import random
8
+
9
+ import nodes
10
+ from nodes import MAX_RESOLUTION
11
+
12
+ def composite(destination, source, x, y, mask = None, multiplier = 8, resize_source = False):
13
+ source = source.to(destination.device)
14
+ if resize_source:
15
+ source = torch.nn.functional.interpolate(source, size=(destination.shape[2], destination.shape[3]), mode="bilinear")
16
+
17
+ source = comfy.utils.repeat_to_batch_size(source, destination.shape[0])
18
+
19
+ x = max(-source.shape[3] * multiplier, min(x, destination.shape[3] * multiplier))
20
+ y = max(-source.shape[2] * multiplier, min(y, destination.shape[2] * multiplier))
21
+
22
+ left, top = (x // multiplier, y // multiplier)
23
+ right, bottom = (left + source.shape[3], top + source.shape[2],)
24
+
25
+ if mask is None:
26
+ mask = torch.ones_like(source)
27
+ else:
28
+ mask = mask.to(destination.device, copy=True)
29
+ mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(source.shape[2], source.shape[3]), mode="bilinear")
30
+ mask = comfy.utils.repeat_to_batch_size(mask, source.shape[0])
31
+
32
+ # calculate the bounds of the source that will be overlapping the destination
33
+ # this prevents the source trying to overwrite latent pixels that are out of bounds
34
+ # of the destination
35
+ visible_width, visible_height = (destination.shape[3] - left + min(0, x), destination.shape[2] - top + min(0, y),)
36
+
37
+ mask = mask[:, :, :visible_height, :visible_width]
38
+ inverse_mask = torch.ones_like(mask) - mask
39
+
40
+ source_portion = mask * source[:, :, :visible_height, :visible_width]
41
+ destination_portion = inverse_mask * destination[:, :, top:bottom, left:right]
42
+
43
+ destination[:, :, top:bottom, left:right] = source_portion + destination_portion
44
+ return destination
45
+
46
+ class LatentCompositeMasked:
47
+ @classmethod
48
+ def INPUT_TYPES(s):
49
+ return {
50
+ "required": {
51
+ "destination": ("LATENT",),
52
+ "source": ("LATENT",),
53
+ "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
54
+ "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
55
+ "resize_source": ("BOOLEAN", {"default": False}),
56
+ },
57
+ "optional": {
58
+ "mask": ("MASK",),
59
+ }
60
+ }
61
+ RETURN_TYPES = ("LATENT",)
62
+ FUNCTION = "composite"
63
+
64
+ CATEGORY = "latent"
65
+
66
+ def composite(self, destination, source, x, y, resize_source, mask = None):
67
+ output = destination.copy()
68
+ destination = destination["samples"].clone()
69
+ source = source["samples"]
70
+ output["samples"] = composite(destination, source, x, y, mask, 8, resize_source)
71
+ return (output,)
72
+
73
+ class ImageCompositeMasked:
74
+ @classmethod
75
+ def INPUT_TYPES(s):
76
+ return {
77
+ "required": {
78
+ "destination": ("IMAGE",),
79
+ "source": ("IMAGE",),
80
+ "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
81
+ "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
82
+ "resize_source": ("BOOLEAN", {"default": False}),
83
+ },
84
+ "optional": {
85
+ "mask": ("MASK",),
86
+ }
87
+ }
88
+ RETURN_TYPES = ("IMAGE",)
89
+ FUNCTION = "composite"
90
+
91
+ CATEGORY = "image"
92
+
93
+ def composite(self, destination, source, x, y, resize_source, mask = None):
94
+ destination, source = node_helpers.image_alpha_fix(destination, source)
95
+ destination = destination.clone().movedim(-1, 1)
96
+ output = composite(destination, source.movedim(-1, 1), x, y, mask, 1, resize_source).movedim(1, -1)
97
+ return (output,)
98
+
99
+ class MaskToImage:
100
+ @classmethod
101
+ def INPUT_TYPES(s):
102
+ return {
103
+ "required": {
104
+ "mask": ("MASK",),
105
+ }
106
+ }
107
+
108
+ CATEGORY = "mask"
109
+
110
+ RETURN_TYPES = ("IMAGE",)
111
+ FUNCTION = "mask_to_image"
112
+
113
+ def mask_to_image(self, mask):
114
+ result = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
115
+ return (result,)
116
+
117
+ class ImageToMask:
118
+ @classmethod
119
+ def INPUT_TYPES(s):
120
+ return {
121
+ "required": {
122
+ "image": ("IMAGE",),
123
+ "channel": (["red", "green", "blue", "alpha"],),
124
+ }
125
+ }
126
+
127
+ CATEGORY = "mask"
128
+
129
+ RETURN_TYPES = ("MASK",)
130
+ FUNCTION = "image_to_mask"
131
+
132
+ def image_to_mask(self, image, channel):
133
+ channels = ["red", "green", "blue", "alpha"]
134
+ mask = image[:, :, :, channels.index(channel)]
135
+ return (mask,)
136
+
137
+ class ImageColorToMask:
138
+ @classmethod
139
+ def INPUT_TYPES(s):
140
+ return {
141
+ "required": {
142
+ "image": ("IMAGE",),
143
+ "color": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFF, "step": 1, "display": "color"}),
144
+ }
145
+ }
146
+
147
+ CATEGORY = "mask"
148
+
149
+ RETURN_TYPES = ("MASK",)
150
+ FUNCTION = "image_to_mask"
151
+
152
+ def image_to_mask(self, image, color):
153
+ temp = (torch.clamp(image, 0, 1.0) * 255.0).round().to(torch.int)
154
+ temp = torch.bitwise_left_shift(temp[:,:,:,0], 16) + torch.bitwise_left_shift(temp[:,:,:,1], 8) + temp[:,:,:,2]
155
+ mask = torch.where(temp == color, 1.0, 0).float()
156
+ return (mask,)
157
+
158
+ class SolidMask:
159
+ @classmethod
160
+ def INPUT_TYPES(cls):
161
+ return {
162
+ "required": {
163
+ "value": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
164
+ "width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
165
+ "height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
166
+ }
167
+ }
168
+
169
+ CATEGORY = "mask"
170
+
171
+ RETURN_TYPES = ("MASK",)
172
+
173
+ FUNCTION = "solid"
174
+
175
+ def solid(self, value, width, height):
176
+ out = torch.full((1, height, width), value, dtype=torch.float32, device="cpu")
177
+ return (out,)
178
+
179
+ class InvertMask:
180
+ @classmethod
181
+ def INPUT_TYPES(cls):
182
+ return {
183
+ "required": {
184
+ "mask": ("MASK",),
185
+ }
186
+ }
187
+
188
+ CATEGORY = "mask"
189
+
190
+ RETURN_TYPES = ("MASK",)
191
+
192
+ FUNCTION = "invert"
193
+
194
+ def invert(self, mask):
195
+ out = 1.0 - mask
196
+ return (out,)
197
+
198
+ class CropMask:
199
+ @classmethod
200
+ def INPUT_TYPES(cls):
201
+ return {
202
+ "required": {
203
+ "mask": ("MASK",),
204
+ "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
205
+ "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
206
+ "width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
207
+ "height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
208
+ }
209
+ }
210
+
211
+ CATEGORY = "mask"
212
+
213
+ RETURN_TYPES = ("MASK",)
214
+
215
+ FUNCTION = "crop"
216
+
217
+ def crop(self, mask, x, y, width, height):
218
+ mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1]))
219
+ out = mask[:, y:y + height, x:x + width]
220
+ return (out,)
221
+
222
+ class MaskComposite:
223
+ @classmethod
224
+ def INPUT_TYPES(cls):
225
+ return {
226
+ "required": {
227
+ "destination": ("MASK",),
228
+ "source": ("MASK",),
229
+ "x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
230
+ "y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
231
+ "operation": (["multiply", "add", "subtract", "and", "or", "xor"],),
232
+ }
233
+ }
234
+
235
+ CATEGORY = "mask"
236
+
237
+ RETURN_TYPES = ("MASK",)
238
+
239
+ FUNCTION = "combine"
240
+
241
+ def combine(self, destination, source, x, y, operation):
242
+ output = destination.reshape((-1, destination.shape[-2], destination.shape[-1])).clone()
243
+ source = source.reshape((-1, source.shape[-2], source.shape[-1]))
244
+
245
+ left, top = (x, y,)
246
+ right, bottom = (min(left + source.shape[-1], destination.shape[-1]), min(top + source.shape[-2], destination.shape[-2]))
247
+ visible_width, visible_height = (right - left, bottom - top,)
248
+
249
+ source_portion = source[:, :visible_height, :visible_width]
250
+ destination_portion = output[:, top:bottom, left:right]
251
+
252
+ if operation == "multiply":
253
+ output[:, top:bottom, left:right] = destination_portion * source_portion
254
+ elif operation == "add":
255
+ output[:, top:bottom, left:right] = destination_portion + source_portion
256
+ elif operation == "subtract":
257
+ output[:, top:bottom, left:right] = destination_portion - source_portion
258
+ elif operation == "and":
259
+ output[:, top:bottom, left:right] = torch.bitwise_and(destination_portion.round().bool(), source_portion.round().bool()).float()
260
+ elif operation == "or":
261
+ output[:, top:bottom, left:right] = torch.bitwise_or(destination_portion.round().bool(), source_portion.round().bool()).float()
262
+ elif operation == "xor":
263
+ output[:, top:bottom, left:right] = torch.bitwise_xor(destination_portion.round().bool(), source_portion.round().bool()).float()
264
+
265
+ output = torch.clamp(output, 0.0, 1.0)
266
+
267
+ return (output,)
268
+
269
+ class FeatherMask:
270
+ @classmethod
271
+ def INPUT_TYPES(cls):
272
+ return {
273
+ "required": {
274
+ "mask": ("MASK",),
275
+ "left": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
276
+ "top": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
277
+ "right": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
278
+ "bottom": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
279
+ }
280
+ }
281
+
282
+ CATEGORY = "mask"
283
+
284
+ RETURN_TYPES = ("MASK",)
285
+
286
+ FUNCTION = "feather"
287
+
288
+ def feather(self, mask, left, top, right, bottom):
289
+ output = mask.reshape((-1, mask.shape[-2], mask.shape[-1])).clone()
290
+
291
+ left = min(left, output.shape[-1])
292
+ right = min(right, output.shape[-1])
293
+ top = min(top, output.shape[-2])
294
+ bottom = min(bottom, output.shape[-2])
295
+
296
+ for x in range(left):
297
+ feather_rate = (x + 1.0) / left
298
+ output[:, :, x] *= feather_rate
299
+
300
+ for x in range(right):
301
+ feather_rate = (x + 1) / right
302
+ output[:, :, -x] *= feather_rate
303
+
304
+ for y in range(top):
305
+ feather_rate = (y + 1) / top
306
+ output[:, y, :] *= feather_rate
307
+
308
+ for y in range(bottom):
309
+ feather_rate = (y + 1) / bottom
310
+ output[:, -y, :] *= feather_rate
311
+
312
+ return (output,)
313
+
314
+ class GrowMask:
315
+ @classmethod
316
+ def INPUT_TYPES(cls):
317
+ return {
318
+ "required": {
319
+ "mask": ("MASK",),
320
+ "expand": ("INT", {"default": 0, "min": -MAX_RESOLUTION, "max": MAX_RESOLUTION, "step": 1}),
321
+ "tapered_corners": ("BOOLEAN", {"default": True}),
322
+ },
323
+ }
324
+
325
+ CATEGORY = "mask"
326
+
327
+ RETURN_TYPES = ("MASK",)
328
+
329
+ FUNCTION = "expand_mask"
330
+
331
+ def expand_mask(self, mask, expand, tapered_corners):
332
+ c = 0 if tapered_corners else 1
333
+ kernel = np.array([[c, 1, c],
334
+ [1, 1, 1],
335
+ [c, 1, c]])
336
+ mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1]))
337
+ out = []
338
+ for m in mask:
339
+ output = m.numpy()
340
+ for _ in range(abs(expand)):
341
+ if expand < 0:
342
+ output = scipy.ndimage.grey_erosion(output, footprint=kernel)
343
+ else:
344
+ output = scipy.ndimage.grey_dilation(output, footprint=kernel)
345
+ output = torch.from_numpy(output)
346
+ out.append(output)
347
+ return (torch.stack(out, dim=0),)
348
+
349
+ class ThresholdMask:
350
+ @classmethod
351
+ def INPUT_TYPES(s):
352
+ return {
353
+ "required": {
354
+ "mask": ("MASK",),
355
+ "value": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
356
+ }
357
+ }
358
+
359
+ CATEGORY = "mask"
360
+
361
+ RETURN_TYPES = ("MASK",)
362
+ FUNCTION = "image_to_mask"
363
+
364
+ def image_to_mask(self, mask, value):
365
+ mask = (mask > value).float()
366
+ return (mask,)
367
+
368
+ # Mask Preview - original implement from
369
+ # https://github.com/cubiq/ComfyUI_essentials/blob/9d9f4bedfc9f0321c19faf71855e228c93bd0dc9/mask.py#L81
370
+ # upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes
371
+ class MaskPreview(nodes.SaveImage):
372
+ def __init__(self):
373
+ self.output_dir = folder_paths.get_temp_directory()
374
+ self.type = "temp"
375
+ self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
376
+ self.compress_level = 4
377
+
378
+ @classmethod
379
+ def INPUT_TYPES(s):
380
+ return {
381
+ "required": {"mask": ("MASK",), },
382
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
383
+ }
384
+
385
+ FUNCTION = "execute"
386
+ CATEGORY = "mask"
387
+
388
+ def execute(self, mask, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
389
+ preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
390
+ return self.save_images(preview, filename_prefix, prompt, extra_pnginfo)
391
+
392
+
393
+ NODE_CLASS_MAPPINGS = {
394
+ "LatentCompositeMasked": LatentCompositeMasked,
395
+ "ImageCompositeMasked": ImageCompositeMasked,
396
+ "MaskToImage": MaskToImage,
397
+ "ImageToMask": ImageToMask,
398
+ "ImageColorToMask": ImageColorToMask,
399
+ "SolidMask": SolidMask,
400
+ "InvertMask": InvertMask,
401
+ "CropMask": CropMask,
402
+ "MaskComposite": MaskComposite,
403
+ "FeatherMask": FeatherMask,
404
+ "GrowMask": GrowMask,
405
+ "ThresholdMask": ThresholdMask,
406
+ "MaskPreview": MaskPreview
407
+ }
408
+
409
+ NODE_DISPLAY_NAME_MAPPINGS = {
410
+ "ImageToMask": "Convert Image to Mask",
411
+ "MaskToImage": "Convert Mask to Image",
412
+ }
ComfyUI/comfy_extras/nodes_mochi.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import nodes
2
+ import torch
3
+ import comfy.model_management
4
+
5
+ class EmptyMochiLatentVideo:
6
+ @classmethod
7
+ def INPUT_TYPES(s):
8
+ return {"required": { "width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
9
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
10
+ "length": ("INT", {"default": 25, "min": 7, "max": nodes.MAX_RESOLUTION, "step": 6}),
11
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}}
12
+ RETURN_TYPES = ("LATENT",)
13
+ FUNCTION = "generate"
14
+
15
+ CATEGORY = "latent/video"
16
+
17
+ def generate(self, width, height, length, batch_size=1):
18
+ latent = torch.zeros([batch_size, 12, ((length - 1) // 6) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
19
+ return ({"samples":latent}, )
20
+
21
+ NODE_CLASS_MAPPINGS = {
22
+ "EmptyMochiLatentVideo": EmptyMochiLatentVideo,
23
+ }
ComfyUI/comfy_extras/nodes_model_advanced.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import comfy.sd
2
+ import comfy.model_sampling
3
+ import comfy.latent_formats
4
+ import nodes
5
+ import torch
6
+ import node_helpers
7
+
8
+
9
+ class LCM(comfy.model_sampling.EPS):
10
+ def calculate_denoised(self, sigma, model_output, model_input):
11
+ timestep = self.timestep(sigma).view(sigma.shape[:1] + (1,) * (model_output.ndim - 1))
12
+ sigma = sigma.view(sigma.shape[:1] + (1,) * (model_output.ndim - 1))
13
+ x0 = model_input - model_output * sigma
14
+
15
+ sigma_data = 0.5
16
+ scaled_timestep = timestep * 10.0 #timestep_scaling
17
+
18
+ c_skip = sigma_data**2 / (scaled_timestep**2 + sigma_data**2)
19
+ c_out = scaled_timestep / (scaled_timestep**2 + sigma_data**2) ** 0.5
20
+
21
+ return c_out * x0 + c_skip * model_input
22
+
23
+ class ModelSamplingDiscreteDistilled(comfy.model_sampling.ModelSamplingDiscrete):
24
+ original_timesteps = 50
25
+
26
+ def __init__(self, model_config=None, zsnr=None):
27
+ super().__init__(model_config, zsnr=zsnr)
28
+
29
+ self.skip_steps = self.num_timesteps // self.original_timesteps
30
+
31
+ sigmas_valid = torch.zeros((self.original_timesteps), dtype=torch.float32)
32
+ for x in range(self.original_timesteps):
33
+ sigmas_valid[self.original_timesteps - 1 - x] = self.sigmas[self.num_timesteps - 1 - x * self.skip_steps]
34
+
35
+ self.set_sigmas(sigmas_valid)
36
+
37
+ def timestep(self, sigma):
38
+ log_sigma = sigma.log()
39
+ dists = log_sigma.to(self.log_sigmas.device) - self.log_sigmas[:, None]
40
+ return (dists.abs().argmin(dim=0).view(sigma.shape) * self.skip_steps + (self.skip_steps - 1)).to(sigma.device)
41
+
42
+ def sigma(self, timestep):
43
+ 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))
44
+ low_idx = t.floor().long()
45
+ high_idx = t.ceil().long()
46
+ w = t.frac()
47
+ log_sigma = (1 - w) * self.log_sigmas[low_idx] + w * self.log_sigmas[high_idx]
48
+ return log_sigma.exp().to(timestep.device)
49
+
50
+
51
+ class ModelSamplingDiscrete:
52
+ @classmethod
53
+ def INPUT_TYPES(s):
54
+ return {"required": { "model": ("MODEL",),
55
+ "sampling": (["eps", "v_prediction", "lcm", "x0", "img_to_img"],),
56
+ "zsnr": ("BOOLEAN", {"default": False}),
57
+ }}
58
+
59
+ RETURN_TYPES = ("MODEL",)
60
+ FUNCTION = "patch"
61
+
62
+ CATEGORY = "advanced/model"
63
+
64
+ def patch(self, model, sampling, zsnr):
65
+ m = model.clone()
66
+
67
+ sampling_base = comfy.model_sampling.ModelSamplingDiscrete
68
+ if sampling == "eps":
69
+ sampling_type = comfy.model_sampling.EPS
70
+ elif sampling == "v_prediction":
71
+ sampling_type = comfy.model_sampling.V_PREDICTION
72
+ elif sampling == "lcm":
73
+ sampling_type = LCM
74
+ sampling_base = ModelSamplingDiscreteDistilled
75
+ elif sampling == "x0":
76
+ sampling_type = comfy.model_sampling.X0
77
+ elif sampling == "img_to_img":
78
+ sampling_type = comfy.model_sampling.IMG_TO_IMG
79
+
80
+ class ModelSamplingAdvanced(sampling_base, sampling_type):
81
+ pass
82
+
83
+ model_sampling = ModelSamplingAdvanced(model.model.model_config, zsnr=zsnr)
84
+
85
+ m.add_object_patch("model_sampling", model_sampling)
86
+ return (m, )
87
+
88
+ class ModelSamplingStableCascade:
89
+ @classmethod
90
+ def INPUT_TYPES(s):
91
+ return {"required": { "model": ("MODEL",),
92
+ "shift": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 100.0, "step":0.01}),
93
+ }}
94
+
95
+ RETURN_TYPES = ("MODEL",)
96
+ FUNCTION = "patch"
97
+
98
+ CATEGORY = "advanced/model"
99
+
100
+ def patch(self, model, shift):
101
+ m = model.clone()
102
+
103
+ sampling_base = comfy.model_sampling.StableCascadeSampling
104
+ sampling_type = comfy.model_sampling.EPS
105
+
106
+ class ModelSamplingAdvanced(sampling_base, sampling_type):
107
+ pass
108
+
109
+ model_sampling = ModelSamplingAdvanced(model.model.model_config)
110
+ model_sampling.set_parameters(shift)
111
+ m.add_object_patch("model_sampling", model_sampling)
112
+ return (m, )
113
+
114
+ class ModelSamplingSD3:
115
+ @classmethod
116
+ def INPUT_TYPES(s):
117
+ return {"required": { "model": ("MODEL",),
118
+ "shift": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step":0.01}),
119
+ }}
120
+
121
+ RETURN_TYPES = ("MODEL",)
122
+ FUNCTION = "patch"
123
+
124
+ CATEGORY = "advanced/model"
125
+
126
+ def patch(self, model, shift, multiplier=1000):
127
+ m = model.clone()
128
+
129
+ sampling_base = comfy.model_sampling.ModelSamplingDiscreteFlow
130
+ sampling_type = comfy.model_sampling.CONST
131
+
132
+ class ModelSamplingAdvanced(sampling_base, sampling_type):
133
+ pass
134
+
135
+ model_sampling = ModelSamplingAdvanced(model.model.model_config)
136
+ model_sampling.set_parameters(shift=shift, multiplier=multiplier)
137
+ m.add_object_patch("model_sampling", model_sampling)
138
+ return (m, )
139
+
140
+ class ModelSamplingAuraFlow(ModelSamplingSD3):
141
+ @classmethod
142
+ def INPUT_TYPES(s):
143
+ return {"required": { "model": ("MODEL",),
144
+ "shift": ("FLOAT", {"default": 1.73, "min": 0.0, "max": 100.0, "step":0.01}),
145
+ }}
146
+
147
+ FUNCTION = "patch_aura"
148
+
149
+ def patch_aura(self, model, shift):
150
+ return self.patch(model, shift, multiplier=1.0)
151
+
152
+ class ModelSamplingFlux:
153
+ @classmethod
154
+ def INPUT_TYPES(s):
155
+ return {"required": { "model": ("MODEL",),
156
+ "max_shift": ("FLOAT", {"default": 1.15, "min": 0.0, "max": 100.0, "step":0.01}),
157
+ "base_shift": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 100.0, "step":0.01}),
158
+ "width": ("INT", {"default": 1024, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
159
+ "height": ("INT", {"default": 1024, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
160
+ }}
161
+
162
+ RETURN_TYPES = ("MODEL",)
163
+ FUNCTION = "patch"
164
+
165
+ CATEGORY = "advanced/model"
166
+
167
+ def patch(self, model, max_shift, base_shift, width, height):
168
+ m = model.clone()
169
+
170
+ x1 = 256
171
+ x2 = 4096
172
+ mm = (max_shift - base_shift) / (x2 - x1)
173
+ b = base_shift - mm * x1
174
+ shift = (width * height / (8 * 8 * 2 * 2)) * mm + b
175
+
176
+ sampling_base = comfy.model_sampling.ModelSamplingFlux
177
+ sampling_type = comfy.model_sampling.CONST
178
+
179
+ class ModelSamplingAdvanced(sampling_base, sampling_type):
180
+ pass
181
+
182
+ model_sampling = ModelSamplingAdvanced(model.model.model_config)
183
+ model_sampling.set_parameters(shift=shift)
184
+ m.add_object_patch("model_sampling", model_sampling)
185
+ return (m, )
186
+
187
+
188
+ class ModelSamplingContinuousEDM:
189
+ @classmethod
190
+ def INPUT_TYPES(s):
191
+ return {"required": { "model": ("MODEL",),
192
+ "sampling": (["v_prediction", "edm", "edm_playground_v2.5", "eps", "cosmos_rflow"],),
193
+ "sigma_max": ("FLOAT", {"default": 120.0, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}),
194
+ "sigma_min": ("FLOAT", {"default": 0.002, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}),
195
+ }}
196
+
197
+ RETURN_TYPES = ("MODEL",)
198
+ FUNCTION = "patch"
199
+
200
+ CATEGORY = "advanced/model"
201
+
202
+ def patch(self, model, sampling, sigma_max, sigma_min):
203
+ m = model.clone()
204
+
205
+ sampling_base = comfy.model_sampling.ModelSamplingContinuousEDM
206
+ latent_format = None
207
+ sigma_data = 1.0
208
+ if sampling == "eps":
209
+ sampling_type = comfy.model_sampling.EPS
210
+ elif sampling == "edm":
211
+ sampling_type = comfy.model_sampling.EDM
212
+ sigma_data = 0.5
213
+ elif sampling == "v_prediction":
214
+ sampling_type = comfy.model_sampling.V_PREDICTION
215
+ elif sampling == "edm_playground_v2.5":
216
+ sampling_type = comfy.model_sampling.EDM
217
+ sigma_data = 0.5
218
+ latent_format = comfy.latent_formats.SDXL_Playground_2_5()
219
+ elif sampling == "cosmos_rflow":
220
+ sampling_type = comfy.model_sampling.COSMOS_RFLOW
221
+ sampling_base = comfy.model_sampling.ModelSamplingCosmosRFlow
222
+
223
+ class ModelSamplingAdvanced(sampling_base, sampling_type):
224
+ pass
225
+
226
+ model_sampling = ModelSamplingAdvanced(model.model.model_config)
227
+ model_sampling.set_parameters(sigma_min, sigma_max, sigma_data)
228
+ m.add_object_patch("model_sampling", model_sampling)
229
+ if latent_format is not None:
230
+ m.add_object_patch("latent_format", latent_format)
231
+ return (m, )
232
+
233
+ class ModelSamplingContinuousV:
234
+ @classmethod
235
+ def INPUT_TYPES(s):
236
+ return {"required": { "model": ("MODEL",),
237
+ "sampling": (["v_prediction"],),
238
+ "sigma_max": ("FLOAT", {"default": 500.0, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}),
239
+ "sigma_min": ("FLOAT", {"default": 0.03, "min": 0.0, "max": 1000.0, "step":0.001, "round": False}),
240
+ }}
241
+
242
+ RETURN_TYPES = ("MODEL",)
243
+ FUNCTION = "patch"
244
+
245
+ CATEGORY = "advanced/model"
246
+
247
+ def patch(self, model, sampling, sigma_max, sigma_min):
248
+ m = model.clone()
249
+
250
+ sigma_data = 1.0
251
+ if sampling == "v_prediction":
252
+ sampling_type = comfy.model_sampling.V_PREDICTION
253
+
254
+ class ModelSamplingAdvanced(comfy.model_sampling.ModelSamplingContinuousV, sampling_type):
255
+ pass
256
+
257
+ model_sampling = ModelSamplingAdvanced(model.model.model_config)
258
+ model_sampling.set_parameters(sigma_min, sigma_max, sigma_data)
259
+ m.add_object_patch("model_sampling", model_sampling)
260
+ return (m, )
261
+
262
+ class RescaleCFG:
263
+ @classmethod
264
+ def INPUT_TYPES(s):
265
+ return {"required": { "model": ("MODEL",),
266
+ "multiplier": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}),
267
+ }}
268
+ RETURN_TYPES = ("MODEL",)
269
+ FUNCTION = "patch"
270
+
271
+ CATEGORY = "advanced/model"
272
+
273
+ def patch(self, model, multiplier):
274
+ def rescale_cfg(args):
275
+ cond = args["cond"]
276
+ uncond = args["uncond"]
277
+ cond_scale = args["cond_scale"]
278
+ sigma = args["sigma"]
279
+ sigma = sigma.view(sigma.shape[:1] + (1,) * (cond.ndim - 1))
280
+ x_orig = args["input"]
281
+
282
+ #rescale cfg has to be done on v-pred model output
283
+ x = x_orig / (sigma * sigma + 1.0)
284
+ cond = ((x - (x_orig - cond)) * (sigma ** 2 + 1.0) ** 0.5) / (sigma)
285
+ uncond = ((x - (x_orig - uncond)) * (sigma ** 2 + 1.0) ** 0.5) / (sigma)
286
+
287
+ #rescalecfg
288
+ x_cfg = uncond + cond_scale * (cond - uncond)
289
+ ro_pos = torch.std(cond, dim=(1,2,3), keepdim=True)
290
+ ro_cfg = torch.std(x_cfg, dim=(1,2,3), keepdim=True)
291
+
292
+ x_rescaled = x_cfg * (ro_pos / ro_cfg)
293
+ x_final = multiplier * x_rescaled + (1.0 - multiplier) * x_cfg
294
+
295
+ return x_orig - (x - x_final * sigma / (sigma * sigma + 1.0) ** 0.5)
296
+
297
+ m = model.clone()
298
+ m.set_model_sampler_cfg_function(rescale_cfg)
299
+ return (m, )
300
+
301
+ class ModelComputeDtype:
302
+ @classmethod
303
+ def INPUT_TYPES(s):
304
+ return {"required": { "model": ("MODEL",),
305
+ "dtype": (["default", "fp32", "fp16", "bf16"],),
306
+ }}
307
+
308
+ RETURN_TYPES = ("MODEL",)
309
+ FUNCTION = "patch"
310
+
311
+ CATEGORY = "advanced/debug/model"
312
+
313
+ def patch(self, model, dtype):
314
+ m = model.clone()
315
+ m.set_model_compute_dtype(node_helpers.string_to_torch_dtype(dtype))
316
+ return (m, )
317
+
318
+
319
+ NODE_CLASS_MAPPINGS = {
320
+ "ModelSamplingDiscrete": ModelSamplingDiscrete,
321
+ "ModelSamplingContinuousEDM": ModelSamplingContinuousEDM,
322
+ "ModelSamplingContinuousV": ModelSamplingContinuousV,
323
+ "ModelSamplingStableCascade": ModelSamplingStableCascade,
324
+ "ModelSamplingSD3": ModelSamplingSD3,
325
+ "ModelSamplingAuraFlow": ModelSamplingAuraFlow,
326
+ "ModelSamplingFlux": ModelSamplingFlux,
327
+ "RescaleCFG": RescaleCFG,
328
+ "ModelComputeDtype": ModelComputeDtype,
329
+ }
ComfyUI/comfy_extras/nodes_pag.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Modified/simplified version of the node from: https://github.com/pamparamm/sd-perturbed-attention
2
+ #If you want the one with more options see the above repo.
3
+
4
+ #My modified one here is more basic but has less chances of breaking with ComfyUI updates.
5
+
6
+ import comfy.model_patcher
7
+ import comfy.samplers
8
+
9
+ class PerturbedAttentionGuidance:
10
+ @classmethod
11
+ def INPUT_TYPES(s):
12
+ return {
13
+ "required": {
14
+ "model": ("MODEL",),
15
+ "scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": 0.01}),
16
+ }
17
+ }
18
+
19
+ RETURN_TYPES = ("MODEL",)
20
+ FUNCTION = "patch"
21
+
22
+ CATEGORY = "model_patches/unet"
23
+
24
+ def patch(self, model, scale):
25
+ unet_block = "middle"
26
+ unet_block_id = 0
27
+ m = model.clone()
28
+
29
+ def perturbed_attention(q, k, v, extra_options, mask=None):
30
+ return v
31
+
32
+ def post_cfg_function(args):
33
+ model = args["model"]
34
+ cond_pred = args["cond_denoised"]
35
+ cond = args["cond"]
36
+ cfg_result = args["denoised"]
37
+ sigma = args["sigma"]
38
+ model_options = args["model_options"].copy()
39
+ x = args["input"]
40
+
41
+ if scale == 0:
42
+ return cfg_result
43
+
44
+ # Replace Self-attention with PAG
45
+ model_options = comfy.model_patcher.set_model_options_patch_replace(model_options, perturbed_attention, "attn1", unet_block, unet_block_id)
46
+ (pag,) = comfy.samplers.calc_cond_batch(model, [cond], x, sigma, model_options)
47
+
48
+ return cfg_result + (cond_pred - pag) * scale
49
+
50
+ m.set_model_sampler_post_cfg_function(post_cfg_function)
51
+
52
+ return (m,)
53
+
54
+ NODE_CLASS_MAPPINGS = {
55
+ "PerturbedAttentionGuidance": PerturbedAttentionGuidance,
56
+ }
ComfyUI/comfy_extras/nodes_perpneg.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import comfy.model_management
3
+ import comfy.sampler_helpers
4
+ import comfy.samplers
5
+ import comfy.utils
6
+ import node_helpers
7
+ import math
8
+
9
+ def perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_nocond, neg_scale, cond_scale):
10
+ pos = noise_pred_pos - noise_pred_nocond
11
+ neg = noise_pred_neg - noise_pred_nocond
12
+
13
+ perp = neg - ((torch.mul(neg, pos).sum())/(torch.norm(pos)**2)) * pos
14
+ perp_neg = perp * neg_scale
15
+ cfg_result = noise_pred_nocond + cond_scale*(pos - perp_neg)
16
+ return cfg_result
17
+
18
+ #TODO: This node should be removed, it has been replaced with PerpNegGuider
19
+ class PerpNeg:
20
+ @classmethod
21
+ def INPUT_TYPES(s):
22
+ return {"required": {"model": ("MODEL", ),
23
+ "empty_conditioning": ("CONDITIONING", ),
24
+ "neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
25
+ }}
26
+ RETURN_TYPES = ("MODEL",)
27
+ FUNCTION = "patch"
28
+
29
+ CATEGORY = "_for_testing"
30
+ DEPRECATED = True
31
+
32
+ def patch(self, model, empty_conditioning, neg_scale):
33
+ m = model.clone()
34
+ nocond = comfy.sampler_helpers.convert_cond(empty_conditioning)
35
+
36
+ def cfg_function(args):
37
+ model = args["model"]
38
+ noise_pred_pos = args["cond_denoised"]
39
+ noise_pred_neg = args["uncond_denoised"]
40
+ cond_scale = args["cond_scale"]
41
+ x = args["input"]
42
+ sigma = args["sigma"]
43
+ model_options = args["model_options"]
44
+ nocond_processed = comfy.samplers.encode_model_conds(model.extra_conds, nocond, x, x.device, "negative")
45
+
46
+ (noise_pred_nocond,) = comfy.samplers.calc_cond_batch(model, [nocond_processed], x, sigma, model_options)
47
+
48
+ cfg_result = x - perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_nocond, neg_scale, cond_scale)
49
+ return cfg_result
50
+
51
+ m.set_model_sampler_cfg_function(cfg_function)
52
+
53
+ return (m, )
54
+
55
+
56
+ class Guider_PerpNeg(comfy.samplers.CFGGuider):
57
+ def set_conds(self, positive, negative, empty_negative_prompt):
58
+ empty_negative_prompt = node_helpers.conditioning_set_values(empty_negative_prompt, {"prompt_type": "negative"})
59
+ self.inner_set_conds({"positive": positive, "empty_negative_prompt": empty_negative_prompt, "negative": negative})
60
+
61
+ def set_cfg(self, cfg, neg_scale):
62
+ self.cfg = cfg
63
+ self.neg_scale = neg_scale
64
+
65
+ def predict_noise(self, x, timestep, model_options={}, seed=None):
66
+ # in CFGGuider.predict_noise, we call sampling_function(), which uses cfg_function() to compute pos & neg
67
+ # but we'd rather do a single batch of sampling pos, neg, and empty, so we call calc_cond_batch([pos,neg,empty]) directly
68
+
69
+ positive_cond = self.conds.get("positive", None)
70
+ negative_cond = self.conds.get("negative", None)
71
+ empty_cond = self.conds.get("empty_negative_prompt", None)
72
+
73
+ if model_options.get("disable_cfg1_optimization", False) == False:
74
+ if math.isclose(self.neg_scale, 0.0):
75
+ negative_cond = None
76
+ if math.isclose(self.cfg, 1.0):
77
+ empty_cond = None
78
+
79
+ conds = [positive_cond, negative_cond, empty_cond]
80
+
81
+ out = comfy.samplers.calc_cond_batch(self.inner_model, conds, x, timestep, model_options)
82
+
83
+ # Apply pre_cfg_functions since sampling_function() is skipped
84
+ for fn in model_options.get("sampler_pre_cfg_function", []):
85
+ args = {"conds":conds, "conds_out": out, "cond_scale": self.cfg, "timestep": timestep,
86
+ "input": x, "sigma": timestep, "model": self.inner_model, "model_options": model_options}
87
+ out = fn(args)
88
+
89
+ noise_pred_pos, noise_pred_neg, noise_pred_empty = out
90
+ cfg_result = perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_empty, self.neg_scale, self.cfg)
91
+
92
+ # normally this would be done in cfg_function, but we skipped
93
+ # that for efficiency: we can compute the noise predictions in
94
+ # a single call to calc_cond_batch() (rather than two)
95
+ # so we replicate the hook here
96
+ for fn in model_options.get("sampler_post_cfg_function", []):
97
+ args = {
98
+ "denoised": cfg_result,
99
+ "cond": positive_cond,
100
+ "uncond": negative_cond,
101
+ "cond_scale": self.cfg,
102
+ "model": self.inner_model,
103
+ "uncond_denoised": noise_pred_neg,
104
+ "cond_denoised": noise_pred_pos,
105
+ "sigma": timestep,
106
+ "model_options": model_options,
107
+ "input": x,
108
+ # not in the original call in samplers.py:cfg_function, but made available for future hooks
109
+ "empty_cond": empty_cond,
110
+ "empty_cond_denoised": noise_pred_empty,}
111
+ cfg_result = fn(args)
112
+
113
+ return cfg_result
114
+
115
+ class PerpNegGuider:
116
+ @classmethod
117
+ def INPUT_TYPES(s):
118
+ return {"required":
119
+ {"model": ("MODEL",),
120
+ "positive": ("CONDITIONING", ),
121
+ "negative": ("CONDITIONING", ),
122
+ "empty_conditioning": ("CONDITIONING", ),
123
+ "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
124
+ "neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
125
+ }
126
+ }
127
+
128
+ RETURN_TYPES = ("GUIDER",)
129
+
130
+ FUNCTION = "get_guider"
131
+ CATEGORY = "_for_testing"
132
+
133
+ def get_guider(self, model, positive, negative, empty_conditioning, cfg, neg_scale):
134
+ guider = Guider_PerpNeg(model)
135
+ guider.set_conds(positive, negative, empty_conditioning)
136
+ guider.set_cfg(cfg, neg_scale)
137
+ return (guider,)
138
+
139
+ NODE_CLASS_MAPPINGS = {
140
+ "PerpNeg": PerpNeg,
141
+ "PerpNegGuider": PerpNegGuider,
142
+ }
143
+
144
+ NODE_DISPLAY_NAME_MAPPINGS = {
145
+ "PerpNeg": "Perp-Neg (DEPRECATED by PerpNegGuider)",
146
+ }
ComfyUI/comfy_extras/nodes_preview_any.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from comfy.comfy_types.node_typing import IO
3
+
4
+ # Preview Any - original implement from
5
+ # https://github.com/rgthree/rgthree-comfy/blob/main/py/display_any.py
6
+ # upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes
7
+ class PreviewAny():
8
+ @classmethod
9
+ def INPUT_TYPES(cls):
10
+ return {
11
+ "required": {"source": (IO.ANY, {})},
12
+ }
13
+
14
+ RETURN_TYPES = ()
15
+ FUNCTION = "main"
16
+ OUTPUT_NODE = True
17
+
18
+ CATEGORY = "utils"
19
+
20
+ def main(self, source=None):
21
+ value = 'None'
22
+ if isinstance(source, str):
23
+ value = source
24
+ elif isinstance(source, (int, float, bool)):
25
+ value = str(source)
26
+ elif source is not None:
27
+ try:
28
+ value = json.dumps(source)
29
+ except Exception:
30
+ try:
31
+ value = str(source)
32
+ except Exception:
33
+ value = 'source exists, but could not be serialized.'
34
+
35
+ return {"ui": {"text": (value,)}}
36
+
37
+ NODE_CLASS_MAPPINGS = {
38
+ "PreviewAny": PreviewAny,
39
+ }
40
+
41
+ NODE_DISPLAY_NAME_MAPPINGS = {
42
+ "PreviewAny": "Preview Any",
43
+ }
ComfyUI/comfy_extras/nodes_tcfg.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TCFG: Tangential Damping Classifier-free Guidance - (arXiv: https://arxiv.org/abs/2503.18137)
2
+
3
+ import torch
4
+
5
+ from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict
6
+
7
+
8
+ def score_tangential_damping(cond_score: torch.Tensor, uncond_score: torch.Tensor) -> torch.Tensor:
9
+ """Drop tangential components from uncond score to align with cond score."""
10
+ # (B, 1, ...)
11
+ batch_num = cond_score.shape[0]
12
+ cond_score_flat = cond_score.reshape(batch_num, 1, -1).float()
13
+ uncond_score_flat = uncond_score.reshape(batch_num, 1, -1).float()
14
+
15
+ # Score matrix A (B, 2, ...)
16
+ score_matrix = torch.cat((uncond_score_flat, cond_score_flat), dim=1)
17
+ try:
18
+ _, _, Vh = torch.linalg.svd(score_matrix, full_matrices=False)
19
+ except RuntimeError:
20
+ # Fallback to CPU
21
+ _, _, Vh = torch.linalg.svd(score_matrix.cpu(), full_matrices=False)
22
+
23
+ # Drop the tangential components
24
+ v1 = Vh[:, 0:1, :].to(uncond_score_flat.device) # (B, 1, ...)
25
+ uncond_score_td = (uncond_score_flat @ v1.transpose(-2, -1)) * v1
26
+ return uncond_score_td.reshape_as(uncond_score).to(uncond_score.dtype)
27
+
28
+
29
+ class TCFG(ComfyNodeABC):
30
+ @classmethod
31
+ def INPUT_TYPES(cls) -> InputTypeDict:
32
+ return {
33
+ "required": {
34
+ "model": (IO.MODEL, {}),
35
+ }
36
+ }
37
+
38
+ RETURN_TYPES = (IO.MODEL,)
39
+ RETURN_NAMES = ("patched_model",)
40
+ FUNCTION = "patch"
41
+
42
+ CATEGORY = "advanced/guidance"
43
+ DESCRIPTION = "TCFG – Tangential Damping CFG (2503.18137)\n\nRefine the uncond (negative) to align with the cond (positive) for improving quality."
44
+
45
+ def patch(self, model):
46
+ m = model.clone()
47
+
48
+ def tangential_damping_cfg(args):
49
+ # Assume [cond, uncond, ...]
50
+ x = args["input"]
51
+ conds_out = args["conds_out"]
52
+ if len(conds_out) <= 1 or None in args["conds"][:2]:
53
+ # Skip when either cond or uncond is None
54
+ return conds_out
55
+ cond_pred = conds_out[0]
56
+ uncond_pred = conds_out[1]
57
+ uncond_td = score_tangential_damping(x - cond_pred, x - uncond_pred)
58
+ uncond_pred_td = x - uncond_td
59
+ return [cond_pred, uncond_pred_td] + conds_out[2:]
60
+
61
+ m.set_model_sampler_pre_cfg_function(tangential_damping_cfg)
62
+ return (m,)
63
+
64
+
65
+ NODE_CLASS_MAPPINGS = {
66
+ "TCFG": TCFG,
67
+ }
68
+
69
+ NODE_DISPLAY_NAME_MAPPINGS = {
70
+ "TCFG": "Tangential Damping CFG",
71
+ }
ComfyUI/comfy_extras/nodes_tomesd.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Taken from: https://github.com/dbolya/tomesd
2
+
3
+ import torch
4
+ from typing import Tuple, Callable
5
+ import math
6
+
7
+ def do_nothing(x: torch.Tensor, mode:str=None):
8
+ return x
9
+
10
+
11
+ def mps_gather_workaround(input, dim, index):
12
+ if input.shape[-1] == 1:
13
+ return torch.gather(
14
+ input.unsqueeze(-1),
15
+ dim - 1 if dim < 0 else dim,
16
+ index.unsqueeze(-1)
17
+ ).squeeze(-1)
18
+ else:
19
+ return torch.gather(input, dim, index)
20
+
21
+
22
+ def bipartite_soft_matching_random2d(metric: torch.Tensor,
23
+ w: int, h: int, sx: int, sy: int, r: int,
24
+ no_rand: bool = False) -> Tuple[Callable, Callable]:
25
+ """
26
+ Partitions the tokens into src and dst and merges r tokens from src to dst.
27
+ Dst tokens are partitioned by choosing one randomy in each (sx, sy) region.
28
+ Args:
29
+ - metric [B, N, C]: metric to use for similarity
30
+ - w: image width in tokens
31
+ - h: image height in tokens
32
+ - sx: stride in the x dimension for dst, must divide w
33
+ - sy: stride in the y dimension for dst, must divide h
34
+ - r: number of tokens to remove (by merging)
35
+ - no_rand: if true, disable randomness (use top left corner only)
36
+ """
37
+ B, N, _ = metric.shape
38
+
39
+ if r <= 0 or w == 1 or h == 1:
40
+ return do_nothing, do_nothing
41
+
42
+ gather = mps_gather_workaround if metric.device.type == "mps" else torch.gather
43
+
44
+ with torch.no_grad():
45
+ hsy, wsx = h // sy, w // sx
46
+
47
+ # For each sy by sx kernel, randomly assign one token to be dst and the rest src
48
+ if no_rand:
49
+ rand_idx = torch.zeros(hsy, wsx, 1, device=metric.device, dtype=torch.int64)
50
+ else:
51
+ rand_idx = torch.randint(sy*sx, size=(hsy, wsx, 1), device=metric.device)
52
+
53
+ # 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
54
+ idx_buffer_view = torch.zeros(hsy, wsx, sy*sx, device=metric.device, dtype=torch.int64)
55
+ idx_buffer_view.scatter_(dim=2, index=rand_idx, src=-torch.ones_like(rand_idx, dtype=rand_idx.dtype))
56
+ idx_buffer_view = idx_buffer_view.view(hsy, wsx, sy, sx).transpose(1, 2).reshape(hsy * sy, wsx * sx)
57
+
58
+ # Image is not divisible by sx or sy so we need to move it into a new buffer
59
+ if (hsy * sy) < h or (wsx * sx) < w:
60
+ idx_buffer = torch.zeros(h, w, device=metric.device, dtype=torch.int64)
61
+ idx_buffer[:(hsy * sy), :(wsx * sx)] = idx_buffer_view
62
+ else:
63
+ idx_buffer = idx_buffer_view
64
+
65
+ # We set dst tokens to be -1 and src to be 0, so an argsort gives us dst|src indices
66
+ rand_idx = idx_buffer.reshape(1, -1, 1).argsort(dim=1)
67
+
68
+ # We're finished with these
69
+ del idx_buffer, idx_buffer_view
70
+
71
+ # rand_idx is currently dst|src, so split them
72
+ num_dst = hsy * wsx
73
+ a_idx = rand_idx[:, num_dst:, :] # src
74
+ b_idx = rand_idx[:, :num_dst, :] # dst
75
+
76
+ def split(x):
77
+ C = x.shape[-1]
78
+ src = gather(x, dim=1, index=a_idx.expand(B, N - num_dst, C))
79
+ dst = gather(x, dim=1, index=b_idx.expand(B, num_dst, C))
80
+ return src, dst
81
+
82
+ # Cosine similarity between A and B
83
+ metric = metric / metric.norm(dim=-1, keepdim=True)
84
+ a, b = split(metric)
85
+ scores = a @ b.transpose(-1, -2)
86
+
87
+ # Can't reduce more than the # tokens in src
88
+ r = min(a.shape[1], r)
89
+
90
+ # Find the most similar greedily
91
+ node_max, node_idx = scores.max(dim=-1)
92
+ edge_idx = node_max.argsort(dim=-1, descending=True)[..., None]
93
+
94
+ unm_idx = edge_idx[..., r:, :] # Unmerged Tokens
95
+ src_idx = edge_idx[..., :r, :] # Merged Tokens
96
+ dst_idx = gather(node_idx[..., None], dim=-2, index=src_idx)
97
+
98
+ def merge(x: torch.Tensor, mode="mean") -> torch.Tensor:
99
+ src, dst = split(x)
100
+ n, t1, c = src.shape
101
+
102
+ unm = gather(src, dim=-2, index=unm_idx.expand(n, t1 - r, c))
103
+ src = gather(src, dim=-2, index=src_idx.expand(n, r, c))
104
+ dst = dst.scatter_reduce(-2, dst_idx.expand(n, r, c), src, reduce=mode)
105
+
106
+ return torch.cat([unm, dst], dim=1)
107
+
108
+ def unmerge(x: torch.Tensor) -> torch.Tensor:
109
+ unm_len = unm_idx.shape[1]
110
+ unm, dst = x[..., :unm_len, :], x[..., unm_len:, :]
111
+ _, _, c = unm.shape
112
+
113
+ src = gather(dst, dim=-2, index=dst_idx.expand(B, r, c))
114
+
115
+ # Combine back to the original shape
116
+ out = torch.zeros(B, N, c, device=x.device, dtype=x.dtype)
117
+ out.scatter_(dim=-2, index=b_idx.expand(B, num_dst, c), src=dst)
118
+ 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)
119
+ 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)
120
+
121
+ return out
122
+
123
+ return merge, unmerge
124
+
125
+
126
+ def get_functions(x, ratio, original_shape):
127
+ b, c, original_h, original_w = original_shape
128
+ original_tokens = original_h * original_w
129
+ downsample = int(math.ceil(math.sqrt(original_tokens // x.shape[1])))
130
+ stride_x = 2
131
+ stride_y = 2
132
+ max_downsample = 1
133
+
134
+ if downsample <= max_downsample:
135
+ w = int(math.ceil(original_w / downsample))
136
+ h = int(math.ceil(original_h / downsample))
137
+ r = int(x.shape[1] * ratio)
138
+ no_rand = False
139
+ m, u = bipartite_soft_matching_random2d(x, w, h, stride_x, stride_y, r, no_rand)
140
+ return m, u
141
+
142
+ nothing = lambda y: y
143
+ return nothing, nothing
144
+
145
+
146
+
147
+ class TomePatchModel:
148
+ @classmethod
149
+ def INPUT_TYPES(s):
150
+ return {"required": { "model": ("MODEL",),
151
+ "ratio": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01}),
152
+ }}
153
+ RETURN_TYPES = ("MODEL",)
154
+ FUNCTION = "patch"
155
+
156
+ CATEGORY = "model_patches/unet"
157
+
158
+ def patch(self, model, ratio):
159
+ self.u = None
160
+ def tomesd_m(q, k, v, extra_options):
161
+ #NOTE: In the reference code get_functions takes x (input of the transformer block) as the argument instead of q
162
+ #however from my basic testing it seems that using q instead gives better results
163
+ m, self.u = get_functions(q, ratio, extra_options["original_shape"])
164
+ return m(q), k, v
165
+ def tomesd_u(n, extra_options):
166
+ return self.u(n)
167
+
168
+ m = model.clone()
169
+ m.set_model_attn1_patch(tomesd_m)
170
+ m.set_model_attn1_output_patch(tomesd_u)
171
+ return (m, )
172
+
173
+
174
+ NODE_CLASS_MAPPINGS = {
175
+ "TomePatchModel": TomePatchModel,
176
+ }
ComfyUI/comfy_extras/nodes_torch_compile.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from comfy_api.torch_helpers import set_torch_compile_wrapper
2
+
3
+
4
+ class TorchCompileModel:
5
+ @classmethod
6
+ def INPUT_TYPES(s):
7
+ return {"required": { "model": ("MODEL",),
8
+ "backend": (["inductor", "cudagraphs"],),
9
+ }}
10
+ RETURN_TYPES = ("MODEL",)
11
+ FUNCTION = "patch"
12
+
13
+ CATEGORY = "_for_testing"
14
+ EXPERIMENTAL = True
15
+
16
+ def patch(self, model, backend):
17
+ m = model.clone()
18
+ set_torch_compile_wrapper(model=m, backend=backend)
19
+ return (m, )
20
+
21
+ NODE_CLASS_MAPPINGS = {
22
+ "TorchCompileModel": TorchCompileModel,
23
+ }
ComfyUI/comfy_extras/nodes_train.py ADDED
@@ -0,0 +1,877 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import json
3
+ import logging
4
+ import os
5
+
6
+ import numpy as np
7
+ import safetensors
8
+ import torch
9
+ from PIL import Image, ImageDraw, ImageFont
10
+ from PIL.PngImagePlugin import PngInfo
11
+ import torch.utils.checkpoint
12
+ import tqdm
13
+
14
+ import comfy.samplers
15
+ import comfy.sd
16
+ import comfy.utils
17
+ import comfy.model_management
18
+ import comfy_extras.nodes_custom_sampler
19
+ import folder_paths
20
+ import node_helpers
21
+ from comfy.cli_args import args
22
+ from comfy.comfy_types.node_typing import IO
23
+ from comfy.weight_adapter import adapters, adapter_maps
24
+
25
+
26
+ def make_batch_extra_option_dict(d, indicies, full_size=None):
27
+ new_dict = {}
28
+ for k, v in d.items():
29
+ newv = v
30
+ if isinstance(v, dict):
31
+ newv = make_batch_extra_option_dict(v, indicies, full_size=full_size)
32
+ elif isinstance(v, torch.Tensor):
33
+ if full_size is None or v.size(0) == full_size:
34
+ newv = v[indicies]
35
+ elif isinstance(v, (list, tuple)) and len(v) == full_size:
36
+ newv = [v[i] for i in indicies]
37
+ new_dict[k] = newv
38
+ return new_dict
39
+
40
+
41
+ class TrainSampler(comfy.samplers.Sampler):
42
+ def __init__(self, loss_fn, optimizer, loss_callback=None, batch_size=1, grad_acc=1, total_steps=1, seed=0, training_dtype=torch.bfloat16):
43
+ self.loss_fn = loss_fn
44
+ self.optimizer = optimizer
45
+ self.loss_callback = loss_callback
46
+ self.batch_size = batch_size
47
+ self.total_steps = total_steps
48
+ self.grad_acc = grad_acc
49
+ self.seed = seed
50
+ self.training_dtype = training_dtype
51
+
52
+ def sample(self, model_wrap, sigmas, extra_args, callback, noise, latent_image=None, denoise_mask=None, disable_pbar=False):
53
+ cond = model_wrap.conds["positive"]
54
+ dataset_size = sigmas.size(0)
55
+ torch.cuda.empty_cache()
56
+ for i in (pbar:=tqdm.trange(self.total_steps, desc="Training LoRA", smoothing=0.01, disable=not comfy.utils.PROGRESS_BAR_ENABLED)):
57
+ noisegen = comfy_extras.nodes_custom_sampler.Noise_RandomNoise(self.seed + i * 1000)
58
+ indicies = torch.randperm(dataset_size)[:self.batch_size].tolist()
59
+
60
+ batch_latent = torch.stack([latent_image[i] for i in indicies])
61
+ batch_noise = noisegen.generate_noise({"samples": batch_latent}).to(batch_latent.device)
62
+ batch_sigmas = [
63
+ model_wrap.inner_model.model_sampling.percent_to_sigma(
64
+ torch.rand((1,)).item()
65
+ ) for _ in range(min(self.batch_size, dataset_size))
66
+ ]
67
+ batch_sigmas = torch.tensor(batch_sigmas).to(batch_latent.device)
68
+
69
+ xt = model_wrap.inner_model.model_sampling.noise_scaling(
70
+ batch_sigmas,
71
+ batch_noise,
72
+ batch_latent,
73
+ False
74
+ )
75
+ x0 = model_wrap.inner_model.model_sampling.noise_scaling(
76
+ torch.zeros_like(batch_sigmas),
77
+ torch.zeros_like(batch_noise),
78
+ batch_latent,
79
+ False
80
+ )
81
+
82
+ model_wrap.conds["positive"] = [
83
+ cond[i] for i in indicies
84
+ ]
85
+ batch_extra_args = make_batch_extra_option_dict(extra_args, indicies, full_size=dataset_size)
86
+
87
+ with torch.autocast(xt.device.type, dtype=self.training_dtype):
88
+ x0_pred = model_wrap(xt, batch_sigmas, **batch_extra_args)
89
+ loss = self.loss_fn(x0_pred, x0)
90
+ loss.backward()
91
+ if self.loss_callback:
92
+ self.loss_callback(loss.item())
93
+ pbar.set_postfix({"loss": f"{loss.item():.4f}"})
94
+
95
+ if (i+1) % self.grad_acc == 0:
96
+ self.optimizer.step()
97
+ self.optimizer.zero_grad()
98
+ torch.cuda.empty_cache()
99
+ return torch.zeros_like(latent_image)
100
+
101
+
102
+ class BiasDiff(torch.nn.Module):
103
+ def __init__(self, bias):
104
+ super().__init__()
105
+ self.bias = bias
106
+
107
+ def __call__(self, b):
108
+ org_dtype = b.dtype
109
+ return (b.to(self.bias) + self.bias).to(org_dtype)
110
+
111
+ def passive_memory_usage(self):
112
+ return self.bias.nelement() * self.bias.element_size()
113
+
114
+ def move_to(self, device):
115
+ self.to(device=device)
116
+ return self.passive_memory_usage()
117
+
118
+
119
+ def load_and_process_images(image_files, input_dir, resize_method="None", w=None, h=None):
120
+ """Utility function to load and process a list of images.
121
+
122
+ Args:
123
+ image_files: List of image filenames
124
+ input_dir: Base directory containing the images
125
+ resize_method: How to handle images of different sizes ("None", "Stretch", "Crop", "Pad")
126
+
127
+ Returns:
128
+ torch.Tensor: Batch of processed images
129
+ """
130
+ if not image_files:
131
+ raise ValueError("No valid images found in input")
132
+
133
+ output_images = []
134
+
135
+ for file in image_files:
136
+ image_path = os.path.join(input_dir, file)
137
+ img = node_helpers.pillow(Image.open, image_path)
138
+
139
+ if img.mode == "I":
140
+ img = img.point(lambda i: i * (1 / 255))
141
+ img = img.convert("RGB")
142
+
143
+ if w is None and h is None:
144
+ w, h = img.size[0], img.size[1]
145
+
146
+ # Resize image to first image
147
+ if img.size[0] != w or img.size[1] != h:
148
+ if resize_method == "Stretch":
149
+ img = img.resize((w, h), Image.Resampling.LANCZOS)
150
+ elif resize_method == "Crop":
151
+ img = img.crop((0, 0, w, h))
152
+ elif resize_method == "Pad":
153
+ img = img.resize((w, h), Image.Resampling.LANCZOS)
154
+ elif resize_method == "None":
155
+ raise ValueError(
156
+ "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."
157
+ )
158
+
159
+ img_array = np.array(img).astype(np.float32) / 255.0
160
+ img_tensor = torch.from_numpy(img_array)[None,]
161
+ output_images.append(img_tensor)
162
+
163
+ return torch.cat(output_images, dim=0)
164
+
165
+
166
+ class LoadImageSetNode:
167
+ @classmethod
168
+ def INPUT_TYPES(s):
169
+ return {
170
+ "required": {
171
+ "images": (
172
+ [
173
+ f
174
+ for f in os.listdir(folder_paths.get_input_directory())
175
+ if f.endswith((".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif", ".jpe", ".apng", ".tif", ".tiff"))
176
+ ],
177
+ {"image_upload": True, "allow_batch": True},
178
+ )
179
+ },
180
+ "optional": {
181
+ "resize_method": (
182
+ ["None", "Stretch", "Crop", "Pad"],
183
+ {"default": "None"},
184
+ ),
185
+ },
186
+ }
187
+
188
+ INPUT_IS_LIST = True
189
+ RETURN_TYPES = ("IMAGE",)
190
+ FUNCTION = "load_images"
191
+ CATEGORY = "loaders"
192
+ EXPERIMENTAL = True
193
+ DESCRIPTION = "Loads a batch of images from a directory for training."
194
+
195
+ @classmethod
196
+ def VALIDATE_INPUTS(s, images, resize_method):
197
+ filenames = images[0] if isinstance(images[0], list) else images
198
+
199
+ for image in filenames:
200
+ if not folder_paths.exists_annotated_filepath(image):
201
+ return "Invalid image file: {}".format(image)
202
+ return True
203
+
204
+ def load_images(self, input_files, resize_method):
205
+ input_dir = folder_paths.get_input_directory()
206
+ valid_extensions = [".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif", ".jpe", ".apng", ".tif", ".tiff"]
207
+ image_files = [
208
+ f
209
+ for f in input_files
210
+ if any(f.lower().endswith(ext) for ext in valid_extensions)
211
+ ]
212
+ output_tensor = load_and_process_images(image_files, input_dir, resize_method)
213
+ return (output_tensor,)
214
+
215
+
216
+ class LoadImageSetFromFolderNode:
217
+ @classmethod
218
+ def INPUT_TYPES(s):
219
+ return {
220
+ "required": {
221
+ "folder": (folder_paths.get_input_subfolders(), {"tooltip": "The folder to load images from."})
222
+ },
223
+ "optional": {
224
+ "resize_method": (
225
+ ["None", "Stretch", "Crop", "Pad"],
226
+ {"default": "None"},
227
+ ),
228
+ },
229
+ }
230
+
231
+ RETURN_TYPES = ("IMAGE",)
232
+ FUNCTION = "load_images"
233
+ CATEGORY = "loaders"
234
+ EXPERIMENTAL = True
235
+ DESCRIPTION = "Loads a batch of images from a directory for training."
236
+
237
+ def load_images(self, folder, resize_method):
238
+ sub_input_dir = os.path.join(folder_paths.get_input_directory(), folder)
239
+ valid_extensions = [".png", ".jpg", ".jpeg", ".webp"]
240
+ image_files = [
241
+ f
242
+ for f in os.listdir(sub_input_dir)
243
+ if any(f.lower().endswith(ext) for ext in valid_extensions)
244
+ ]
245
+ output_tensor = load_and_process_images(image_files, sub_input_dir, resize_method)
246
+ return (output_tensor,)
247
+
248
+
249
+ class LoadImageTextSetFromFolderNode:
250
+ @classmethod
251
+ def INPUT_TYPES(s):
252
+ return {
253
+ "required": {
254
+ "folder": (folder_paths.get_input_subfolders(), {"tooltip": "The folder to load images from."}),
255
+ "clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}),
256
+ },
257
+ "optional": {
258
+ "resize_method": (
259
+ ["None", "Stretch", "Crop", "Pad"],
260
+ {"default": "None"},
261
+ ),
262
+ "width": (
263
+ IO.INT,
264
+ {
265
+ "default": -1,
266
+ "min": -1,
267
+ "max": 10000,
268
+ "step": 1,
269
+ "tooltip": "The width to resize the images to. -1 means use the original width.",
270
+ },
271
+ ),
272
+ "height": (
273
+ IO.INT,
274
+ {
275
+ "default": -1,
276
+ "min": -1,
277
+ "max": 10000,
278
+ "step": 1,
279
+ "tooltip": "The height to resize the images to. -1 means use the original height.",
280
+ },
281
+ )
282
+ },
283
+ }
284
+
285
+ RETURN_TYPES = ("IMAGE", IO.CONDITIONING,)
286
+ FUNCTION = "load_images"
287
+ CATEGORY = "loaders"
288
+ EXPERIMENTAL = True
289
+ DESCRIPTION = "Loads a batch of images and caption from a directory for training."
290
+
291
+ def load_images(self, folder, clip, resize_method, width=None, height=None):
292
+ if clip is None:
293
+ 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.")
294
+
295
+ logging.info(f"Loading images from folder: {folder}")
296
+
297
+ sub_input_dir = os.path.join(folder_paths.get_input_directory(), folder)
298
+ valid_extensions = [".png", ".jpg", ".jpeg", ".webp"]
299
+
300
+ image_files = []
301
+ for item in os.listdir(sub_input_dir):
302
+ path = os.path.join(sub_input_dir, item)
303
+ if any(item.lower().endswith(ext) for ext in valid_extensions):
304
+ image_files.append(path)
305
+ elif os.path.isdir(path):
306
+ # Support kohya-ss/sd-scripts folder structure
307
+ repeat = 1
308
+ if item.split("_")[0].isdigit():
309
+ repeat = int(item.split("_")[0])
310
+ image_files.extend([
311
+ os.path.join(path, f) for f in os.listdir(path) if any(f.lower().endswith(ext) for ext in valid_extensions)
312
+ ] * repeat)
313
+
314
+ caption_file_path = [
315
+ f.replace(os.path.splitext(f)[1], ".txt")
316
+ for f in image_files
317
+ ]
318
+ captions = []
319
+ for caption_file in caption_file_path:
320
+ caption_path = os.path.join(sub_input_dir, caption_file)
321
+ if os.path.exists(caption_path):
322
+ with open(caption_path, "r", encoding="utf-8") as f:
323
+ caption = f.read().strip()
324
+ captions.append(caption)
325
+ else:
326
+ captions.append("")
327
+
328
+ width = width if width != -1 else None
329
+ height = height if height != -1 else None
330
+ output_tensor = load_and_process_images(image_files, sub_input_dir, resize_method, width, height)
331
+
332
+ logging.info(f"Loaded {len(output_tensor)} images from {sub_input_dir}.")
333
+
334
+ logging.info(f"Encoding captions from {sub_input_dir}.")
335
+ conditions = []
336
+ empty_cond = clip.encode_from_tokens_scheduled(clip.tokenize(""))
337
+ for text in captions:
338
+ if text == "":
339
+ conditions.append(empty_cond)
340
+ tokens = clip.tokenize(text)
341
+ conditions.extend(clip.encode_from_tokens_scheduled(tokens))
342
+ logging.info(f"Encoded {len(conditions)} captions from {sub_input_dir}.")
343
+ return (output_tensor, conditions)
344
+
345
+
346
+ def draw_loss_graph(loss_map, steps):
347
+ width, height = 500, 300
348
+ img = Image.new("RGB", (width, height), "white")
349
+ draw = ImageDraw.Draw(img)
350
+
351
+ min_loss, max_loss = min(loss_map.values()), max(loss_map.values())
352
+ scaled_loss = [(l - min_loss) / (max_loss - min_loss) for l in loss_map.values()]
353
+
354
+ prev_point = (0, height - int(scaled_loss[0] * height))
355
+ for i, l in enumerate(scaled_loss[1:], start=1):
356
+ x = int(i / (steps - 1) * width)
357
+ y = height - int(l * height)
358
+ draw.line([prev_point, (x, y)], fill="blue", width=2)
359
+ prev_point = (x, y)
360
+
361
+ return img
362
+
363
+
364
+ def find_all_highest_child_module_with_forward(model: torch.nn.Module, result = None, name = None):
365
+ if result is None:
366
+ result = []
367
+ elif hasattr(model, "forward") and not isinstance(model, (torch.nn.ModuleList, torch.nn.Sequential, torch.nn.ModuleDict)):
368
+ result.append(model)
369
+ logging.debug(f"Found module with forward: {name} ({model.__class__.__name__})")
370
+ return result
371
+ name = name or "root"
372
+ for next_name, child in model.named_children():
373
+ find_all_highest_child_module_with_forward(child, result, f"{name}.{next_name}")
374
+ return result
375
+
376
+
377
+ def patch(m):
378
+ if not hasattr(m, "forward"):
379
+ return
380
+ org_forward = m.forward
381
+ def fwd(args, kwargs):
382
+ return org_forward(*args, **kwargs)
383
+ def checkpointing_fwd(*args, **kwargs):
384
+ return torch.utils.checkpoint.checkpoint(
385
+ fwd, args, kwargs, use_reentrant=False
386
+ )
387
+ m.org_forward = org_forward
388
+ m.forward = checkpointing_fwd
389
+
390
+
391
+ def unpatch(m):
392
+ if hasattr(m, "org_forward"):
393
+ m.forward = m.org_forward
394
+ del m.org_forward
395
+
396
+
397
+ class TrainLoraNode:
398
+ @classmethod
399
+ def INPUT_TYPES(s):
400
+ return {
401
+ "required": {
402
+ "model": (IO.MODEL, {"tooltip": "The model to train the LoRA on."}),
403
+ "latents": (
404
+ "LATENT",
405
+ {
406
+ "tooltip": "The Latents to use for training, serve as dataset/input of the model."
407
+ },
408
+ ),
409
+ "positive": (
410
+ IO.CONDITIONING,
411
+ {"tooltip": "The positive conditioning to use for training."},
412
+ ),
413
+ "batch_size": (
414
+ IO.INT,
415
+ {
416
+ "default": 1,
417
+ "min": 1,
418
+ "max": 10000,
419
+ "step": 1,
420
+ "tooltip": "The batch size to use for training.",
421
+ },
422
+ ),
423
+ "grad_accumulation_steps": (
424
+ IO.INT,
425
+ {
426
+ "default": 1,
427
+ "min": 1,
428
+ "max": 1024,
429
+ "step": 1,
430
+ "tooltip": "The number of gradient accumulation steps to use for training.",
431
+ }
432
+ ),
433
+ "steps": (
434
+ IO.INT,
435
+ {
436
+ "default": 16,
437
+ "min": 1,
438
+ "max": 100000,
439
+ "tooltip": "The number of steps to train the LoRA for.",
440
+ },
441
+ ),
442
+ "learning_rate": (
443
+ IO.FLOAT,
444
+ {
445
+ "default": 0.0005,
446
+ "min": 0.0000001,
447
+ "max": 1.0,
448
+ "step": 0.000001,
449
+ "tooltip": "The learning rate to use for training.",
450
+ },
451
+ ),
452
+ "rank": (
453
+ IO.INT,
454
+ {
455
+ "default": 8,
456
+ "min": 1,
457
+ "max": 128,
458
+ "tooltip": "The rank of the LoRA layers.",
459
+ },
460
+ ),
461
+ "optimizer": (
462
+ ["AdamW", "Adam", "SGD", "RMSprop"],
463
+ {
464
+ "default": "AdamW",
465
+ "tooltip": "The optimizer to use for training.",
466
+ },
467
+ ),
468
+ "loss_function": (
469
+ ["MSE", "L1", "Huber", "SmoothL1"],
470
+ {
471
+ "default": "MSE",
472
+ "tooltip": "The loss function to use for training.",
473
+ },
474
+ ),
475
+ "seed": (
476
+ IO.INT,
477
+ {
478
+ "default": 0,
479
+ "min": 0,
480
+ "max": 0xFFFFFFFFFFFFFFFF,
481
+ "tooltip": "The seed to use for training (used in generator for LoRA weight initialization and noise sampling)",
482
+ },
483
+ ),
484
+ "training_dtype": (
485
+ ["bf16", "fp32"],
486
+ {"default": "bf16", "tooltip": "The dtype to use for training."},
487
+ ),
488
+ "lora_dtype": (
489
+ ["bf16", "fp32"],
490
+ {"default": "bf16", "tooltip": "The dtype to use for lora."},
491
+ ),
492
+ "algorithm": (
493
+ list(adapter_maps.keys()),
494
+ {"default": list(adapter_maps.keys())[0], "tooltip": "The algorithm to use for training."},
495
+ ),
496
+ "gradient_checkpointing": (
497
+ IO.BOOLEAN,
498
+ {
499
+ "default": True,
500
+ "tooltip": "Use gradient checkpointing for training.",
501
+ }
502
+ ),
503
+ "existing_lora": (
504
+ folder_paths.get_filename_list("loras") + ["[None]"],
505
+ {
506
+ "default": "[None]",
507
+ "tooltip": "The existing LoRA to append to. Set to None for new LoRA.",
508
+ },
509
+ ),
510
+ },
511
+ }
512
+
513
+ RETURN_TYPES = (IO.MODEL, IO.LORA_MODEL, IO.LOSS_MAP, IO.INT)
514
+ RETURN_NAMES = ("model_with_lora", "lora", "loss", "steps")
515
+ FUNCTION = "train"
516
+ CATEGORY = "training"
517
+ EXPERIMENTAL = True
518
+
519
+ def train(
520
+ self,
521
+ model,
522
+ latents,
523
+ positive,
524
+ batch_size,
525
+ steps,
526
+ grad_accumulation_steps,
527
+ learning_rate,
528
+ rank,
529
+ optimizer,
530
+ loss_function,
531
+ seed,
532
+ training_dtype,
533
+ lora_dtype,
534
+ algorithm,
535
+ gradient_checkpointing,
536
+ existing_lora,
537
+ ):
538
+ mp = model.clone()
539
+ dtype = node_helpers.string_to_torch_dtype(training_dtype)
540
+ lora_dtype = node_helpers.string_to_torch_dtype(lora_dtype)
541
+ mp.set_model_compute_dtype(dtype)
542
+
543
+ latents = latents["samples"].to(dtype)
544
+ num_images = latents.shape[0]
545
+ logging.info(f"Total Images: {num_images}, Total Captions: {len(positive)}")
546
+ if len(positive) == 1 and num_images > 1:
547
+ positive = positive * num_images
548
+ elif len(positive) != num_images:
549
+ raise ValueError(
550
+ f"Number of positive conditions ({len(positive)}) does not match number of images ({num_images})."
551
+ )
552
+
553
+ with torch.inference_mode(False):
554
+ lora_sd = {}
555
+ generator = torch.Generator()
556
+ generator.manual_seed(seed)
557
+
558
+ # Load existing LoRA weights if provided
559
+ existing_weights = {}
560
+ existing_steps = 0
561
+ if existing_lora != "[None]":
562
+ lora_path = folder_paths.get_full_path_or_raise("loras", existing_lora)
563
+ # Extract steps from filename like "trained_lora_10_steps_20250225_203716"
564
+ existing_steps = int(existing_lora.split("_steps_")[0].split("_")[-1])
565
+ if lora_path:
566
+ existing_weights = comfy.utils.load_torch_file(lora_path)
567
+
568
+ all_weight_adapters = []
569
+ for n, m in mp.model.named_modules():
570
+ if hasattr(m, "weight_function"):
571
+ if m.weight is not None:
572
+ key = "{}.weight".format(n)
573
+ shape = m.weight.shape
574
+ if len(shape) >= 2:
575
+ alpha = float(existing_weights.get(f"{key}.alpha", 1.0))
576
+ dora_scale = existing_weights.get(
577
+ f"{key}.dora_scale", None
578
+ )
579
+ for adapter_cls in adapters:
580
+ existing_adapter = adapter_cls.load(
581
+ n, existing_weights, alpha, dora_scale
582
+ )
583
+ if existing_adapter is not None:
584
+ break
585
+ else:
586
+ existing_adapter = None
587
+ adapter_cls = adapter_maps[algorithm]
588
+
589
+ if existing_adapter is not None:
590
+ train_adapter = existing_adapter.to_train().to(lora_dtype)
591
+ else:
592
+ # Use LoRA with alpha=1.0 by default
593
+ train_adapter = adapter_cls.create_train(
594
+ m.weight, rank=rank, alpha=1.0
595
+ ).to(lora_dtype)
596
+ for name, parameter in train_adapter.named_parameters():
597
+ lora_sd[f"{n}.{name}"] = parameter
598
+
599
+ mp.add_weight_wrapper(key, train_adapter)
600
+ all_weight_adapters.append(train_adapter)
601
+ else:
602
+ diff = torch.nn.Parameter(
603
+ torch.zeros(
604
+ m.weight.shape, dtype=lora_dtype, requires_grad=True
605
+ )
606
+ )
607
+ diff_module = BiasDiff(diff)
608
+ mp.add_weight_wrapper(key, BiasDiff(diff))
609
+ all_weight_adapters.append(diff_module)
610
+ lora_sd["{}.diff".format(n)] = diff
611
+ if hasattr(m, "bias") and m.bias is not None:
612
+ key = "{}.bias".format(n)
613
+ bias = torch.nn.Parameter(
614
+ torch.zeros(m.bias.shape, dtype=lora_dtype, requires_grad=True)
615
+ )
616
+ bias_module = BiasDiff(bias)
617
+ lora_sd["{}.diff_b".format(n)] = bias
618
+ mp.add_weight_wrapper(key, BiasDiff(bias))
619
+ all_weight_adapters.append(bias_module)
620
+
621
+ if optimizer == "Adam":
622
+ optimizer = torch.optim.Adam(lora_sd.values(), lr=learning_rate)
623
+ elif optimizer == "AdamW":
624
+ optimizer = torch.optim.AdamW(lora_sd.values(), lr=learning_rate)
625
+ elif optimizer == "SGD":
626
+ optimizer = torch.optim.SGD(lora_sd.values(), lr=learning_rate)
627
+ elif optimizer == "RMSprop":
628
+ optimizer = torch.optim.RMSprop(lora_sd.values(), lr=learning_rate)
629
+
630
+ # Setup loss function based on selection
631
+ if loss_function == "MSE":
632
+ criterion = torch.nn.MSELoss()
633
+ elif loss_function == "L1":
634
+ criterion = torch.nn.L1Loss()
635
+ elif loss_function == "Huber":
636
+ criterion = torch.nn.HuberLoss()
637
+ elif loss_function == "SmoothL1":
638
+ criterion = torch.nn.SmoothL1Loss()
639
+
640
+ # setup models
641
+ if gradient_checkpointing:
642
+ for m in find_all_highest_child_module_with_forward(mp.model.diffusion_model):
643
+ patch(m)
644
+ mp.model.requires_grad_(False)
645
+ comfy.model_management.load_models_gpu([mp], memory_required=1e20, force_full_load=True)
646
+
647
+ # Setup sampler and guider like in test script
648
+ loss_map = {"loss": []}
649
+ def loss_callback(loss):
650
+ loss_map["loss"].append(loss)
651
+ train_sampler = TrainSampler(
652
+ criterion,
653
+ optimizer,
654
+ loss_callback=loss_callback,
655
+ batch_size=batch_size,
656
+ grad_acc=grad_accumulation_steps,
657
+ total_steps=steps*grad_accumulation_steps,
658
+ seed=seed,
659
+ training_dtype=dtype
660
+ )
661
+ guider = comfy_extras.nodes_custom_sampler.Guider_Basic(mp)
662
+ guider.set_conds(positive) # Set conditioning from input
663
+
664
+ # Training loop
665
+ try:
666
+ # Generate dummy sigmas and noise
667
+ sigmas = torch.tensor(range(num_images))
668
+ noise = comfy_extras.nodes_custom_sampler.Noise_RandomNoise(seed)
669
+ guider.sample(
670
+ noise.generate_noise({"samples": latents}),
671
+ latents,
672
+ train_sampler,
673
+ sigmas,
674
+ seed=noise.seed
675
+ )
676
+ finally:
677
+ for m in mp.model.modules():
678
+ unpatch(m)
679
+ del train_sampler, optimizer
680
+
681
+ for adapter in all_weight_adapters:
682
+ adapter.requires_grad_(False)
683
+
684
+ for param in lora_sd:
685
+ lora_sd[param] = lora_sd[param].to(lora_dtype)
686
+
687
+ return (mp, lora_sd, loss_map, steps + existing_steps)
688
+
689
+
690
+ class LoraModelLoader:
691
+ def __init__(self):
692
+ self.loaded_lora = None
693
+
694
+ @classmethod
695
+ def INPUT_TYPES(s):
696
+ return {
697
+ "required": {
698
+ "model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}),
699
+ "lora": (IO.LORA_MODEL, {"tooltip": "The LoRA model to apply to the diffusion model."}),
700
+ "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."}),
701
+ }
702
+ }
703
+
704
+ RETURN_TYPES = ("MODEL",)
705
+ OUTPUT_TOOLTIPS = ("The modified diffusion model.",)
706
+ FUNCTION = "load_lora_model"
707
+
708
+ CATEGORY = "loaders"
709
+ DESCRIPTION = "Load Trained LoRA weights from Train LoRA node."
710
+ EXPERIMENTAL = True
711
+
712
+ def load_lora_model(self, model, lora, strength_model):
713
+ if strength_model == 0:
714
+ return (model, )
715
+
716
+ model_lora, _ = comfy.sd.load_lora_for_models(model, None, lora, strength_model, 0)
717
+ return (model_lora, )
718
+
719
+
720
+ class SaveLoRA:
721
+ def __init__(self):
722
+ self.output_dir = folder_paths.get_output_directory()
723
+
724
+ @classmethod
725
+ def INPUT_TYPES(s):
726
+ return {
727
+ "required": {
728
+ "lora": (
729
+ IO.LORA_MODEL,
730
+ {
731
+ "tooltip": "The LoRA model to save. Do not use the model with LoRA layers."
732
+ },
733
+ ),
734
+ "prefix": (
735
+ "STRING",
736
+ {
737
+ "default": "loras/ComfyUI_trained_lora",
738
+ "tooltip": "The prefix to use for the saved LoRA file.",
739
+ },
740
+ ),
741
+ },
742
+ "optional": {
743
+ "steps": (
744
+ IO.INT,
745
+ {
746
+ "forceInput": True,
747
+ "tooltip": "Optional: The number of steps to LoRA has been trained for, used to name the saved file.",
748
+ },
749
+ ),
750
+ },
751
+ }
752
+
753
+ RETURN_TYPES = ()
754
+ FUNCTION = "save"
755
+ CATEGORY = "loaders"
756
+ EXPERIMENTAL = True
757
+ OUTPUT_NODE = True
758
+
759
+ def save(self, lora, prefix, steps=None):
760
+ full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(prefix, self.output_dir)
761
+ if steps is None:
762
+ output_checkpoint = f"{filename}_{counter:05}_.safetensors"
763
+ else:
764
+ output_checkpoint = f"{filename}_{steps}_steps_{counter:05}_.safetensors"
765
+ output_checkpoint = os.path.join(full_output_folder, output_checkpoint)
766
+ safetensors.torch.save_file(lora, output_checkpoint)
767
+ return {}
768
+
769
+
770
+ class LossGraphNode:
771
+ def __init__(self):
772
+ self.output_dir = folder_paths.get_temp_directory()
773
+
774
+ @classmethod
775
+ def INPUT_TYPES(s):
776
+ return {
777
+ "required": {
778
+ "loss": (IO.LOSS_MAP, {"default": {}}),
779
+ "filename_prefix": (IO.STRING, {"default": "loss_graph"}),
780
+ },
781
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
782
+ }
783
+
784
+ RETURN_TYPES = ()
785
+ FUNCTION = "plot_loss"
786
+ OUTPUT_NODE = True
787
+ CATEGORY = "training"
788
+ EXPERIMENTAL = True
789
+ DESCRIPTION = "Plots the loss graph and saves it to the output directory."
790
+
791
+ def plot_loss(self, loss, filename_prefix, prompt=None, extra_pnginfo=None):
792
+ loss_values = loss["loss"]
793
+ width, height = 800, 480
794
+ margin = 40
795
+
796
+ img = Image.new(
797
+ "RGB", (width + margin, height + margin), "white"
798
+ ) # Extend canvas
799
+ draw = ImageDraw.Draw(img)
800
+
801
+ min_loss, max_loss = min(loss_values), max(loss_values)
802
+ scaled_loss = [(l - min_loss) / (max_loss - min_loss) for l in loss_values]
803
+
804
+ steps = len(loss_values)
805
+
806
+ prev_point = (margin, height - int(scaled_loss[0] * height))
807
+ for i, l in enumerate(scaled_loss[1:], start=1):
808
+ x = margin + int(i / steps * width) # Scale X properly
809
+ y = height - int(l * height)
810
+ draw.line([prev_point, (x, y)], fill="blue", width=2)
811
+ prev_point = (x, y)
812
+
813
+ draw.line([(margin, 0), (margin, height)], fill="black", width=2) # Y-axis
814
+ draw.line(
815
+ [(margin, height), (width + margin, height)], fill="black", width=2
816
+ ) # X-axis
817
+
818
+ font = None
819
+ try:
820
+ font = ImageFont.truetype("arial.ttf", 12)
821
+ except IOError:
822
+ font = ImageFont.load_default()
823
+
824
+ # Add axis labels
825
+ draw.text((5, height // 2), "Loss", font=font, fill="black")
826
+ draw.text((width // 2, height + 10), "Steps", font=font, fill="black")
827
+
828
+ # Add min/max loss values
829
+ draw.text((margin - 30, 0), f"{max_loss:.2f}", font=font, fill="black")
830
+ draw.text(
831
+ (margin - 30, height - 10), f"{min_loss:.2f}", font=font, fill="black"
832
+ )
833
+
834
+ metadata = None
835
+ if not args.disable_metadata:
836
+ metadata = PngInfo()
837
+ if prompt is not None:
838
+ metadata.add_text("prompt", json.dumps(prompt))
839
+ if extra_pnginfo is not None:
840
+ for x in extra_pnginfo:
841
+ metadata.add_text(x, json.dumps(extra_pnginfo[x]))
842
+
843
+ date = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
844
+ img.save(
845
+ os.path.join(self.output_dir, f"{filename_prefix}_{date}.png"),
846
+ pnginfo=metadata,
847
+ )
848
+ return {
849
+ "ui": {
850
+ "images": [
851
+ {
852
+ "filename": f"{filename_prefix}_{date}.png",
853
+ "subfolder": "",
854
+ "type": "temp",
855
+ }
856
+ ]
857
+ }
858
+ }
859
+
860
+
861
+ NODE_CLASS_MAPPINGS = {
862
+ "TrainLoraNode": TrainLoraNode,
863
+ "SaveLoRANode": SaveLoRA,
864
+ "LoraModelLoader": LoraModelLoader,
865
+ "LoadImageSetFromFolderNode": LoadImageSetFromFolderNode,
866
+ "LoadImageTextSetFromFolderNode": LoadImageTextSetFromFolderNode,
867
+ "LossGraphNode": LossGraphNode,
868
+ }
869
+
870
+ NODE_DISPLAY_NAME_MAPPINGS = {
871
+ "TrainLoraNode": "Train LoRA",
872
+ "SaveLoRANode": "Save LoRA Weights",
873
+ "LoraModelLoader": "Load LoRA Model",
874
+ "LoadImageSetFromFolderNode": "Load Image Dataset from Folder",
875
+ "LoadImageTextSetFromFolderNode": "Load Image and Text Dataset from Folder",
876
+ "LossGraphNode": "Plot Loss Graph",
877
+ }
ComfyUI/comfy_extras/nodes_video.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import av
5
+ import torch
6
+ import folder_paths
7
+ import json
8
+ from typing import Optional, Literal
9
+ from fractions import Fraction
10
+ from comfy.comfy_types import IO, FileLocator, ComfyNodeABC
11
+ from comfy_api.input import ImageInput, AudioInput, VideoInput
12
+ from comfy_api.util import VideoContainer, VideoCodec, VideoComponents
13
+ from comfy_api.input_impl import VideoFromFile, VideoFromComponents
14
+ from comfy.cli_args import args
15
+
16
+ class SaveWEBM:
17
+ def __init__(self):
18
+ self.output_dir = folder_paths.get_output_directory()
19
+ self.type = "output"
20
+ self.prefix_append = ""
21
+
22
+ @classmethod
23
+ def INPUT_TYPES(s):
24
+ return {"required":
25
+ {"images": ("IMAGE", ),
26
+ "filename_prefix": ("STRING", {"default": "ComfyUI"}),
27
+ "codec": (["vp9", "av1"],),
28
+ "fps": ("FLOAT", {"default": 24.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
29
+ "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."}),
30
+ },
31
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
32
+ }
33
+
34
+ RETURN_TYPES = ()
35
+ FUNCTION = "save_images"
36
+
37
+ OUTPUT_NODE = True
38
+
39
+ CATEGORY = "image/video"
40
+
41
+ EXPERIMENTAL = True
42
+
43
+ def save_images(self, images, codec, fps, filename_prefix, crf, prompt=None, extra_pnginfo=None):
44
+ filename_prefix += self.prefix_append
45
+ 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])
46
+
47
+ file = f"{filename}_{counter:05}_.webm"
48
+ container = av.open(os.path.join(full_output_folder, file), mode="w")
49
+
50
+ if prompt is not None:
51
+ container.metadata["prompt"] = json.dumps(prompt)
52
+
53
+ if extra_pnginfo is not None:
54
+ for x in extra_pnginfo:
55
+ container.metadata[x] = json.dumps(extra_pnginfo[x])
56
+
57
+ codec_map = {"vp9": "libvpx-vp9", "av1": "libsvtav1"}
58
+ stream = container.add_stream(codec_map[codec], rate=Fraction(round(fps * 1000), 1000))
59
+ stream.width = images.shape[-2]
60
+ stream.height = images.shape[-3]
61
+ stream.pix_fmt = "yuv420p10le" if codec == "av1" else "yuv420p"
62
+ stream.bit_rate = 0
63
+ stream.options = {'crf': str(crf)}
64
+ if codec == "av1":
65
+ stream.options["preset"] = "6"
66
+
67
+ for frame in images:
68
+ 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")
69
+ for packet in stream.encode(frame):
70
+ container.mux(packet)
71
+ container.mux(stream.encode())
72
+ container.close()
73
+
74
+ results: list[FileLocator] = [{
75
+ "filename": file,
76
+ "subfolder": subfolder,
77
+ "type": self.type
78
+ }]
79
+
80
+ return {"ui": {"images": results, "animated": (True,)}} # TODO: frontend side
81
+
82
+ class SaveVideo(ComfyNodeABC):
83
+ def __init__(self):
84
+ self.output_dir = folder_paths.get_output_directory()
85
+ self.type: Literal["output"] = "output"
86
+ self.prefix_append = ""
87
+
88
+ @classmethod
89
+ def INPUT_TYPES(cls):
90
+ return {
91
+ "required": {
92
+ "video": (IO.VIDEO, {"tooltip": "The video to save."}),
93
+ "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."}),
94
+ "format": (VideoContainer.as_input(), {"default": "auto", "tooltip": "The format to save the video as."}),
95
+ "codec": (VideoCodec.as_input(), {"default": "auto", "tooltip": "The codec to use for the video."}),
96
+ },
97
+ "hidden": {
98
+ "prompt": "PROMPT",
99
+ "extra_pnginfo": "EXTRA_PNGINFO"
100
+ },
101
+ }
102
+
103
+ RETURN_TYPES = ()
104
+ FUNCTION = "save_video"
105
+
106
+ OUTPUT_NODE = True
107
+
108
+ CATEGORY = "image/video"
109
+ DESCRIPTION = "Saves the input images to your ComfyUI output directory."
110
+
111
+ def save_video(self, video: VideoInput, filename_prefix, format, codec, prompt=None, extra_pnginfo=None):
112
+ filename_prefix += self.prefix_append
113
+ width, height = video.get_dimensions()
114
+ full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
115
+ filename_prefix,
116
+ self.output_dir,
117
+ width,
118
+ height
119
+ )
120
+ results: list[FileLocator] = list()
121
+ saved_metadata = None
122
+ if not args.disable_metadata:
123
+ metadata = {}
124
+ if extra_pnginfo is not None:
125
+ metadata.update(extra_pnginfo)
126
+ if prompt is not None:
127
+ metadata["prompt"] = prompt
128
+ if len(metadata) > 0:
129
+ saved_metadata = metadata
130
+ file = f"{filename}_{counter:05}_.{VideoContainer.get_extension(format)}"
131
+ video.save_to(
132
+ os.path.join(full_output_folder, file),
133
+ format=format,
134
+ codec=codec,
135
+ metadata=saved_metadata
136
+ )
137
+
138
+ results.append({
139
+ "filename": file,
140
+ "subfolder": subfolder,
141
+ "type": self.type
142
+ })
143
+ counter += 1
144
+
145
+ return { "ui": { "images": results, "animated": (True,) } }
146
+
147
+ class CreateVideo(ComfyNodeABC):
148
+ @classmethod
149
+ def INPUT_TYPES(cls):
150
+ return {
151
+ "required": {
152
+ "images": (IO.IMAGE, {"tooltip": "The images to create a video from."}),
153
+ "fps": ("FLOAT", {"default": 30.0, "min": 1.0, "max": 120.0, "step": 1.0}),
154
+ },
155
+ "optional": {
156
+ "audio": (IO.AUDIO, {"tooltip": "The audio to add to the video."}),
157
+ }
158
+ }
159
+
160
+ RETURN_TYPES = (IO.VIDEO,)
161
+ FUNCTION = "create_video"
162
+
163
+ CATEGORY = "image/video"
164
+ DESCRIPTION = "Create a video from images."
165
+
166
+ def create_video(self, images: ImageInput, fps: float, audio: Optional[AudioInput] = None):
167
+ return (VideoFromComponents(
168
+ VideoComponents(
169
+ images=images,
170
+ audio=audio,
171
+ frame_rate=Fraction(fps),
172
+ )
173
+ ),)
174
+
175
+ class GetVideoComponents(ComfyNodeABC):
176
+ @classmethod
177
+ def INPUT_TYPES(cls):
178
+ return {
179
+ "required": {
180
+ "video": (IO.VIDEO, {"tooltip": "The video to extract components from."}),
181
+ }
182
+ }
183
+ RETURN_TYPES = (IO.IMAGE, IO.AUDIO, IO.FLOAT)
184
+ RETURN_NAMES = ("images", "audio", "fps")
185
+ FUNCTION = "get_components"
186
+
187
+ CATEGORY = "image/video"
188
+ DESCRIPTION = "Extracts all components from a video: frames, audio, and framerate."
189
+
190
+ def get_components(self, video: VideoInput):
191
+ components = video.get_components()
192
+
193
+ return (components.images, components.audio, float(components.frame_rate))
194
+
195
+ class LoadVideo(ComfyNodeABC):
196
+ @classmethod
197
+ def INPUT_TYPES(cls):
198
+ input_dir = folder_paths.get_input_directory()
199
+ files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
200
+ files = folder_paths.filter_files_content_types(files, ["video"])
201
+ return {"required":
202
+ {"file": (sorted(files), {"video_upload": True})},
203
+ }
204
+
205
+ CATEGORY = "image/video"
206
+
207
+ RETURN_TYPES = (IO.VIDEO,)
208
+ FUNCTION = "load_video"
209
+ def load_video(self, file):
210
+ video_path = folder_paths.get_annotated_filepath(file)
211
+ return (VideoFromFile(video_path),)
212
+
213
+ @classmethod
214
+ def IS_CHANGED(cls, file):
215
+ video_path = folder_paths.get_annotated_filepath(file)
216
+ mod_time = os.path.getmtime(video_path)
217
+ # Instead of hashing the file, we can just use the modification time to avoid
218
+ # rehashing large files.
219
+ return mod_time
220
+
221
+ @classmethod
222
+ def VALIDATE_INPUTS(cls, file):
223
+ if not folder_paths.exists_annotated_filepath(file):
224
+ return "Invalid video file: {}".format(file)
225
+
226
+ return True
227
+
228
+ NODE_CLASS_MAPPINGS = {
229
+ "SaveWEBM": SaveWEBM,
230
+ "SaveVideo": SaveVideo,
231
+ "CreateVideo": CreateVideo,
232
+ "GetVideoComponents": GetVideoComponents,
233
+ "LoadVideo": LoadVideo,
234
+ }
235
+
236
+ NODE_DISPLAY_NAME_MAPPINGS = {
237
+ "SaveVideo": "Save Video",
238
+ "CreateVideo": "Create Video",
239
+ "GetVideoComponents": "Get Video Components",
240
+ "LoadVideo": "Load Video",
241
+ }
ComfyUI/comfy_extras/nodes_wan.py ADDED
@@ -0,0 +1,742 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import nodes
3
+ import node_helpers
4
+ import torch
5
+ import comfy.model_management
6
+ import comfy.utils
7
+ import comfy.latent_formats
8
+ import comfy.clip_vision
9
+ import json
10
+ import numpy as np
11
+ from typing import Tuple
12
+
13
+ class WanImageToVideo:
14
+ @classmethod
15
+ def INPUT_TYPES(s):
16
+ return {"required": {"positive": ("CONDITIONING", ),
17
+ "negative": ("CONDITIONING", ),
18
+ "vae": ("VAE", ),
19
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
20
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
21
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
22
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
23
+ },
24
+ "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ),
25
+ "start_image": ("IMAGE", ),
26
+ }}
27
+
28
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
29
+ RETURN_NAMES = ("positive", "negative", "latent")
30
+ FUNCTION = "encode"
31
+
32
+ CATEGORY = "conditioning/video_models"
33
+
34
+ def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, clip_vision_output=None):
35
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
36
+ if start_image is not None:
37
+ start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
38
+ image = torch.ones((length, height, width, start_image.shape[-1]), device=start_image.device, dtype=start_image.dtype) * 0.5
39
+ image[:start_image.shape[0]] = start_image
40
+
41
+ concat_latent_image = vae.encode(image[:, :, :, :3])
42
+ 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)
43
+ mask[:, :, :((start_image.shape[0] - 1) // 4) + 1] = 0.0
44
+
45
+ positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
46
+ negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
47
+
48
+ if clip_vision_output is not None:
49
+ positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output})
50
+ negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output})
51
+
52
+ out_latent = {}
53
+ out_latent["samples"] = latent
54
+ return (positive, negative, out_latent)
55
+
56
+
57
+ class WanFunControlToVideo:
58
+ @classmethod
59
+ def INPUT_TYPES(s):
60
+ return {"required": {"positive": ("CONDITIONING", ),
61
+ "negative": ("CONDITIONING", ),
62
+ "vae": ("VAE", ),
63
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
64
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
65
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
66
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
67
+ },
68
+ "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ),
69
+ "start_image": ("IMAGE", ),
70
+ "control_video": ("IMAGE", ),
71
+ }}
72
+
73
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
74
+ RETURN_NAMES = ("positive", "negative", "latent")
75
+ FUNCTION = "encode"
76
+
77
+ CATEGORY = "conditioning/video_models"
78
+
79
+ def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, clip_vision_output=None, control_video=None):
80
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
81
+ concat_latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
82
+ concat_latent = comfy.latent_formats.Wan21().process_out(concat_latent)
83
+ concat_latent = concat_latent.repeat(1, 2, 1, 1, 1)
84
+
85
+ if start_image is not None:
86
+ start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
87
+ concat_latent_image = vae.encode(start_image[:, :, :, :3])
88
+ concat_latent[:,16:,:concat_latent_image.shape[2]] = concat_latent_image[:,:,:concat_latent.shape[2]]
89
+
90
+ if control_video is not None:
91
+ control_video = comfy.utils.common_upscale(control_video[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
92
+ concat_latent_image = vae.encode(control_video[:, :, :, :3])
93
+ concat_latent[:,:16,:concat_latent_image.shape[2]] = concat_latent_image[:,:,:concat_latent.shape[2]]
94
+
95
+ positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent})
96
+ negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent})
97
+
98
+ if clip_vision_output is not None:
99
+ positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output})
100
+ negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output})
101
+
102
+ out_latent = {}
103
+ out_latent["samples"] = latent
104
+ return (positive, negative, out_latent)
105
+
106
+ class WanFirstLastFrameToVideo:
107
+ @classmethod
108
+ def INPUT_TYPES(s):
109
+ return {"required": {"positive": ("CONDITIONING", ),
110
+ "negative": ("CONDITIONING", ),
111
+ "vae": ("VAE", ),
112
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
113
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
114
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
115
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
116
+ },
117
+ "optional": {"clip_vision_start_image": ("CLIP_VISION_OUTPUT", ),
118
+ "clip_vision_end_image": ("CLIP_VISION_OUTPUT", ),
119
+ "start_image": ("IMAGE", ),
120
+ "end_image": ("IMAGE", ),
121
+ }}
122
+
123
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
124
+ RETURN_NAMES = ("positive", "negative", "latent")
125
+ FUNCTION = "encode"
126
+
127
+ CATEGORY = "conditioning/video_models"
128
+
129
+ 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):
130
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
131
+ if start_image is not None:
132
+ start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
133
+ if end_image is not None:
134
+ end_image = comfy.utils.common_upscale(end_image[-length:].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
135
+
136
+ image = torch.ones((length, height, width, 3)) * 0.5
137
+ mask = torch.ones((1, 1, latent.shape[2] * 4, latent.shape[-2], latent.shape[-1]))
138
+
139
+ if start_image is not None:
140
+ image[:start_image.shape[0]] = start_image
141
+ mask[:, :, :start_image.shape[0] + 3] = 0.0
142
+
143
+ if end_image is not None:
144
+ image[-end_image.shape[0]:] = end_image
145
+ mask[:, :, -end_image.shape[0]:] = 0.0
146
+
147
+ concat_latent_image = vae.encode(image[:, :, :, :3])
148
+ mask = mask.view(1, mask.shape[2] // 4, 4, mask.shape[3], mask.shape[4]).transpose(1, 2)
149
+ positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
150
+ negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
151
+
152
+ if clip_vision_start_image is not None:
153
+ clip_vision_output = clip_vision_start_image
154
+
155
+ if clip_vision_end_image is not None:
156
+ if clip_vision_output is not None:
157
+ states = torch.cat([clip_vision_output.penultimate_hidden_states, clip_vision_end_image.penultimate_hidden_states], dim=-2)
158
+ clip_vision_output = comfy.clip_vision.Output()
159
+ clip_vision_output.penultimate_hidden_states = states
160
+ else:
161
+ clip_vision_output = clip_vision_end_image
162
+
163
+ if clip_vision_output is not None:
164
+ positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output})
165
+ negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output})
166
+
167
+ out_latent = {}
168
+ out_latent["samples"] = latent
169
+ return (positive, negative, out_latent)
170
+
171
+
172
+ class WanFunInpaintToVideo:
173
+ @classmethod
174
+ def INPUT_TYPES(s):
175
+ return {"required": {"positive": ("CONDITIONING", ),
176
+ "negative": ("CONDITIONING", ),
177
+ "vae": ("VAE", ),
178
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
179
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
180
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
181
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
182
+ },
183
+ "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ),
184
+ "start_image": ("IMAGE", ),
185
+ "end_image": ("IMAGE", ),
186
+ }}
187
+
188
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
189
+ RETURN_NAMES = ("positive", "negative", "latent")
190
+ FUNCTION = "encode"
191
+
192
+ CATEGORY = "conditioning/video_models"
193
+
194
+ def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, end_image=None, clip_vision_output=None):
195
+ flfv = WanFirstLastFrameToVideo()
196
+ 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)
197
+
198
+
199
+ class WanVaceToVideo:
200
+ @classmethod
201
+ def INPUT_TYPES(s):
202
+ return {"required": {"positive": ("CONDITIONING", ),
203
+ "negative": ("CONDITIONING", ),
204
+ "vae": ("VAE", ),
205
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
206
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
207
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
208
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
209
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
210
+ },
211
+ "optional": {"control_video": ("IMAGE", ),
212
+ "control_masks": ("MASK", ),
213
+ "reference_image": ("IMAGE", ),
214
+ }}
215
+
216
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT", "INT")
217
+ RETURN_NAMES = ("positive", "negative", "latent", "trim_latent")
218
+ FUNCTION = "encode"
219
+
220
+ CATEGORY = "conditioning/video_models"
221
+
222
+ EXPERIMENTAL = True
223
+
224
+ def encode(self, positive, negative, vae, width, height, length, batch_size, strength, control_video=None, control_masks=None, reference_image=None):
225
+ latent_length = ((length - 1) // 4) + 1
226
+ if control_video is not None:
227
+ control_video = comfy.utils.common_upscale(control_video[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
228
+ if control_video.shape[0] < length:
229
+ control_video = torch.nn.functional.pad(control_video, (0, 0, 0, 0, 0, 0, 0, length - control_video.shape[0]), value=0.5)
230
+ else:
231
+ control_video = torch.ones((length, height, width, 3)) * 0.5
232
+
233
+ if reference_image is not None:
234
+ reference_image = comfy.utils.common_upscale(reference_image[:1].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
235
+ reference_image = vae.encode(reference_image[:, :, :, :3])
236
+ reference_image = torch.cat([reference_image, comfy.latent_formats.Wan21().process_out(torch.zeros_like(reference_image))], dim=1)
237
+
238
+ if control_masks is None:
239
+ mask = torch.ones((length, height, width, 1))
240
+ else:
241
+ mask = control_masks
242
+ if mask.ndim == 3:
243
+ mask = mask.unsqueeze(1)
244
+ mask = comfy.utils.common_upscale(mask[:length], width, height, "bilinear", "center").movedim(1, -1)
245
+ if mask.shape[0] < length:
246
+ mask = torch.nn.functional.pad(mask, (0, 0, 0, 0, 0, 0, 0, length - mask.shape[0]), value=1.0)
247
+
248
+ control_video = control_video - 0.5
249
+ inactive = (control_video * (1 - mask)) + 0.5
250
+ reactive = (control_video * mask) + 0.5
251
+
252
+ inactive = vae.encode(inactive[:, :, :, :3])
253
+ reactive = vae.encode(reactive[:, :, :, :3])
254
+ control_video_latent = torch.cat((inactive, reactive), dim=1)
255
+ if reference_image is not None:
256
+ control_video_latent = torch.cat((reference_image, control_video_latent), dim=2)
257
+
258
+ vae_stride = 8
259
+ height_mask = height // vae_stride
260
+ width_mask = width // vae_stride
261
+ mask = mask.view(length, height_mask, vae_stride, width_mask, vae_stride)
262
+ mask = mask.permute(2, 4, 0, 1, 3)
263
+ mask = mask.reshape(vae_stride * vae_stride, length, height_mask, width_mask)
264
+ mask = torch.nn.functional.interpolate(mask.unsqueeze(0), size=(latent_length, height_mask, width_mask), mode='nearest-exact').squeeze(0)
265
+
266
+ trim_latent = 0
267
+ if reference_image is not None:
268
+ mask_pad = torch.zeros_like(mask[:, :reference_image.shape[2], :, :])
269
+ mask = torch.cat((mask_pad, mask), dim=1)
270
+ latent_length += reference_image.shape[2]
271
+ trim_latent = reference_image.shape[2]
272
+
273
+ mask = mask.unsqueeze(0)
274
+
275
+ positive = node_helpers.conditioning_set_values(positive, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True)
276
+ negative = node_helpers.conditioning_set_values(negative, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True)
277
+
278
+ latent = torch.zeros([batch_size, 16, latent_length, height // 8, width // 8], device=comfy.model_management.intermediate_device())
279
+ out_latent = {}
280
+ out_latent["samples"] = latent
281
+ return (positive, negative, out_latent, trim_latent)
282
+
283
+ class TrimVideoLatent:
284
+ @classmethod
285
+ def INPUT_TYPES(s):
286
+ return {"required": { "samples": ("LATENT",),
287
+ "trim_amount": ("INT", {"default": 0, "min": 0, "max": 99999}),
288
+ }}
289
+
290
+ RETURN_TYPES = ("LATENT",)
291
+ FUNCTION = "op"
292
+
293
+ CATEGORY = "latent/video"
294
+
295
+ EXPERIMENTAL = True
296
+
297
+ def op(self, samples, trim_amount):
298
+ samples_out = samples.copy()
299
+
300
+ s1 = samples["samples"]
301
+ samples_out["samples"] = s1[:, :, trim_amount:]
302
+ return (samples_out,)
303
+
304
+ class WanCameraImageToVideo:
305
+ @classmethod
306
+ def INPUT_TYPES(s):
307
+ return {"required": {"positive": ("CONDITIONING", ),
308
+ "negative": ("CONDITIONING", ),
309
+ "vae": ("VAE", ),
310
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
311
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
312
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
313
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
314
+ },
315
+ "optional": {"clip_vision_output": ("CLIP_VISION_OUTPUT", ),
316
+ "start_image": ("IMAGE", ),
317
+ "camera_conditions": ("WAN_CAMERA_EMBEDDING", ),
318
+ }}
319
+
320
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
321
+ RETURN_NAMES = ("positive", "negative", "latent")
322
+ FUNCTION = "encode"
323
+
324
+ CATEGORY = "conditioning/video_models"
325
+
326
+ def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, clip_vision_output=None, camera_conditions=None):
327
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
328
+ concat_latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
329
+ concat_latent = comfy.latent_formats.Wan21().process_out(concat_latent)
330
+
331
+ if start_image is not None:
332
+ start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
333
+ concat_latent_image = vae.encode(start_image[:, :, :, :3])
334
+ concat_latent[:,:,:concat_latent_image.shape[2]] = concat_latent_image[:,:,:concat_latent.shape[2]]
335
+
336
+ positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent})
337
+ negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent})
338
+
339
+ if camera_conditions is not None:
340
+ positive = node_helpers.conditioning_set_values(positive, {'camera_conditions': camera_conditions})
341
+ negative = node_helpers.conditioning_set_values(negative, {'camera_conditions': camera_conditions})
342
+
343
+ if clip_vision_output is not None:
344
+ positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output})
345
+ negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output})
346
+
347
+ out_latent = {}
348
+ out_latent["samples"] = latent
349
+ return (positive, negative, out_latent)
350
+
351
+ class WanPhantomSubjectToVideo:
352
+ @classmethod
353
+ def INPUT_TYPES(s):
354
+ return {"required": {"positive": ("CONDITIONING", ),
355
+ "negative": ("CONDITIONING", ),
356
+ "vae": ("VAE", ),
357
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
358
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
359
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
360
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
361
+ },
362
+ "optional": {"images": ("IMAGE", ),
363
+ }}
364
+
365
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "CONDITIONING", "LATENT")
366
+ RETURN_NAMES = ("positive", "negative_text", "negative_img_text", "latent")
367
+ FUNCTION = "encode"
368
+
369
+ CATEGORY = "conditioning/video_models"
370
+
371
+ def encode(self, positive, negative, vae, width, height, length, batch_size, images):
372
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
373
+ cond2 = negative
374
+ if images is not None:
375
+ images = comfy.utils.common_upscale(images[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
376
+ latent_images = []
377
+ for i in images:
378
+ latent_images += [vae.encode(i.unsqueeze(0)[:, :, :, :3])]
379
+ concat_latent_image = torch.cat(latent_images, dim=2)
380
+
381
+ positive = node_helpers.conditioning_set_values(positive, {"time_dim_concat": concat_latent_image})
382
+ cond2 = node_helpers.conditioning_set_values(negative, {"time_dim_concat": concat_latent_image})
383
+ negative = node_helpers.conditioning_set_values(negative, {"time_dim_concat": comfy.latent_formats.Wan21().process_out(torch.zeros_like(concat_latent_image))})
384
+
385
+ out_latent = {}
386
+ out_latent["samples"] = latent
387
+ return (positive, cond2, negative, out_latent)
388
+
389
+ def parse_json_tracks(tracks):
390
+ """Parse JSON track data into a standardized format"""
391
+ tracks_data = []
392
+ try:
393
+ # If tracks is a string, try to parse it as JSON
394
+ if isinstance(tracks, str):
395
+ parsed = json.loads(tracks.replace("'", '"'))
396
+ tracks_data.extend(parsed)
397
+ else:
398
+ # If tracks is a list of strings, parse each one
399
+ for track_str in tracks:
400
+ parsed = json.loads(track_str.replace("'", '"'))
401
+ tracks_data.append(parsed)
402
+
403
+ # Check if we have a single track (dict with x,y) or a list of tracks
404
+ if tracks_data and isinstance(tracks_data[0], dict) and 'x' in tracks_data[0]:
405
+ # Single track detected, wrap it in a list
406
+ tracks_data = [tracks_data]
407
+ elif tracks_data and isinstance(tracks_data[0], list) and tracks_data[0] and isinstance(tracks_data[0][0], dict) and 'x' in tracks_data[0][0]:
408
+ # Already a list of tracks, nothing to do
409
+ pass
410
+ else:
411
+ # Unexpected format
412
+ pass
413
+
414
+ except json.JSONDecodeError:
415
+ tracks_data = []
416
+ return tracks_data
417
+
418
+ def process_tracks(tracks_np: np.ndarray, frame_size: Tuple[int, int], num_frames, quant_multi: int = 8, **kwargs):
419
+ # tracks: shape [t, h, w, 3] => samples align with 24 fps, model trained with 16 fps.
420
+ # frame_size: tuple (W, H)
421
+ tracks = torch.from_numpy(tracks_np).float()
422
+
423
+ if tracks.shape[1] == 121:
424
+ tracks = torch.permute(tracks, (1, 0, 2, 3))
425
+
426
+ tracks, visibles = tracks[..., :2], tracks[..., 2:3]
427
+
428
+ short_edge = min(*frame_size)
429
+
430
+ frame_center = torch.tensor([*frame_size]).type_as(tracks) / 2
431
+ tracks = tracks - frame_center
432
+
433
+ tracks = tracks / short_edge * 2
434
+
435
+ visibles = visibles * 2 - 1
436
+
437
+ trange = torch.linspace(-1, 1, tracks.shape[0]).view(-1, 1, 1, 1).expand(*visibles.shape)
438
+
439
+ out_ = torch.cat([trange, tracks, visibles], dim=-1).view(121, -1, 4)
440
+
441
+ out_0 = out_[:1]
442
+
443
+ out_l = out_[1:] # 121 => 120 | 1
444
+ a = 120 // math.gcd(120, num_frames)
445
+ b = num_frames // math.gcd(120, num_frames)
446
+ out_l = torch.repeat_interleave(out_l, b, dim=0)[1::a] # 120 => 120 * b => 120 * b / a == F
447
+
448
+ final_result = torch.cat([out_0, out_l], dim=0)
449
+
450
+ return final_result
451
+
452
+ FIXED_LENGTH = 121
453
+ def pad_pts(tr):
454
+ """Convert list of {x,y} to (FIXED_LENGTH,1,3) array, padding/truncating."""
455
+ pts = np.array([[p['x'], p['y'], 1] for p in tr], dtype=np.float32)
456
+ n = pts.shape[0]
457
+ if n < FIXED_LENGTH:
458
+ pad = np.zeros((FIXED_LENGTH - n, 3), dtype=np.float32)
459
+ pts = np.vstack((pts, pad))
460
+ else:
461
+ pts = pts[:FIXED_LENGTH]
462
+ return pts.reshape(FIXED_LENGTH, 1, 3)
463
+
464
+ def ind_sel(target: torch.Tensor, ind: torch.Tensor, dim: int = 1):
465
+ """Index selection utility function"""
466
+ assert (
467
+ len(ind.shape) > dim
468
+ ), "Index must have the target dim, but get dim: %d, ind shape: %s" % (dim, str(ind.shape))
469
+
470
+ target = target.expand(
471
+ *tuple(
472
+ [ind.shape[k] if target.shape[k] == 1 else -1 for k in range(dim)]
473
+ + [
474
+ -1,
475
+ ]
476
+ * (len(target.shape) - dim)
477
+ )
478
+ )
479
+
480
+ ind_pad = ind
481
+
482
+ if len(target.shape) > dim + 1:
483
+ for _ in range(len(target.shape) - (dim + 1)):
484
+ ind_pad = ind_pad.unsqueeze(-1)
485
+ ind_pad = ind_pad.expand(*(-1,) * (dim + 1), *target.shape[(dim + 1) : :])
486
+
487
+ return torch.gather(target, dim=dim, index=ind_pad)
488
+
489
+ def merge_final(vert_attr: torch.Tensor, weight: torch.Tensor, vert_assign: torch.Tensor):
490
+ """Merge vertex attributes with weights"""
491
+ target_dim = len(vert_assign.shape) - 1
492
+ if len(vert_attr.shape) == 2:
493
+ assert vert_attr.shape[0] > vert_assign.max()
494
+ new_shape = [1] * target_dim + list(vert_attr.shape)
495
+ tensor = vert_attr.reshape(new_shape)
496
+ sel_attr = ind_sel(tensor, vert_assign.type(torch.long), dim=target_dim)
497
+ else:
498
+ assert vert_attr.shape[1] > vert_assign.max()
499
+ new_shape = [vert_attr.shape[0]] + [1] * (target_dim - 1) + list(vert_attr.shape[1:])
500
+ tensor = vert_attr.reshape(new_shape)
501
+ sel_attr = ind_sel(tensor, vert_assign.type(torch.long), dim=target_dim)
502
+
503
+ final_attr = torch.sum(sel_attr * weight.unsqueeze(-1), dim=-2)
504
+ return final_attr
505
+
506
+
507
+ def _patch_motion_single(
508
+ tracks: torch.FloatTensor, # (B, T, N, 4)
509
+ vid: torch.FloatTensor, # (C, T, H, W)
510
+ temperature: float,
511
+ vae_divide: tuple,
512
+ topk: int,
513
+ ):
514
+ """Apply motion patching based on tracks"""
515
+ _, T, H, W = vid.shape
516
+ N = tracks.shape[2]
517
+ _, tracks_xy, visible = torch.split(
518
+ tracks, [1, 2, 1], dim=-1
519
+ ) # (B, T, N, 2) | (B, T, N, 1)
520
+ tracks_n = tracks_xy / torch.tensor([W / min(H, W), H / min(H, W)], device=tracks_xy.device)
521
+ tracks_n = tracks_n.clamp(-1, 1)
522
+ visible = visible.clamp(0, 1)
523
+
524
+ xx = torch.linspace(-W / min(H, W), W / min(H, W), W)
525
+ yy = torch.linspace(-H / min(H, W), H / min(H, W), H)
526
+
527
+ grid = torch.stack(torch.meshgrid(yy, xx, indexing="ij")[::-1], dim=-1).to(
528
+ tracks_xy.device
529
+ )
530
+
531
+ tracks_pad = tracks_xy[:, 1:]
532
+ visible_pad = visible[:, 1:]
533
+
534
+ visible_align = visible_pad.view(T - 1, 4, *visible_pad.shape[2:]).sum(1)
535
+ tracks_align = (tracks_pad * visible_pad).view(T - 1, 4, *tracks_pad.shape[2:]).sum(
536
+ 1
537
+ ) / (visible_align + 1e-5)
538
+ dist_ = (
539
+ (tracks_align[:, None, None] - grid[None, :, :, None]).pow(2).sum(-1)
540
+ ) # T, H, W, N
541
+ weight = torch.exp(-dist_ * temperature) * visible_align.clamp(0, 1).view(
542
+ T - 1, 1, 1, N
543
+ )
544
+ vert_weight, vert_index = torch.topk(
545
+ weight, k=min(topk, weight.shape[-1]), dim=-1
546
+ )
547
+
548
+ grid_mode = "bilinear"
549
+ point_feature = torch.nn.functional.grid_sample(
550
+ vid.permute(1, 0, 2, 3)[:1],
551
+ tracks_n[:, :1].type(vid.dtype),
552
+ mode=grid_mode,
553
+ padding_mode="zeros",
554
+ align_corners=False,
555
+ )
556
+ point_feature = point_feature.squeeze(0).squeeze(1).permute(1, 0) # N, C=16
557
+
558
+ out_feature = merge_final(point_feature, vert_weight, vert_index).permute(3, 0, 1, 2) # T - 1, H, W, C => C, T - 1, H, W
559
+ out_weight = vert_weight.sum(-1) # T - 1, H, W
560
+
561
+ # out feature -> already soft weighted
562
+ mix_feature = out_feature + vid[:, 1:] * (1 - out_weight.clamp(0, 1))
563
+
564
+ out_feature_full = torch.cat([vid[:, :1], mix_feature], dim=1) # C, T, H, W
565
+ out_mask_full = torch.cat([torch.ones_like(out_weight[:1]), out_weight], dim=0) # T, H, W
566
+
567
+ return out_mask_full[None].expand(vae_divide[0], -1, -1, -1), out_feature_full
568
+
569
+
570
+ def patch_motion(
571
+ tracks: torch.FloatTensor, # (B, TB, T, N, 4)
572
+ vid: torch.FloatTensor, # (C, T, H, W)
573
+ temperature: float = 220.0,
574
+ vae_divide: tuple = (4, 16),
575
+ topk: int = 2,
576
+ ):
577
+ B = len(tracks)
578
+
579
+ # Process each batch separately
580
+ out_masks = []
581
+ out_features = []
582
+
583
+ for b in range(B):
584
+ mask, feature = _patch_motion_single(
585
+ tracks[b], # (T, N, 4)
586
+ vid[b], # (C, T, H, W)
587
+ temperature,
588
+ vae_divide,
589
+ topk
590
+ )
591
+ out_masks.append(mask)
592
+ out_features.append(feature)
593
+
594
+ # Stack results: (B, C, T, H, W)
595
+ out_mask_full = torch.stack(out_masks, dim=0)
596
+ out_feature_full = torch.stack(out_features, dim=0)
597
+
598
+ return out_mask_full, out_feature_full
599
+
600
+ class WanTrackToVideo:
601
+ @classmethod
602
+ def INPUT_TYPES(s):
603
+ return {"required": {
604
+ "positive": ("CONDITIONING", ),
605
+ "negative": ("CONDITIONING", ),
606
+ "vae": ("VAE", ),
607
+ "tracks": ("STRING", {"multiline": True, "default": "[]"}),
608
+ "width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
609
+ "height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
610
+ "length": ("INT", {"default": 81, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
611
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
612
+ "temperature": ("FLOAT", {"default": 220.0, "min": 1.0, "max": 1000.0, "step": 0.1}),
613
+ "topk": ("INT", {"default": 2, "min": 1, "max": 10}),
614
+ "start_image": ("IMAGE", ),
615
+ },
616
+ "optional": {
617
+ "clip_vision_output": ("CLIP_VISION_OUTPUT", ),
618
+ }}
619
+
620
+ RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
621
+ RETURN_NAMES = ("positive", "negative", "latent")
622
+ FUNCTION = "encode"
623
+
624
+ CATEGORY = "conditioning/video_models"
625
+
626
+ def encode(self, positive, negative, vae, tracks, width, height, length, batch_size,
627
+ temperature, topk, start_image=None, clip_vision_output=None):
628
+
629
+ tracks_data = parse_json_tracks(tracks)
630
+
631
+ if not tracks_data:
632
+ return WanImageToVideo().encode(positive, negative, vae, width, height, length, batch_size, start_image=start_image, clip_vision_output=clip_vision_output)
633
+
634
+ latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8],
635
+ device=comfy.model_management.intermediate_device())
636
+
637
+ if isinstance(tracks_data[0][0], dict):
638
+ tracks_data = [tracks_data]
639
+
640
+ processed_tracks = []
641
+ for batch in tracks_data:
642
+ arrs = []
643
+ for track in batch:
644
+ pts = pad_pts(track)
645
+ arrs.append(pts)
646
+
647
+ tracks_np = np.stack(arrs, axis=0)
648
+ processed_tracks.append(process_tracks(tracks_np, (width, height), length - 1).unsqueeze(0))
649
+
650
+ if start_image is not None:
651
+ start_image = comfy.utils.common_upscale(start_image[:batch_size].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
652
+ videos = torch.ones((start_image.shape[0], length, height, width, start_image.shape[-1]), device=start_image.device, dtype=start_image.dtype) * 0.5
653
+ for i in range(start_image.shape[0]):
654
+ videos[i, 0] = start_image[i]
655
+
656
+ latent_videos = []
657
+ videos = comfy.utils.resize_to_batch_size(videos, batch_size)
658
+ for i in range(batch_size):
659
+ latent_videos += [vae.encode(videos[i, :, :, :, :3])]
660
+ y = torch.cat(latent_videos, dim=0)
661
+
662
+ # Scale latent since patch_motion is non-linear
663
+ y = comfy.latent_formats.Wan21().process_in(y)
664
+
665
+ processed_tracks = comfy.utils.resize_list_to_batch_size(processed_tracks, batch_size)
666
+ res = patch_motion(
667
+ processed_tracks, y, temperature=temperature, topk=topk, vae_divide=(4, 16)
668
+ )
669
+
670
+ mask, concat_latent_image = res
671
+ concat_latent_image = comfy.latent_formats.Wan21().process_out(concat_latent_image)
672
+ mask = -mask + 1.0 # Invert mask to match expected format
673
+ positive = node_helpers.conditioning_set_values(positive,
674
+ {"concat_mask": mask,
675
+ "concat_latent_image": concat_latent_image})
676
+ negative = node_helpers.conditioning_set_values(negative,
677
+ {"concat_mask": mask,
678
+ "concat_latent_image": concat_latent_image})
679
+
680
+ if clip_vision_output is not None:
681
+ positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output})
682
+ negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output})
683
+
684
+ out_latent = {}
685
+ out_latent["samples"] = latent
686
+ return (positive, negative, out_latent)
687
+
688
+
689
+ class Wan22ImageToVideoLatent:
690
+ @classmethod
691
+ def INPUT_TYPES(s):
692
+ return {"required": {"vae": ("VAE", ),
693
+ "width": ("INT", {"default": 1280, "min": 32, "max": nodes.MAX_RESOLUTION, "step": 32}),
694
+ "height": ("INT", {"default": 704, "min": 32, "max": nodes.MAX_RESOLUTION, "step": 32}),
695
+ "length": ("INT", {"default": 49, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
696
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
697
+ },
698
+ "optional": {"start_image": ("IMAGE", ),
699
+ }}
700
+
701
+
702
+ RETURN_TYPES = ("LATENT",)
703
+ FUNCTION = "encode"
704
+
705
+ CATEGORY = "conditioning/inpaint"
706
+
707
+ def encode(self, vae, width, height, length, batch_size, start_image=None):
708
+ latent = torch.zeros([1, 48, ((length - 1) // 4) + 1, height // 16, width // 16], device=comfy.model_management.intermediate_device())
709
+
710
+ if start_image is None:
711
+ out_latent = {}
712
+ out_latent["samples"] = latent
713
+ return (out_latent,)
714
+
715
+ mask = torch.ones([latent.shape[0], 1, ((length - 1) // 4) + 1, latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device())
716
+
717
+ if start_image is not None:
718
+ start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
719
+ latent_temp = vae.encode(start_image)
720
+ latent[:, :, :latent_temp.shape[-3]] = latent_temp
721
+ mask[:, :, :latent_temp.shape[-3]] *= 0.0
722
+
723
+ out_latent = {}
724
+ latent_format = comfy.latent_formats.Wan22()
725
+ latent = latent_format.process_out(latent) * mask + latent * (1.0 - mask)
726
+ out_latent["samples"] = latent.repeat((batch_size, ) + (1,) * (latent.ndim - 1))
727
+ out_latent["noise_mask"] = mask.repeat((batch_size, ) + (1,) * (mask.ndim - 1))
728
+ return (out_latent,)
729
+
730
+
731
+ NODE_CLASS_MAPPINGS = {
732
+ "WanTrackToVideo": WanTrackToVideo,
733
+ "WanImageToVideo": WanImageToVideo,
734
+ "WanFunControlToVideo": WanFunControlToVideo,
735
+ "WanFunInpaintToVideo": WanFunInpaintToVideo,
736
+ "WanFirstLastFrameToVideo": WanFirstLastFrameToVideo,
737
+ "WanVaceToVideo": WanVaceToVideo,
738
+ "TrimVideoLatent": TrimVideoLatent,
739
+ "WanCameraImageToVideo": WanCameraImageToVideo,
740
+ "WanPhantomSubjectToVideo": WanPhantomSubjectToVideo,
741
+ "Wan22ImageToVideoLatent": Wan22ImageToVideoLatent,
742
+ }
ComfyUI/custom_nodes/comfyui-kjnodes/web/js/jsnodes.js ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../../scripts/app.js";
2
+ import { applyTextReplacements } from "../../../scripts/utils.js";
3
+
4
+ app.registerExtension({
5
+ name: "KJNodes.jsnodes",
6
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
7
+ if(!nodeData?.category?.startsWith("KJNodes")) {
8
+ return;
9
+ }
10
+ switch (nodeData.name) {
11
+ case "ConditioningMultiCombine":
12
+ nodeType.prototype.onNodeCreated = function () {
13
+ this._type = "CONDITIONING"
14
+ this.inputs_offset = nodeData.name.includes("selective")?1:0
15
+ this.addWidget("button", "Update inputs", null, () => {
16
+ if (!this.inputs) {
17
+ this.inputs = [];
18
+ }
19
+ const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"];
20
+ const num_inputs = this.inputs.filter(input => input.type === this._type).length
21
+ if(target_number_of_inputs===num_inputs)return; // already set, do nothing
22
+
23
+ if(target_number_of_inputs < num_inputs){
24
+ const inputs_to_remove = num_inputs - target_number_of_inputs;
25
+ for(let i = 0; i < inputs_to_remove; i++) {
26
+ this.removeInput(this.inputs.length - 1);
27
+ }
28
+ }
29
+ else{
30
+ for(let i = num_inputs+1; i <= target_number_of_inputs; ++i)
31
+ this.addInput(`conditioning_${i}`, this._type)
32
+ }
33
+ });
34
+ }
35
+ break;
36
+ case "ImageBatchMulti":
37
+ case "ImageAddMulti":
38
+ case "ImageConcatMulti":
39
+ case "CrossFadeImagesMulti":
40
+ case "TransitionImagesMulti":
41
+ nodeType.prototype.onNodeCreated = function () {
42
+ this._type = "IMAGE"
43
+ this.addWidget("button", "Update inputs", null, () => {
44
+ if (!this.inputs) {
45
+ this.inputs = [];
46
+ }
47
+ const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"];
48
+ const num_inputs = this.inputs.filter(input => input.type === this._type).length
49
+ if(target_number_of_inputs===num_inputs)return; // already set, do nothing
50
+
51
+ if(target_number_of_inputs < num_inputs){
52
+ const inputs_to_remove = num_inputs - target_number_of_inputs;
53
+ for(let i = 0; i < inputs_to_remove; i++) {
54
+ this.removeInput(this.inputs.length - 1);
55
+ }
56
+ }
57
+ else{
58
+ for(let i = num_inputs+1; i <= target_number_of_inputs; ++i)
59
+ this.addInput(`image_${i}`, this._type, {shape: 7});
60
+ }
61
+
62
+ });
63
+ }
64
+ break;
65
+ case "MaskBatchMulti":
66
+ nodeType.prototype.onNodeCreated = function () {
67
+ this._type = "MASK"
68
+ this.addWidget("button", "Update inputs", null, () => {
69
+ if (!this.inputs) {
70
+ this.inputs = [];
71
+ }
72
+ const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"];
73
+ const num_inputs = this.inputs.filter(input => input.type === this._type).length
74
+ if(target_number_of_inputs===num_inputs)return; // already set, do nothing
75
+
76
+ if(target_number_of_inputs < num_inputs){
77
+ const inputs_to_remove = num_inputs - target_number_of_inputs;
78
+ for(let i = 0; i < inputs_to_remove; i++) {
79
+ this.removeInput(this.inputs.length - 1);
80
+ }
81
+ }
82
+ else{
83
+ for(let i = num_inputs+1; i <= target_number_of_inputs; ++i)
84
+ this.addInput(`mask_${i}`, this._type)
85
+ }
86
+ });
87
+ }
88
+ break;
89
+
90
+ case "FluxBlockLoraSelect":
91
+ case "HunyuanVideoBlockLoraSelect":
92
+ case "Wan21BlockLoraSelect":
93
+ nodeType.prototype.onNodeCreated = function () {
94
+ this.addWidget("button", "Set all", null, () => {
95
+ const userInput = prompt("Enter the values to set for widgets (e.g., s0,1,2-7=2.0, d0,1,2-7=2.0, or 1.0):", "");
96
+ if (userInput) {
97
+ const regex = /([sd])?(\d+(?:,\d+|-?\d+)*?)?=(\d+(\.\d+)?)/;
98
+ const match = userInput.match(regex);
99
+ if (match) {
100
+ const type = match[1];
101
+ const indicesPart = match[2];
102
+ const value = parseFloat(match[3]);
103
+
104
+ let targetWidgets = [];
105
+ if (type === 's') {
106
+ targetWidgets = this.widgets.filter(widget => widget.name.includes("single"));
107
+ } else if (type === 'd') {
108
+ targetWidgets = this.widgets.filter(widget => widget.name.includes("double"));
109
+ } else {
110
+ targetWidgets = this.widgets; // No type specified, all widgets
111
+ }
112
+
113
+ if (indicesPart) {
114
+ const indices = indicesPart.split(',').flatMap(part => {
115
+ if (part.includes('-')) {
116
+ const [start, end] = part.split('-').map(Number);
117
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
118
+ }
119
+ return Number(part);
120
+ });
121
+
122
+ for (const index of indices) {
123
+ if (index < targetWidgets.length) {
124
+ targetWidgets[index].value = value;
125
+ }
126
+ }
127
+ } else {
128
+ // No indices provided, set value for all target widgets
129
+ for (const widget of targetWidgets) {
130
+ widget.value = value;
131
+ }
132
+ }
133
+ } else if (!isNaN(parseFloat(userInput))) {
134
+ // Single value provided, set it for all widgets
135
+ const value = parseFloat(userInput);
136
+ for (const widget of this.widgets) {
137
+ widget.value = value;
138
+ }
139
+ } else {
140
+ alert("Invalid input format. Please use the format s0,1,2-7=2.0, d0,1,2-7=2.0, or 1.0");
141
+ }
142
+ } else {
143
+ alert("Invalid input. Please enter a value.");
144
+ }
145
+ });
146
+ };
147
+ break;
148
+
149
+ case "GetMaskSizeAndCount":
150
+ const onGetMaskSizeConnectInput = nodeType.prototype.onConnectInput;
151
+ nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) {
152
+ const v = onGetMaskSizeConnectInput? onGetMaskSizeConnectInput.apply(this, arguments): undefined
153
+ this.outputs[1]["label"] = "width"
154
+ this.outputs[2]["label"] = "height"
155
+ this.outputs[3]["label"] = "count"
156
+ return v;
157
+ }
158
+ const onGetMaskSizeExecuted = nodeType.prototype.onAfterExecuteNode;
159
+ nodeType.prototype.onExecuted = function(message) {
160
+ const r = onGetMaskSizeExecuted? onGetMaskSizeExecuted.apply(this,arguments): undefined
161
+ let values = message["text"].toString().split('x').map(Number);
162
+ this.outputs[1]["label"] = values[1] + " width"
163
+ this.outputs[2]["label"] = values[2] + " height"
164
+ this.outputs[3]["label"] = values[0] + " count"
165
+ return r
166
+ }
167
+ break;
168
+
169
+ case "GetImageSizeAndCount":
170
+ const onGetImageSizeConnectInput = nodeType.prototype.onConnectInput;
171
+ nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) {
172
+ console.log(this)
173
+ const v = onGetImageSizeConnectInput? onGetImageSizeConnectInput.apply(this, arguments): undefined
174
+ //console.log(this)
175
+ this.outputs[1]["label"] = "width"
176
+ this.outputs[2]["label"] = "height"
177
+ this.outputs[3]["label"] = "count"
178
+ return v;
179
+ }
180
+ //const onGetImageSizeExecuted = nodeType.prototype.onExecuted;
181
+ const onGetImageSizeExecuted = nodeType.prototype.onAfterExecuteNode;
182
+ nodeType.prototype.onExecuted = function(message) {
183
+ console.log(this)
184
+ const r = onGetImageSizeExecuted? onGetImageSizeExecuted.apply(this,arguments): undefined
185
+ let values = message["text"].toString().split('x').map(Number);
186
+ console.log(values)
187
+ this.outputs[1]["label"] = values[1] + " width"
188
+ this.outputs[2]["label"] = values[2] + " height"
189
+ this.outputs[3]["label"] = values[0] + " count"
190
+ return r
191
+ }
192
+ break;
193
+
194
+ case "GetLatentSizeAndCount":
195
+ const onGetLatentConnectInput = nodeType.prototype.onConnectInput;
196
+ nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) {
197
+ console.log(this)
198
+ const v = onGetLatentConnectInput? onGetLatentConnectInput.apply(this, arguments): undefined
199
+ //console.log(this)
200
+ this.outputs[1]["label"] = "width"
201
+ this.outputs[2]["label"] = "height"
202
+ this.outputs[3]["label"] = "count"
203
+ return v;
204
+ }
205
+ //const onGetImageSizeExecuted = nodeType.prototype.onExecuted;
206
+ const onGetLatentSizeExecuted = nodeType.prototype.onAfterExecuteNode;
207
+ nodeType.prototype.onExecuted = function(message) {
208
+ console.log(this)
209
+ const r = onGetLatentSizeExecuted? onGetLatentSizeExecuted.apply(this,arguments): undefined
210
+ let values = message["text"].toString().split('x').map(Number);
211
+ console.log(values)
212
+ this.outputs[1]["label"] = values[0] + " batch"
213
+ this.outputs[2]["label"] = values[1] + " channels"
214
+ this.outputs[3]["label"] = values[2] + " frames"
215
+ this.outputs[4]["label"] = values[3] + " height"
216
+ this.outputs[5]["label"] = values[4] + " width"
217
+ return r
218
+ }
219
+ break;
220
+
221
+ case "PreviewAnimation":
222
+ const onPreviewAnimationConnectInput = nodeType.prototype.onConnectInput;
223
+ nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) {
224
+ const v = onPreviewAnimationConnectInput? onPreviewAnimationConnectInput.apply(this, arguments): undefined
225
+ this.title = "Preview Animation"
226
+ return v;
227
+ }
228
+ const onPreviewAnimationExecuted = nodeType.prototype.onAfterExecuteNode;
229
+ nodeType.prototype.onExecuted = function(message) {
230
+ const r = onPreviewAnimationExecuted? onPreviewAnimationExecuted.apply(this,arguments): undefined
231
+ let values = message["text"].toString();
232
+ this.title = "Preview Animation " + values
233
+ return r
234
+ }
235
+ break;
236
+
237
+ case "VRAM_Debug":
238
+ const onVRAM_DebugConnectInput = nodeType.prototype.onConnectInput;
239
+ nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) {
240
+ const v = onVRAM_DebugConnectInput? onVRAM_DebugConnectInput.apply(this, arguments): undefined
241
+ this.outputs[3]["label"] = "freemem_before"
242
+ this.outputs[4]["label"] = "freemem_after"
243
+ return v;
244
+ }
245
+ const onVRAM_DebugExecuted = nodeType.prototype.onAfterExecuteNode;
246
+ nodeType.prototype.onExecuted = function(message) {
247
+ const r = onVRAM_DebugExecuted? onVRAM_DebugExecuted.apply(this,arguments): undefined
248
+ let values = message["text"].toString().split('x');
249
+ this.outputs[3]["label"] = values[0] + " freemem_before"
250
+ this.outputs[4]["label"] = values[1] + " freemem_after"
251
+ return r
252
+ }
253
+ break;
254
+
255
+ case "JoinStringMulti":
256
+ const originalOnNodeCreated = nodeType.prototype.onNodeCreated || function() {};
257
+ nodeType.prototype.onNodeCreated = function () {
258
+ originalOnNodeCreated.apply(this, arguments);
259
+
260
+ this._type = "STRING";
261
+ this.addWidget("button", "Update inputs", null, () => {
262
+ if (!this.inputs) {
263
+ this.inputs = [];
264
+ }
265
+ const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"];
266
+ const num_inputs = this.inputs.filter(input => input.name && input.name.toLowerCase().includes("string_")).length
267
+ if (target_number_of_inputs === num_inputs) return; // already set, do nothing
268
+
269
+ if(target_number_of_inputs < num_inputs){
270
+ const inputs_to_remove = num_inputs - target_number_of_inputs;
271
+ for(let i = 0; i < inputs_to_remove; i++) {
272
+ this.removeInput(this.inputs.length - 1);
273
+ }
274
+ }
275
+ else{
276
+ for(let i = num_inputs+1; i <= target_number_of_inputs; ++i)
277
+ this.addInput(`string_${i}`, this._type, {shape: 7});
278
+ }
279
+ });
280
+ }
281
+ break;
282
+ case "SoundReactive":
283
+ nodeType.prototype.onNodeCreated = function () {
284
+ let audioContext;
285
+ let microphoneStream;
286
+ let animationFrameId;
287
+ let analyser;
288
+ let dataArray;
289
+ let startRangeHz;
290
+ let endRangeHz;
291
+ let smoothingFactor = 0.5;
292
+ let smoothedSoundLevel = 0;
293
+
294
+ // Function to update the widget value in real-time
295
+ const updateWidgetValueInRealTime = () => {
296
+ // Ensure analyser and dataArray are defined before using them
297
+ if (analyser && dataArray) {
298
+ analyser.getByteFrequencyData(dataArray);
299
+
300
+ const startRangeHzWidget = this.widgets.find(w => w.name === "start_range_hz");
301
+ if (startRangeHzWidget) startRangeHz = startRangeHzWidget.value;
302
+ const endRangeHzWidget = this.widgets.find(w => w.name === "end_range_hz");
303
+ if (endRangeHzWidget) endRangeHz = endRangeHzWidget.value;
304
+ const smoothingFactorWidget = this.widgets.find(w => w.name === "smoothing_factor");
305
+ if (smoothingFactorWidget) smoothingFactor = smoothingFactorWidget.value;
306
+
307
+ // Calculate frequency bin width (frequency resolution)
308
+ const frequencyBinWidth = audioContext.sampleRate / analyser.fftSize;
309
+ // Convert the widget values from Hz to indices
310
+ const startRangeIndex = Math.floor(startRangeHz / frequencyBinWidth);
311
+ const endRangeIndex = Math.floor(endRangeHz / frequencyBinWidth);
312
+
313
+ // Function to calculate the average value for a frequency range
314
+ const calculateAverage = (start, end) => {
315
+ const sum = dataArray.slice(start, end).reduce((acc, val) => acc + val, 0);
316
+ const average = sum / (end - start);
317
+
318
+ // Apply exponential moving average smoothing
319
+ smoothedSoundLevel = (average * (1 - smoothingFactor)) + (smoothedSoundLevel * smoothingFactor);
320
+ return smoothedSoundLevel;
321
+ };
322
+ // Calculate the average levels for each frequency range
323
+ const soundLevel = calculateAverage(startRangeIndex, endRangeIndex);
324
+
325
+ // Update the widget values
326
+
327
+ const lowLevelWidget = this.widgets.find(w => w.name === "sound_level");
328
+ if (lowLevelWidget) lowLevelWidget.value = soundLevel;
329
+
330
+ animationFrameId = requestAnimationFrame(updateWidgetValueInRealTime);
331
+ }
332
+ };
333
+
334
+ // Function to start capturing audio from the microphone
335
+ const startMicrophoneCapture = () => {
336
+ // Only create the audio context and analyser once
337
+ if (!audioContext) {
338
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
339
+ // Access the sample rate of the audio context
340
+ console.log(`Sample rate: ${audioContext.sampleRate}Hz`);
341
+ analyser = audioContext.createAnalyser();
342
+ analyser.fftSize = 2048;
343
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
344
+ // Get the range values from widgets (assumed to be in Hz)
345
+ const lowRangeWidget = this.widgets.find(w => w.name === "low_range_hz");
346
+ if (lowRangeWidget) startRangeHz = lowRangeWidget.value;
347
+
348
+ const midRangeWidget = this.widgets.find(w => w.name === "mid_range_hz");
349
+ if (midRangeWidget) endRangeHz = midRangeWidget.value;
350
+ }
351
+
352
+ navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
353
+ microphoneStream = stream;
354
+ const microphone = audioContext.createMediaStreamSource(stream);
355
+ microphone.connect(analyser);
356
+ updateWidgetValueInRealTime();
357
+ }).catch(error => {
358
+ console.error('Access to microphone was denied or an error occurred:', error);
359
+ });
360
+ };
361
+
362
+ // Function to stop capturing audio from the microphone
363
+ const stopMicrophoneCapture = () => {
364
+ if (animationFrameId) {
365
+ cancelAnimationFrame(animationFrameId);
366
+ }
367
+ if (microphoneStream) {
368
+ microphoneStream.getTracks().forEach(track => track.stop());
369
+ }
370
+ if (audioContext) {
371
+ audioContext.close();
372
+ // Reset audioContext to ensure it can be created again when starting
373
+ audioContext = null;
374
+ }
375
+ };
376
+
377
+ // Add start button
378
+ this.addWidget("button", "Start mic capture", null, startMicrophoneCapture);
379
+
380
+ // Add stop button
381
+ this.addWidget("button", "Stop mic capture", null, stopMicrophoneCapture);
382
+ };
383
+ break;
384
+ case "SaveImageKJ":
385
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
386
+ nodeType.prototype.onNodeCreated = function() {
387
+ const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : void 0;
388
+ const widget = this.widgets.find((w) => w.name === "filename_prefix");
389
+ widget.serializeValue = () => {
390
+ return applyTextReplacements(app, widget.value);
391
+ };
392
+ return r;
393
+ };
394
+ break;
395
+
396
+ }
397
+
398
+ },
399
+ async setup() {
400
+ // to keep Set/Get node virtual connections visible when offscreen
401
+ const originalComputeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes;
402
+ LGraphCanvas.prototype.computeVisibleNodes = function () {
403
+ const visibleNodesSet = new Set(originalComputeVisibleNodes.apply(this, arguments));
404
+ for (const node of this.graph._nodes) {
405
+ if ((node.type === "SetNode" || node.type === "GetNode") && node.drawConnection) {
406
+ visibleNodesSet.add(node);
407
+ }
408
+ }
409
+ return Array.from(visibleNodesSet);
410
+ };
411
+
412
+ }
413
+ });
ComfyUI/models/configs/anything_v3.yaml ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-04
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false # Note: different from the one we trained before
15
+ conditioning_key: crossattn
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ use_ema: False
19
+
20
+ scheduler_config: # 10000 warmup steps
21
+ target: ldm.lr_scheduler.LambdaLinearScheduler
22
+ params:
23
+ warm_up_steps: [ 10000 ]
24
+ cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases
25
+ f_start: [ 1.e-6 ]
26
+ f_max: [ 1. ]
27
+ f_min: [ 1. ]
28
+
29
+ unet_config:
30
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
31
+ params:
32
+ image_size: 32 # unused
33
+ in_channels: 4
34
+ out_channels: 4
35
+ model_channels: 320
36
+ attention_resolutions: [ 4, 2, 1 ]
37
+ num_res_blocks: 2
38
+ channel_mult: [ 1, 2, 4, 4 ]
39
+ num_heads: 8
40
+ use_spatial_transformer: True
41
+ transformer_depth: 1
42
+ context_dim: 768
43
+ use_checkpoint: True
44
+ legacy: False
45
+
46
+ first_stage_config:
47
+ target: ldm.models.autoencoder.AutoencoderKL
48
+ params:
49
+ embed_dim: 4
50
+ monitor: val/rec_loss
51
+ ddconfig:
52
+ double_z: true
53
+ z_channels: 4
54
+ resolution: 256
55
+ in_channels: 3
56
+ out_ch: 3
57
+ ch: 128
58
+ ch_mult:
59
+ - 1
60
+ - 2
61
+ - 4
62
+ - 4
63
+ num_res_blocks: 2
64
+ attn_resolutions: []
65
+ dropout: 0.0
66
+ lossconfig:
67
+ target: torch.nn.Identity
68
+
69
+ cond_stage_config:
70
+ target: ldm.modules.encoders.modules.FrozenCLIPEmbedder
71
+ params:
72
+ layer: "hidden"
73
+ layer_idx: -2
ComfyUI/models/configs/v1-inference.yaml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-04
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false # Note: different from the one we trained before
15
+ conditioning_key: crossattn
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ use_ema: False
19
+
20
+ scheduler_config: # 10000 warmup steps
21
+ target: ldm.lr_scheduler.LambdaLinearScheduler
22
+ params:
23
+ warm_up_steps: [ 10000 ]
24
+ cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases
25
+ f_start: [ 1.e-6 ]
26
+ f_max: [ 1. ]
27
+ f_min: [ 1. ]
28
+
29
+ unet_config:
30
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
31
+ params:
32
+ image_size: 32 # unused
33
+ in_channels: 4
34
+ out_channels: 4
35
+ model_channels: 320
36
+ attention_resolutions: [ 4, 2, 1 ]
37
+ num_res_blocks: 2
38
+ channel_mult: [ 1, 2, 4, 4 ]
39
+ num_heads: 8
40
+ use_spatial_transformer: True
41
+ transformer_depth: 1
42
+ context_dim: 768
43
+ use_checkpoint: True
44
+ legacy: False
45
+
46
+ first_stage_config:
47
+ target: ldm.models.autoencoder.AutoencoderKL
48
+ params:
49
+ embed_dim: 4
50
+ monitor: val/rec_loss
51
+ ddconfig:
52
+ double_z: true
53
+ z_channels: 4
54
+ resolution: 256
55
+ in_channels: 3
56
+ out_ch: 3
57
+ ch: 128
58
+ ch_mult:
59
+ - 1
60
+ - 2
61
+ - 4
62
+ - 4
63
+ num_res_blocks: 2
64
+ attn_resolutions: []
65
+ dropout: 0.0
66
+ lossconfig:
67
+ target: torch.nn.Identity
68
+
69
+ cond_stage_config:
70
+ target: ldm.modules.encoders.modules.FrozenCLIPEmbedder
ComfyUI/models/configs/v1-inference_clip_skip_2.yaml ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-04
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false # Note: different from the one we trained before
15
+ conditioning_key: crossattn
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ use_ema: False
19
+
20
+ scheduler_config: # 10000 warmup steps
21
+ target: ldm.lr_scheduler.LambdaLinearScheduler
22
+ params:
23
+ warm_up_steps: [ 10000 ]
24
+ cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases
25
+ f_start: [ 1.e-6 ]
26
+ f_max: [ 1. ]
27
+ f_min: [ 1. ]
28
+
29
+ unet_config:
30
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
31
+ params:
32
+ image_size: 32 # unused
33
+ in_channels: 4
34
+ out_channels: 4
35
+ model_channels: 320
36
+ attention_resolutions: [ 4, 2, 1 ]
37
+ num_res_blocks: 2
38
+ channel_mult: [ 1, 2, 4, 4 ]
39
+ num_heads: 8
40
+ use_spatial_transformer: True
41
+ transformer_depth: 1
42
+ context_dim: 768
43
+ use_checkpoint: True
44
+ legacy: False
45
+
46
+ first_stage_config:
47
+ target: ldm.models.autoencoder.AutoencoderKL
48
+ params:
49
+ embed_dim: 4
50
+ monitor: val/rec_loss
51
+ ddconfig:
52
+ double_z: true
53
+ z_channels: 4
54
+ resolution: 256
55
+ in_channels: 3
56
+ out_ch: 3
57
+ ch: 128
58
+ ch_mult:
59
+ - 1
60
+ - 2
61
+ - 4
62
+ - 4
63
+ num_res_blocks: 2
64
+ attn_resolutions: []
65
+ dropout: 0.0
66
+ lossconfig:
67
+ target: torch.nn.Identity
68
+
69
+ cond_stage_config:
70
+ target: ldm.modules.encoders.modules.FrozenCLIPEmbedder
71
+ params:
72
+ layer: "hidden"
73
+ layer_idx: -2
ComfyUI/models/configs/v1-inference_clip_skip_2_fp16.yaml ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-04
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false # Note: different from the one we trained before
15
+ conditioning_key: crossattn
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ use_ema: False
19
+
20
+ scheduler_config: # 10000 warmup steps
21
+ target: ldm.lr_scheduler.LambdaLinearScheduler
22
+ params:
23
+ warm_up_steps: [ 10000 ]
24
+ cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases
25
+ f_start: [ 1.e-6 ]
26
+ f_max: [ 1. ]
27
+ f_min: [ 1. ]
28
+
29
+ unet_config:
30
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
31
+ params:
32
+ use_fp16: True
33
+ image_size: 32 # unused
34
+ in_channels: 4
35
+ out_channels: 4
36
+ model_channels: 320
37
+ attention_resolutions: [ 4, 2, 1 ]
38
+ num_res_blocks: 2
39
+ channel_mult: [ 1, 2, 4, 4 ]
40
+ num_heads: 8
41
+ use_spatial_transformer: True
42
+ transformer_depth: 1
43
+ context_dim: 768
44
+ use_checkpoint: True
45
+ legacy: False
46
+
47
+ first_stage_config:
48
+ target: ldm.models.autoencoder.AutoencoderKL
49
+ params:
50
+ embed_dim: 4
51
+ monitor: val/rec_loss
52
+ ddconfig:
53
+ double_z: true
54
+ z_channels: 4
55
+ resolution: 256
56
+ in_channels: 3
57
+ out_ch: 3
58
+ ch: 128
59
+ ch_mult:
60
+ - 1
61
+ - 2
62
+ - 4
63
+ - 4
64
+ num_res_blocks: 2
65
+ attn_resolutions: []
66
+ dropout: 0.0
67
+ lossconfig:
68
+ target: torch.nn.Identity
69
+
70
+ cond_stage_config:
71
+ target: ldm.modules.encoders.modules.FrozenCLIPEmbedder
72
+ params:
73
+ layer: "hidden"
74
+ layer_idx: -2
ComfyUI/models/configs/v1-inference_fp16.yaml ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-04
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false # Note: different from the one we trained before
15
+ conditioning_key: crossattn
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ use_ema: False
19
+
20
+ scheduler_config: # 10000 warmup steps
21
+ target: ldm.lr_scheduler.LambdaLinearScheduler
22
+ params:
23
+ warm_up_steps: [ 10000 ]
24
+ cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases
25
+ f_start: [ 1.e-6 ]
26
+ f_max: [ 1. ]
27
+ f_min: [ 1. ]
28
+
29
+ unet_config:
30
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
31
+ params:
32
+ use_fp16: True
33
+ image_size: 32 # unused
34
+ in_channels: 4
35
+ out_channels: 4
36
+ model_channels: 320
37
+ attention_resolutions: [ 4, 2, 1 ]
38
+ num_res_blocks: 2
39
+ channel_mult: [ 1, 2, 4, 4 ]
40
+ num_heads: 8
41
+ use_spatial_transformer: True
42
+ transformer_depth: 1
43
+ context_dim: 768
44
+ use_checkpoint: True
45
+ legacy: False
46
+
47
+ first_stage_config:
48
+ target: ldm.models.autoencoder.AutoencoderKL
49
+ params:
50
+ embed_dim: 4
51
+ monitor: val/rec_loss
52
+ ddconfig:
53
+ double_z: true
54
+ z_channels: 4
55
+ resolution: 256
56
+ in_channels: 3
57
+ out_ch: 3
58
+ ch: 128
59
+ ch_mult:
60
+ - 1
61
+ - 2
62
+ - 4
63
+ - 4
64
+ num_res_blocks: 2
65
+ attn_resolutions: []
66
+ dropout: 0.0
67
+ lossconfig:
68
+ target: torch.nn.Identity
69
+
70
+ cond_stage_config:
71
+ target: ldm.modules.encoders.modules.FrozenCLIPEmbedder
ComfyUI/models/configs/v1-inpainting-inference.yaml ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 7.5e-05
3
+ target: ldm.models.diffusion.ddpm.LatentInpaintDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false # Note: different from the one we trained before
15
+ conditioning_key: hybrid # important
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ finetune_keys: null
19
+
20
+ scheduler_config: # 10000 warmup steps
21
+ target: ldm.lr_scheduler.LambdaLinearScheduler
22
+ params:
23
+ warm_up_steps: [ 2500 ] # NOTE for resuming. use 10000 if starting from scratch
24
+ cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases
25
+ f_start: [ 1.e-6 ]
26
+ f_max: [ 1. ]
27
+ f_min: [ 1. ]
28
+
29
+ unet_config:
30
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
31
+ params:
32
+ image_size: 32 # unused
33
+ in_channels: 9 # 4 data + 4 downscaled image + 1 mask
34
+ out_channels: 4
35
+ model_channels: 320
36
+ attention_resolutions: [ 4, 2, 1 ]
37
+ num_res_blocks: 2
38
+ channel_mult: [ 1, 2, 4, 4 ]
39
+ num_heads: 8
40
+ use_spatial_transformer: True
41
+ transformer_depth: 1
42
+ context_dim: 768
43
+ use_checkpoint: True
44
+ legacy: False
45
+
46
+ first_stage_config:
47
+ target: ldm.models.autoencoder.AutoencoderKL
48
+ params:
49
+ embed_dim: 4
50
+ monitor: val/rec_loss
51
+ ddconfig:
52
+ double_z: true
53
+ z_channels: 4
54
+ resolution: 256
55
+ in_channels: 3
56
+ out_ch: 3
57
+ ch: 128
58
+ ch_mult:
59
+ - 1
60
+ - 2
61
+ - 4
62
+ - 4
63
+ num_res_blocks: 2
64
+ attn_resolutions: []
65
+ dropout: 0.0
66
+ lossconfig:
67
+ target: torch.nn.Identity
68
+
69
+ cond_stage_config:
70
+ target: ldm.modules.encoders.modules.FrozenCLIPEmbedder
71
+
ComfyUI/models/configs/v2-inference-v.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-4
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ parameterization: "v"
6
+ linear_start: 0.00085
7
+ linear_end: 0.0120
8
+ num_timesteps_cond: 1
9
+ log_every_t: 200
10
+ timesteps: 1000
11
+ first_stage_key: "jpg"
12
+ cond_stage_key: "txt"
13
+ image_size: 64
14
+ channels: 4
15
+ cond_stage_trainable: false
16
+ conditioning_key: crossattn
17
+ monitor: val/loss_simple_ema
18
+ scale_factor: 0.18215
19
+ use_ema: False # we set this to false because this is an inference only config
20
+
21
+ unet_config:
22
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
23
+ params:
24
+ use_checkpoint: True
25
+ use_fp16: True
26
+ image_size: 32 # unused
27
+ in_channels: 4
28
+ out_channels: 4
29
+ model_channels: 320
30
+ attention_resolutions: [ 4, 2, 1 ]
31
+ num_res_blocks: 2
32
+ channel_mult: [ 1, 2, 4, 4 ]
33
+ num_head_channels: 64 # need to fix for flash-attn
34
+ use_spatial_transformer: True
35
+ use_linear_in_transformer: True
36
+ transformer_depth: 1
37
+ context_dim: 1024
38
+ legacy: False
39
+
40
+ first_stage_config:
41
+ target: ldm.models.autoencoder.AutoencoderKL
42
+ params:
43
+ embed_dim: 4
44
+ monitor: val/rec_loss
45
+ ddconfig:
46
+ #attn_type: "vanilla-xformers"
47
+ double_z: true
48
+ z_channels: 4
49
+ resolution: 256
50
+ in_channels: 3
51
+ out_ch: 3
52
+ ch: 128
53
+ ch_mult:
54
+ - 1
55
+ - 2
56
+ - 4
57
+ - 4
58
+ num_res_blocks: 2
59
+ attn_resolutions: []
60
+ dropout: 0.0
61
+ lossconfig:
62
+ target: torch.nn.Identity
63
+
64
+ cond_stage_config:
65
+ target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
66
+ params:
67
+ freeze: True
68
+ layer: "penultimate"
ComfyUI/models/configs/v2-inference-v_fp32.yaml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-4
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ parameterization: "v"
6
+ linear_start: 0.00085
7
+ linear_end: 0.0120
8
+ num_timesteps_cond: 1
9
+ log_every_t: 200
10
+ timesteps: 1000
11
+ first_stage_key: "jpg"
12
+ cond_stage_key: "txt"
13
+ image_size: 64
14
+ channels: 4
15
+ cond_stage_trainable: false
16
+ conditioning_key: crossattn
17
+ monitor: val/loss_simple_ema
18
+ scale_factor: 0.18215
19
+ use_ema: False # we set this to false because this is an inference only config
20
+
21
+ unet_config:
22
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
23
+ params:
24
+ use_checkpoint: True
25
+ use_fp16: False
26
+ image_size: 32 # unused
27
+ in_channels: 4
28
+ out_channels: 4
29
+ model_channels: 320
30
+ attention_resolutions: [ 4, 2, 1 ]
31
+ num_res_blocks: 2
32
+ channel_mult: [ 1, 2, 4, 4 ]
33
+ num_head_channels: 64 # need to fix for flash-attn
34
+ use_spatial_transformer: True
35
+ use_linear_in_transformer: True
36
+ transformer_depth: 1
37
+ context_dim: 1024
38
+ legacy: False
39
+
40
+ first_stage_config:
41
+ target: ldm.models.autoencoder.AutoencoderKL
42
+ params:
43
+ embed_dim: 4
44
+ monitor: val/rec_loss
45
+ ddconfig:
46
+ #attn_type: "vanilla-xformers"
47
+ double_z: true
48
+ z_channels: 4
49
+ resolution: 256
50
+ in_channels: 3
51
+ out_ch: 3
52
+ ch: 128
53
+ ch_mult:
54
+ - 1
55
+ - 2
56
+ - 4
57
+ - 4
58
+ num_res_blocks: 2
59
+ attn_resolutions: []
60
+ dropout: 0.0
61
+ lossconfig:
62
+ target: torch.nn.Identity
63
+
64
+ cond_stage_config:
65
+ target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
66
+ params:
67
+ freeze: True
68
+ layer: "penultimate"
ComfyUI/models/configs/v2-inference.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-4
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false
15
+ conditioning_key: crossattn
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ use_ema: False # we set this to false because this is an inference only config
19
+
20
+ unet_config:
21
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
22
+ params:
23
+ use_checkpoint: True
24
+ use_fp16: True
25
+ image_size: 32 # unused
26
+ in_channels: 4
27
+ out_channels: 4
28
+ model_channels: 320
29
+ attention_resolutions: [ 4, 2, 1 ]
30
+ num_res_blocks: 2
31
+ channel_mult: [ 1, 2, 4, 4 ]
32
+ num_head_channels: 64 # need to fix for flash-attn
33
+ use_spatial_transformer: True
34
+ use_linear_in_transformer: True
35
+ transformer_depth: 1
36
+ context_dim: 1024
37
+ legacy: False
38
+
39
+ first_stage_config:
40
+ target: ldm.models.autoencoder.AutoencoderKL
41
+ params:
42
+ embed_dim: 4
43
+ monitor: val/rec_loss
44
+ ddconfig:
45
+ #attn_type: "vanilla-xformers"
46
+ double_z: true
47
+ z_channels: 4
48
+ resolution: 256
49
+ in_channels: 3
50
+ out_ch: 3
51
+ ch: 128
52
+ ch_mult:
53
+ - 1
54
+ - 2
55
+ - 4
56
+ - 4
57
+ num_res_blocks: 2
58
+ attn_resolutions: []
59
+ dropout: 0.0
60
+ lossconfig:
61
+ target: torch.nn.Identity
62
+
63
+ cond_stage_config:
64
+ target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
65
+ params:
66
+ freeze: True
67
+ layer: "penultimate"
ComfyUI/models/configs/v2-inference_fp32.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 1.0e-4
3
+ target: ldm.models.diffusion.ddpm.LatentDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false
15
+ conditioning_key: crossattn
16
+ monitor: val/loss_simple_ema
17
+ scale_factor: 0.18215
18
+ use_ema: False # we set this to false because this is an inference only config
19
+
20
+ unet_config:
21
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
22
+ params:
23
+ use_checkpoint: True
24
+ use_fp16: False
25
+ image_size: 32 # unused
26
+ in_channels: 4
27
+ out_channels: 4
28
+ model_channels: 320
29
+ attention_resolutions: [ 4, 2, 1 ]
30
+ num_res_blocks: 2
31
+ channel_mult: [ 1, 2, 4, 4 ]
32
+ num_head_channels: 64 # need to fix for flash-attn
33
+ use_spatial_transformer: True
34
+ use_linear_in_transformer: True
35
+ transformer_depth: 1
36
+ context_dim: 1024
37
+ legacy: False
38
+
39
+ first_stage_config:
40
+ target: ldm.models.autoencoder.AutoencoderKL
41
+ params:
42
+ embed_dim: 4
43
+ monitor: val/rec_loss
44
+ ddconfig:
45
+ #attn_type: "vanilla-xformers"
46
+ double_z: true
47
+ z_channels: 4
48
+ resolution: 256
49
+ in_channels: 3
50
+ out_ch: 3
51
+ ch: 128
52
+ ch_mult:
53
+ - 1
54
+ - 2
55
+ - 4
56
+ - 4
57
+ num_res_blocks: 2
58
+ attn_resolutions: []
59
+ dropout: 0.0
60
+ lossconfig:
61
+ target: torch.nn.Identity
62
+
63
+ cond_stage_config:
64
+ target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
65
+ params:
66
+ freeze: True
67
+ layer: "penultimate"
ComfyUI/models/configs/v2-inpainting-inference.yaml ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ model:
2
+ base_learning_rate: 5.0e-05
3
+ target: ldm.models.diffusion.ddpm.LatentInpaintDiffusion
4
+ params:
5
+ linear_start: 0.00085
6
+ linear_end: 0.0120
7
+ num_timesteps_cond: 1
8
+ log_every_t: 200
9
+ timesteps: 1000
10
+ first_stage_key: "jpg"
11
+ cond_stage_key: "txt"
12
+ image_size: 64
13
+ channels: 4
14
+ cond_stage_trainable: false
15
+ conditioning_key: hybrid
16
+ scale_factor: 0.18215
17
+ monitor: val/loss_simple_ema
18
+ finetune_keys: null
19
+ use_ema: False
20
+
21
+ unet_config:
22
+ target: ldm.modules.diffusionmodules.openaimodel.UNetModel
23
+ params:
24
+ use_checkpoint: True
25
+ image_size: 32 # unused
26
+ in_channels: 9
27
+ out_channels: 4
28
+ model_channels: 320
29
+ attention_resolutions: [ 4, 2, 1 ]
30
+ num_res_blocks: 2
31
+ channel_mult: [ 1, 2, 4, 4 ]
32
+ num_head_channels: 64 # need to fix for flash-attn
33
+ use_spatial_transformer: True
34
+ use_linear_in_transformer: True
35
+ transformer_depth: 1
36
+ context_dim: 1024
37
+ legacy: False
38
+
39
+ first_stage_config:
40
+ target: ldm.models.autoencoder.AutoencoderKL
41
+ params:
42
+ embed_dim: 4
43
+ monitor: val/rec_loss
44
+ ddconfig:
45
+ #attn_type: "vanilla-xformers"
46
+ double_z: true
47
+ z_channels: 4
48
+ resolution: 256
49
+ in_channels: 3
50
+ out_ch: 3
51
+ ch: 128
52
+ ch_mult:
53
+ - 1
54
+ - 2
55
+ - 4
56
+ - 4
57
+ num_res_blocks: 2
58
+ attn_resolutions: [ ]
59
+ dropout: 0.0
60
+ lossconfig:
61
+ target: torch.nn.Identity
62
+
63
+ cond_stage_config:
64
+ target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
65
+ params:
66
+ freeze: True
67
+ layer: "penultimate"
68
+
69
+
70
+ data:
71
+ target: ldm.data.laion.WebDataModuleFromConfig
72
+ params:
73
+ tar_base: null # for concat as in LAION-A
74
+ p_unsafe_threshold: 0.1
75
+ filter_word_list: "data/filters.yaml"
76
+ max_pwatermark: 0.45
77
+ batch_size: 8
78
+ num_workers: 6
79
+ multinode: True
80
+ min_size: 512
81
+ train:
82
+ shards:
83
+ - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-0/{00000..18699}.tar -"
84
+ - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-1/{00000..18699}.tar -"
85
+ - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-2/{00000..18699}.tar -"
86
+ - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-3/{00000..18699}.tar -"
87
+ - "pipe:aws s3 cp s3://stability-aws/laion-a-native/part-4/{00000..18699}.tar -" #{00000-94333}.tar"
88
+ shuffle: 10000
89
+ image_key: jpg
90
+ image_transforms:
91
+ - target: torchvision.transforms.Resize
92
+ params:
93
+ size: 512
94
+ interpolation: 3
95
+ - target: torchvision.transforms.RandomCrop
96
+ params:
97
+ size: 512
98
+ postprocess:
99
+ target: ldm.data.laion.AddMask
100
+ params:
101
+ mode: "512train-large"
102
+ p_drop: 0.25
103
+ # NOTE use enough shards to avoid empty validation loops in workers
104
+ validation:
105
+ shards:
106
+ - "pipe:aws s3 cp s3://deep-floyd-s3/datasets/laion_cleaned-part5/{93001..94333}.tar - "
107
+ shuffle: 0
108
+ image_key: jpg
109
+ image_transforms:
110
+ - target: torchvision.transforms.Resize
111
+ params:
112
+ size: 512
113
+ interpolation: 3
114
+ - target: torchvision.transforms.CenterCrop
115
+ params:
116
+ size: 512
117
+ postprocess:
118
+ target: ldm.data.laion.AddMask
119
+ params:
120
+ mode: "512train-large"
121
+ p_drop: 0.25
122
+
123
+ lightning:
124
+ find_unused_parameters: True
125
+ modelcheckpoint:
126
+ params:
127
+ every_n_train_steps: 5000
128
+
129
+ callbacks:
130
+ metrics_over_trainsteps_checkpoint:
131
+ params:
132
+ every_n_train_steps: 10000
133
+
134
+ image_logger:
135
+ target: main.ImageLogger
136
+ params:
137
+ enable_autocast: False
138
+ disabled: False
139
+ batch_frequency: 1000
140
+ max_images: 4
141
+ increase_log_steps: False
142
+ log_first_step: False
143
+ log_images_kwargs:
144
+ use_ema_scope: False
145
+ inpaint: False
146
+ plot_progressive_rows: False
147
+ plot_diffusion_rows: False
148
+ N: 4
149
+ unconditional_guidance_scale: 5.0
150
+ unconditional_guidance_label: [""]
151
+ ddim_steps: 50 # todo check these out for depth2img,
152
+ ddim_eta: 0.0 # todo check these out for depth2img,
153
+
154
+ trainer:
155
+ benchmark: True
156
+ val_check_interval: 5000000
157
+ num_sanity_val_steps: 0
158
+ accumulate_grad_batches: 1
ComfyUI/models/controlnet/put_controlnets_and_t2i_here ADDED
File without changes
ComfyUI/models/diffusers/put_diffusers_models_here ADDED
File without changes
ComfyUI/models/diffusion_models/put_diffusion_model_files_here.safetensors ADDED
File without changes