add experimental tiled IPAdapter

This commit is contained in:
matt3o
2024-02-02 20:06:27 +01:00
parent 46241f3ba5
commit 90d3451cd9
3 changed files with 856 additions and 45 deletions
+189 -32
View File
@@ -1,15 +1,15 @@
import torch
import contextlib
import os
import math
import comfy.utils
import comfy.model_management
from comfy.clip_vision import clip_preprocess
from comfy.clip_vision import clip_preprocess, Output
from comfy.ldm.modules.attention import optimized_attention
from nodes import MAX_RESOLUTION
import folder_paths
from torch import nn
import torch.nn as nn
from PIL import Image
import torch.nn.functional as F
import torchvision.transforms as TT
@@ -167,6 +167,82 @@ def set_model_patch_replace(model, patch_kwargs, key):
else:
to["patches_replace"]["attn2"][key].set_new_condition(**patch_kwargs)
def masked_tiling(image, short_side_tiles, weight=0.6, blur=0):
_, orig_height, orig_width, _ = image.shape
tile_size = 224
if orig_width < orig_height:
num_tiles_x = short_side_tiles
new_width = tile_size * num_tiles_x
new_height = orig_height * new_width // orig_width
num_tiles_y = new_height // tile_size
else:
num_tiles_y = short_side_tiles
new_height = tile_size * num_tiles_y
new_width = orig_width * new_height // orig_height
num_tiles_x = new_width // tile_size
start_x = start_y = 0
if (new_height % tile_size) >= tile_size//4:
num_tiles_y += 1
else:
start_y = (new_height - tile_size*num_tiles_y) // 2
if (new_width % tile_size) >= tile_size//4:
num_tiles_x += 1
else:
start_x = (new_width - tile_size*num_tiles_x) // 2
weight = 1.0 if num_tiles_x == 1 or num_tiles_y == 1 else weight
ref_image = F.interpolate(image.permute([0,3,1,2]), size=(new_height, new_width), mode="bicubic").permute([0,2,3,1])
tiles = []
attn_mask = []
for i in range(num_tiles_y):
for j in range(num_tiles_x):
start_height = i * tile_size + start_y
end_height = start_height + tile_size + start_y
start_width = j * tile_size + start_x
end_width = start_width + tile_size + start_x
if end_height > new_height:
start_height = new_height - tile_size
end_height = new_height
if end_width > new_width:
start_width = new_width - tile_size
end_width = new_width
tile = ref_image[:1, start_height:end_height, start_width:end_width, :]
tiles.append(tile.squeeze(0))
# create mask
mask = torch.zeros([new_height, new_width], dtype=image.dtype, device=image.device)
mask[start_height:end_height, start_width:end_width] = weight
attn_mask.append(mask)
image = torch.stack(tiles, dim=0)
attn_mask = torch.stack(attn_mask, dim=0)
# If we have a lot of tiles we add bigger tiles with a higher weight to give clipvision a better idea of the overall image
if num_tiles_x != 1 and num_tiles_y != 1:
comp_tiles, comp_attn_mask = masked_tiling(ref_image, 1, 1.0, blur)
if image.shape[1:3] != comp_tiles.shape[1:3]:
comp_tiles = F.interpolate(comp_tiles.permute([0,3,1,2]), size=(image.shape[1], image.shape[2]), mode="bicubic").permute([0,2,3,1])
image = torch.cat([comp_tiles, image], dim=0)
if attn_mask.shape[1:3] != comp_attn_mask.shape[1:3]:
comp_attn_mask = F.interpolate(comp_attn_mask.unsqueeze(1), size=(attn_mask.shape[1], attn_mask.shape[2]), mode="bicubic").squeeze(1)
attn_mask = torch.cat([comp_attn_mask, attn_mask], dim=0)
if blur > 0:
attn_mask = TT.GaussianBlur(int(6*blur+1), blur)(attn_mask.unsqueeze(1)).permute([0,2,3,1]).squeeze(-1)
return image, attn_mask
def image_add_noise(image, noise):
image = image.permute([0,3,1,2])
torch.manual_seed(0) # use a fixed random for reproducible results
@@ -187,8 +263,23 @@ def zeroed_hidden_states(clip_vision, batch_size):
comfy.model_management.load_model_gpu(clip_vision.patcher)
pixel_values = clip_preprocess(image.to(clip_vision.load_device)).float()
outputs = clip_vision.model(pixel_values=pixel_values, intermediate_output=-2)
# we only need the penultimate hidden states
outputs = outputs[1].to(comfy.model_management.intermediate_device())
return outputs[1].to(comfy.model_management.intermediate_device())
def encode_image_masked(clip_vision, image, mask=None):
comfy.model_management.load_model_gpu(clip_vision.patcher)
pixel_values = clip_preprocess(image.to(clip_vision.load_device)).float()
if mask is not None:
pixel_values = pixel_values * mask.to(clip_vision.load_device)
out = clip_vision.model(pixel_values=pixel_values, intermediate_output=-2)
outputs = Output()
outputs["last_hidden_state"] = out[0].to(comfy.model_management.intermediate_device())
outputs["image_embeds"] = out[2].to(comfy.model_management.intermediate_device())
outputs["penultimate_hidden_states"] = out[1].to(comfy.model_management.intermediate_device())
return outputs
def min_(tensor_list):
@@ -238,8 +329,8 @@ def contrast_adaptive_sharpening(image, amount):
div = torch.reciprocal(1 + 4*w)
output = ((b + d + f + h)*w + e) * div
output = output.clamp(0, 1)
output = torch.nan_to_num(output)
output = output.clamp(0, 1)
return (output)
@@ -430,7 +521,6 @@ class CrossAttentionPatch:
if weight_type.startswith("channel"):
# code by Lvmin Zhang at Stanford University as also seen on Fooocus IPAdapter implementation
# please read licensing notes https://github.com/lllyasviel/Fooocus/blob/69a23c4d60c9e627409d0cb0f8862cdb015488eb/extras/ip_adapter.py#L234
ip_v_mean = torch.mean(ip_v, dim=1, keepdim=True)
ip_v_offset = ip_v - ip_v_mean
_, _, C = ip_k.shape
@@ -510,7 +600,7 @@ class IPAdapterModelLoader:
elif key.startswith("ip_adapter."):
st_model["ip_adapter"][key.replace("ip_adapter.", "")] = model[key]
model = st_model
if not "ip_adapter" in model.keys() or not model["ip_adapter"]:
raise Exception("invalid IPAdapter model {}".format(ckpt_path))
@@ -566,11 +656,32 @@ class IPAdapterApply:
}
}
RETURN_TYPES = ("MODEL", )
RETURN_TYPES = ("MODEL",)
FUNCTION = "apply_ipadapter"
CATEGORY = "ipadapter"
def apply_ipadapter(self, ipadapter, model, weight, clip_vision=None, image=None, weight_type="original", noise=None, embeds=None, attn_mask=None, start_at=0.0, end_at=1.0, unfold_batch=False, insightface=None, faceid_v2=False, weight_v2=False):
def apply_ipadapter(self,
ipadapter,
model,
weight,
clip_vision=None,
image=None,
weight_type="original",
noise=None,
embeds=None,
attn_mask=None,
start_at=0.0,
end_at=1.0,
unfold_batch=False,
insightface=None,
faceid_v2=False,
weight_v2=False,
clip_vision_mask=None,
short_side_tiles=0,
tile_weight=0.0,
tile_blur=0,
):
self.dtype = torch.float16 if comfy.model_management.should_use_fp16() else torch.float32
self.device = comfy.model_management.get_torch_device()
self.weight = weight
@@ -578,6 +689,10 @@ class IPAdapterApply:
self.is_portrait = "proj.2.weight" in ipadapter["image_proj"] and not "proj.3.weight" in ipadapter["image_proj"] and not "0.to_q_lora.down.weight" in ipadapter["ip_adapter"]
self.is_faceid = self.is_portrait or "0.to_q_lora.down.weight" in ipadapter["ip_adapter"]
self.is_plus = (self.is_full or "latents" in ipadapter["image_proj"] or "perceiver_resampler.proj_in.weight" in ipadapter["image_proj"])
is_tiled = True if short_side_tiles > 0 else False
if is_tiled:
image, attn_mask = masked_tiling(image, short_side_tiles, tile_weight, tile_blur)
if self.is_faceid and not insightface:
raise Exception('InsightFace must be provided for FaceID models.')
@@ -633,7 +748,7 @@ class IPAdapterApply:
if image.shape[1] != image.shape[2]:
print("\033[33mINFO: the IPAdapter reference image is not a square, CLIPImageProcessor will resize and crop it at the center. If the main focus of the picture is not in the middle the result might not be what you are expecting.\033[0m")
clip_embed = clip_vision.encode_image(image)
clip_embed = encode_image_masked(clip_vision, image, clip_vision_mask)
neg_image = image_add_noise(image, noise) if noise > 0 else None
if self.is_plus:
@@ -674,52 +789,92 @@ class IPAdapterApply:
image_prompt_embeds = image_prompt_embeds.to(self.device, dtype=self.dtype)
uncond_image_prompt_embeds = uncond_image_prompt_embeds.to(self.device, dtype=self.dtype)
work_model = model.clone()
self.work_model = model.clone()
if attn_mask is not None:
attn_mask = attn_mask.to(self.device)
sigma_start = model.model.model_sampling.percent_to_sigma(start_at)
sigma_end = model.model.model_sampling.percent_to_sigma(end_at)
sigma_start = self.work_model.model.model_sampling.percent_to_sigma(start_at)
sigma_end = self.work_model.model.model_sampling.percent_to_sigma(end_at)
if is_tiled:
for i in range(image_prompt_embeds.shape[0]): #TODO: check if we can apply one mask per image in the attention patch phase
patch_kwargs = {
"number": 0,
"weight": self.weight,
"ipadapter": self.ipadapter,
"cond": image_prompt_embeds[i].unsqueeze(0),
"uncond": uncond_image_prompt_embeds[i].unsqueeze(0),
"weight_type": weight_type,
"mask": attn_mask[i].unsqueeze(0),
"sigma_start": sigma_start,
"sigma_end": sigma_end,
"unfold_batch": unfold_batch,
}
self.apply_patch(patch_kwargs)
else:
patch_kwargs = {
"number": 0,
"weight": self.weight,
"ipadapter": self.ipadapter,
"cond": image_prompt_embeds,
"uncond": uncond_image_prompt_embeds,
"weight_type": weight_type,
"mask": attn_mask,
"sigma_start": sigma_start,
"sigma_end": sigma_end,
"unfold_batch": unfold_batch,
}
self.apply_patch(patch_kwargs)
patch_kwargs = {
"number": 0,
"weight": self.weight,
"ipadapter": self.ipadapter,
"cond": image_prompt_embeds,
"uncond": uncond_image_prompt_embeds,
"weight_type": weight_type,
"mask": attn_mask,
"sigma_start": sigma_start,
"sigma_end": sigma_end,
"unfold_batch": unfold_batch,
}
return (self.work_model, attn_mask, image,)
def apply_patch(self, patch_kwargs):
if not self.is_sdxl:
for id in [1,2,4,5,7,8]: # id of input_blocks that have cross attention
set_model_patch_replace(work_model, patch_kwargs, ("input", id))
set_model_patch_replace(self.work_model, patch_kwargs, ("input", id))
patch_kwargs["number"] += 1
for id in [3,4,5,6,7,8,9,10,11]: # id of output_blocks that have cross attention
set_model_patch_replace(work_model, patch_kwargs, ("output", id))
set_model_patch_replace(self.work_model, patch_kwargs, ("output", id))
patch_kwargs["number"] += 1
set_model_patch_replace(work_model, patch_kwargs, ("middle", 0))
set_model_patch_replace(self.work_model, patch_kwargs, ("middle", 0))
else:
for id in [4,5,7,8]: # id of input_blocks that have cross attention
block_indices = range(2) if id in [4, 5] else range(10) # transformer_depth
for index in block_indices:
set_model_patch_replace(work_model, patch_kwargs, ("input", id, index))
set_model_patch_replace(self.work_model, patch_kwargs, ("input", id, index))
patch_kwargs["number"] += 1
for id in range(6): # id of output_blocks that have cross attention
block_indices = range(2) if id in [3, 4, 5] else range(10) # transformer_depth
for index in block_indices:
set_model_patch_replace(work_model, patch_kwargs, ("output", id, index))
set_model_patch_replace(self.work_model, patch_kwargs, ("output", id, index))
patch_kwargs["number"] += 1
for index in range(10):
set_model_patch_replace(work_model, patch_kwargs, ("middle", 0, index))
set_model_patch_replace(self.work_model, patch_kwargs, ("middle", 0, index))
patch_kwargs["number"] += 1
return (work_model, )
class IPAdapterTilesMasked(IPAdapterApply):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"ipadapter": ("IPADAPTER", ),
"clip_vision": ("CLIP_VISION",),
"image": ("IMAGE",),
"model": ("MODEL", ),
"weight": ("FLOAT", { "default": 0.7, "min": -1, "max": 3, "step": 0.05 }),
"noise": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
"weight_type": (["original", "linear", "channel penalty"], ),
"start_at": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001 }),
"end_at": ("FLOAT", { "default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001 }),
"short_side_tiles": ("INT", { "default": 1, "min": 0, "max": 12, "step": 1 }),
"tile_weight": ("FLOAT", { "default": 0.6, "min": 0.0, "max": 1.0, "step": 0.05 }),
#"tile_blur": ("INT", { "default": 0, "min": 0, "max": 112, "step": 1 }),
},
}
RETURN_TYPES = ("MODEL",)
class IPAdapterApplyFaceID(IPAdapterApply):
@classmethod
def INPUT_TYPES(s):
@@ -995,6 +1150,7 @@ class IPAdapterBatchEmbeds:
NODE_CLASS_MAPPINGS = {
"IPAdapterModelLoader": IPAdapterModelLoader,
"IPAdapterApply": IPAdapterApply,
"IPAdapterTilesMasked": IPAdapterTilesMasked,
"IPAdapterApplyFaceID": IPAdapterApplyFaceID,
"IPAdapterApplyEncoded": IPAdapterApplyEncoded,
"PrepImageForClipVision": PrepImageForClipVision,
@@ -1009,6 +1165,7 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = {
"IPAdapterModelLoader": "Load IPAdapter Model",
"IPAdapterApply": "Apply IPAdapter",
"IPAdapterTilesMasked": "IPAdapter Masked Tiles (experimental)",
"IPAdapterApplyFaceID": "Apply IPAdapter FaceID",
"IPAdapterApplyEncoded": "Apply IPAdapter from Encoded",
"PrepImageForClipVision": "Prepare Image For Clip Vision",
+18 -13
View File
@@ -5,6 +5,8 @@ IPAdapter implementation that follows the ComfyUI way of doing things. The code
## Important updates
**2024/02/02**: Added experimental [tiled IPAdapter](#tiled-ipadapter). It lets you easily handle reference images that are not square. Can be useful for upscaling.
**2024/01/19**: Support for FaceID Portrait models.
**2024/01/16**: Notably increased quality of FaceID Plus/v2 models. Check the [comparison](https://github.com/cubiq/ComfyUI_IPAdapter_plus/issues/195) of all face models.
@@ -19,12 +21,6 @@ IPAdapter implementation that follows the ComfyUI way of doing things. The code
**2023/11/29**: Added `unfold_batch` option to send the reference images sequentially to a latent batch. Useful for animations.
**2023/11/26**: Added [timestepping](#timestepping). You may need to delete the old nodes and recreate them. **Important:** For this to work you need to update ComfyUI to the latest version.
**2023/11/24**: Support for multiple attention masks.
**2023/11/23**: Small but important update: the new default location for the IPAdapter models is `ComfyUI/models/ipadapter`. **No panic**: the legacy `ComfyUI/custom_nodes/ComfyUI_IPAdapter_plus/models` location still works and nothing will break.
*(previous updates removed for better readability)*
## What is it?
@@ -194,6 +190,22 @@ In the `Apply IPAdapter` node you can set a start and an end point. The IPAdapte
<img src="./examples/timestepping.jpg" width="100%" alt="timestepping" />
### Tiled IPAdapter
This is an experimental node that automatically splits a reference image in quadrants. It can be especially useful when the reference image is not in 1:1 ratio as the Clip Vision encoder only works with 224x224 square images.
The `short_side_tiles` parameter defines the number of tiles to use for ther shorter side of the reference image; the number of tiles for the other side are calculated automatically. If the image is in landscape or portrait mode that generally means that only 2 tiles are created. If the image is a square, the value must be at least `2` for the node to have any meaninful effect.
**If the aspect ratio of the refence image and the latent are very diffirent the image will almost certainly be stretched or squished.**
`tile_weight` is very important any time the `short_side_tiles` value is greater than 1. The default value of `0.6` should be good for most scenarios but you may need to lower it a little (0.5) for SDXL. **This parameter has no effect if the `short_side_tiles` is = 1.**
The main IPAdapter `weight` should also be lowered, `0.7` is a good starting point.
**Important:** With a high number of tiles we are going to reiterate on the same _concept_ multiple times so I suggest to lower the CFG or better use the CFG Rescale node. Please check the workflow in the examples directory for reference.
The node is experimental and will likely change in the future.
### FaceID
FaceID is a new IPAdapter model that takes the embeddings from [InsightFace](https://github.com/deepinsight/insightface). As such you need to install `insightface` in your ComfyUI python environment. You may also need `onnxruntime` and `onnxruntime-gpu`. Note that your CUDA version might not be compatible with onnxruntime, in that case you can select the "CPU" provider from the `Load InsightFace model` node.
@@ -229,10 +241,3 @@ If you are interested I've also implemented the same features for [Huggingface D
- [laksjdjf](https://github.com/laksjdjf/IPAdapter-ComfyUI/)
- [fooocus](https://github.com/lllyasviel/Fooocus/blob/main/fooocus_extras/ip_adapter.py)
## IPAdapter in the wild
Let me know if you spot the IPAdapter in the wild or tag @latentvision in the video description!
- For German speakers you can find interesting YouTube tutorials on [A Latent Place](https://www.youtube.com/watch?v=rAWn_0YOBU0).
- In Chinese [Introversify](https://www.youtube.com/watch?v=xl8f3oxZgY8)
- [Scott Detweiler](https://www.youtube.com/watch?v=xzGdynQDzsM) covered this extension.
+649
View File
@@ -0,0 +1,649 @@
{
"last_node_id": 27,
"last_link_id": 53,
"nodes": [
{
"id": 1,
"type": "CheckpointLoaderSimple",
"pos": [
140,
770
],
"size": {
"0": 300,
"1": 100
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
51
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
5,
6
],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"sd15/realisticVisionV51_v51VAE.safetensors"
]
},
{
"id": 2,
"type": "VAELoader",
"pos": [
890,
820
],
"size": {
"0": 300,
"1": 60
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [
12
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAELoader"
},
"widgets_values": [
"vae-ft-mse-840000-ema-pruned.safetensors"
]
},
{
"id": 25,
"type": "IPAdapterTilesMasked",
"pos": [
530,
170
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "ipadapter",
"type": "IPADAPTER",
"link": 48
},
{
"name": "clip_vision",
"type": "CLIP_VISION",
"link": 49
},
{
"name": "image",
"type": "IMAGE",
"link": 50
},
{
"name": "model",
"type": "MODEL",
"link": 51
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
52
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "IPAdapterTilesMasked"
},
"widgets_values": [
0.65,
0,
"original",
0,
1,
1,
0.6
]
},
{
"id": 9,
"type": "KSampler",
"pos": [
880,
500
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 31
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 8
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 9
},
{
"name": "latent_image",
"type": "LATENT",
"link": 10
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
11
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
11,
"fixed",
30,
6,
"ddpm",
"karras",
1
]
},
{
"id": 18,
"type": "RescaleCFG",
"pos": [
980,
390
],
"size": [
210,
58
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 52
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
31
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "RescaleCFG"
},
"widgets_values": [
0.9
]
},
{
"id": 4,
"type": "CLIPVisionLoader",
"pos": [
150,
190
],
"size": {
"0": 300,
"1": 60
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "CLIP_VISION",
"type": "CLIP_VISION",
"links": [
49
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPVisionLoader"
},
"widgets_values": [
"IPAdapter_image_encoder_sd15.safetensors"
]
},
{
"id": 3,
"type": "IPAdapterModelLoader",
"pos": [
150,
80
],
"size": {
"0": 300,
"1": 60
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "IPADAPTER",
"type": "IPADAPTER",
"links": [
48
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "IPAdapterModelLoader"
},
"widgets_values": [
"ip-adapter-plus_sd15.safetensors"
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
570,
500
],
"size": {
"0": 210,
"1": 120
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
8
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 8,
"type": "CLIPTextEncode",
"pos": [
570,
680
],
"size": {
"0": 210,
"1": 120
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 6
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
9
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
],
"color": "#322",
"bgcolor": "#533"
},
{
"id": 6,
"type": "LoadImage",
"pos": [
50,
310
],
"size": {
"0": 404.8474426269531,
"1": 404.49200439453125
},
"flags": {},
"order": 4,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
50
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 11,
"type": "VAEDecode",
"pos": [
1253,
502
],
"size": {
"0": 140,
"1": 50
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 11
},
{
"name": "vae",
"type": "VAE",
"link": 12
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
53
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 27,
"type": "SaveImage",
"pos": [
1450,
503
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 53
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 10,
"type": "EmptyLatentImage",
"pos": [
570,
860
],
"size": {
"0": 210,
"1": 110
},
"flags": {},
"order": 5,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
10
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
}
],
"links": [
[
5,
1,
1,
7,
0,
"CLIP"
],
[
6,
1,
1,
8,
0,
"CLIP"
],
[
8,
7,
0,
9,
1,
"CONDITIONING"
],
[
9,
8,
0,
9,
2,
"CONDITIONING"
],
[
10,
10,
0,
9,
3,
"LATENT"
],
[
11,
9,
0,
11,
0,
"LATENT"
],
[
12,
2,
0,
11,
1,
"VAE"
],
[
31,
18,
0,
9,
0,
"MODEL"
],
[
48,
3,
0,
25,
0,
"IPADAPTER"
],
[
49,
4,
0,
25,
1,
"CLIP_VISION"
],
[
50,
6,
0,
25,
2,
"IMAGE"
],
[
51,
1,
0,
25,
3,
"MODEL"
],
[
52,
25,
0,
18,
0,
"MODEL"
],
[
53,
11,
0,
27,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}