add: LatentSender, LatentReceiver

This commit is contained in:
Dr.Lt.Data
2023-06-27 00:37:40 +09:00
parent 15338bcd11
commit 20f0ea25c3
6 changed files with 260 additions and 4 deletions
+2
View File
@@ -137,6 +137,8 @@ NODE_CLASS_MAPPINGS = {
"PreviewBridge": PreviewBridge,
"ImageSender": ImageSender,
"ImageReceiver": ImageReceiver,
"LatentSender": LatentSender,
"LatentReceiver": LatentReceiver,
"ImageMaskSwitch": ImageMaskSwitch,
"LatentSwitch": LatentSwitch,
"SEGSSwitch": SEGSSwitch,
+30 -1
View File
@@ -104,7 +104,28 @@ function imgSendHandler(event) {
for(let i in nodes) {
if(nodes[i].type == 'ImageReceiver') {
if(nodes[i].widgets[1].value == event.detail.link_id) {
nodes[i].widgets[0].value = filename;
nodes[i].widgets[0].value = `${data.subfolder}/${data.filename} [${data.type}]`;
let img = new Image();
img.src = `/view?filename=${data.filename}&type=${data.type}&subfolder=${data.subfolder}`+app.getPreviewFormatParam();
nodes[i].imgs = [img];
nodes[i].size[1] = Math.max(200, nodes[i].size[1]);
}
}
}
}
}
function latentSendHandler(event) {
if(event.detail.images.length > 0){
let data = event.detail.images[0];
let filename = `${data.filename} [${data.type}]`;
let nodes = app.graph._nodes;
for(let i in nodes) {
if(nodes[i].type == 'LatentReceiver') {
if(nodes[i].widgets[1].value == event.detail.link_id) {
nodes[i].widgets[0].value = `${data.subfolder}/${data.filename} [${data.type}]`;
let img = new Image();
img.src = `/view?filename=${data.filename}&type=${data.type}&subfolder=${data.subfolder}`+app.getPreviewFormatParam();
nodes[i].imgs = [img];
@@ -117,6 +138,7 @@ function imgSendHandler(event) {
var progressEventRegistered = false;
var imgSendEventRegistered = false;
var latentSendEventRegistered = false;
const impactProgressBadge = new ImpactProgressBadge();
app.registerExtension({
@@ -137,6 +159,13 @@ app.registerExtension({
imgSendEventRegistered = true;
}
}
if (node.comfyClass == "LatentSender") {
if (!latentSendEventRegistered) {
api.addEventListener("latent-send", latentSendHandler);
latentSendEventRegistered = true;
}
}
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

+3 -1
View File
@@ -1,12 +1,14 @@
import configparser
import os
version = "V2.14"
version = "V2.15"
dependency_version = 1
my_path = os.path.dirname(__file__)
config_path = os.path.join(my_path, "impact-pack.ini")
latent_letter_path = os.path.join(my_path, "..", "..", "latent.png")
MAX_RESOLUTION = 8192
def write_config(comfy_path):
+30 -1
View File
@@ -1157,4 +1157,33 @@ def update_node_status(node, text, progress=None):
"node": node,
"progress": progress,
"text": text
}, PromptServer.instance.client_id)
}, PromptServer.instance.client_id)
from comfy.cli_args import args, LatentPreviewMethod
import folder_paths
from latent_preview import TAESD, TAESDPreviewerImpl, Latent2RGBPreviewer
import comfy.latent_formats as latent_formats
def get_previewer(device, latent_format=latent_formats.SD15(), force=False):
previewer = None
method = args.preview_method
if method != LatentPreviewMethod.NoPreviews or force:
# TODO previewer methods
taesd_decoder_path = folder_paths.get_full_path("vae_approx", latent_format.taesd_decoder_name)
if method == LatentPreviewMethod.Auto:
method = LatentPreviewMethod.Latent2RGB
if taesd_decoder_path:
method = LatentPreviewMethod.TAESD
if method == LatentPreviewMethod.TAESD:
if taesd_decoder_path:
taesd = TAESD(None, taesd_decoder_path).to(device)
previewer = TAESDPreviewerImpl(taesd)
else:
print("Warning: TAESD previews enabled, but could not find models/vae_approx/{}".format(latent_format.taesd_decoder_name))
if previewer is None:
previewer = Latent2RGBPreviewer(latent_format.latent_rgb_factors)
return previewer
+195 -1
View File
@@ -9,12 +9,14 @@ from segment_anything import sam_model_registry
from impact.utils import *
import impact.core as core
from impact.core import SEG, NO_BBOX_DETECTOR, NO_SEGM_DETECTOR
from impact.config import MAX_RESOLUTION
from impact.config import MAX_RESOLUTION, latent_letter_path
from PIL import Image
import numpy as np
import hashlib
import json
import safetensors.torch
from PIL.PngImagePlugin import PngInfo
import latent_preview
warnings.filterwarnings('ignore', category=UserWarning, message='TypedStorage is deprecated')
@@ -1495,6 +1497,198 @@ class ImageSender(nodes.PreviewImage):
return result
from io import BytesIO
import piexif
import zipfile
from server import PromptServer
class LatentReceiver:
def __init__(self):
self.input_dir = folder_paths.get_input_directory()
self.type = "input"
@classmethod
def INPUT_TYPES(s):
def check_file_extension(x):
return x.endswith(".latent") or x.endswith(".latent.png")
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and check_file_extension(f)]
return {"required": {
"latent": (sorted(files), ),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
},
}
FUNCTION = "doit"
CATEGORY = "ImpactPack/Util"
RETURN_TYPES = ("LATENT",)
@staticmethod
def load_preview_latent(image_path):
image = Image.open(image_path)
exif_data = piexif.load(image.info["exif"])
if piexif.ExifIFD.UserComment in exif_data["Exif"]:
compressed_data = exif_data["Exif"][piexif.ExifIFD.UserComment]
compressed_data_io = BytesIO(compressed_data)
with zipfile.ZipFile(compressed_data_io, mode='r') as archive:
tensor_bytes = archive.read("latent")
tensor = safetensors.torch.load(tensor_bytes)
return {"samples": tensor['latent_tensor']}
return None
def doit(self, latent, link_id):
latent_path = folder_paths.get_annotated_filepath(latent)
if latent.endswith(".latent"):
latent = safetensors.torch.load_file(latent_path, device="cpu")
multiplier = 1.0
if "latent_format_version_0" not in latent:
multiplier = 1.0 / 0.18215
samples = {"samples": latent["latent_tensor"].float() * multiplier}
else:
samples = LatentReceiver.load_preview_latent(latent_path)
preview = {
'filename': latent_path,
'subfolder': '',
'type': self.type
}
return {
'ui': {"images": [preview]},
'result': (samples, )
}
@classmethod
def IS_CHANGED(s, latent, link_id):
image_path = folder_paths.get_annotated_filepath(latent)
m = hashlib.sha256()
with open(image_path, 'rb') as f:
m.update(f.read())
return m.digest().hex()
@classmethod
def VALIDATE_INPUTS(s, latent, link_id):
if not folder_paths.exists_annotated_filepath(latent):
return "Invalid latent file: {}".format(latent)
return True
class LatentSender(nodes.SaveLatent):
def __init__(self):
self.output_dir = folder_paths.get_temp_directory()
self.type = "temp"
@classmethod
def INPUT_TYPES(s):
return {"required": {
"samples": ("LATENT", ),
"filename_prefix": ("STRING", {"default": "latents/LatentSender"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), },
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
OUTPUT_NODE = True
RETURN_TYPES = ()
FUNCTION = "doit"
CATEGORY = "ImpactPack/Util"
@staticmethod
def save_to_file(tensor_bytes, prompt, extra_pnginfo, image, image_path):
compressed_data = BytesIO()
with zipfile.ZipFile(compressed_data, mode='w') as archive:
archive.writestr("latent", tensor_bytes)
image = image.copy()
exif_data = {"Exif": {piexif.ExifIFD.UserComment: compressed_data.getvalue()}}
metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
exif_bytes = piexif.dump(exif_data)
image.save(image_path, format='png', exif=exif_bytes, pnginfo=metadata, optimize=True)
@staticmethod
def prepare_preview(latent_tensor):
lower_bound = 128
upper_bound = 256
previewer = core.get_previewer("cpu", force=True)
image = previewer.decode_latent_to_preview(latent_tensor)
min_size = min(image.size[0], image.size[1])
max_size = max(image.size[0], image.size[1])
scale_factor = 1
if max_size > upper_bound:
scale_factor = upper_bound/max_size
# prevent too small preview
if min_size*scale_factor < lower_bound:
scale_factor = lower_bound/min_size
w = int(image.size[0] * scale_factor)
h = int(image.size[1] * scale_factor)
image = image.resize((w, h), resample=Image.NEAREST)
return LatentSender.attach_format_text(image)
@staticmethod
def attach_format_text(image):
width_a, height_a = image.size
letter_image = Image.open(latent_letter_path)
width_b, height_b = letter_image.size
new_width = max(width_a, width_b)
new_height = height_a + height_b
new_image = Image.new('RGB', (new_width, new_height), (0, 0, 0))
offset_x = (new_width - width_b) // 2
offset_y = (height_a + (new_height - height_a - height_b) // 2)
new_image.paste(letter_image, (offset_x, offset_y))
new_image.paste(image, (0, 0))
return new_image
def doit(self, samples, filename_prefix="latents/LatentSender", link_id=0, prompt=None, extra_pnginfo=None):
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
# load preview
preview = LatentSender.prepare_preview(samples['samples'])
# support save metadata for latent sharing
file = f"{filename}_{counter:05}_.latent.png"
fullpath = os.path.join(full_output_folder, file)
output = {"latent_tensor": samples["samples"]}
tensor_bytes = safetensors.torch.save(output)
LatentSender.save_to_file(tensor_bytes, prompt, extra_pnginfo, preview, fullpath)
latent_path = {
'filename': file,
'subfolder': subfolder,
'type': self.type
}
PromptServer.instance.send_sync("latent-send", {"link_id": link_id, "images": [latent_path]})
return {'ui': {'images': [latent_path]}}
class ImageMaskSwitch:
@classmethod
def INPUT_TYPES(s):