diff --git a/README.md b/README.md index 51d33e4..a60ed8f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ ![basic workflow](examples/pulid_wf.jpg) +## Important updates + +- **2024.05.12:** Added the Advanced node, allows fine tuning of the generation. + ## Notes The code can be considered beta, things may change in the coming days. In the `examples` directory you'll find some basic workflows. @@ -18,7 +22,11 @@ Testing other models though I noticed some quality degradation. You may need to ## The 'method' parameter -`method` applies the weights in different ways. `Fidelity` is closer to the reference ID, `Style` leaves more freedom to the checkpoint. Sometimes the difference is minimal. I've added `neutral` that doesn't do any normalization so the reference is very strong and you need to lower the weight. +`method` applies the weights in different ways. `Fidelity` is closer to the reference ID, `Style` leaves more freedom to the checkpoint. Sometimes the difference is minimal. I've added `neutral` that doesn't do any normalization, if you use this option with the standard Apply node be sure to lower the weight. With the Advanced node you can simply increase the `fidelity` value. + +The Advanced node has a `fidelity` slider and a `projection` option. `ortho_v2` with `fidelity: 8` is the same as `fidelity` method in the standard node. Projection `ortho` and `fidelity: 16` is the same as method `style`. + +**Lower `fidelity` values grant higher resemblance to the reference image.** ## Installation diff --git a/examples/PuLID_attention_mask.json b/examples/PuLID_attention_mask.json new file mode 100644 index 0000000..305aa56 --- /dev/null +++ b/examples/PuLID_attention_mask.json @@ -0,0 +1,946 @@ +{ + "last_node_id": 88, + "last_link_id": 248, + "nodes": [ + { + "id": 5, + "type": "EmptyLatentImage", + "pos": [ + 350, + 265 + ], + "size": { + "0": 315, + "1": 106 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 2 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [ + 1280, + 960, + 1 + ] + }, + { + "id": 33, + "type": "ApplyPulid", + "pos": [ + 350, + -10 + ], + "size": { + "0": 315, + "1": 230 + }, + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 133 + }, + { + "name": "pulid", + "type": "PULID", + "link": 117 + }, + { + "name": "eva_clip", + "type": "EVA_CLIP", + "link": 81 + }, + { + "name": "face_analysis", + "type": "FACEANALYSIS", + "link": 82 + }, + { + "name": "image", + "type": "IMAGE", + "link": 114 + }, + { + "name": "attn_mask", + "type": "MASK", + "link": 247 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 141 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ApplyPulid" + }, + "widgets_values": [ + "fidelity", + 0.7000000000000001, + 0, + 1 + ] + }, + { + "id": 85, + "type": "SolidMask", + "pos": [ + -307, + 584 + ], + "size": [ + 210, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 244 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 0, + 1280, + 960 + ] + }, + { + "id": 49, + "type": "LoadImage", + "pos": [ + 407, + 550 + ], + "size": [ + 248.03589794921936, + 339.7795556640626 + ], + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 145 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "venere.jpg", + "image" + ] + }, + { + "id": 48, + "type": "InvertMask", + "pos": [ + 526, + 438 + ], + "size": [ + 140, + 26 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 246 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 151 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "InvertMask" + } + }, + { + "id": 8, + "type": "VAEDecode", + "pos": [ + 1575, + 160 + ], + "size": { + "0": 140, + "1": 46 + }, + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 7 + }, + { + "name": "vae", + "type": "VAE", + "link": 8 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 10 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 10, + "type": "PreviewImage", + "pos": [ + 1592, + 279 + ], + "size": [ + 1370.7157657734379, + 1041.8039240156252 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 10 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 16, + "type": "PulidModelLoader", + "pos": [ + -111, + -181 + ], + "size": { + "0": 304.0072021484375, + "1": 58 + }, + "flags": {}, + "order": 3, + "mode": 0, + "outputs": [ + { + "name": "PULID", + "type": "PULID", + "links": [ + 117, + 136 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "PulidModelLoader" + }, + "widgets_values": [ + "ip-adapter_pulid_sdxl_fp16.safetensors" + ] + }, + { + "id": 19, + "type": "PulidEvaClipLoader", + "pos": [ + 54, + -69 + ], + "size": { + "0": 140, + "1": 26 + }, + "flags": {}, + "order": 4, + "mode": 0, + "outputs": [ + { + "name": "EVA_CLIP", + "type": "EVA_CLIP", + "links": [ + 81, + 137 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "PulidEvaClipLoader" + } + }, + { + "id": 17, + "type": "PulidInsightFaceLoader", + "pos": [ + -18, + 12 + ], + "size": { + "0": 210, + "1": 58 + }, + "flags": {}, + "order": 5, + "mode": 0, + "outputs": [ + { + "name": "FACEANALYSIS", + "type": "FACEANALYSIS", + "links": [ + 82, + 138 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "PulidInsightFaceLoader" + }, + "widgets_values": [ + "CPU" + ] + }, + { + "id": 12, + "type": "LoadImage", + "pos": [ + -34, + 145 + ], + "size": [ + 261.645185990767, + 346.38255171342325 + ], + "flags": {}, + "order": 6, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 114 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "monalisa.png", + "image" + ] + }, + { + "id": 87, + "type": "MaskComposite", + "pos": [ + 15, + 546 + ], + "size": [ + 210, + 126 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "MASK", + "link": 244 + }, + { + "name": "source", + "type": "MASK", + "link": 245 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 246, + 247 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskComposite" + }, + "widgets_values": [ + 0, + 0, + "add" + ] + }, + { + "id": 86, + "type": "SolidMask", + "pos": [ + -304, + 747 + ], + "size": { + "0": 210, + "1": 106 + }, + "flags": {}, + "order": 7, + "mode": 0, + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 245 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 1, + 640, + 960 + ] + }, + { + "id": 23, + "type": "CLIPTextEncode", + "pos": [ + 756, + -47 + ], + "size": [ + 316.32471195096673, + 101.97065006593618 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 94 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 34 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "blurry, malformed, low quality, worst quality, artifacts, noise, text, watermark, glitch, deformed, ugly, horror, ill" + ] + }, + { + "id": 47, + "type": "ApplyPulid", + "pos": [ + 765, + 128 + ], + "size": { + "0": 315, + "1": 230 + }, + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 141 + }, + { + "name": "pulid", + "type": "PULID", + "link": 136 + }, + { + "name": "eva_clip", + "type": "EVA_CLIP", + "link": 137 + }, + { + "name": "face_analysis", + "type": "FACEANALYSIS", + "link": 138 + }, + { + "name": "image", + "type": "IMAGE", + "link": 145 + }, + { + "name": "attn_mask", + "type": "MASK", + "link": 151 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 142 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ApplyPulid" + }, + "widgets_values": [ + "fidelity", + 0.7000000000000001, + 0, + 1 + ] + }, + { + "id": 55, + "type": "CLIPTextEncode", + "pos": [ + 755, + -211 + ], + "size": { + "0": 315.23089599609375, + "1": 113.96450805664062 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 156 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 160 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "closeup two girl friends on the streets of a cyberpunk city, cinematic, hoodie, multicolored hair, highly detailed, 4k, high resolution" + ] + }, + { + "id": 3, + "type": "KSampler", + "pos": [ + 1162, + 38 + ], + "size": { + "0": 341.2750244140625, + "1": 262 + }, + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 142 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 160 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 34 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 2 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 7 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 70, + "fixed", + 30, + 6, + "dpmpp_2m", + "karras", + 1 + ] + }, + { + "id": 4, + "type": "CheckpointLoaderSimple", + "pos": [ + -131, + -342 + ], + "size": { + "0": 319.03692626953125, + "1": 101.3391342163086 + }, + "flags": {}, + "order": 8, + "mode": 0, + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 133 + ], + "slot_index": 0 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 94, + 156 + ], + "slot_index": 1 + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 8 + ], + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "sdxl/AlbedoBaseXL.safetensors" + ] + } + ], + "links": [ + [ + 2, + 5, + 0, + 3, + 3, + "LATENT" + ], + [ + 7, + 3, + 0, + 8, + 0, + "LATENT" + ], + [ + 8, + 4, + 2, + 8, + 1, + "VAE" + ], + [ + 10, + 8, + 0, + 10, + 0, + "IMAGE" + ], + [ + 34, + 23, + 0, + 3, + 2, + "CONDITIONING" + ], + [ + 81, + 19, + 0, + 33, + 2, + "EVA_CLIP" + ], + [ + 82, + 17, + 0, + 33, + 3, + "FACEANALYSIS" + ], + [ + 94, + 4, + 1, + 23, + 0, + "CLIP" + ], + [ + 114, + 12, + 0, + 33, + 4, + "IMAGE" + ], + [ + 117, + 16, + 0, + 33, + 1, + "PULID" + ], + [ + 133, + 4, + 0, + 33, + 0, + "MODEL" + ], + [ + 136, + 16, + 0, + 47, + 1, + "PULID" + ], + [ + 137, + 19, + 0, + 47, + 2, + "EVA_CLIP" + ], + [ + 138, + 17, + 0, + 47, + 3, + "FACEANALYSIS" + ], + [ + 141, + 33, + 0, + 47, + 0, + "MODEL" + ], + [ + 142, + 47, + 0, + 3, + 0, + "MODEL" + ], + [ + 145, + 49, + 0, + 47, + 4, + "IMAGE" + ], + [ + 151, + 48, + 0, + 47, + 5, + "MASK" + ], + [ + 156, + 4, + 1, + 55, + 0, + "CLIP" + ], + [ + 160, + 55, + 0, + 3, + 1, + "CONDITIONING" + ], + [ + 244, + 85, + 0, + 87, + 0, + "MASK" + ], + [ + 245, + 86, + 0, + 87, + 1, + "MASK" + ], + [ + 246, + 87, + 0, + 48, + 0, + "MASK" + ], + [ + 247, + 87, + 0, + 33, + 5, + "MASK" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/examples/Pulid_simple.json b/examples/PuLID_simple.json similarity index 100% rename from examples/Pulid_simple.json rename to examples/PuLID_simple.json diff --git a/pulid.py b/pulid.py index 4e7a63e..867e7cf 100644 --- a/pulid.py +++ b/pulid.py @@ -1,7 +1,9 @@ import torch from torch import nn import torchvision.transforms as T +import torch.nn.functional as F import os +import math import folder_paths import comfy.utils from insightface.app import FaceAnalysis @@ -68,6 +70,8 @@ def tensor_to_size(source, dest_size): source = torch.cat((source, source[-1:].repeat(shape)), dim=0) elif source_size > dest_size: source = source[:dest_size] + + return source def set_model_patch_replace(model, patch_kwargs, key): to = model.model_options["transformer_options"].copy() @@ -110,14 +114,16 @@ class Attn2Replace: return out.to(dtype=dtype) -def pulid_attention(out, q, k, v, extra_options, module_key='', pulid=None, cond=None, uncond=None, weight=1.0, num_zero=8, ortho=False, ortho_v2=False, **kwargs): +def pulid_attention(out, q, k, v, extra_options, module_key='', pulid=None, cond=None, uncond=None, weight=1.0, ortho=False, ortho_v2=False, mask=None, **kwargs): k_key = module_key + "_to_k_ip" v_key = module_key + "_to_v_ip" dtype = q.dtype + seq_len = q.shape[1] cond_or_uncond = extra_options["cond_or_uncond"] b = q.shape[0] batch_prompt = b // len(cond_or_uncond) + _, _, oh, ow = extra_options["original_shape"] #conds = torch.cat([uncond.repeat(batch_prompt, 1, 1), cond.repeat(batch_prompt, 1, 1)], dim=0) #zero_tensor = torch.zeros((conds.size(0), num_zero, conds.size(-1)), dtype=conds.dtype, device=conds.device) @@ -125,10 +131,6 @@ def pulid_attention(out, q, k, v, extra_options, module_key='', pulid=None, cond #ip_k = pulid.ip_layers.to_kvs[k_key](conds) #ip_v = pulid.ip_layers.to_kvs[v_key](conds) - if num_zero > 0: - zero_tensor = torch.zeros((cond.size(0), num_zero, cond.size(-1)), dtype=cond.dtype, device=cond.device) - cond = torch.cat([cond, zero_tensor], dim=1) - uncond = torch.cat([uncond, zero_tensor], dim=1) k_cond = pulid.ip_layers.to_kvs[k_key](cond).repeat(batch_prompt, 1, 1) k_uncond = pulid.ip_layers.to_kvs[k_key](uncond).repeat(batch_prompt, 1, 1) v_cond = pulid.ip_layers.to_kvs[v_key](cond).repeat(batch_prompt, 1, 1) @@ -137,13 +139,13 @@ def pulid_attention(out, q, k, v, extra_options, module_key='', pulid=None, cond ip_v = torch.cat([(v_cond, v_uncond)[i] for i in cond_or_uncond], dim=0) out_ip = optimized_attention(q, ip_k, ip_v, extra_options["n_heads"]) - + if ortho: out = out.to(dtype=torch.float32) out_ip = out_ip.to(dtype=torch.float32) projection = (torch.sum((out * out_ip), dim=-2, keepdim=True) / torch.sum((out * out), dim=-2, keepdim=True) * out) orthogonal = out_ip - projection - out = weight * orthogonal + out_ip = weight * orthogonal elif ortho_v2: out = out.to(dtype=torch.float32) out_ip = out_ip.to(dtype=torch.float32) @@ -152,11 +154,35 @@ def pulid_attention(out, q, k, v, extra_options, module_key='', pulid=None, cond attn_mean = attn_mean[:, :, :5].sum(dim=-1, keepdim=True) projection = (torch.sum((out * out_ip), dim=-2, keepdim=True) / torch.sum((out * out), dim=-2, keepdim=True) * out) orthogonal = out_ip + (attn_mean - 1) * projection - out = weight * orthogonal + out_ip = weight * orthogonal else: - out = out_ip * weight + out_ip = out_ip * weight - return out.to(dtype=dtype) + if mask is not None: + mask_h = oh / math.sqrt(oh * ow / seq_len) + mask_h = int(mask_h) + int((seq_len % int(mask_h)) != 0) + mask_w = seq_len // mask_h + + mask = F.interpolate(mask.unsqueeze(1), size=(mask_h, mask_w), mode="bilinear").squeeze(1) + mask = tensor_to_size(mask, batch_prompt) + + mask = mask.repeat(len(cond_or_uncond), 1, 1) + mask = mask.view(mask.shape[0], -1, 1).repeat(1, 1, out.shape[2]) + + # covers cases where extreme aspect ratios can cause the mask to have a wrong size + mask_len = mask_h * mask_w + if mask_len < seq_len: + pad_len = seq_len - mask_len + pad1 = pad_len // 2 + pad2 = pad_len - pad1 + mask = F.pad(mask, (0, 0, pad1, pad2), value=0.0) + elif mask_len > seq_len: + crop_start = (mask_len - seq_len) // 2 + mask = mask[:, crop_start:crop_start+seq_len, :] + + out_ip = out_ip * mask + + return out_ip.to(dtype=dtype) def to_gray(img): x = 0.299 * img[:, 0:1] + 0.587 * img[:, 1:2] + 0.114 * img[:, 2:3] @@ -256,13 +282,16 @@ class ApplyPulid: "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 }), }, + "optional": { + "attn_mask": ("MASK", ), + }, } RETURN_TYPES = ("MODEL",) FUNCTION = "apply_pulid" CATEGORY = "pulid" - def apply_pulid(self, model, pulid, eva_clip, face_analysis, image, method, weight, start_at, end_at): + def apply_pulid(self, model, pulid, eva_clip, face_analysis, image, weight, start_at, end_at, method=None, noise=0.0, fidelity=None, projection=None, attn_mask=None): work_model = model.clone() device = comfy.model_management.get_torch_device() @@ -273,11 +302,18 @@ class ApplyPulid: eva_clip.to(device, dtype=dtype) pulid_model = PulidModel(pulid).to(device, dtype=dtype) - if method == "fidelity": + if attn_mask is not None: + if attn_mask.dim() > 3: + attn_mask = attn_mask.squeeze(-1) + elif attn_mask.dim() < 3: + attn_mask = attn_mask.unsqueeze(0) + attn_mask = attn_mask.to(device, dtype=dtype) + + if method == "fidelity" or projection == "ortho_v2": num_zero = 8 ortho = False ortho_v2 = True - elif method == "style": + elif method == "style" or projection == "ortho": num_zero = 16 ortho = True ortho_v2 = False @@ -285,6 +321,9 @@ class ApplyPulid: num_zero = 0 ortho = False ortho_v2 = False + + if fidelity is not None: + num_zero = fidelity #face_analysis.det_model.input_size = (640,640) image = tensor_to_image(image) @@ -346,10 +385,16 @@ class ApplyPulid: # combine embeddings id_cond = torch.cat([iface_embeds, id_cond_vit], dim=-1) - id_uncond = torch.zeros_like(id_cond) + if noise == 0: + id_uncond = torch.zeros_like(id_cond) + else: + id_uncond = torch.rand_like(id_cond) * noise id_vit_hidden_uncond = [] for idx in range(len(id_vit_hidden)): - id_vit_hidden_uncond.append(torch.zeros_like(id_vit_hidden[idx])) + if noise == 0: + id_vit_hidden_uncond.append(torch.zeros_like(id_vit_hidden[idx])) + else: + id_vit_hidden_uncond.append(torch.rand_like(id_vit_hidden[idx]) * noise) cond.append(pulid_model.get_image_embeds(id_cond, id_vit_hidden)) uncond.append(pulid_model.get_image_embeds(id_uncond, id_vit_hidden_uncond)) @@ -361,6 +406,14 @@ class ApplyPulid: cond = torch.mean(cond, dim=0, keepdim=True) uncond = torch.mean(uncond, dim=0, keepdim=True) + if num_zero > 0: + if noise == 0: + zero_tensor = torch.zeros((cond.size(0), num_zero, cond.size(-1)), dtype=dtype, device=device) + else: + zero_tensor = torch.rand((cond.size(0), num_zero, cond.size(-1)), dtype=dtype, device=device) * noise + cond = torch.cat([cond, zero_tensor], dim=1) + uncond = torch.cat([uncond, zero_tensor], dim=1) + sigma_start = work_model.get_model_object("model_sampling").percent_to_sigma(start_at) sigma_end = work_model.get_model_object("model_sampling").percent_to_sigma(end_at) @@ -371,9 +424,9 @@ class ApplyPulid: "uncond": uncond, "sigma_start": sigma_start, "sigma_end": sigma_end, - "num_zero": num_zero, "ortho": ortho, "ortho_v2": ortho_v2, + "mask": attn_mask, } number = 0 @@ -396,16 +449,40 @@ class ApplyPulid: return (work_model,) +class ApplyPulidAdvanced(ApplyPulid): + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL", ), + "pulid": ("PULID", ), + "eva_clip": ("EVA_CLIP", ), + "face_analysis": ("FACEANALYSIS", ), + "image": ("IMAGE", ), + "weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 5.0, "step": 0.05 }), + "projection": (["ortho_v2", "ortho", "none"],), + "fidelity": ("INT", {"default": 8, "min": 0, "max": 32, "step": 1 }), + "noise": ("FLOAT", {"default": 0.0, "min": -1.0, "max": 1.0, "step": 0.1 }), + "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 }), + }, + "optional": { + "attn_mask": ("MASK", ), + }, + } + NODE_CLASS_MAPPINGS = { "PulidModelLoader": PulidModelLoader, "PulidInsightFaceLoader": PulidInsightFaceLoader, "PulidEvaClipLoader": PulidEvaClipLoader, "ApplyPulid": ApplyPulid, + "ApplyPulidAdvanced": ApplyPulidAdvanced, } NODE_DISPLAY_NAME_MAPPINGS = { - "PulidModelLoader": "Load Pulid Model", - "PulidInsightFaceLoader": "Load InsightFace", - "PulidEvaClipLoader": "Load Eva Clip", - "ApplyPulid": "Apply Pulid", + "PulidModelLoader": "Load PuLID Model", + "PulidInsightFaceLoader": "Load InsightFace (PuLID)", + "PulidEvaClipLoader": "Load Eva Clip (PuLID)", + "ApplyPulid": "Apply PuLID", + "ApplyPulidAdvanced": "Apply PuLID Advanced", } \ No newline at end of file