Compare commits

..
11 Commits
Author SHA1 Message Date
Dr.Lt.Data 25ae8d522f feat: MakeLBW, ApplyLBW, SaveLBW, LoadLBW 2024-09-01 22:46:38 +09:00
Dr.Lt.Data 50d42c30b5 Upgrade LoRA Block Weight
- support % syntax
2024-08-29 01:35:27 +09:00
Dr.Lt.Data 89e24b7529 fixed: LoadImagesFromDirBatch - invalid mask processing
https://github.com/ltdrdata/ComfyUI-Inspire-Pack/issues/151
2024-08-28 20:33:00 +09:00
Dr.Lt.Data 6db6ca54c0 Merge pull request #150 from jesperol/fix_noise_return
fix: return_with_leftover_noise propagation
2024-08-28 10:45:11 +09:00
Jesper Olsson 69b7c486a9 fix: return_with_leftover_noise propagation 2024-08-21 17:22:31 +02:00
Dr.Lt.Data b09b295009 FIXED: mix_noise - device mismatch error 2024-08-21 01:45:10 +09:00
Dr.Lt.Data a15d3362d8 Merge pull request #143 from pixelprotest/feature/load-last-image-batch-from-dir
Add ability to use Load Image Batch from Dir node, to load the last / latest image in a directory
2024-08-20 22:56:12 +09:00
pixelprotest 019713040c Allow min start_index to drop to -1 to make node get last/latest image from dir 2024-08-20 13:30:59 +01:00
Dr.Lt.Data bc075b1a4f version marker 2024-08-18 19:32:37 +09:00
Dr.Lt.Data 452d8cfc00 Merge pull request #142 from anhkhoatranle30/main
Add filepaths as outputs
2024-08-18 19:30:24 +09:00
khoatrn 17716ddb52 feat: Add filepaths as outputs 2024-08-18 16:14:57 +07:00
8 changed files with 498 additions and 48 deletions
+6 -2
View File
@@ -13,12 +13,16 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
## Nodes
### Lora Block Weight - This is a node that provides functionality related to Lora block weight.
* This provides similar functionality to [sd-webui-lora-block-weight](https://github.com/hako-mikan/sd-webui-lora-block-weight)
* `Lora Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
* `LoRA Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
* In the block vector, you can use numbers, R, A, a, B, and b.
* R is determined sequentially based on a random seed, while A and B represent the values of the A and B parameters, respectively. a and b are half of the values of A and B, respectively.
* `XY Input: Lora Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
* `XY Input: LoRA Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
* You must ensure that X and Y connections are made, and dependencies should be connected to the XY Plot.
* Note: To use this feature, update `Efficient Nodes` to a version released after September 3rd.
* Make LoRA Block Weight: Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form
* Apply LoRA Block Weight: Apply LBW_MODEL to MODEL and CLIP
* Save LoRA Block Weight: Save LBW_MODEL as a .lbw.safetensors file
* Load LoRA Block Weight: Load LBW_MODEL from .lbw.safetensors file
### SEGS Supports nodes - This is a node that supports ApplyControlNet (SEGS) from the Impact Pack.
* `OpenPose Preprocessor Provider (SEGS)`: OpenPose preprocessor is applied for the purpose of using OpenPose ControlNet in SEGS.
+1 -1
View File
@@ -7,7 +7,7 @@
import importlib
version_code = [0, 85, 2]
version_code = [1, 1]
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
print(f"### Loading: ComfyUI-Inspire-Pack ({version_str})")
+9 -6
View File
@@ -18,7 +18,7 @@ class LoadImagesFromDirBatch:
},
"optional": {
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
"start_index": ("INT", {"default": 0, "min": -1, "step": 1}),
"load_always": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
}
}
@@ -94,10 +94,10 @@ class LoadImagesFromDirBatch:
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", "center").movedim(1, -1)
image1 = torch.cat((image1, image2), dim=0)
for mask2 in masks[1:]:
for mask2 in masks:
if has_non_empty_mask:
if image1.shape[1:3] != mask2.shape:
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[2], image1.shape[1]), mode='bilinear', align_corners=False)
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[1], image1.shape[2]), mode='bilinear', align_corners=False)
mask2 = mask2.squeeze(0)
else:
mask2 = mask2.unsqueeze(0)
@@ -126,8 +126,9 @@ class LoadImagesFromDirList:
}
}
RETURN_TYPES = ("IMAGE", "MASK")
OUTPUT_IS_LIST = (True, True)
RETURN_TYPES = ("IMAGE", "MASK", "STRING")
RETURN_NAMES = ("IMAGE", "MASK", "FILE PATH")
OUTPUT_IS_LIST = (True, True, True)
FUNCTION = "load_images"
@@ -159,6 +160,7 @@ class LoadImagesFromDirList:
images = []
masks = []
file_paths = []
limit_images = False
if image_load_cap > 0:
@@ -184,9 +186,10 @@ class LoadImagesFromDirList:
images.append(image)
masks.append(mask)
file_paths.append(str(image_path))
image_count += 1
return images, masks
return (images, masks, file_paths)
class LoadImageInspire:
+14
View File
@@ -5,6 +5,7 @@ import torch
from PIL import Image, ImageDraw
import math
import cv2
import folder_paths
def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None, variation_method='linear'):
@@ -56,6 +57,8 @@ def slerp(val, low, high):
def mix_noise(from_noise, to_noise, strength, variation_method):
to_noise = to_noise.to(from_noise.device)
if variation_method == 'slerp':
mixed_noise = slerp(strength, from_noise, to_noise)
else:
@@ -334,3 +337,14 @@ def flatten_non_zero_override(masks: torch.Tensor):
final_mask[non_zero_mask] = masks[i][non_zero_mask]
return final_mask
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
for full_folder_path in full_folder_paths:
folder_paths.add_model_folder_path(folder_name, full_folder_path)
if folder_name in folder_paths.folder_names_and_paths:
current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
updated_extensions = current_extensions | extensions
folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
else:
folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)
+459 -36
View File
@@ -6,11 +6,20 @@ import torch
import numpy as np
import nodes
import re
import json
from comfy.cli_args import args
from safetensors.torch import safe_open
import ast
from server import PromptServer
from .libs import utils
model_path = folder_paths.models_dir
utils.add_folder_path_and_extensions("lbw_models", [os.path.join(model_path, "lbw_models")], {'.safetensors'})
def is_numeric_string(input_str):
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None
@@ -34,6 +43,76 @@ def load_lbw_preset(filename):
return []
def parse_unet_num(s):
if s[1] == '.':
return int(s[0])
else:
return int(s)
class MakeLBW:
def __init__(self):
self.loaded_lora = None
@classmethod
def INPUT_TYPES(s):
preset = ["Preset"] # 20
preset += load_lbw_preset("lbw-preset.txt")
preset += load_lbw_preset("lbw-preset.custom.txt")
preset = [name for name in preset if not name.startswith('@')]
lora_names = folder_paths.get_filename_list("loras")
lora_dirs = [os.path.dirname(name) for name in lora_names]
lora_dirs = ["All"] + list(set(lora_dirs))
return {"required": {"model": ("MODEL",),
"clip": ("CLIP", ),
"category_filter": (lora_dirs,),
"lora_name": (lora_names, ),
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False", "tooltip": "Apply the following weights for each block:\nTrue: 1 - weight\nFalse: weight"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": ""}),
"A": ("FLOAT", {"default": 4.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"B": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"preset": (preset,),
"block_vector": ("STRING", {"multiline": True, "placeholder": "block weight vectors", "default": "1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1", "pysssss.autocomplete": False}),
"bypass": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
}
}
RETURN_TYPES = ("LBW_MODEL", "STRING")
RETURN_NAMES = ("lbw_model", "populated_vector")
FUNCTION = "doit"
CATEGORY = "InspirePack/LoraBlockWeight"
DESCRIPTION = "Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form."
def __init__(self):
self.loaded_lora = None
def doit(self, model, clip, lora_name, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
lora_path = folder_paths.get_full_path("loras", lora_name)
lora = None
if self.loaded_lora is not None:
if self.loaded_lora[0] == lora_path:
lora = self.loaded_lora[1]
else:
temp = self.loaded_lora
self.loaded_lora = None
del temp
if lora is None:
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
self.loaded_lora = (lora_path, lora)
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
lbw_model = {
'blocks': block_weights,
'muted': muted_weights
}
return lbw_model, populated_vector
class LoraLoaderBlockWeight:
def __init__(self):
self.loaded_lora = None
@@ -55,8 +134,8 @@ class LoraLoaderBlockWeight:
"lora_name": (lora_names, ),
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False", "tooltip": "Apply the following weights for each block:\nTrue: 1 - weight\nFalse: weight"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": ""}),
"A": ("FLOAT", {"default": 4.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"B": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"preset": (preset,),
@@ -130,11 +209,150 @@ class LoraLoaderBlockWeight:
return value
@staticmethod
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
def block_spec_parser(loaded, spec):
if not spec.startswith("%"):
return spec
else:
items = [x.strip() for x in spec[1:].split(',')]
input_blocks_set = set()
middle_blocks_set= set()
output_blocks_set = set()
double_blocks_set = set()
single_blocks_set = set()
for key, v in loaded.items():
if isinstance(key, tuple):
k = key[0]
else:
k = key
k_unet = k[len("diffusion_model."):]
if k_unet.startswith("input_blocks."):
k_unet_num = k_unet[len("input_blocks."):len("input_blocks.")+2]
k_unet_int = parse_unet_num(k_unet_num)
input_blocks_set.add(k_unet_int)
elif k_unet.startswith("middle_block."):
k_unet_num = k_unet[len("middle_block."):len("middle_block.")+2]
k_unet_int = parse_unet_num(k_unet_num)
middle_blocks_set.add(k_unet_int)
elif k_unet.startswith("output_blocks."):
k_unet_num = k_unet[len("output_blocks."):len("output_blocks.")+2]
k_unet_int = parse_unet_num(k_unet_num)
output_blocks_set.add(k_unet_int)
elif k_unet.startswith("double_blocks."):
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.") + 2]
k_unet_int = parse_unet_num(k_unet_num)
double_blocks_set.add(k_unet_int)
elif k_unet.startswith("single_blocks."):
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.") + 2]
k_unet_int = parse_unet_num(k_unet_num)
single_blocks_set.add(k_unet_int)
pat1 = re.compile(r"(default|base)=([0-9.]+)")
pat2 = re.compile(r"(in|out|mid|double|single)([0-9]+)-([0-9]+)=([0-9.]+)")
pat3 = re.compile(r"(in|out|mid|double|single)([0-9]+)=([0-9.]+)")
pat4 = re.compile(r"(in|out|mid|double|single)=([0-9.]+)")
base_spec = None
default_spec = 1.0
for item in items:
match = pat1.match(item)
if match:
if match[1] == 'base':
base_spec = match[2]
continue
if match[1] == 'default':
default_spec = match[2]
continue
if base_spec is None:
base_spec = default_spec
input_blocks = [default_spec] * len(input_blocks_set)
middle_blocks = [default_spec] * len(middle_blocks_set)
output_blocks = [default_spec] * len(output_blocks_set)
double_blocks = [default_spec] * len(double_blocks_set)
single_blocks = [default_spec] * len(single_blocks_set)
for item in items:
match = pat2.match(item)
if match:
for x in range(int(match[2])-1, int(match[3])):
value = float(match[4])
if x < 0:
continue
if match[1] == 'in' and len(input_blocks) > x:
input_blocks[x] = value
elif match[1] == 'out' and len(output_blocks) > x:
output_blocks[x] = value
elif match[1] == 'mid' and len(middle_blocks) > x:
middle_blocks[x] = value
elif match[1] == 'double' and len(double_blocks) > x:
double_blocks[x] = value
elif match[1] == 'single' and len(single_blocks) > x:
single_blocks[x] = value
continue
match = pat3.match(item)
if match:
value = float(match[3])
x = int(match[2]) - 1
if x < 0:
continue
if match[1] == 'in' and len(input_blocks) > x:
input_blocks[x] = value
elif match[1] == 'out' and len(output_blocks) > x:
output_blocks[x] = value
elif match[1] == 'mid' and len(middle_blocks) > x:
middle_blocks[x] = value
elif match[1] == 'double' and len(double_blocks) > x:
double_blocks[x] = value
elif match[1] == 'single' and len(single_blocks) > x:
single_blocks[x] = value
continue
match = pat4.match(item)
if match:
value = float(match[2])
if match[1] == 'in':
input_blocks = [value] * len(input_blocks)
elif match[1] == 'out':
output_blocks = [value] * len(output_blocks)
elif match[1] == 'mid':
middle_blocks = [value] * len(middle_blocks)
elif match[1] == 'double':
double_blocks = [value] * len(double_blocks)
elif match[1] == 'single':
single_blocks = [value] * len(single_blocks)
continue
# concat specs
res = [str(base_spec)]
for x in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
res.append(str(x))
return ",".join(res)
@staticmethod
def load_lbw(model, clip, lora, inverse, seed, A, B, block_vector):
key_map = comfy.lora.model_lora_keys_unet(model.model)
key_map = comfy.lora.model_lora_keys_clip(clip.cond_stage_model, key_map)
loaded = comfy.lora.load_lora(lora, key_map)
block_vector = LoraLoaderBlockWeight.block_spec_parser(loaded, block_vector)
block_vector = block_vector.split(":")
if len(block_vector) > 1:
block_vector = block_vector[1]
@@ -142,7 +360,6 @@ class LoraLoaderBlockWeight:
block_vector = block_vector[0]
vector = block_vector.split(",")
vector_i = 1
if not LoraLoaderBlockWeight.validate(vector):
preset_dict = load_preset_dict()
@@ -151,16 +368,6 @@ class LoraLoaderBlockWeight:
else:
raise ValueError(f"[LoraLoaderBlockWeight] invalid block_vector '{block_vector}'")
last_k_unet_num = None
new_modelpatcher = model.clone()
populated_ratio = strength_model
def parse_unet_num(s):
if s[1] == '.':
return int(s[0])
else:
return int(s)
# sort: input, middle, output, others
input_blocks = []
middle_blocks = []
@@ -204,6 +411,14 @@ class LoraLoaderBlockWeight:
np.random.seed(seed % (2**31))
populated_vector_list = []
ratios = []
ratio = 1.0
vector_i = 1
last_k_unet_num = None
block_weights = {}
muted_weights = []
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
if last_k_unet_num != k_unet_num and len(vector) > vector_i:
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[vector_i].strip())
@@ -220,6 +435,8 @@ class LoraLoaderBlockWeight:
else:
if len(ratios) > 0:
ratio = ratios.pop(0)
else:
pass # use last used ratio if no more user specified ratio is given
if inverse:
populated_ratio = 1 - ratio
@@ -229,12 +446,9 @@ class LoraLoaderBlockWeight:
last_k_unet_num = k_unet_num
if populated_ratio != 0:
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
# if inverse:
# print(f"\t{k_unet} -> inv({ratio}) ")
# else:
# print(f"\t{k_unet} -> ({ratio}) ")
block_weights[k] = v, populated_ratio
else:
muted_weights.append(k)
# prepare base patch
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[0].strip())
@@ -243,25 +457,43 @@ class LoraLoaderBlockWeight:
if inverse:
populated_ratio = 1 - ratio
else:
populated_ratio = 1
populated_ratio = ratio
populated_vector_list.insert(0, LoraLoaderBlockWeight.norm_value(populated_ratio))
for k, v, k_unet in others:
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
# if inverse:
# print(f"\t{k_unet} -> inv({ratio}) ")
# else:
# print(f"\t{k_unet} -> ({ratio}) ")
if populated_ratio != 0:
block_weights[k] = v, populated_ratio
else:
muted_weights.append(k)
new_clip = clip.clone()
new_clip.add_patches(loaded, strength_clip)
populated_vector = ','.join(map(str, populated_vector_list))
return (new_modelpatcher, new_clip, populated_vector)
return block_weights, muted_weights, populated_vector
@staticmethod
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
new_modelpatcher = model.clone()
new_clip = clip.clone()
muted_weights = set(muted_weights)
for k, v in block_weights.items():
weights, ratio = v
if k in muted_weights:
pass
elif 'text' in k:
new_clip.add_patches({k: weights}, strength_clip * ratio)
else:
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
return new_modelpatcher, new_clip, populated_vector
def doit(self, model, clip, lora_name, strength_model, strength_clip, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
if strength_model == 0 and strength_clip == 0 or bypass:
return (model, clip, "")
return model, clip, ""
lora_path = folder_paths.get_full_path("loras", lora_name)
lora = None
@@ -278,7 +510,48 @@ class LoraLoaderBlockWeight:
self.loaded_lora = (lora_path, lora)
model_lora, clip_lora, populated_vector = LoraLoaderBlockWeight.load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector)
return (model_lora, clip_lora, populated_vector)
return model_lora, clip_lora, populated_vector
class ApplyLBW:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL", ),
"clip": ("CLIP", ),
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"lbw_model": ("LBW_MODEL",),
}}
RETURN_TYPES = ("MODEL", "CLIP")
FUNCTION = "doit"
CATEGORY = "InspirePack/LoraBlockWeight"
DESCRIPTION = "Apply LBW_MODEL to MODEL and CLIP"
@staticmethod
def doit(model, clip, strength_model, strength_clip, lbw_model):
block_weights = lbw_model['blocks']
muted_weights = lbw_model['muted']
new_modelpatcher = model.clone()
new_clip = clip.clone()
muted_weights = set(muted_weights)
for k, v in block_weights.items():
weights, ratio = v
if k in muted_weights:
pass
elif 'text' in k:
new_clip.add_patches({k: weights}, strength_clip * ratio)
else:
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
return new_modelpatcher, new_clip
class XY_Capsule_LoraBlockWeight:
@@ -345,6 +618,7 @@ class XY_Capsule_LoraBlockWeight:
else:
image = torch.abs(weighted_image - reference_image)
self.storage[(self.another_capsule.x, self.y)] = image
elif self.y == 3:
import matplotlib.cm as cm
# heatmap
@@ -353,7 +627,7 @@ class XY_Capsule_LoraBlockWeight:
if image == "fail":
image = utils.empty_pil_tensor(8,8)
latent = utils.empty_latent()
return (image, latent)
return image, latent
else:
image = image.clone()
@@ -385,7 +659,7 @@ class XY_Capsule_LoraBlockWeight:
image = heatmap_alpha * heatmap + (1 - heatmap_alpha) * image
latent = nodes.VAEEncode().encode(vae, image)[0]
return (image, latent)
return image, latent
def getLabel(self):
return self.label
@@ -697,13 +971,162 @@ class LoraBlockInfo:
return {}
class LoadLBW:
@classmethod
def INPUT_TYPES(s):
files = folder_paths.get_filename_list('lbw_models')
return {"required": {
"lbw_model": [sorted(files), ]},
}
RETURN_TYPES = ("LBW_MODEL",)
FUNCTION = "doit"
CATEGORY = "InspirePack/LoraBlockWeight"
DESCRIPTION = "Load LBW_MODEL from .lbw.safetensors file"
@staticmethod
def decode_dict(encoded_dict, tensor_dict):
original_dict = {}
def decode_value(value):
if isinstance(value, str) and value.startswith('t') and value[1:].isdigit():
return tensor_dict[value]
return value
for k, tuple_value in encoded_dict.items():
decoded_tuple = tuple(decode_value(v) for v in tuple_value[0][1])
key = ast.literal_eval(k) if isinstance(k, str) and (k.startswith('(') or k.startswith('[')) else k
original_dict[key] = ((tuple_value[0][0], decoded_tuple), tuple_value[1])
return original_dict
@staticmethod
def load(file):
tensor_dict = comfy.utils.load_torch_file(file)
with safe_open(file, framework="pt") as f:
metadata = f.metadata()
encoded_dict = json.loads(metadata.get('blocks', '{}'))
muted_blocks = ast.literal_eval(metadata.get('muted_blocks', '[]'))
decoded_dict = LoadLBW.decode_dict(encoded_dict, tensor_dict)
lbw_model = {
'blocks': decoded_dict,
'muted': muted_blocks
}
return lbw_model, metadata
def doit(self, lbw_model):
lbw_path = folder_paths.get_full_path("lbw_models", lbw_model)
lbw_model, _ = LoadLBW.load(lbw_path)
return (lbw_model,)
class SaveLBW:
def __init__(self):
self.output_dir = folder_paths.get_folder_paths('lbw_models')[-1]
@classmethod
def INPUT_TYPES(s):
return {"required": { "lbw_model": ("LBW_MODEL", ),
"filename_prefix": ("STRING", {"default": "ComfyUI"}) },
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "doit"
OUTPUT_NODE = True
CATEGORY = "InspirePack/LoraBlockWeight"
DESCRIPTION = "Save LBW_MODEL as a .lbw.safetensors file"
@staticmethod
def encode_dict(original_dict):
tensor_dict = {}
encoded_dict = {}
counter = 0
def generate_unique_id():
nonlocal counter
counter += 1
return f"t{counter}"
def encode_value(value):
if isinstance(value, torch.Tensor):
unique_id = generate_unique_id()
tensor_dict[unique_id] = value
return unique_id
return value
for k, tuple_value in original_dict.items():
encoded_tuple = tuple(encode_value(v) for v in tuple_value[0][1])
encoded_dict[str(k)] = (tuple_value[0][0], encoded_tuple), tuple_value[1]
return encoded_dict, tensor_dict
@staticmethod
def save(lbw_model, file, metadata):
metadata['format'] = 'Inspire LBW 1.0'
weighted_blocks = lbw_model['blocks']
metadata['muted_blocks'] = str(lbw_model['muted'])
encoded_dict, tensor_dict = SaveLBW.encode_dict(weighted_blocks)
metadata['blocks'] = json.dumps(encoded_dict)
comfy.utils.save_torch_file(tensor_dict, file, metadata=metadata)
def doit(self, lbw_model, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
# support save metadata for lbw sharing
prompt_info = ""
if prompt is not None:
prompt_info = json.dumps(prompt)
metadata = {}
if not args.disable_metadata:
metadata = {"prompt": prompt_info}
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata[x] = json.dumps(extra_pnginfo[x])
file = f"{filename}_{counter:05}_.lbw.safetensors"
results = list()
results.append({
"filename": file,
"subfolder": subfolder,
"type": "output"
})
file = os.path.join(full_output_folder, file)
SaveLBW.save(lbw_model, file, metadata)
return {}
NODE_CLASS_MAPPINGS = {
"XY Input: Lora Block Weight //Inspire": XYInput_LoraBlockWeight,
"LoraLoaderBlockWeight //Inspire": LoraLoaderBlockWeight,
"LoraBlockInfo //Inspire": LoraBlockInfo,
"MakeLBW //Inspire": MakeLBW,
"ApplyLBW //Inspire": ApplyLBW,
"SaveLBW //Inspire": SaveLBW,
"LoadLBW //Inspire": LoadLBW,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"XY Input: Lora Block Weight //Inspire": "XY Input: Lora Block Weight",
"LoraLoaderBlockWeight //Inspire": "Lora Loader (Block Weight)",
"LoraBlockInfo //Inspire": "Lora Block Info",
"XY Input: Lora Block Weight //Inspire": "XY Input: LoRA Block Weight",
"LoraLoaderBlockWeight //Inspire": "LoRA Loader (Block Weight)",
"LoraBlockInfo //Inspire": "LoRA Block Info",
"MakeLBW //Inspire": "Make LoRA Block Weight",
"ApplyLBW //Inspire": "Apply LoRA Block Weight",
"SaveLBW //Inspire": "Save LoRA Block Weight",
"LoadLBW //Inspire": "Load LoRA Block Weight",
}
+1 -1
View File
@@ -122,7 +122,7 @@ class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
result.append(x)
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step,
noise_mode, False, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
noise_mode, return_with_leftover_noise, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
if not omit_final_latent:
result.append(latent_image['samples'].cpu())
+7 -1
View File
@@ -4,7 +4,7 @@ app.registerExtension({
name: "Comfy.Inspire.LBW",
nodeCreated(node, app) {
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire") {
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire" || node.comfyClass == "MakeLBW //Inspire") {
// category filter
const lora_names_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'lora_name')];
var full_lora_list = lora_names_widget.options.values;
@@ -26,6 +26,12 @@ app.registerExtension({
// vector selector
let preset_i = 9;
let vector_i = 10;
if(node.comfyClass == "MakeLBW //Inspire") {
preset_i = 7;
vector_i = 8;
}
node._value = "Preset";
Object.defineProperty(node.widgets[preset_i], "value", {
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-inspire-pack"
description = "This extension provides various nodes to support Lora Block Weight and the Impact Pack. Provides many easily applicable regional features and applications for Variation Seed."
version = "0.85.2"
version = "1.1"
license = { file = "LICENSE" }
dependencies = ["matplotlib", "cachetools"]