diff --git a/IPAdapterPlus.py b/IPAdapterPlus.py index 65b95a7..912bcb1 100644 --- a/IPAdapterPlus.py +++ b/IPAdapterPlus.py @@ -1334,20 +1334,26 @@ class IPAdapterWeights: @classmethod def INPUT_TYPES(s): return {"required": { - "weights": ("STRING", {"default": '1.0', "multiline": True }), - "timing": (["custom", "linear", "ease_in_out", "ease_in", "ease_out", "reverse_in_out", "random"], ), + "weights": ("STRING", {"default": '1.0, 0.0', "multiline": True }), + "timing": (["custom", "linear", "ease_in_out", "ease_in", "ease_out", "random"], ), "frames": ("INT", {"default": 0, "min": 0, "max": 9999, "step": 1 }), "start_frame": ("INT", {"default": 0, "min": 0, "max": 9999, "step": 1 }), "end_frame": ("INT", {"default": 9999, "min": 0, "max": 9999, "step": 1 }), - }, + "add_starting_frames": ("INT", {"default": 0, "min": 0, "max": 9999, "step": 1 }), + "add_ending_frames": ("INT", {"default": 0, "min": 0, "max": 9999, "step": 1 }), + "method": (["full batch", "shift batches", "alternate batches"], { "default": "full batch" }), + }, "optional": { + "image": ("IMAGE",), + } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = ("FLOAT", "FLOAT", "INT", "IMAGE", "IMAGE") + RETURN_NAMES = ("weights", "weights_invert", "total_frames", "image_1", "image_2") FUNCTION = "weights" CATEGORY = "ipadapter/utils" - def weights(self, weights, timing, frames, start_frame, end_frame): + def weights(self, weights, timing, frames, start_frame, end_frame, add_starting_frames, add_ending_frames, method, image=None): import random # convert the string to a list of floats separated by commas or newlines @@ -1355,6 +1361,7 @@ class IPAdapterWeights: weights = [float(weight) for weight in weights.split(",") if weight.strip() != ""] if timing != "custom": + frames = max(frames, 2) start = 0.0 end = 1.0 @@ -1379,19 +1386,92 @@ class IPAdapterWeights: weights.append(start + (end - start) * math.sin(i / n * math.pi / 2)) elif timing == "ease_out": weights.append(start + (end - start) * (1 - math.cos(i / n * math.pi / 2))) - elif timing == "reverse_in_out": - weights.append(start + (end - start) * (1 - math.sin((1 - i / n) * math.pi / 2))) elif timing == "random": weights.append(random.uniform(start, end)) - weights[-1] = end if timing != "random" else weights[-1] + weights[-1] = end if timing != "random" else weights[-1] if end_frame < frames: weights.extend([end] * (frames - end_frame)) if len(weights) == 0: weights = [0.0] + + frames = len(weights) + + # repeat the images for cross fade + image_1 = None + image_2 = None + if image is not None: + if "shift" in method: + image_1 = image[:-1] + image_2 = image[1:] + + weights = weights * image_1.shape[0] + #weights_invert = [1.0 - w for w in weights] - return (weights, ) + image_1 = image_1.repeat_interleave(frames, 0) + image_2 = image_2.repeat_interleave(frames, 0) + elif "alternate" in method: + image_1 = image[::2].repeat_interleave(2, 0) + image_1 = image_1[1:] + image_2 = image[1::2].repeat_interleave(2, 0) + + mew_weights = weights + [1.0 - w for w in weights] + mew_weights = mew_weights * (image_1.shape[0] // 2) + if image.shape[0] % 2: + image_1 = image_1[:-1] + else: + image_2 = image_2[:-1] + mew_weights = mew_weights + weights + + weights = mew_weights + #weights_invert = [1.0 - w for w in weights] + + image_1 = image_1.repeat_interleave(frames, 0) + image_2 = image_2.repeat_interleave(frames, 0) + else: + weights = weights * image.shape[0] + #weights_invert = [1.0 - w for w in weights] + + image_1 = image.repeat_interleave(frames, 0) + + # add starting and ending frames + if add_starting_frames > 0: + weights = [weights[0]] * add_starting_frames + weights + image_1 = torch.cat([image[:1].repeat(add_starting_frames, 1, 1, 1), image_1], dim=0) + if image_2 is not None: + image_2 = torch.cat([image[:1].repeat(add_starting_frames, 1, 1, 1), image_2], dim=0) + if add_ending_frames > 0: + weights = weights + [weights[-1]] * add_ending_frames + image_1 = torch.cat([image_1, image[-1:].repeat(add_ending_frames, 1, 1, 1)], dim=0) + if image_2 is not None: + image_2 = torch.cat([image_2, image[-1:].repeat(add_ending_frames, 1, 1, 1)], dim=0) + + weights_invert = [1.0 - w for w in weights] + + return (weights, weights_invert, len(weights), image_1, image_2, ) + +class IPAdapterCombineWeights: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "weights_1": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05 }), + "weights_2": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05 }), + }} + RETURN_TYPES = ("FLOAT", "INT") + RETURN_NAMES = ("weights", "count") + FUNCTION = "combine" + CATEGORY = "ipadapter/utils" + + def combine(self, weights_1, weights_2): + if not isinstance(weights_1, list): + weights_1 = [weights_1] + if not isinstance(weights_2, list): + weights_2 = [weights_2] + weights = weights_1 + weights_2 + + return (weights, len(weights), ) class IPAdapterRegionalConditioning: @classmethod @@ -1521,6 +1601,7 @@ NODE_CLASS_MAPPINGS = { "IPAdapterSaveEmbeds": IPAdapterSaveEmbeds, "IPAdapterLoadEmbeds": IPAdapterLoadEmbeds, "IPAdapterWeights": IPAdapterWeights, + "IPAdapterCombineWeights": IPAdapterCombineWeights, "IPAdapterRegionalConditioning": IPAdapterRegionalConditioning, "IPAdapterCombineParams": IPAdapterCombineParams, } @@ -1555,6 +1636,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "IPAdapterSaveEmbeds": "IPAdapter Save Embeds", "IPAdapterLoadEmbeds": "IPAdapter Load Embeds", "IPAdapterWeights": "IPAdapter Weights", + "IPAdapterCombineWeights": "IPAdapter Combine Weights", "IPAdapterRegionalConditioning": "IPAdapter Regional Conditioning", "IPAdapterCombineParams": "IPAdapter Combine Params", } \ No newline at end of file diff --git a/README.md b/README.md index 5a04b8c..fdb6dba 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ Please consider a [Github Sponsorship](https://github.com/sponsors/cubiq) or [Pa ## Important updates +**2024/04/27**: Refactored the IPAdapterWeights mostly useful for AnimateDiff animations. + **2024/04/21**: Added Regional Conditioning nodes to simplify attention masking and masked text conditioning. **2024/04/16**: Added support for the new SDXL portrait unnorm model (link below). It's very strong and tends to ignore the text conditioning. Lower the CFG to 3-4 or use a RescaleCFG node. @@ -144,7 +146,7 @@ It's only thanks to generous sponsors that **the whole community** can enjoy ope ### :trophy: Gold sponsors -[![Kaiber.ai](https://f.latent.vision/imgs/kaiber.png)](https://kaiber.ai/) +[![Kaiber.ai](https://f.latent.vision/imgs/kaiber.png)](https://kaiber.ai/)   [![Kaiber.ai](https://f.latent.vision/imgs/replicate.png)](https://replicate.com/) ### :tada: Silver sponsors diff --git a/examples/ipadapter_weights.json b/examples/ipadapter_weights.json index 8388cb3..7a715d7 100644 --- a/examples/ipadapter_weights.json +++ b/examples/ipadapter_weights.json @@ -1,6 +1,6 @@ { - "last_node_id": 21, - "last_link_id": 37, + "last_node_id": 22, + "last_link_id": 40, "nodes": [ { "id": 7, @@ -14,7 +14,7 @@ "1": 180.6060791015625 }, "flags": {}, - "order": 8, + "order": 5, "mode": 0, "inputs": [ { @@ -80,49 +80,6 @@ "Node name for S&R": "VAEDecode" } }, - { - "id": 5, - "type": "EmptyLatentImage", - "pos": [ - 801, - 1097 - ], - "size": [ - 315, - 106 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "name": "batch_size", - "type": "INT", - "link": 35, - "widget": { - "name": "batch_size" - } - } - ], - "outputs": [ - { - "name": "LATENT", - "type": "LATENT", - "links": [ - 2 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "EmptyLatentImage" - }, - "widgets_values": [ - 512, - 512, - 6 - ] - }, { "id": 6, "type": "CLIPTextEncode", @@ -135,7 +92,7 @@ "1": 164.31304931640625 }, "flags": {}, - "order": 7, + "order": 4, "mode": 0, "inputs": [ { @@ -220,170 +177,6 @@ 1 ] }, - { - "id": 12, - "type": "LoadImage", - "pos": [ - 311, - 270 - ], - "size": { - "0": 315, - "1": 314 - }, - "flags": {}, - "order": 0, - "mode": 0, - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 25 - ], - "shape": 3, - "slot_index": 0 - }, - { - "name": "MASK", - "type": "MASK", - "links": null, - "shape": 3 - } - ], - "properties": { - "Node name for S&R": "LoadImage" - }, - "widgets_values": [ - "warrior_woman.png", - "image" - ] - }, - { - "id": 17, - "type": "PrepImageForClipVision", - "pos": [ - 797, - 87 - ], - "size": { - "0": 315, - "1": 106 - }, - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 25 - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 30 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "PrepImageForClipVision" - }, - "widgets_values": [ - "LANCZOS", - "top", - 0.15 - ] - }, - { - "id": 20, - "type": "IPAdapterWeights", - "pos": [ - 757, - 318 - ], - "size": [ - 263.5047280787487, - 183.75987616018006 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "name": "frames", - "type": "INT", - "link": 34, - "widget": { - "name": "frames" - }, - "slot_index": 0 - } - ], - "outputs": [ - { - "name": "FLOAT", - "type": "FLOAT", - "links": [ - 32 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "IPAdapterWeights" - }, - "widgets_values": [ - "1.0,0.0", - "linear", - 6, - 0, - 9999 - ] - }, - { - "id": 21, - "type": "PrimitiveNode", - "pos": [ - 340, - 1093 - ], - "size": { - "0": 210, - "1": 82 - }, - "flags": {}, - "order": 1, - "mode": 0, - "outputs": [ - { - "name": "INT", - "type": "INT", - "links": [ - 34, - 35 - ], - "widget": { - "name": "frames" - }, - "slot_index": 0 - } - ], - "title": "frames", - "properties": { - "Run widget replace on values": false - }, - "widgets_values": [ - 6, - "fixed" - ] - }, { "id": 19, "type": "IPAdapterBatch", @@ -391,10 +184,10 @@ 1173, 251 ], - "size": [ - 315, - 254 - ], + "size": { + "0": 315, + "1": 254 + }, "flags": {}, "order": 9, "mode": 0, @@ -432,7 +225,7 @@ { "name": "weight", "type": "FLOAT", - "link": 32, + "link": 38, "widget": { "name": "weight" }, @@ -473,7 +266,7 @@ "1": 78 }, "flags": {}, - "order": 6, + "order": 3, "mode": 0, "inputs": [ { @@ -525,7 +318,7 @@ "1": 98 }, "flags": {}, - "order": 2, + "order": 0, "mode": 0, "outputs": [ { @@ -561,6 +354,47 @@ "sd15/realisticVisionV51_v51VAE.safetensors" ] }, + { + "id": 17, + "type": "PrepImageForClipVision", + "pos": [ + 788, + 43 + ], + "size": { + "0": 315, + "1": 106 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 25 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 30 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "PrepImageForClipVision" + }, + "widgets_values": [ + "LANCZOS", + "top", + 0.15 + ] + }, { "id": 9, "type": "SaveImage", @@ -568,10 +402,10 @@ 1770, 710 ], - "size": [ - 556.2374508110479, - 892.1895739499892 - ], + "size": { + "0": 556.2374267578125, + "1": 892.1895751953125 + }, "flags": {}, "order": 12, "mode": 0, @@ -586,6 +420,203 @@ "widgets_values": [ "IPAdapter" ] + }, + { + "id": 12, + "type": "LoadImage", + "pos": [ + 311, + 270 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 25 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "warrior_woman.png", + "image" + ] + }, + { + "id": 5, + "type": "EmptyLatentImage", + "pos": [ + 801, + 1097 + ], + "size": [ + 309.1109879864148, + 82 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "batch_size", + "type": "INT", + "link": 35, + "widget": { + "name": "batch_size" + } + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 2 + ], + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [ + 512, + 512, + 6 + ] + }, + { + "id": 21, + "type": "PrimitiveNode", + "pos": [ + 340, + 1093 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 35, + 40 + ], + "widget": { + "name": "batch_size" + }, + "slot_index": 0 + } + ], + "title": "frames", + "properties": { + "Run widget replace on values": false + }, + "widgets_values": [ + 6, + "fixed" + ] + }, + { + "id": 22, + "type": "IPAdapterWeights", + "pos": [ + 761, + 208 + ], + "size": [ + 299.9049990375719, + 324.00000762939453 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": null + }, + { + "name": "frames", + "type": "INT", + "link": 40, + "widget": { + "name": "frames" + } + } + ], + "outputs": [ + { + "name": "weights", + "type": "FLOAT", + "links": [ + 38 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "weights_invert", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "total_frames", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "image_1", + "type": "IMAGE", + "links": null, + "shape": 3 + }, + { + "name": "image_2", + "type": "IMAGE", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "IPAdapterWeights" + }, + "widgets_values": [ + "1.0, 0.0", + "linear", + 6, + 0, + 9999, + 0, + 0, + "full batch" + ] } ], "links": [ @@ -685,22 +716,6 @@ 0, "MODEL" ], - [ - 32, - 20, - 0, - 19, - 6, - "FLOAT" - ], - [ - 34, - 21, - 0, - 20, - 0, - "INT" - ], [ 35, 21, @@ -724,6 +739,22 @@ 19, 0, "MODEL" + ], + [ + 38, + 22, + 0, + 19, + 6, + "FLOAT" + ], + [ + 40, + 21, + 0, + 22, + 1, + "INT" ] ], "groups": [],