mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
commit 9bb0b5d0036c4dffbb72ce11e097fae4ab63defd Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Sat Oct 15 23:43:41 2022 +0200 undo local_files_only stuff commit eed93f5d30c34cfccaf7497618ae9af17a5ecfbb Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Sat Oct 15 23:40:37 2022 +0200 Revert "Merge branch 'development-invoke' into fix-prompts" This reverts commit 7c40892a9f184f7e216f14d14feb0411c5a90e24, reversing changes made to e3f2dd62b0548ca6988818ef058093a4f5b022f2. commit f06d6024e345c69e6d5a91ab5423925a68ee95a7 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Thu Oct 13 23:30:16 2022 +0200 more efficiently handle multiple conditioning commit 5efdfcbcd980ce6202ab74e7f90e7415ce7260da Merge: b9c0dc5 ac08bb6 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Thu Oct 13 14:51:01 2022 +0200 Merge branch 'optional-disable-karras-schedule' into fix-prompts commit ac08bb6fd25e19a9d35cf6c199e66500fb604af1 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Thu Oct 13 14:50:43 2022 +0200 append '*use_model_sigmas*' to prompt string to use model sigmas commit 70d8c05a3ff329409f76204f4af94e55d468ab8b Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Thu Oct 13 12:12:17 2022 +0200 make karras scheduling switchable commit d60df54f69968e2fb22809c55e23b3c02f37ad63 replaced the model's own scheduling with karras scheduling. this has changed image generation (seems worse now?) this commit wraps the change in a bool. commit b9c0dc5f1a658a0e6c3936000e9ae559e1c7a1db Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 20:16:00 2022 +0200 add test of more complex conjunction commit 9ac0c15cc0d7b5f6df3289d3ad474260972a17be Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 17:18:25 2022 +0200 improve comments commit ad33bce60590b87b2a93e90f16dc9d3e935d04a5 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 17:04:46 2022 +0200 put back thresholding stuff commit 4852c698a325049834ba0d4b358f07210bc7171a Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 14:25:02 2022 +0200 notes on improving conjunction efficiency commit a53bb1e5b68025d09642b935ae6a9a015cfaf2d6 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 14:14:33 2022 +0200 optional weights support for Conjunction commit fec79ab15e4f0c84dd61cb1b45a5e6a72ae4aaeb Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 12:07:27 2022 +0200 fix blend error and log parsing output commit 1f751c2a039f9c97af57b18e0f019512631d5a25 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 10:33:33 2022 +0200 fix broken euler sampler commit 02f8148d17efe4b6bde8d29b827092a0626363ee Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 10:24:20 2022 +0200 cleanup prompt parser commit 8028d49ae6c16c0d6ec9c9de9c12d56c32201421 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Wed Oct 12 10:14:18 2022 +0200 explicit conjunction, improve flattening logic commit 8a1710892185f07eb77483f7edae0fc4d6bbb250 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 22:59:30 2022 +0200 adapt multi-conditioning to also work with ddim commit 53802a839850d0d1ff017c6bafe457c4bed750b0 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 22:31:42 2022 +0200 unconditioning is also fancy-prompt-syntaxable commit 7c40892a9f184f7e216f14d14feb0411c5a90e24 Merge: e3f2dd6 dbe0da4 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 21:39:54 2022 +0200 Merge branch 'development-invoke' into fix-prompts commit e3f2dd62b0548ca6988818ef058093a4f5b022f2 Merge: eef0e48 06f542e Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 21:38:09 2022 +0200 Merge remote-tracking branch 'upstream/development' into fix-prompts commit eef0e484c2eaa1bd4e0e0b1d3f8d7bba38478144 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 21:26:25 2022 +0200 fix run-on paren-less attention, add some comments commit fd29afdf0e9f5e0cdc60239e22480c36ca0aaeca Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 21:03:02 2022 +0200 python 3.9 compatibility commit 26f7646eef7f39bc8f7ce805e747df0f723464da Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 20:58:42 2022 +0200 first pass connecting PromptParser to conditioning commit ae53dff3796d7b9a5e7ed30fa1edb0374af6cd8d Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 20:51:15 2022 +0200 update frontend dist commit 9be4a59a2d76f49e635474b5984bfca826a5dab4 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 19:01:39 2022 +0200 fix issues with correctness checking FlattenedPrompt commit 3be212323eab68e72a363a654124edd9809e4cf0 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 18:43:16 2022 +0200 parsing nested seems to work pretty ok commit acd73eb08cf67c27cac8a22934754321256f56a9 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 18:26:17 2022 +0200 wip introducing FlattenedPrompt class commit 71698d5c7c2ac855b690d8ef67e8830148c59eda Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 15:59:42 2022 +0200 recursive attention weighting seems to actually work commit a4e1ec6b20deb7cc0cd12737bdbd266e56144709 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 15:06:24 2022 +0200 now apparently almost supported nested attention commit da76fd1ddf22a3888cdc08fd4fed38d8b178e524 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 11 13:23:37 2022 +0200 wip prompt parsing commit dbe0da4572c2ac22f26a7afd722349a5680a9e47 Author: Kyle Schouviller <kyle0654@hotmail.com> Date: Mon Oct 10 22:32:35 2022 -0700 Adding node-based invocation apps commit 8f2a2ffc083366de74d7dae471b50b6f98a7c5f8 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Mon Oct 10 19:03:18 2022 +0200 fix merge issues commit 73118dee2a8f4891700756e014caf1c9ca629267 Merge: fd00844 12413b0 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Mon Oct 10 12:42:48 2022 +0200 Merge remote-tracking branch 'upstream/development' into fix-prompts commit fd0084413541013c2cf71e006af0392719bef53d Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Mon Oct 10 12:39:38 2022 +0200 wip prompt parsing commit 0be9363db9307859d2b65cffc6af01f57d7873a4 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Mon Oct 10 03:20:06 2022 +0200 better +/- attention parsing commit 5383f691874a58ab01cda1e4fac6cf330146526a Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Mon Oct 10 02:27:47 2022 +0200 prompt parser seems to work commit 591d098a33ce35462428d8c169501d8ed73615ab Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Sun Oct 9 20:25:37 2022 +0200 supports weighting unconditioning, cross-attention with | commit 7a7220563aa05a2980235b5b908362f66b728309 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Sun Oct 9 18:15:56 2022 +0200 i think cross attention might be working? commit 951ed391e7126bff228c18b2db304ad28d59644a Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Sun Oct 9 16:04:54 2022 +0200 weighted CFG denoiser working with a single item commit ee532a0c2827368c9e45a6a5f3975666402873da Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Sun Oct 9 06:33:40 2022 +0200 wip probably doesn't work or compile commit 14654bcbd207b9ca28a6cbd37dbd967d699b062d Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Fri Oct 7 18:11:48 2022 +0200 use tan() to calculate embedding weight for <1 attentions commit 1a8e76b31aa5abf5150419ebf3b29d4658d07f2b Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Fri Oct 7 16:14:54 2022 +0200 fix bad math.max reference commit f697ff896875876ccaa1e5527405bdaa7ed27cde Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Fri Oct 7 15:55:57 2022 +0200 respect http[s]x protocol when making socket.io middleware commit 41d3dd4eeae8d4efb05dfb44fc6d8aac5dc468ab Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Fri Oct 7 13:29:54 2022 +0200 fractional weighting works, by blending with prompts excluding the word commit 087fb6dfb3e8f5e84de8c911f75faa3e3fa3553c Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Fri Oct 7 10:52:03 2022 +0200 wip doing weights <1 by averaging with conditioning absent the lower-weighted fragment commit 3c49e3f3ec7c18dc60f3e18ed2f7f0d97aad3a47 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Fri Oct 7 10:36:15 2022 +0200 notate CFGDenoiser, perhaps commit d2bcf1bb522026ebf209ad0103f6b370383e5070 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Thu Oct 6 05:04:47 2022 +0200 hack blending syntax to test attention weighting more extensively commit 94904ef2cf917f74ec23ef7a570e12ff8255b048 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Thu Oct 6 04:56:37 2022 +0200 conditioning works, apparently commit 7c6663ddd70f665fd1308b6dd74f92ca393a8df5 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Thu Oct 6 02:20:24 2022 +0200 attention weighting, definitely works in positive direction commit 5856d453a9b020bc1a28ff643ae1f58c12c9be73 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 4 19:02:14 2022 +0200 wip bubbling weights down commit a2ed14fd9b7d3cb36b6c5348018b364c76d1e892 Author: Damian at mba <damian@frey.NOSPAMco.nz> Date: Tue Oct 4 17:35:39 2022 +0200 bring in changes from PC
823 lines
23 KiB
Python
823 lines
23 KiB
Python
import mimetypes
|
|
import transformers
|
|
import json
|
|
import os
|
|
import traceback
|
|
import eventlet
|
|
import glob
|
|
import shlex
|
|
import math
|
|
import shutil
|
|
import sys
|
|
|
|
sys.path.append(".")
|
|
|
|
from argparse import ArgumentTypeError
|
|
from modules.create_cmd_parser import create_cmd_parser
|
|
|
|
parser = create_cmd_parser()
|
|
opt = parser.parse_args()
|
|
|
|
|
|
from flask_socketio import SocketIO
|
|
from flask import Flask, send_from_directory, url_for, jsonify
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
from pytorch_lightning import logging
|
|
from threading import Event
|
|
from uuid import uuid4
|
|
from send2trash import send2trash
|
|
|
|
|
|
from ldm.generate import Generate
|
|
from ldm.invoke.restoration import Restoration
|
|
from ldm.invoke.pngwriter import PngWriter, retrieve_metadata
|
|
from ldm.invoke.args import APP_ID, APP_VERSION, calculate_init_img_hash
|
|
from ldm.invoke.conditioning import split_weighted_subprompts
|
|
|
|
from modules.parameters import parameters_to_command
|
|
|
|
|
|
"""
|
|
USER CONFIG
|
|
"""
|
|
if opt.cors and "*" in opt.cors:
|
|
raise ArgumentTypeError('"*" is not an allowed CORS origin')
|
|
|
|
|
|
output_dir = "outputs/" # Base output directory for images
|
|
host = opt.host # Web & socket.io host
|
|
port = opt.port # Web & socket.io port
|
|
verbose = opt.verbose # enables copious socket.io logging
|
|
precision = opt.precision
|
|
free_gpu_mem = opt.free_gpu_mem
|
|
embedding_path = opt.embedding_path
|
|
additional_allowed_origins = (
|
|
opt.cors if opt.cors else []
|
|
) # additional CORS allowed origins
|
|
model = "stable-diffusion-1.4"
|
|
|
|
"""
|
|
END USER CONFIG
|
|
"""
|
|
|
|
|
|
print("* Initializing, be patient...\n")
|
|
|
|
|
|
"""
|
|
SERVER SETUP
|
|
"""
|
|
|
|
|
|
# fix missing mimetypes on windows due to registry wonkiness
|
|
mimetypes.add_type("application/javascript", ".js")
|
|
mimetypes.add_type("text/css", ".css")
|
|
|
|
app = Flask(__name__, static_url_path="", static_folder="../frontend/dist/")
|
|
|
|
|
|
app.config["OUTPUTS_FOLDER"] = "../outputs"
|
|
|
|
|
|
@app.route("/outputs/<path:filename>")
|
|
def outputs(filename):
|
|
return send_from_directory(app.config["OUTPUTS_FOLDER"], filename)
|
|
|
|
|
|
@app.route("/", defaults={"path": ""})
|
|
def serve(path):
|
|
return send_from_directory(app.static_folder, "index.html")
|
|
|
|
|
|
logger = True if verbose else False
|
|
engineio_logger = True if verbose else False
|
|
|
|
# default 1,000,000, needs to be higher for socketio to accept larger images
|
|
max_http_buffer_size = 10000000
|
|
|
|
cors_allowed_origins = [f"http://{host}:{port}"] + additional_allowed_origins
|
|
|
|
socketio = SocketIO(
|
|
app,
|
|
logger=logger,
|
|
engineio_logger=engineio_logger,
|
|
max_http_buffer_size=max_http_buffer_size,
|
|
cors_allowed_origins=cors_allowed_origins,
|
|
ping_interval=(50, 50),
|
|
ping_timeout=60,
|
|
)
|
|
|
|
|
|
"""
|
|
END SERVER SETUP
|
|
"""
|
|
|
|
|
|
"""
|
|
APP SETUP
|
|
"""
|
|
|
|
|
|
class CanceledException(Exception):
|
|
pass
|
|
|
|
|
|
try:
|
|
gfpgan, codeformer, esrgan = None, None, None
|
|
from ldm.invoke.restoration.base import Restoration
|
|
|
|
restoration = Restoration()
|
|
gfpgan, codeformer = restoration.load_face_restore_models()
|
|
esrgan = restoration.load_esrgan()
|
|
|
|
# coreformer.process(self, image, strength, device, seed=None, fidelity=0.75)
|
|
|
|
except (ModuleNotFoundError, ImportError):
|
|
print(traceback.format_exc(), file=sys.stderr)
|
|
print(">> You may need to install the ESRGAN and/or GFPGAN modules")
|
|
|
|
canceled = Event()
|
|
|
|
# reduce logging outputs to error
|
|
transformers.logging.set_verbosity_error()
|
|
logging.getLogger("pytorch_lightning").setLevel(logging.ERROR)
|
|
|
|
# Initialize and load model
|
|
generate = Generate(
|
|
model,
|
|
precision=precision,
|
|
embedding_path=embedding_path,
|
|
)
|
|
generate.free_gpu_mem = free_gpu_mem
|
|
generate.load_model()
|
|
|
|
|
|
# location for "finished" images
|
|
result_path = os.path.join(output_dir, "img-samples/")
|
|
|
|
# temporary path for intermediates
|
|
intermediate_path = os.path.join(result_path, "intermediates/")
|
|
|
|
# path for user-uploaded init images and masks
|
|
init_image_path = os.path.join(result_path, "init-images/")
|
|
mask_image_path = os.path.join(result_path, "mask-images/")
|
|
|
|
# txt log
|
|
log_path = os.path.join(result_path, "invoke_log.txt")
|
|
|
|
# make all output paths
|
|
[
|
|
os.makedirs(path, exist_ok=True)
|
|
for path in [result_path, intermediate_path, init_image_path, mask_image_path]
|
|
]
|
|
|
|
|
|
"""
|
|
END APP SETUP
|
|
"""
|
|
|
|
|
|
"""
|
|
SOCKET.IO LISTENERS
|
|
"""
|
|
|
|
|
|
@socketio.on("requestSystemConfig")
|
|
def handle_request_capabilities():
|
|
print(f">> System config requested")
|
|
config = get_system_config()
|
|
socketio.emit("systemConfig", config)
|
|
|
|
|
|
@socketio.on("requestImages")
|
|
def handle_request_images(page=1, offset=0, last_mtime=None):
|
|
chunk_size = 50
|
|
|
|
if last_mtime:
|
|
print(f">> Latest images requested")
|
|
else:
|
|
print(
|
|
f">> Page {page} of images requested (page size {chunk_size} offset {offset})"
|
|
)
|
|
|
|
paths = glob.glob(os.path.join(result_path, "*.png"))
|
|
sorted_paths = sorted(paths, key=lambda x: os.path.getmtime(x), reverse=True)
|
|
|
|
if last_mtime:
|
|
image_paths = filter(lambda x: os.path.getmtime(x) > last_mtime, sorted_paths)
|
|
else:
|
|
|
|
image_paths = sorted_paths[
|
|
slice(chunk_size * (page - 1) + offset, chunk_size * page + offset)
|
|
]
|
|
page = page + 1
|
|
|
|
image_array = []
|
|
|
|
for path in image_paths:
|
|
metadata = retrieve_metadata(path)
|
|
image_array.append(
|
|
{
|
|
"url": path,
|
|
"mtime": os.path.getmtime(path),
|
|
"metadata": metadata["sd-metadata"],
|
|
}
|
|
)
|
|
|
|
socketio.emit(
|
|
"galleryImages",
|
|
{
|
|
"images": image_array,
|
|
"nextPage": page,
|
|
"offset": offset,
|
|
"onlyNewImages": True if last_mtime else False,
|
|
},
|
|
)
|
|
|
|
|
|
@socketio.on("generateImage")
|
|
def handle_generate_image_event(
|
|
generation_parameters, esrgan_parameters, gfpgan_parameters
|
|
):
|
|
print(
|
|
f">> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}"
|
|
)
|
|
generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters)
|
|
|
|
|
|
@socketio.on("runESRGAN")
|
|
def handle_run_esrgan_event(original_image, esrgan_parameters):
|
|
print(
|
|
f'>> ESRGAN upscale requested for "{original_image["url"]}": {esrgan_parameters}'
|
|
)
|
|
progress = {
|
|
"currentStep": 1,
|
|
"totalSteps": 1,
|
|
"currentIteration": 1,
|
|
"totalIterations": 1,
|
|
"currentStatus": "Preparing",
|
|
"isProcessing": True,
|
|
"currentStatusHasSteps": False,
|
|
}
|
|
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
image = Image.open(original_image["url"])
|
|
|
|
seed = (
|
|
original_image["metadata"]["seed"]
|
|
if "seed" in original_image["metadata"]
|
|
else "unknown_seed"
|
|
)
|
|
|
|
progress["currentStatus"] = "Upscaling"
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
image = esrgan.process(
|
|
image=image,
|
|
upsampler_scale=esrgan_parameters["upscale"][0],
|
|
strength=esrgan_parameters["upscale"][1],
|
|
seed=seed,
|
|
)
|
|
|
|
progress["currentStatus"] = "Saving image"
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
esrgan_parameters["seed"] = seed
|
|
metadata = parameters_to_post_processed_image_metadata(
|
|
parameters=esrgan_parameters,
|
|
original_image_path=original_image["url"],
|
|
type="esrgan",
|
|
)
|
|
command = parameters_to_command(esrgan_parameters)
|
|
|
|
path = save_image(image, command, metadata, result_path, postprocessing="esrgan")
|
|
|
|
write_log_message(f'[Upscaled] "{original_image["url"]}" > "{path}": {command}')
|
|
|
|
progress["currentStatus"] = "Finished"
|
|
progress["currentStep"] = 0
|
|
progress["totalSteps"] = 0
|
|
progress["currentIteration"] = 0
|
|
progress["totalIterations"] = 0
|
|
progress["isProcessing"] = False
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
socketio.emit(
|
|
"esrganResult",
|
|
{
|
|
"url": os.path.relpath(path),
|
|
"mtime": os.path.getmtime(path),
|
|
"metadata": metadata,
|
|
},
|
|
)
|
|
|
|
|
|
@socketio.on("runGFPGAN")
|
|
def handle_run_gfpgan_event(original_image, gfpgan_parameters):
|
|
print(
|
|
f'>> GFPGAN face fix requested for "{original_image["url"]}": {gfpgan_parameters}'
|
|
)
|
|
progress = {
|
|
"currentStep": 1,
|
|
"totalSteps": 1,
|
|
"currentIteration": 1,
|
|
"totalIterations": 1,
|
|
"currentStatus": "Preparing",
|
|
"isProcessing": True,
|
|
"currentStatusHasSteps": False,
|
|
}
|
|
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
image = Image.open(original_image["url"])
|
|
|
|
seed = (
|
|
original_image["metadata"]["seed"]
|
|
if "seed" in original_image["metadata"]
|
|
else "unknown_seed"
|
|
)
|
|
|
|
progress["currentStatus"] = "Fixing faces"
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
image = gfpgan.process(
|
|
image=image, strength=gfpgan_parameters["facetool_strength"], seed=seed
|
|
)
|
|
|
|
progress["currentStatus"] = "Saving image"
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
gfpgan_parameters["seed"] = seed
|
|
metadata = parameters_to_post_processed_image_metadata(
|
|
parameters=gfpgan_parameters,
|
|
original_image_path=original_image["url"],
|
|
type="gfpgan",
|
|
)
|
|
command = parameters_to_command(gfpgan_parameters)
|
|
|
|
path = save_image(image, command, metadata, result_path, postprocessing="gfpgan")
|
|
|
|
write_log_message(f'[Fixed faces] "{original_image["url"]}" > "{path}": {command}')
|
|
|
|
progress["currentStatus"] = "Finished"
|
|
progress["currentStep"] = 0
|
|
progress["totalSteps"] = 0
|
|
progress["currentIteration"] = 0
|
|
progress["totalIterations"] = 0
|
|
progress["isProcessing"] = False
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
socketio.emit(
|
|
"gfpganResult",
|
|
{
|
|
"url": os.path.relpath(path),
|
|
"mtime": os.path.mtime(path),
|
|
"metadata": metadata,
|
|
},
|
|
)
|
|
|
|
|
|
@socketio.on("cancel")
|
|
def handle_cancel():
|
|
print(f">> Cancel processing requested")
|
|
canceled.set()
|
|
socketio.emit("processingCanceled")
|
|
|
|
|
|
# TODO: I think this needs a safety mechanism.
|
|
@socketio.on("deleteImage")
|
|
def handle_delete_image(path, uuid):
|
|
print(f'>> Delete requested "{path}"')
|
|
send2trash(path)
|
|
socketio.emit("imageDeleted", {"url": path, "uuid": uuid})
|
|
|
|
|
|
# TODO: I think this needs a safety mechanism.
|
|
@socketio.on("uploadInitialImage")
|
|
def handle_upload_initial_image(bytes, name):
|
|
print(f'>> Init image upload requested "{name}"')
|
|
uuid = uuid4().hex
|
|
split = os.path.splitext(name)
|
|
name = f"{split[0]}.{uuid}{split[1]}"
|
|
file_path = os.path.join(init_image_path, name)
|
|
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
newFile = open(file_path, "wb")
|
|
newFile.write(bytes)
|
|
socketio.emit("initialImageUploaded", {"url": file_path, "uuid": ""})
|
|
|
|
|
|
# TODO: I think this needs a safety mechanism.
|
|
@socketio.on("uploadMaskImage")
|
|
def handle_upload_mask_image(bytes, name):
|
|
print(f'>> Mask image upload requested "{name}"')
|
|
uuid = uuid4().hex
|
|
split = os.path.splitext(name)
|
|
name = f"{split[0]}.{uuid}{split[1]}"
|
|
file_path = os.path.join(mask_image_path, name)
|
|
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
newFile = open(file_path, "wb")
|
|
newFile.write(bytes)
|
|
socketio.emit("maskImageUploaded", {"url": file_path, "uuid": ""})
|
|
|
|
|
|
"""
|
|
END SOCKET.IO LISTENERS
|
|
"""
|
|
|
|
|
|
"""
|
|
ADDITIONAL FUNCTIONS
|
|
"""
|
|
|
|
|
|
def get_system_config():
|
|
return {
|
|
"model": "stable diffusion",
|
|
"model_id": model,
|
|
"model_hash": generate.model_hash,
|
|
"app_id": APP_ID,
|
|
"app_version": APP_VERSION,
|
|
}
|
|
|
|
|
|
def parameters_to_post_processed_image_metadata(parameters, original_image_path, type):
|
|
# top-level metadata minus `image` or `images`
|
|
metadata = get_system_config()
|
|
|
|
orig_hash = calculate_init_img_hash(original_image_path)
|
|
|
|
image = {"orig_path": original_image_path, "orig_hash": orig_hash}
|
|
|
|
if type == "esrgan":
|
|
image["type"] = "esrgan"
|
|
image["scale"] = parameters["upscale"][0]
|
|
image["strength"] = parameters["upscale"][1]
|
|
elif type == "gfpgan":
|
|
image["type"] = "gfpgan"
|
|
image["strength"] = parameters["facetool_strength"]
|
|
else:
|
|
raise TypeError(f"Invalid type: {type}")
|
|
|
|
metadata["image"] = image
|
|
return metadata
|
|
|
|
|
|
def parameters_to_generated_image_metadata(parameters):
|
|
# top-level metadata minus `image` or `images`
|
|
|
|
metadata = get_system_config()
|
|
# remove any image keys not mentioned in RFC #266
|
|
rfc266_img_fields = [
|
|
"type",
|
|
"postprocessing",
|
|
"sampler",
|
|
"prompt",
|
|
"seed",
|
|
"variations",
|
|
"steps",
|
|
"cfg_scale",
|
|
"threshold",
|
|
"perlin",
|
|
"step_number",
|
|
"width",
|
|
"height",
|
|
"extra",
|
|
"seamless",
|
|
"hires_fix",
|
|
]
|
|
|
|
rfc_dict = {}
|
|
|
|
for item in parameters.items():
|
|
key, value = item
|
|
if key in rfc266_img_fields:
|
|
rfc_dict[key] = value
|
|
|
|
postprocessing = []
|
|
|
|
# 'postprocessing' is either null or an
|
|
if "facetool_strength" in parameters:
|
|
|
|
postprocessing.append(
|
|
{"type": "gfpgan", "strength": float(parameters["facetool_strength"])}
|
|
)
|
|
|
|
if "upscale" in parameters:
|
|
postprocessing.append(
|
|
{
|
|
"type": "esrgan",
|
|
"scale": int(parameters["upscale"][0]),
|
|
"strength": float(parameters["upscale"][1]),
|
|
}
|
|
)
|
|
|
|
rfc_dict["postprocessing"] = postprocessing if len(postprocessing) > 0 else None
|
|
|
|
# semantic drift
|
|
rfc_dict["sampler"] = parameters["sampler_name"]
|
|
|
|
# display weighted subprompts (liable to change)
|
|
subprompts = split_weighted_subprompts(parameters["prompt"], skip_normalize=True)
|
|
subprompts = [{"prompt": x[0], "weight": x[1]} for x in subprompts]
|
|
rfc_dict["prompt"] = subprompts
|
|
|
|
# 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs
|
|
variations = []
|
|
|
|
if "with_variations" in parameters:
|
|
variations = [
|
|
{"seed": x[0], "weight": x[1]} for x in parameters["with_variations"]
|
|
]
|
|
|
|
rfc_dict["variations"] = variations
|
|
|
|
if "init_img" in parameters:
|
|
rfc_dict["type"] = "img2img"
|
|
rfc_dict["strength"] = parameters["strength"]
|
|
rfc_dict["fit"] = parameters["fit"] # TODO: Noncompliant
|
|
rfc_dict["orig_hash"] = calculate_init_img_hash(parameters["init_img"])
|
|
rfc_dict["init_image_path"] = parameters["init_img"] # TODO: Noncompliant
|
|
rfc_dict["sampler"] = "ddim" # TODO: FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS
|
|
if "init_mask" in parameters:
|
|
rfc_dict["mask_hash"] = calculate_init_img_hash(
|
|
parameters["init_mask"]
|
|
) # TODO: Noncompliant
|
|
rfc_dict["mask_image_path"] = parameters["init_mask"] # TODO: Noncompliant
|
|
else:
|
|
rfc_dict["type"] = "txt2img"
|
|
|
|
metadata["image"] = rfc_dict
|
|
|
|
return metadata
|
|
|
|
|
|
def make_unique_init_image_filename(name):
|
|
uuid = uuid4().hex
|
|
split = os.path.splitext(name)
|
|
name = f"{split[0]}.{uuid}{split[1]}"
|
|
return name
|
|
|
|
|
|
def write_log_message(message, log_path=log_path):
|
|
"""Logs the filename and parameters used to generate or process that image to log file"""
|
|
message = f"{message}\n"
|
|
with open(log_path, "a", encoding="utf-8") as file:
|
|
file.writelines(message)
|
|
|
|
|
|
def save_image(
|
|
image, command, metadata, output_dir, step_index=None, postprocessing=False
|
|
):
|
|
pngwriter = PngWriter(output_dir)
|
|
prefix = pngwriter.unique_prefix()
|
|
|
|
seed = "unknown_seed"
|
|
|
|
if "image" in metadata:
|
|
if "seed" in metadata["image"]:
|
|
seed = metadata["image"]["seed"]
|
|
|
|
filename = f"{prefix}.{seed}"
|
|
|
|
if step_index:
|
|
filename += f".{step_index}"
|
|
if postprocessing:
|
|
filename += f".postprocessed"
|
|
|
|
filename += ".png"
|
|
|
|
path = pngwriter.save_image_and_prompt_to_png(
|
|
image=image, dream_prompt=command, metadata=metadata, name=filename
|
|
)
|
|
|
|
return path
|
|
|
|
|
|
def calculate_real_steps(steps, strength, has_init_image):
|
|
return math.floor(strength * steps) if has_init_image else steps
|
|
|
|
|
|
def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters):
|
|
canceled.clear()
|
|
|
|
step_index = 1
|
|
prior_variations = (
|
|
generation_parameters["with_variations"]
|
|
if "with_variations" in generation_parameters
|
|
else []
|
|
)
|
|
"""
|
|
If a result image is used as an init image, and then deleted, we will want to be
|
|
able to use it as an init image in the future. Need to copy it.
|
|
|
|
If the init/mask image doesn't exist in the init_image_path/mask_image_path,
|
|
make a unique filename for it and copy it there.
|
|
"""
|
|
if "init_img" in generation_parameters:
|
|
filename = os.path.basename(generation_parameters["init_img"])
|
|
if not os.path.exists(os.path.join(init_image_path, filename)):
|
|
unique_filename = make_unique_init_image_filename(filename)
|
|
new_path = os.path.join(init_image_path, unique_filename)
|
|
shutil.copy(generation_parameters["init_img"], new_path)
|
|
generation_parameters["init_img"] = new_path
|
|
if "init_mask" in generation_parameters:
|
|
filename = os.path.basename(generation_parameters["init_mask"])
|
|
if not os.path.exists(os.path.join(mask_image_path, filename)):
|
|
unique_filename = make_unique_init_image_filename(filename)
|
|
new_path = os.path.join(init_image_path, unique_filename)
|
|
shutil.copy(generation_parameters["init_img"], new_path)
|
|
generation_parameters["init_mask"] = new_path
|
|
|
|
totalSteps = calculate_real_steps(
|
|
steps=generation_parameters["steps"],
|
|
strength=generation_parameters["strength"]
|
|
if "strength" in generation_parameters
|
|
else None,
|
|
has_init_image="init_img" in generation_parameters,
|
|
)
|
|
|
|
progress = {
|
|
"currentStep": 1,
|
|
"totalSteps": totalSteps,
|
|
"currentIteration": 1,
|
|
"totalIterations": generation_parameters["iterations"],
|
|
"currentStatus": "Preparing",
|
|
"isProcessing": True,
|
|
"currentStatusHasSteps": False,
|
|
}
|
|
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
def image_progress(sample, step):
|
|
if canceled.is_set():
|
|
raise CanceledException
|
|
|
|
nonlocal step_index
|
|
nonlocal generation_parameters
|
|
nonlocal progress
|
|
|
|
progress["currentStep"] = step + 1
|
|
progress["currentStatus"] = "Generating"
|
|
progress["currentStatusHasSteps"] = True
|
|
|
|
if (
|
|
generation_parameters["progress_images"]
|
|
and step % 5 == 0
|
|
and step < generation_parameters["steps"] - 1
|
|
):
|
|
image = generate.sample_to_image(sample)
|
|
|
|
metadata = parameters_to_generated_image_metadata(generation_parameters)
|
|
command = parameters_to_command(generation_parameters)
|
|
path = save_image(image, command, metadata, intermediate_path, step_index=step_index, postprocessing=False)
|
|
|
|
step_index += 1
|
|
socketio.emit(
|
|
"intermediateResult",
|
|
{
|
|
"url": os.path.relpath(path),
|
|
"mtime": os.path.getmtime(path),
|
|
"metadata": metadata,
|
|
},
|
|
)
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
def image_done(image, seed, first_seed):
|
|
nonlocal generation_parameters
|
|
nonlocal esrgan_parameters
|
|
nonlocal gfpgan_parameters
|
|
nonlocal progress
|
|
|
|
step_index = 1
|
|
nonlocal prior_variations
|
|
|
|
progress["currentStatus"] = "Generation complete"
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
all_parameters = generation_parameters
|
|
postprocessing = False
|
|
|
|
if (
|
|
"variation_amount" in all_parameters
|
|
and all_parameters["variation_amount"] > 0
|
|
):
|
|
first_seed = first_seed or seed
|
|
this_variation = [[seed, all_parameters["variation_amount"]]]
|
|
all_parameters["with_variations"] = prior_variations + this_variation
|
|
all_parameters["seed"] = first_seed
|
|
elif ("with_variations" in all_parameters):
|
|
all_parameters["seed"] = first_seed
|
|
else:
|
|
all_parameters["seed"] = seed
|
|
|
|
if esrgan_parameters:
|
|
progress["currentStatus"] = "Upscaling"
|
|
progress["currentStatusHasSteps"] = False
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
image = esrgan.process(
|
|
image=image,
|
|
upsampler_scale=esrgan_parameters["level"],
|
|
strength=esrgan_parameters["strength"],
|
|
seed=seed,
|
|
)
|
|
|
|
postprocessing = True
|
|
all_parameters["upscale"] = [
|
|
esrgan_parameters["level"],
|
|
esrgan_parameters["strength"],
|
|
]
|
|
|
|
if gfpgan_parameters:
|
|
progress["currentStatus"] = "Fixing faces"
|
|
progress["currentStatusHasSteps"] = False
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
image = gfpgan.process(
|
|
image=image, strength=gfpgan_parameters["strength"], seed=seed
|
|
)
|
|
postprocessing = True
|
|
all_parameters["facetool_strength"] = gfpgan_parameters["strength"]
|
|
|
|
progress["currentStatus"] = "Saving image"
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
metadata = parameters_to_generated_image_metadata(all_parameters)
|
|
command = parameters_to_command(all_parameters)
|
|
|
|
path = save_image(
|
|
image, command, metadata, result_path, postprocessing=postprocessing
|
|
)
|
|
|
|
print(f'>> Image generated: "{path}"')
|
|
write_log_message(f'[Generated] "{path}": {command}')
|
|
|
|
if progress["totalIterations"] > progress["currentIteration"]:
|
|
progress["currentStep"] = 1
|
|
progress["currentIteration"] += 1
|
|
progress["currentStatus"] = "Iteration finished"
|
|
progress["currentStatusHasSteps"] = False
|
|
else:
|
|
progress["currentStep"] = 0
|
|
progress["totalSteps"] = 0
|
|
progress["currentIteration"] = 0
|
|
progress["totalIterations"] = 0
|
|
progress["currentStatus"] = "Finished"
|
|
progress["isProcessing"] = False
|
|
|
|
socketio.emit("progressUpdate", progress)
|
|
eventlet.sleep(0)
|
|
|
|
socketio.emit(
|
|
"generationResult",
|
|
{
|
|
"url": os.path.relpath(path),
|
|
"mtime": os.path.getmtime(path),
|
|
"metadata": metadata,
|
|
},
|
|
)
|
|
eventlet.sleep(0)
|
|
|
|
try:
|
|
generate.prompt2image(
|
|
**generation_parameters,
|
|
step_callback=image_progress,
|
|
image_callback=image_done,
|
|
)
|
|
|
|
except KeyboardInterrupt:
|
|
raise
|
|
except CanceledException:
|
|
pass
|
|
except Exception as e:
|
|
socketio.emit("error", {"message": (str(e))})
|
|
print("\n")
|
|
traceback.print_exc()
|
|
print("\n")
|
|
|
|
|
|
"""
|
|
END ADDITIONAL FUNCTIONS
|
|
"""
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print(f">> Starting server at http://{host}:{port}")
|
|
socketio.run(app, host=host, port=port)
|