From 29d0d65504bca16e118a36ea355e7951ec12c11f Mon Sep 17 00:00:00 2001 From: hlky <106811348+hlky@users.noreply.github.com> Date: Wed, 24 Aug 2022 12:44:48 +0100 Subject: [PATCH 1/6] jpg grid, named grid Grid file named with seed and prompt, output as jpg --- webui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 9c589bba..6ba76407 100644 --- a/webui.py +++ b/webui.py @@ -401,7 +401,8 @@ def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, output_images.insert(0, grid) - grid.save(os.path.join(outpath, f'grid-{grid_count:04}.png')) + grid_file = f"grid-{grid_count:05}-{seed}_{prompts[i].replace(' ', '_').translate({ord(x): '' for x in invalid_filename_chars})[:128]}.jpg" + grid.save(os.path.join(outpath, grid_file), 'jpeg', quality=80, optimize=True) grid_count += 1 info = f""" From abb83239e5d708adf72fa457c3de81603e7281d3 Mon Sep 17 00:00:00 2001 From: hlky <106811348+hlky@users.noreply.github.com> Date: Wed, 24 Aug 2022 14:12:33 +0100 Subject: [PATCH 2/6] torch_gc/empty cache after generation added torch_gc() which calls both cuda.empty_cache() and cuda.ipc_collect() called before and after generation --- webui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/webui.py b/webui.py index 6ba76407..7521b3ce 100644 --- a/webui.py +++ b/webui.py @@ -126,6 +126,9 @@ def create_random_tensors(shape, seeds): x = torch.stack(xs) return x +def torch_gc(): + torch.cuda.empty_cache() + torch.cuda.ipc_collect() def load_GFPGAN(): model_name = 'GFPGANv1.3' @@ -300,7 +303,7 @@ def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, """this is the main loop that both txt2img and img2img use; it calls func_init once inside all the scopes and func_sample once per batch""" assert prompt is not None - torch.cuda.empty_cache() + torch_gc() if seed == -1: seed = random.randrange(4294967294) @@ -412,7 +415,7 @@ Steps: {steps}, Sampler: {sampler_name}, CFG scale: {cfg_scale}, Seed: {seed}{', for comment in comments: info += "\n\n" + comment - + torch_gc() return output_images, seed, info From da96bbf485b9fbe3d5e82b1795f5dd090fb14f0d Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 24 Aug 2022 17:41:37 +0300 Subject: [PATCH 3/6] commandline options to control saved file type/quality/names --- webui.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/webui.py b/webui.py index 14b0dd24..7f8c928c 100644 --- a/webui.py +++ b/webui.py @@ -50,7 +50,10 @@ parser.add_argument("--no-verify-input", action='store_true', help="do not verif parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats") parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware accleration in browser)") parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI") +parser.add_argument("--save-format", type=str, default='png', help="file format for saved indiviual samples; can be png or jpg") parser.add_argument("--grid-format", type=str, default='png', help="file format for saved grids; can be png or jpg") +parser.add_argument("--grid-extended-filename", action='store_true', help="save grid images to filenames with extended info: seed, prompt") +parser.add_argument("--jpeg-quality", type=int, default=80, help="quality for saved jpeg images") opt = parser.parse_args() GFPGAN_dir = opt.gfpgan_dir @@ -128,10 +131,27 @@ def create_random_tensors(shape, seeds): x = torch.stack(xs) return x + def torch_gc(): torch.cuda.empty_cache() torch.cuda.ipc_collect() + +def sanitize_filename_part(text): + return text.replace(' ', '_').translate({ord(x): '' for x in invalid_filename_chars})[:128] + + +def save_image(image, path, basename, seed, prompt, extension, short_filename=False): + prompt = sanitize_filename_part(prompt) + + if short_filename: + filename = f"{basename}.{extension}" + else: + filename = f"{basename}-{seed}-{prompt[:128]}.{extension}" + + image.save(os.path.join(path, filename), quality=opt.jpeg_quality) + + def load_GFPGAN(): model_name = 'GFPGANv1.3' model_path = os.path.join(GFPGAN_dir, 'experiments/pretrained_models', model_name + '.pth') @@ -387,9 +407,7 @@ def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, x_sample = restored_img image = Image.fromarray(x_sample) - filename = f"{base_count:05}-{seeds[i]}_{prompts[i].replace(' ', '_').translate({ord(x): '' for x in invalid_filename_chars})[:128]}.png" - - image.save(os.path.join(sample_path, filename)) + save_image(image, sample_path, f"{base_count:05}", seeds[i], prompts[i], opt.save_format) output_images.append(image) base_count += 1 @@ -408,8 +426,7 @@ def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, output_images.insert(0, grid) - - grid.save(os.path.join(outpath, f'grid-{grid_count:04}.{opt.grid_format}')) + save_image(grid, outpath, f"grid-{grid_count:04}", seed, prompt, opt.grid_format, short_filename=not opt.grid_extended_filename) grid_count += 1 info = f""" @@ -601,7 +618,8 @@ def img2img(prompt: str, init_img, ddim_steps: int, use_GFPGAN: bool, prompt_mat grid_count = len(os.listdir(outpath)) - 1 grid = image_grid(history, batch_size, force_n_rows=1) - grid.save(os.path.join(outpath, f'grid-{grid_count:04}.{opt.grid_format}')) + + save_image(grid, outpath, f"grid-{grid_count:04}", initial_seed, prompt, opt.grid_format, short_filename=not opt.grid_extended_filename) output_images = history seed = initial_seed From 29f7e7ab895e33367934130685f88430d1d8ed37 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 24 Aug 2022 17:57:49 +0300 Subject: [PATCH 4/6] save image generation params into text chunks for png images --- webui.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/webui.py b/webui.py index 7f8c928c..742615e1 100644 --- a/webui.py +++ b/webui.py @@ -4,7 +4,7 @@ import torch.nn as nn import numpy as np import gradio as gr from omegaconf import OmegaConf -from PIL import Image, ImageFont, ImageDraw +from PIL import Image, ImageFont, ImageDraw, PngImagePlugin from itertools import islice from einops import rearrange, repeat from torch import autocast @@ -49,11 +49,13 @@ parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=( parser.add_argument("--no-verify-input", action='store_true', help="do not verify input to check if it's too long") parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats") parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware accleration in browser)") -parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI") -parser.add_argument("--save-format", type=str, default='png', help="file format for saved indiviual samples; can be png or jpg") -parser.add_argument("--grid-format", type=str, default='png', help="file format for saved grids; can be png or jpg") +parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI") +parser.add_argument("--save-format", type=str, default='png', help="file format for saved indiviual samples; can be png or jpg") +parser.add_argument("--grid-format", type=str, default='png', help="file format for saved grids; can be png or jpg") parser.add_argument("--grid-extended-filename", action='store_true', help="save grid images to filenames with extended info: seed, prompt") -parser.add_argument("--jpeg-quality", type=int, default=80, help="quality for saved jpeg images") +parser.add_argument("--jpeg-quality", type=int, default=80, help="quality for saved jpeg images") +parser.add_argument("--disable-pnginfo", action='store_true', help="disable saving text information about generation parameters as chunks to png files") + opt = parser.parse_args() GFPGAN_dir = opt.gfpgan_dir @@ -141,7 +143,7 @@ def sanitize_filename_part(text): return text.replace(' ', '_').translate({ord(x): '' for x in invalid_filename_chars})[:128] -def save_image(image, path, basename, seed, prompt, extension, short_filename=False): +def save_image(image, path, basename, seed, prompt, extension, info=None, short_filename=False): prompt = sanitize_filename_part(prompt) if short_filename: @@ -149,7 +151,13 @@ def save_image(image, path, basename, seed, prompt, extension, short_filename=Fa else: filename = f"{basename}-{seed}-{prompt[:128]}.{extension}" - image.save(os.path.join(path, filename), quality=opt.jpeg_quality) + if extension == 'png' and not opt.disable_pnginfo: + pnginfo = PngImagePlugin.PngInfo() + pnginfo.add_text("parameters", info) + else: + pnginfo = None + + image.save(os.path.join(path, filename), quality=opt.jpeg_quality, pnginfo=pnginfo) def load_GFPGAN(): @@ -373,6 +381,11 @@ def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, all_prompts = batch_size * n_iter * [prompt] all_seeds = [seed + x for x in range(len(all_prompts))] + info = f""" + {prompt} + Steps: {steps}, Sampler: {sampler_name}, CFG scale: {cfg_scale}, Seed: {seed}{', GFPGAN' if use_GFPGAN and GFPGAN is not None else ''} + """.strip() + "".join(["\n\n" + x for x in comments]) + precision_scope = autocast if opt.precision == "autocast" else nullcontext output_images = [] with torch.no_grad(), precision_scope("cuda"), model.ema_scope(): @@ -407,7 +420,7 @@ def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, x_sample = restored_img image = Image.fromarray(x_sample) - save_image(image, sample_path, f"{base_count:05}", seeds[i], prompts[i], opt.save_format) + save_image(image, sample_path, f"{base_count:05}", seeds[i], prompts[i], opt.save_format, info=info) output_images.append(image) base_count += 1 @@ -426,16 +439,9 @@ def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, output_images.insert(0, grid) - save_image(grid, outpath, f"grid-{grid_count:04}", seed, prompt, opt.grid_format, short_filename=not opt.grid_extended_filename) + save_image(grid, outpath, f"grid-{grid_count:04}", seed, prompt, opt.grid_format, info=info, short_filename=not opt.grid_extended_filename) grid_count += 1 - info = f""" -{prompt} -Steps: {steps}, Sampler: {sampler_name}, CFG scale: {cfg_scale}, Seed: {seed}{', GFPGAN' if use_GFPGAN and GFPGAN is not None else ''} - """.strip() - - for comment in comments: - info += "\n\n" + comment torch_gc() return output_images, seed, info @@ -619,7 +625,7 @@ def img2img(prompt: str, init_img, ddim_steps: int, use_GFPGAN: bool, prompt_mat grid_count = len(os.listdir(outpath)) - 1 grid = image_grid(history, batch_size, force_n_rows=1) - save_image(grid, outpath, f"grid-{grid_count:04}", initial_seed, prompt, opt.grid_format, short_filename=not opt.grid_extended_filename) + save_image(grid, outpath, f"grid-{grid_count:04}", initial_seed, prompt, opt.grid_format, info=info, short_filename=not opt.grid_extended_filename) output_images = history seed = initial_seed From 199123e98ddd4a2747e12e83ba4442d21e2109db Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 24 Aug 2022 18:47:23 +0300 Subject: [PATCH 5/6] add execution timings to output change the text output element to HTML --- webui.py | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/webui.py b/webui.py index 742615e1..51ce02c7 100644 --- a/webui.py +++ b/webui.py @@ -12,6 +12,8 @@ from contextlib import contextmanager, nullcontext import mimetypes import random import math +import html +import time import k_diffusion as K from ldm.util import instantiate_from_config @@ -160,6 +162,11 @@ def save_image(image, path, basename, seed, prompt, extension, info=None, short_ image.save(os.path.join(path, filename), quality=opt.jpeg_quality, pnginfo=pnginfo) +def plaintext_to_html(text): + text = "".join([f"
{html.escape(x)}
\n" for x in text.split('\n')]) + return text + + def load_GFPGAN(): model_name = 'GFPGANv1.3' model_path = os.path.join(GFPGAN_dir, 'experiments/pretrained_models', model_name + '.pth') @@ -331,6 +338,20 @@ def check_prompt_length(prompt, comments): comments.append(f"Warning: too many input tokens; some ({len(overflowing_words)}) have been truncated:\n{overflowing_text}\n") +def wrap_gradio_call(func): + def f(*p1, **p2): + t = time.perf_counter() + res = list(func(*p1, **p2)) + elapsed = time.perf_counter() - t + + # last item is always HTML + res[-1] = res[-1] + f"Time taken: {elapsed:.2f}s
" + + return tuple(res) + + return f + + def process_images(outpath, func_init, func_sample, prompt, seed, sampler_name, batch_size, n_iter, steps, cfg_scale, width, height, prompt_matrix, use_GFPGAN, do_not_save_grid=False): """this is the main loop that both txt2img and img2img use; it calls func_init once inside all the scopes and func_sample once per batch""" @@ -484,7 +505,7 @@ def txt2img(prompt: str, ddim_steps: int, sampler_name: str, use_GFPGAN: bool, p del sampler - return output_images, seed, info + return output_images, seed, plaintext_to_html(info) class Flagging(gr.FlaggingCallback): @@ -529,7 +550,7 @@ class Flagging(gr.FlaggingCallback): txt2img_interface = gr.Interface( - txt2img, + wrap_gradio_call(txt2img), inputs=[ gr.Textbox(label="Prompt", placeholder="A corgi wearing a top hat as an oil painting.", lines=1), gr.Slider(minimum=1, maximum=150, step=1, label="Sampling Steps", value=50), @@ -547,7 +568,7 @@ txt2img_interface = gr.Interface( outputs=[ gr.Gallery(label="Images"), gr.Number(label='Seed'), - gr.Textbox(label="Copy-paste generation parameters"), + gr.HTML(), ], title="Stable Diffusion Text-to-Image K", description="Generate images from text with Stable Diffusion (using K-LMS)", @@ -650,14 +671,14 @@ def img2img(prompt: str, init_img, ddim_steps: int, use_GFPGAN: bool, prompt_mat del sampler - return output_images, seed, info + return output_images, seed, plaintext_to_html(info) sample_img2img = "assets/stable-samples/img2img/sketch-mountains-input.jpg" sample_img2img = sample_img2img if os.path.exists(sample_img2img) else None img2img_interface = gr.Interface( - img2img, + wrap_gradio_call(img2img), inputs=[ gr.Textbox(placeholder="A fantasy landscape, trending on artstation.", lines=1), gr.Image(value=sample_img2img, source="upload", interactive=True, type="pil"), @@ -677,7 +698,7 @@ img2img_interface = gr.Interface( outputs=[ gr.Gallery(), gr.Number(label='Seed'), - gr.Textbox(label="Copy-paste generation parameters"), + gr.HTML(), ], title="Stable Diffusion Image-to-Image", description="Generate images from images with Stable Diffusion", @@ -698,7 +719,7 @@ def run_GFPGAN(image, strength): if strength < 1.0: res = Image.blend(image, res, strength) - return res + return res, 0, '' if GFPGAN is not None: @@ -710,6 +731,8 @@ if GFPGAN is not None: ], outputs=[ gr.Image(label="Result"), + gr.Number(label='Seed', visible=False), + gr.HTML(), ], title="GFPGAN", description="Fix faces on images", @@ -719,7 +742,10 @@ if GFPGAN is not None: demo = gr.TabbedInterface( interface_list=[x[0] for x in interfaces], tab_names=[x[1] for x in interfaces], - css=("" if opt.no_progressbar_hiding else css_hide_progressbar) + css=("" if opt.no_progressbar_hiding else css_hide_progressbar) + """ +.output-html p {margin: 0 0.5em;} +.performance { font-size: 0.85em; color: #444; } +""" ) demo.launch() From e0339e29a5a294d9280580aca189129356740633 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 24 Aug 2022 19:05:03 +0300 Subject: [PATCH 6/6] png chunk info for readme --- README.md | 9 +++++++++ images/pnginfo.png | Bin 0 -> 115866 bytes 2 files changed, 9 insertions(+) create mode 100644 images/pnginfo.png diff --git a/README.md b/README.md index 2b6d33f0..a3c36aac 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,12 @@ to get otherwise. Example: (cherrypicked result; original picture by anon) ![](images/loopback.jpg) + +### Png info +Adds information about generation parameters to PNG as a text chunk. You +can view this information later using any software that supports viewing +PNG chunk info, for example: https://www.nayuki.io/page/png-file-chunk-inspector + +This can be disabled using the `--disable-pnginfo` command line option. + +![](images/pnginfo.png) diff --git a/images/pnginfo.png b/images/pnginfo.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e0f6d17003c057aa2f0a32c6854c41bef1cd80 GIT binary patch literal 115866 zcmaI-1C%98*DVZ}ZQEV8ZQHhO8(r?Q?dr0+Y}-{`=(275+kMXSy!Vbf{`cQwXu(SA>h0>i+O)?mG-v AXo$Taa9
z=1s{b5!0foqQOAZr|-TRQ?^(cHyvMZ;?39r=K6|xV$!#@(e`-UWVe}QnW8oz5eI+o
z^r3ivcrvt+^czh)j@?u3n#}X6{@R60@Ep?*CS(q3eNxT9ucrOF)1vQ2fu+j(JwtTM
zuZ?_Ei0j?iEk)OZ%Qz=H@%6q>&4pHVXKcP}3`e&ocdQ8gZ`6TZ50u!#)HFHkP4V?m
zS4m0#m7ys@0kA6b<130GgRL^lS5#Cd=i-z-UDF_})k;jPQPo&(^d45tei~I*QzPW|
zq!b{;{S&%2rdpAZa1$H~tIgqcbC;MWCnqPy8+ro(CDuGfOXNpVEaU!{eDbfESm?Kt
zsmI9JEs8|$3^_eLJ;35ctmFWCqxm@<18qc+zSjm2#q`CX9n|1mZ*zINE(jSPr)I2~
zeJCizjEB;d-Hde6#aDQ?
xrv=z0S>{
zZD$T${wEd}an#KDtJ#p@7Q6pq=
zF7z3)SDy_vUeDZN2ZqPrvT35VExA{GF(?2{T7lIpS={^Z(;Z)N!lEdU>d8grfH;UJ
z_EMprY9WCjkJEW?k1(19?*`jH^W2e=e8Q*(hwpb(b2q1w=jKWEx^p8sZqm)d#x$d=
zA{GvLTc;d15=%JwB+AEbi_^l95oPmHyCJF7=
-aa&=j@CI)|@5;ck)TCrMQ1%j=W4
zS(*ST7Vn>aWjkNYe86J;6nQ_4qIg~$@KqKfu9#TAA^)gx?uSo@u5VUrA52Cg#-^}2
zIC6?EbeAMojq;O0lfBKUu
+
%SqHwSGmS&?kww#=6L+O;oMBJIH?PJs4-2`
k2vQn?R-dOzIS)
zE5(;=uAEC3k?PCisHbQ5m%O0#jq6rluKoG&hNXfj1rOPFinjb~LH8|4ygBqAVzSc(
z@g#m!m;(~$t)98;{e1OaP4u0p4gz?s!9PY!O&D^_Y7q+-Ha6D;GG*^d+GYf=GTSXB
za#&nCIv?I!ZW{=&^1U9a&tJ%e+?*qxd@Wv!`