From 70931652a4289e28d83869b6d10cf11e80a70345 Mon Sep 17 00:00:00 2001 From: RnDMonkey Date: Fri, 30 Sep 2022 18:02:46 -0700 Subject: [PATCH 0001/1118] [xy_grid] made -1 seed fixing apply to Var. seed too --- scripts/xy_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index 146663b0..9c078888 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -218,7 +218,7 @@ class Script(scripts.Script): ys = process_axis(y_opt, y_values) def fix_axis_seeds(axis_opt, axis_list): - if axis_opt.label == 'Seed': + if axis_opt.label == 'Seed' or 'Var. seed': return [int(random.randrange(4294967294)) if val is None or val == '' or val == -1 else val for val in axis_list] else: return axis_list From cf141157e7b49b0b3a6e57dc7aa0d1345158b4c8 Mon Sep 17 00:00:00 2001 From: RnDMonkey Date: Fri, 30 Sep 2022 22:02:29 -0700 Subject: [PATCH 0002/1118] Added X/Y plot parameters to extra_generation_params --- scripts/xy_grid.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index 9c078888..d9f8d55b 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -244,6 +244,14 @@ class Script(scripts.Script): return process_images(pc) + if not x_opt.label == 'Nothing': + p.extra_generation_params["X/Y Plot X Type"] = x_opt.label + p.extra_generation_params["X Values"] = '{' + ", ".join([f'{x}' for x in xs]) + '}' + + if not y_opt.label == 'Nothing': + p.extra_generation_params["X/Y Plot Y Type"] = y_opt.label + p.extra_generation_params["Y Values"] = '{' + ", ".join([f'{y}' for y in ys]) + '}' + processed = draw_xy_grid( p, xs=xs, From eba0c29dbc3bad8c4e32f1fa3a03dc6f9caf1f5a Mon Sep 17 00:00:00 2001 From: RnDMonkey Date: Sat, 1 Oct 2022 13:56:29 -0700 Subject: [PATCH 0003/1118] Updated xy_grid infotext formatting, parser regex --- modules/generation_parameters_copypaste.py | 2 +- scripts/xy_grid.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index ac1ba7f4..39d67d94 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -1,7 +1,7 @@ import re import gradio as gr -re_param_code = r"\s*([\w ]+):\s*([^,]+)(?:,|$)" +re_param_code = r"\s*([\w ]+):\s*((?:{[^}]+})|(?:[^,]+))(?:,|$)" re_param = re.compile(re_param_code) re_params = re.compile(r"^(?:" + re_param_code + "){3,}$") re_imagesize = re.compile(r"^(\d+)x(\d+)$") diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index d9f8d55b..f87c6c1f 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -245,12 +245,16 @@ class Script(scripts.Script): return process_images(pc) if not x_opt.label == 'Nothing': - p.extra_generation_params["X/Y Plot X Type"] = x_opt.label - p.extra_generation_params["X Values"] = '{' + ", ".join([f'{x}' for x in xs]) + '}' + p.extra_generation_params["XY Plot X Type"] = x_opt.label + p.extra_generation_params["X Values"] = '{' + x_values + '}' + if x_opt.label in ["Seed","Var. seed"] and not no_fixed_seeds: + p.extra_generation_params["Fixed X Values"] = '{' + ", ".join([str(x) for x in xs])+ '}' if not y_opt.label == 'Nothing': - p.extra_generation_params["X/Y Plot Y Type"] = y_opt.label - p.extra_generation_params["Y Values"] = '{' + ", ".join([f'{y}' for y in ys]) + '}' + p.extra_generation_params["XY Plot Y Type"] = y_opt.label + p.extra_generation_params["Y Values"] = '{' + y_values + '}' + if y_opt.label in ["Seed","Var. seed"] and not no_fixed_seeds: + p.extra_generation_params["Fixed Y Values"] = '{' + ", ".join([str(y) for y in ys])+ '}' processed = draw_xy_grid( p, From b99a4f769f11ed74df0344a23069d3858613fbef Mon Sep 17 00:00:00 2001 From: RnDMonkey Date: Sat, 1 Oct 2022 14:26:12 -0700 Subject: [PATCH 0004/1118] fixed expression error in condition --- scripts/xy_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index f87c6c1f..f1f54d9c 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -218,7 +218,7 @@ class Script(scripts.Script): ys = process_axis(y_opt, y_values) def fix_axis_seeds(axis_opt, axis_list): - if axis_opt.label == 'Seed' or 'Var. seed': + if axis_opt.label in ["Seed","Var. seed"]: return [int(random.randrange(4294967294)) if val is None or val == '' or val == -1 else val for val in axis_list] else: return axis_list From f6a97868e57e44fba6c4283769fedd30ee11cacf Mon Sep 17 00:00:00 2001 From: RnDMonkey Date: Sat, 1 Oct 2022 14:36:09 -0700 Subject: [PATCH 0005/1118] fix to allow empty {} values --- modules/generation_parameters_copypaste.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 39d67d94..27d58dfd 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -1,7 +1,7 @@ import re import gradio as gr -re_param_code = r"\s*([\w ]+):\s*((?:{[^}]+})|(?:[^,]+))(?:,|$)" +re_param_code = r"\s*([\w ]+):\s*((?:{[^}]*})|(?:[^,]+))(?:,|$)" re_param = re.compile(re_param_code) re_params = re.compile(r"^(?:" + re_param_code + "){3,}$") re_imagesize = re.compile(r"^(\d+)x(\d+)$") From fe6e2362e8fa5d739de6997ab155a26686d20a49 Mon Sep 17 00:00:00 2001 From: RnDMonkey Date: Sun, 2 Oct 2022 22:04:28 -0700 Subject: [PATCH 0006/1118] Update xy_grid.py Changed XY Plot infotext value keys to not be so generic. --- scripts/xy_grid.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index f1f54d9c..ae011a17 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -246,15 +246,15 @@ class Script(scripts.Script): if not x_opt.label == 'Nothing': p.extra_generation_params["XY Plot X Type"] = x_opt.label - p.extra_generation_params["X Values"] = '{' + x_values + '}' + p.extra_generation_params["XY Plot X Values"] = '{' + x_values + '}' if x_opt.label in ["Seed","Var. seed"] and not no_fixed_seeds: - p.extra_generation_params["Fixed X Values"] = '{' + ", ".join([str(x) for x in xs])+ '}' + p.extra_generation_params["XY Plot Fixed X Values"] = '{' + ", ".join([str(x) for x in xs])+ '}' if not y_opt.label == 'Nothing': p.extra_generation_params["XY Plot Y Type"] = y_opt.label - p.extra_generation_params["Y Values"] = '{' + y_values + '}' + p.extra_generation_params["XY Plot Y Values"] = '{' + y_values + '}' if y_opt.label in ["Seed","Var. seed"] and not no_fixed_seeds: - p.extra_generation_params["Fixed Y Values"] = '{' + ", ".join([str(y) for y in ys])+ '}' + p.extra_generation_params["XY Plot Fixed Y Values"] = '{' + ", ".join([str(y) for y in ys])+ '}' processed = draw_xy_grid( p, From 065364445d4ea1ddec44c3f87d1b6b8acda592a6 Mon Sep 17 00:00:00 2001 From: EternalNooblet Date: Fri, 7 Oct 2022 15:25:01 -0400 Subject: [PATCH 0007/1118] added a flag to run as root if needed --- webui.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index 05ca497d..41649b9a 100755 --- a/webui.sh +++ b/webui.sh @@ -3,6 +3,7 @@ # Please do not make any changes to this file, # # change the variables in webui-user.sh instead # ################################################# + # Read variables from webui-user.sh # shellcheck source=/dev/null if [[ -f webui-user.sh ]] @@ -46,6 +47,17 @@ then LAUNCH_SCRIPT="launch.py" fi +# this script cannot be run as root by default +can_run_as_root=0 + +# read any command line flags to the webui.sh script +while getopts "f" flag +do + case ${flag} in + f) can_run_as_root=1;; + esac +done + # Disable sentry logging export ERROR_REPORTING=FALSE @@ -61,7 +73,7 @@ printf "\e[1m\e[34mTested on Debian 11 (Bullseye)\e[0m" printf "\n%s\n" "${delimiter}" # Do not run as root -if [[ $(id -u) -eq 0 ]] +if [[ $(id -u) -eq 0 && can_run_as_root -eq 0 ]] then printf "\n%s\n" "${delimiter}" printf "\e[1m\e[31mERROR: This script must not be launched as root, aborting...\e[0m" From ad4de819c43997f2666b5bad95301f5c37f9018e Mon Sep 17 00:00:00 2001 From: victorca25 Date: Sun, 9 Oct 2022 13:02:12 +0200 Subject: [PATCH 0008/1118] update ESRGAN architecture and model to support all ESRGAN models in the DB, BSRGAN and real-ESRGAN models --- modules/bsrgan_model.py | 76 ------ modules/bsrgan_model_arch.py | 102 -------- modules/esrgam_model_arch.py | 80 ------ modules/esrgan_model.py | 178 +++++++++----- modules/esrgan_model_arch.py | 463 +++++++++++++++++++++++++++++++++++ 5 files changed, 585 insertions(+), 314 deletions(-) delete mode 100644 modules/bsrgan_model.py delete mode 100644 modules/bsrgan_model_arch.py delete mode 100644 modules/esrgam_model_arch.py create mode 100644 modules/esrgan_model_arch.py diff --git a/modules/bsrgan_model.py b/modules/bsrgan_model.py deleted file mode 100644 index 737e1a76..00000000 --- a/modules/bsrgan_model.py +++ /dev/null @@ -1,76 +0,0 @@ -import os.path -import sys -import traceback - -import PIL.Image -import numpy as np -import torch -from basicsr.utils.download_util import load_file_from_url - -import modules.upscaler -from modules import devices, modelloader -from modules.bsrgan_model_arch import RRDBNet - - -class UpscalerBSRGAN(modules.upscaler.Upscaler): - def __init__(self, dirname): - self.name = "BSRGAN" - self.model_name = "BSRGAN 4x" - self.model_url = "https://github.com/cszn/KAIR/releases/download/v1.0/BSRGAN.pth" - self.user_path = dirname - super().__init__() - model_paths = self.find_models(ext_filter=[".pt", ".pth"]) - scalers = [] - if len(model_paths) == 0: - scaler_data = modules.upscaler.UpscalerData(self.model_name, self.model_url, self, 4) - scalers.append(scaler_data) - for file in model_paths: - if "http" in file: - name = self.model_name - else: - name = modelloader.friendly_name(file) - try: - scaler_data = modules.upscaler.UpscalerData(name, file, self, 4) - scalers.append(scaler_data) - except Exception: - print(f"Error loading BSRGAN model: {file}", file=sys.stderr) - print(traceback.format_exc(), file=sys.stderr) - self.scalers = scalers - - def do_upscale(self, img: PIL.Image, selected_file): - torch.cuda.empty_cache() - model = self.load_model(selected_file) - if model is None: - return img - model.to(devices.device_bsrgan) - torch.cuda.empty_cache() - img = np.array(img) - img = img[:, :, ::-1] - img = np.moveaxis(img, 2, 0) / 255 - img = torch.from_numpy(img).float() - img = img.unsqueeze(0).to(devices.device_bsrgan) - with torch.no_grad(): - output = model(img) - output = output.squeeze().float().cpu().clamp_(0, 1).numpy() - output = 255. * np.moveaxis(output, 0, 2) - output = output.astype(np.uint8) - output = output[:, :, ::-1] - torch.cuda.empty_cache() - return PIL.Image.fromarray(output, 'RGB') - - def load_model(self, path: str): - if "http" in path: - filename = load_file_from_url(url=self.model_url, model_dir=self.model_path, file_name="%s.pth" % self.name, - progress=True) - else: - filename = path - if not os.path.exists(filename) or filename is None: - print(f"BSRGAN: Unable to load model from {filename}", file=sys.stderr) - return None - model = RRDBNet(in_nc=3, out_nc=3, nf=64, nb=23, gc=32, sf=4) # define network - model.load_state_dict(torch.load(filename), strict=True) - model.eval() - for k, v in model.named_parameters(): - v.requires_grad = False - return model - diff --git a/modules/bsrgan_model_arch.py b/modules/bsrgan_model_arch.py deleted file mode 100644 index cb4d1c13..00000000 --- a/modules/bsrgan_model_arch.py +++ /dev/null @@ -1,102 +0,0 @@ -import functools -import torch -import torch.nn as nn -import torch.nn.functional as F -import torch.nn.init as init - - -def initialize_weights(net_l, scale=1): - if not isinstance(net_l, list): - net_l = [net_l] - for net in net_l: - for m in net.modules(): - if isinstance(m, nn.Conv2d): - init.kaiming_normal_(m.weight, a=0, mode='fan_in') - m.weight.data *= scale # for residual block - if m.bias is not None: - m.bias.data.zero_() - elif isinstance(m, nn.Linear): - init.kaiming_normal_(m.weight, a=0, mode='fan_in') - m.weight.data *= scale - if m.bias is not None: - m.bias.data.zero_() - elif isinstance(m, nn.BatchNorm2d): - init.constant_(m.weight, 1) - init.constant_(m.bias.data, 0.0) - - -def make_layer(block, n_layers): - layers = [] - for _ in range(n_layers): - layers.append(block()) - return nn.Sequential(*layers) - - -class ResidualDenseBlock_5C(nn.Module): - def __init__(self, nf=64, gc=32, bias=True): - super(ResidualDenseBlock_5C, self).__init__() - # gc: growth channel, i.e. intermediate channels - self.conv1 = nn.Conv2d(nf, gc, 3, 1, 1, bias=bias) - self.conv2 = nn.Conv2d(nf + gc, gc, 3, 1, 1, bias=bias) - self.conv3 = nn.Conv2d(nf + 2 * gc, gc, 3, 1, 1, bias=bias) - self.conv4 = nn.Conv2d(nf + 3 * gc, gc, 3, 1, 1, bias=bias) - self.conv5 = nn.Conv2d(nf + 4 * gc, nf, 3, 1, 1, bias=bias) - self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True) - - # initialization - initialize_weights([self.conv1, self.conv2, self.conv3, self.conv4, self.conv5], 0.1) - - def forward(self, x): - x1 = self.lrelu(self.conv1(x)) - x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1))) - x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1))) - x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1))) - x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1)) - return x5 * 0.2 + x - - -class RRDB(nn.Module): - '''Residual in Residual Dense Block''' - - def __init__(self, nf, gc=32): - super(RRDB, self).__init__() - self.RDB1 = ResidualDenseBlock_5C(nf, gc) - self.RDB2 = ResidualDenseBlock_5C(nf, gc) - self.RDB3 = ResidualDenseBlock_5C(nf, gc) - - def forward(self, x): - out = self.RDB1(x) - out = self.RDB2(out) - out = self.RDB3(out) - return out * 0.2 + x - - -class RRDBNet(nn.Module): - def __init__(self, in_nc=3, out_nc=3, nf=64, nb=23, gc=32, sf=4): - super(RRDBNet, self).__init__() - RRDB_block_f = functools.partial(RRDB, nf=nf, gc=gc) - self.sf = sf - - self.conv_first = nn.Conv2d(in_nc, nf, 3, 1, 1, bias=True) - self.RRDB_trunk = make_layer(RRDB_block_f, nb) - self.trunk_conv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - #### upsampling - self.upconv1 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - if self.sf==4: - self.upconv2 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - self.HRconv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - self.conv_last = nn.Conv2d(nf, out_nc, 3, 1, 1, bias=True) - - self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True) - - def forward(self, x): - fea = self.conv_first(x) - trunk = self.trunk_conv(self.RRDB_trunk(fea)) - fea = fea + trunk - - fea = self.lrelu(self.upconv1(F.interpolate(fea, scale_factor=2, mode='nearest'))) - if self.sf==4: - fea = self.lrelu(self.upconv2(F.interpolate(fea, scale_factor=2, mode='nearest'))) - out = self.conv_last(self.lrelu(self.HRconv(fea))) - - return out \ No newline at end of file diff --git a/modules/esrgam_model_arch.py b/modules/esrgam_model_arch.py deleted file mode 100644 index e413d36e..00000000 --- a/modules/esrgam_model_arch.py +++ /dev/null @@ -1,80 +0,0 @@ -# this file is taken from https://github.com/xinntao/ESRGAN - -import functools -import torch -import torch.nn as nn -import torch.nn.functional as F - - -def make_layer(block, n_layers): - layers = [] - for _ in range(n_layers): - layers.append(block()) - return nn.Sequential(*layers) - - -class ResidualDenseBlock_5C(nn.Module): - def __init__(self, nf=64, gc=32, bias=True): - super(ResidualDenseBlock_5C, self).__init__() - # gc: growth channel, i.e. intermediate channels - self.conv1 = nn.Conv2d(nf, gc, 3, 1, 1, bias=bias) - self.conv2 = nn.Conv2d(nf + gc, gc, 3, 1, 1, bias=bias) - self.conv3 = nn.Conv2d(nf + 2 * gc, gc, 3, 1, 1, bias=bias) - self.conv4 = nn.Conv2d(nf + 3 * gc, gc, 3, 1, 1, bias=bias) - self.conv5 = nn.Conv2d(nf + 4 * gc, nf, 3, 1, 1, bias=bias) - self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True) - - # initialization - # mutil.initialize_weights([self.conv1, self.conv2, self.conv3, self.conv4, self.conv5], 0.1) - - def forward(self, x): - x1 = self.lrelu(self.conv1(x)) - x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1))) - x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1))) - x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1))) - x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1)) - return x5 * 0.2 + x - - -class RRDB(nn.Module): - '''Residual in Residual Dense Block''' - - def __init__(self, nf, gc=32): - super(RRDB, self).__init__() - self.RDB1 = ResidualDenseBlock_5C(nf, gc) - self.RDB2 = ResidualDenseBlock_5C(nf, gc) - self.RDB3 = ResidualDenseBlock_5C(nf, gc) - - def forward(self, x): - out = self.RDB1(x) - out = self.RDB2(out) - out = self.RDB3(out) - return out * 0.2 + x - - -class RRDBNet(nn.Module): - def __init__(self, in_nc, out_nc, nf, nb, gc=32): - super(RRDBNet, self).__init__() - RRDB_block_f = functools.partial(RRDB, nf=nf, gc=gc) - - self.conv_first = nn.Conv2d(in_nc, nf, 3, 1, 1, bias=True) - self.RRDB_trunk = make_layer(RRDB_block_f, nb) - self.trunk_conv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - #### upsampling - self.upconv1 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - self.upconv2 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - self.HRconv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True) - self.conv_last = nn.Conv2d(nf, out_nc, 3, 1, 1, bias=True) - - self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True) - - def forward(self, x): - fea = self.conv_first(x) - trunk = self.trunk_conv(self.RRDB_trunk(fea)) - fea = fea + trunk - - fea = self.lrelu(self.upconv1(F.interpolate(fea, scale_factor=2, mode='nearest'))) - fea = self.lrelu(self.upconv2(F.interpolate(fea, scale_factor=2, mode='nearest'))) - out = self.conv_last(self.lrelu(self.HRconv(fea))) - - return out diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index 3970e6e4..a49e2258 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -5,68 +5,115 @@ import torch from PIL import Image from basicsr.utils.download_util import load_file_from_url -import modules.esrgam_model_arch as arch +import modules.esrgan_model_arch as arch from modules import shared, modelloader, images, devices from modules.upscaler import Upscaler, UpscalerData from modules.shared import opts -def fix_model_layers(crt_model, pretrained_net): - # this code is adapted from https://github.com/xinntao/ESRGAN - if 'conv_first.weight' in pretrained_net: - return pretrained_net - if 'model.0.weight' not in pretrained_net: - is_realesrgan = "params_ema" in pretrained_net and 'body.0.rdb1.conv1.weight' in pretrained_net["params_ema"] - if is_realesrgan: - raise Exception("The file is a RealESRGAN model, it can't be used as a ESRGAN model.") - else: - raise Exception("The file is not a ESRGAN model.") +def mod2normal(state_dict): + # this code is copied from https://github.com/victorca25/iNNfer + if 'conv_first.weight' in state_dict: + crt_net = {} + items = [] + for k, v in state_dict.items(): + items.append(k) - crt_net = crt_model.state_dict() - load_net_clean = {} - for k, v in pretrained_net.items(): - if k.startswith('module.'): - load_net_clean[k[7:]] = v - else: - load_net_clean[k] = v - pretrained_net = load_net_clean + crt_net['model.0.weight'] = state_dict['conv_first.weight'] + crt_net['model.0.bias'] = state_dict['conv_first.bias'] - tbd = [] - for k, v in crt_net.items(): - tbd.append(k) + for k in items.copy(): + if 'RDB' in k: + ori_k = k.replace('RRDB_trunk.', 'model.1.sub.') + if '.weight' in k: + ori_k = ori_k.replace('.weight', '.0.weight') + elif '.bias' in k: + ori_k = ori_k.replace('.bias', '.0.bias') + crt_net[ori_k] = state_dict[k] + items.remove(k) - # directly copy - for k, v in crt_net.items(): - if k in pretrained_net and pretrained_net[k].size() == v.size(): - crt_net[k] = pretrained_net[k] - tbd.remove(k) + crt_net['model.1.sub.23.weight'] = state_dict['trunk_conv.weight'] + crt_net['model.1.sub.23.bias'] = state_dict['trunk_conv.bias'] + crt_net['model.3.weight'] = state_dict['upconv1.weight'] + crt_net['model.3.bias'] = state_dict['upconv1.bias'] + crt_net['model.6.weight'] = state_dict['upconv2.weight'] + crt_net['model.6.bias'] = state_dict['upconv2.bias'] + crt_net['model.8.weight'] = state_dict['HRconv.weight'] + crt_net['model.8.bias'] = state_dict['HRconv.bias'] + crt_net['model.10.weight'] = state_dict['conv_last.weight'] + crt_net['model.10.bias'] = state_dict['conv_last.bias'] + state_dict = crt_net + return state_dict - crt_net['conv_first.weight'] = pretrained_net['model.0.weight'] - crt_net['conv_first.bias'] = pretrained_net['model.0.bias'] - for k in tbd.copy(): - if 'RDB' in k: - ori_k = k.replace('RRDB_trunk.', 'model.1.sub.') - if '.weight' in k: - ori_k = ori_k.replace('.weight', '.0.weight') - elif '.bias' in k: - ori_k = ori_k.replace('.bias', '.0.bias') - crt_net[k] = pretrained_net[ori_k] - tbd.remove(k) +def resrgan2normal(state_dict, nb=23): + # this code is copied from https://github.com/victorca25/iNNfer + if "conv_first.weight" in state_dict and "body.0.rdb1.conv1.weight" in state_dict: + crt_net = {} + items = [] + for k, v in state_dict.items(): + items.append(k) - crt_net['trunk_conv.weight'] = pretrained_net['model.1.sub.23.weight'] - crt_net['trunk_conv.bias'] = pretrained_net['model.1.sub.23.bias'] - crt_net['upconv1.weight'] = pretrained_net['model.3.weight'] - crt_net['upconv1.bias'] = pretrained_net['model.3.bias'] - crt_net['upconv2.weight'] = pretrained_net['model.6.weight'] - crt_net['upconv2.bias'] = pretrained_net['model.6.bias'] - crt_net['HRconv.weight'] = pretrained_net['model.8.weight'] - crt_net['HRconv.bias'] = pretrained_net['model.8.bias'] - crt_net['conv_last.weight'] = pretrained_net['model.10.weight'] - crt_net['conv_last.bias'] = pretrained_net['model.10.bias'] + crt_net['model.0.weight'] = state_dict['conv_first.weight'] + crt_net['model.0.bias'] = state_dict['conv_first.bias'] + + for k in items.copy(): + if "rdb" in k: + ori_k = k.replace('body.', 'model.1.sub.') + ori_k = ori_k.replace('.rdb', '.RDB') + if '.weight' in k: + ori_k = ori_k.replace('.weight', '.0.weight') + elif '.bias' in k: + ori_k = ori_k.replace('.bias', '.0.bias') + crt_net[ori_k] = state_dict[k] + items.remove(k) + + crt_net[f'model.1.sub.{nb}.weight'] = state_dict['conv_body.weight'] + crt_net[f'model.1.sub.{nb}.bias'] = state_dict['conv_body.bias'] + crt_net['model.3.weight'] = state_dict['conv_up1.weight'] + crt_net['model.3.bias'] = state_dict['conv_up1.bias'] + crt_net['model.6.weight'] = state_dict['conv_up2.weight'] + crt_net['model.6.bias'] = state_dict['conv_up2.bias'] + crt_net['model.8.weight'] = state_dict['conv_hr.weight'] + crt_net['model.8.bias'] = state_dict['conv_hr.bias'] + crt_net['model.10.weight'] = state_dict['conv_last.weight'] + crt_net['model.10.bias'] = state_dict['conv_last.bias'] + state_dict = crt_net + return state_dict + + +def infer_params(state_dict): + # this code is copied from https://github.com/victorca25/iNNfer + scale2x = 0 + scalemin = 6 + n_uplayer = 0 + plus = False + + for block in list(state_dict): + parts = block.split(".") + n_parts = len(parts) + if n_parts == 5 and parts[2] == "sub": + nb = int(parts[3]) + elif n_parts == 3: + part_num = int(parts[1]) + if (part_num > scalemin + and parts[0] == "model" + and parts[2] == "weight"): + scale2x += 1 + if part_num > n_uplayer: + n_uplayer = part_num + out_nc = state_dict[block].shape[0] + if not plus and "conv1x1" in block: + plus = True + + nf = state_dict["model.0.weight"].shape[0] + in_nc = state_dict["model.0.weight"].shape[1] + out_nc = out_nc + scale = 2 ** scale2x + + return in_nc, out_nc, nf, nb, plus, scale - return crt_net class UpscalerESRGAN(Upscaler): def __init__(self, dirname): @@ -109,20 +156,39 @@ class UpscalerESRGAN(Upscaler): print("Unable to load %s from %s" % (self.model_path, filename)) return None - pretrained_net = torch.load(filename, map_location='cpu' if devices.device_esrgan.type == 'mps' else None) - crt_model = arch.RRDBNet(3, 3, 64, 23, gc=32) + state_dict = torch.load(filename, map_location='cpu' if devices.device_esrgan.type == 'mps' else None) - pretrained_net = fix_model_layers(crt_model, pretrained_net) - crt_model.load_state_dict(pretrained_net) - crt_model.eval() + if "params_ema" in state_dict: + state_dict = state_dict["params_ema"] + elif "params" in state_dict: + state_dict = state_dict["params"] + num_conv = 16 if "realesr-animevideov3" in filename else 32 + model = arch.SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=num_conv, upscale=4, act_type='prelu') + model.load_state_dict(state_dict) + model.eval() + return model - return crt_model + if "body.0.rdb1.conv1.weight" in state_dict and "conv_first.weight" in state_dict: + nb = 6 if "RealESRGAN_x4plus_anime_6B" in filename else 23 + state_dict = resrgan2normal(state_dict, nb) + elif "conv_first.weight" in state_dict: + state_dict = mod2normal(state_dict) + elif "model.0.weight" not in state_dict: + raise Exception("The file is not a recognized ESRGAN model.") + + in_nc, out_nc, nf, nb, plus, mscale = infer_params(state_dict) + + model = arch.RRDBNet(in_nc=in_nc, out_nc=out_nc, nf=nf, nb=nb, upscale=mscale, plus=plus) + model.load_state_dict(state_dict) + model.eval() + + return model def upscale_without_tiling(model, img): img = np.array(img) img = img[:, :, ::-1] - img = np.moveaxis(img, 2, 0) / 255 + img = np.ascontiguousarray(np.transpose(img, (2, 0, 1))) / 255 img = torch.from_numpy(img).float() img = img.unsqueeze(0).to(devices.device_esrgan) with torch.no_grad(): diff --git a/modules/esrgan_model_arch.py b/modules/esrgan_model_arch.py new file mode 100644 index 00000000..bc9ceb2a --- /dev/null +++ b/modules/esrgan_model_arch.py @@ -0,0 +1,463 @@ +# this file is adapted from https://github.com/victorca25/iNNfer + +import math +import functools +import torch +import torch.nn as nn +import torch.nn.functional as F + + +#################### +# RRDBNet Generator +#################### + +class RRDBNet(nn.Module): + def __init__(self, in_nc, out_nc, nf, nb, nr=3, gc=32, upscale=4, norm_type=None, + act_type='leakyrelu', mode='CNA', upsample_mode='upconv', convtype='Conv2D', + finalact=None, gaussian_noise=False, plus=False): + super(RRDBNet, self).__init__() + n_upscale = int(math.log(upscale, 2)) + if upscale == 3: + n_upscale = 1 + + self.resrgan_scale = 0 + if in_nc % 16 == 0: + self.resrgan_scale = 1 + elif in_nc != 4 and in_nc % 4 == 0: + self.resrgan_scale = 2 + + fea_conv = conv_block(in_nc, nf, kernel_size=3, norm_type=None, act_type=None, convtype=convtype) + rb_blocks = [RRDB(nf, nr, kernel_size=3, gc=32, stride=1, bias=1, pad_type='zero', + norm_type=norm_type, act_type=act_type, mode='CNA', convtype=convtype, + gaussian_noise=gaussian_noise, plus=plus) for _ in range(nb)] + LR_conv = conv_block(nf, nf, kernel_size=3, norm_type=norm_type, act_type=None, mode=mode, convtype=convtype) + + if upsample_mode == 'upconv': + upsample_block = upconv_block + elif upsample_mode == 'pixelshuffle': + upsample_block = pixelshuffle_block + else: + raise NotImplementedError('upsample mode [{:s}] is not found'.format(upsample_mode)) + if upscale == 3: + upsampler = upsample_block(nf, nf, 3, act_type=act_type, convtype=convtype) + else: + upsampler = [upsample_block(nf, nf, act_type=act_type, convtype=convtype) for _ in range(n_upscale)] + HR_conv0 = conv_block(nf, nf, kernel_size=3, norm_type=None, act_type=act_type, convtype=convtype) + HR_conv1 = conv_block(nf, out_nc, kernel_size=3, norm_type=None, act_type=None, convtype=convtype) + + outact = act(finalact) if finalact else None + + self.model = sequential(fea_conv, ShortcutBlock(sequential(*rb_blocks, LR_conv)), + *upsampler, HR_conv0, HR_conv1, outact) + + def forward(self, x, outm=None): + if self.resrgan_scale == 1: + feat = pixel_unshuffle(x, scale=4) + elif self.resrgan_scale == 2: + feat = pixel_unshuffle(x, scale=2) + else: + feat = x + + return self.model(feat) + + +class RRDB(nn.Module): + """ + Residual in Residual Dense Block + (ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks) + """ + + def __init__(self, nf, nr=3, kernel_size=3, gc=32, stride=1, bias=1, pad_type='zero', + norm_type=None, act_type='leakyrelu', mode='CNA', convtype='Conv2D', + spectral_norm=False, gaussian_noise=False, plus=False): + super(RRDB, self).__init__() + # This is for backwards compatibility with existing models + if nr == 3: + self.RDB1 = ResidualDenseBlock_5C(nf, kernel_size, gc, stride, bias, pad_type, + norm_type, act_type, mode, convtype, spectral_norm=spectral_norm, + gaussian_noise=gaussian_noise, plus=plus) + self.RDB2 = ResidualDenseBlock_5C(nf, kernel_size, gc, stride, bias, pad_type, + norm_type, act_type, mode, convtype, spectral_norm=spectral_norm, + gaussian_noise=gaussian_noise, plus=plus) + self.RDB3 = ResidualDenseBlock_5C(nf, kernel_size, gc, stride, bias, pad_type, + norm_type, act_type, mode, convtype, spectral_norm=spectral_norm, + gaussian_noise=gaussian_noise, plus=plus) + else: + RDB_list = [ResidualDenseBlock_5C(nf, kernel_size, gc, stride, bias, pad_type, + norm_type, act_type, mode, convtype, spectral_norm=spectral_norm, + gaussian_noise=gaussian_noise, plus=plus) for _ in range(nr)] + self.RDBs = nn.Sequential(*RDB_list) + + def forward(self, x): + if hasattr(self, 'RDB1'): + out = self.RDB1(x) + out = self.RDB2(out) + out = self.RDB3(out) + else: + out = self.RDBs(x) + return out * 0.2 + x + + +class ResidualDenseBlock_5C(nn.Module): + """ + Residual Dense Block + The core module of paper: (Residual Dense Network for Image Super-Resolution, CVPR 18) + Modified options that can be used: + - "Partial Convolution based Padding" arXiv:1811.11718 + - "Spectral normalization" arXiv:1802.05957 + - "ICASSP 2020 - ESRGAN+ : Further Improving ESRGAN" N. C. + {Rakotonirina} and A. {Rasoanaivo} + """ + + def __init__(self, nf=64, kernel_size=3, gc=32, stride=1, bias=1, pad_type='zero', + norm_type=None, act_type='leakyrelu', mode='CNA', convtype='Conv2D', + spectral_norm=False, gaussian_noise=False, plus=False): + super(ResidualDenseBlock_5C, self).__init__() + + self.noise = GaussianNoise() if gaussian_noise else None + self.conv1x1 = conv1x1(nf, gc) if plus else None + + self.conv1 = conv_block(nf, gc, kernel_size, stride, bias=bias, pad_type=pad_type, + norm_type=norm_type, act_type=act_type, mode=mode, convtype=convtype, + spectral_norm=spectral_norm) + self.conv2 = conv_block(nf+gc, gc, kernel_size, stride, bias=bias, pad_type=pad_type, + norm_type=norm_type, act_type=act_type, mode=mode, convtype=convtype, + spectral_norm=spectral_norm) + self.conv3 = conv_block(nf+2*gc, gc, kernel_size, stride, bias=bias, pad_type=pad_type, + norm_type=norm_type, act_type=act_type, mode=mode, convtype=convtype, + spectral_norm=spectral_norm) + self.conv4 = conv_block(nf+3*gc, gc, kernel_size, stride, bias=bias, pad_type=pad_type, + norm_type=norm_type, act_type=act_type, mode=mode, convtype=convtype, + spectral_norm=spectral_norm) + if mode == 'CNA': + last_act = None + else: + last_act = act_type + self.conv5 = conv_block(nf+4*gc, nf, 3, stride, bias=bias, pad_type=pad_type, + norm_type=norm_type, act_type=last_act, mode=mode, convtype=convtype, + spectral_norm=spectral_norm) + + def forward(self, x): + x1 = self.conv1(x) + x2 = self.conv2(torch.cat((x, x1), 1)) + if self.conv1x1: + x2 = x2 + self.conv1x1(x) + x3 = self.conv3(torch.cat((x, x1, x2), 1)) + x4 = self.conv4(torch.cat((x, x1, x2, x3), 1)) + if self.conv1x1: + x4 = x4 + x2 + x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1)) + if self.noise: + return self.noise(x5.mul(0.2) + x) + else: + return x5 * 0.2 + x + + +#################### +# ESRGANplus +#################### + +class GaussianNoise(nn.Module): + def __init__(self, sigma=0.1, is_relative_detach=False): + super().__init__() + self.sigma = sigma + self.is_relative_detach = is_relative_detach + self.noise = torch.tensor(0, dtype=torch.float) + + def forward(self, x): + if self.training and self.sigma != 0: + self.noise = self.noise.to(x.device) + scale = self.sigma * x.detach() if self.is_relative_detach else self.sigma * x + sampled_noise = self.noise.repeat(*x.size()).normal_() * scale + x = x + sampled_noise + return x + +def conv1x1(in_planes, out_planes, stride=1): + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +#################### +# SRVGGNetCompact +#################### + +class SRVGGNetCompact(nn.Module): + """A compact VGG-style network structure for super-resolution. + This class is copied from https://github.com/xinntao/Real-ESRGAN + """ + + def __init__(self, num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=16, upscale=4, act_type='prelu'): + super(SRVGGNetCompact, self).__init__() + self.num_in_ch = num_in_ch + self.num_out_ch = num_out_ch + self.num_feat = num_feat + self.num_conv = num_conv + self.upscale = upscale + self.act_type = act_type + + self.body = nn.ModuleList() + # the first conv + self.body.append(nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)) + # the first activation + if act_type == 'relu': + activation = nn.ReLU(inplace=True) + elif act_type == 'prelu': + activation = nn.PReLU(num_parameters=num_feat) + elif act_type == 'leakyrelu': + activation = nn.LeakyReLU(negative_slope=0.1, inplace=True) + self.body.append(activation) + + # the body structure + for _ in range(num_conv): + self.body.append(nn.Conv2d(num_feat, num_feat, 3, 1, 1)) + # activation + if act_type == 'relu': + activation = nn.ReLU(inplace=True) + elif act_type == 'prelu': + activation = nn.PReLU(num_parameters=num_feat) + elif act_type == 'leakyrelu': + activation = nn.LeakyReLU(negative_slope=0.1, inplace=True) + self.body.append(activation) + + # the last conv + self.body.append(nn.Conv2d(num_feat, num_out_ch * upscale * upscale, 3, 1, 1)) + # upsample + self.upsampler = nn.PixelShuffle(upscale) + + def forward(self, x): + out = x + for i in range(0, len(self.body)): + out = self.body[i](out) + + out = self.upsampler(out) + # add the nearest upsampled image, so that the network learns the residual + base = F.interpolate(x, scale_factor=self.upscale, mode='nearest') + out += base + return out + + +#################### +# Upsampler +#################### + +class Upsample(nn.Module): + r"""Upsamples a given multi-channel 1D (temporal), 2D (spatial) or 3D (volumetric) data. + The input data is assumed to be of the form + `minibatch x channels x [optional depth] x [optional height] x width`. + """ + + def __init__(self, size=None, scale_factor=None, mode="nearest", align_corners=None): + super(Upsample, self).__init__() + if isinstance(scale_factor, tuple): + self.scale_factor = tuple(float(factor) for factor in scale_factor) + else: + self.scale_factor = float(scale_factor) if scale_factor else None + self.mode = mode + self.size = size + self.align_corners = align_corners + + def forward(self, x): + return nn.functional.interpolate(x, size=self.size, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners) + + def extra_repr(self): + if self.scale_factor is not None: + info = 'scale_factor=' + str(self.scale_factor) + else: + info = 'size=' + str(self.size) + info += ', mode=' + self.mode + return info + + +def pixel_unshuffle(x, scale): + """ Pixel unshuffle. + Args: + x (Tensor): Input feature with shape (b, c, hh, hw). + scale (int): Downsample ratio. + Returns: + Tensor: the pixel unshuffled feature. + """ + b, c, hh, hw = x.size() + out_channel = c * (scale**2) + assert hh % scale == 0 and hw % scale == 0 + h = hh // scale + w = hw // scale + x_view = x.view(b, c, h, scale, w, scale) + return x_view.permute(0, 1, 3, 5, 2, 4).reshape(b, out_channel, h, w) + + +def pixelshuffle_block(in_nc, out_nc, upscale_factor=2, kernel_size=3, stride=1, bias=True, + pad_type='zero', norm_type=None, act_type='relu', convtype='Conv2D'): + """ + Pixel shuffle layer + (Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional + Neural Network, CVPR17) + """ + conv = conv_block(in_nc, out_nc * (upscale_factor ** 2), kernel_size, stride, bias=bias, + pad_type=pad_type, norm_type=None, act_type=None, convtype=convtype) + pixel_shuffle = nn.PixelShuffle(upscale_factor) + + n = norm(norm_type, out_nc) if norm_type else None + a = act(act_type) if act_type else None + return sequential(conv, pixel_shuffle, n, a) + + +def upconv_block(in_nc, out_nc, upscale_factor=2, kernel_size=3, stride=1, bias=True, + pad_type='zero', norm_type=None, act_type='relu', mode='nearest', convtype='Conv2D'): + """ Upconv layer """ + upscale_factor = (1, upscale_factor, upscale_factor) if convtype == 'Conv3D' else upscale_factor + upsample = Upsample(scale_factor=upscale_factor, mode=mode) + conv = conv_block(in_nc, out_nc, kernel_size, stride, bias=bias, + pad_type=pad_type, norm_type=norm_type, act_type=act_type, convtype=convtype) + return sequential(upsample, conv) + + + + + + + + +#################### +# Basic blocks +#################### + + +def make_layer(basic_block, num_basic_block, **kwarg): + """Make layers by stacking the same blocks. + Args: + basic_block (nn.module): nn.module class for basic block. (block) + num_basic_block (int): number of blocks. (n_layers) + Returns: + nn.Sequential: Stacked blocks in nn.Sequential. + """ + layers = [] + for _ in range(num_basic_block): + layers.append(basic_block(**kwarg)) + return nn.Sequential(*layers) + + +def act(act_type, inplace=True, neg_slope=0.2, n_prelu=1, beta=1.0): + """ activation helper """ + act_type = act_type.lower() + if act_type == 'relu': + layer = nn.ReLU(inplace) + elif act_type in ('leakyrelu', 'lrelu'): + layer = nn.LeakyReLU(neg_slope, inplace) + elif act_type == 'prelu': + layer = nn.PReLU(num_parameters=n_prelu, init=neg_slope) + elif act_type == 'tanh': # [-1, 1] range output + layer = nn.Tanh() + elif act_type == 'sigmoid': # [0, 1] range output + layer = nn.Sigmoid() + else: + raise NotImplementedError('activation layer [{:s}] is not found'.format(act_type)) + return layer + + +class Identity(nn.Module): + def __init__(self, *kwargs): + super(Identity, self).__init__() + + def forward(self, x, *kwargs): + return x + + +def norm(norm_type, nc): + """ Return a normalization layer """ + norm_type = norm_type.lower() + if norm_type == 'batch': + layer = nn.BatchNorm2d(nc, affine=True) + elif norm_type == 'instance': + layer = nn.InstanceNorm2d(nc, affine=False) + elif norm_type == 'none': + def norm_layer(x): return Identity() + else: + raise NotImplementedError('normalization layer [{:s}] is not found'.format(norm_type)) + return layer + + +def pad(pad_type, padding): + """ padding layer helper """ + pad_type = pad_type.lower() + if padding == 0: + return None + if pad_type == 'reflect': + layer = nn.ReflectionPad2d(padding) + elif pad_type == 'replicate': + layer = nn.ReplicationPad2d(padding) + elif pad_type == 'zero': + layer = nn.ZeroPad2d(padding) + else: + raise NotImplementedError('padding layer [{:s}] is not implemented'.format(pad_type)) + return layer + + +def get_valid_padding(kernel_size, dilation): + kernel_size = kernel_size + (kernel_size - 1) * (dilation - 1) + padding = (kernel_size - 1) // 2 + return padding + + +class ShortcutBlock(nn.Module): + """ Elementwise sum the output of a submodule to its input """ + def __init__(self, submodule): + super(ShortcutBlock, self).__init__() + self.sub = submodule + + def forward(self, x): + output = x + self.sub(x) + return output + + def __repr__(self): + return 'Identity + \n|' + self.sub.__repr__().replace('\n', '\n|') + + +def sequential(*args): + """ Flatten Sequential. It unwraps nn.Sequential. """ + if len(args) == 1: + if isinstance(args[0], OrderedDict): + raise NotImplementedError('sequential does not support OrderedDict input.') + return args[0] # No sequential is needed. + modules = [] + for module in args: + if isinstance(module, nn.Sequential): + for submodule in module.children(): + modules.append(submodule) + elif isinstance(module, nn.Module): + modules.append(module) + return nn.Sequential(*modules) + + +def conv_block(in_nc, out_nc, kernel_size, stride=1, dilation=1, groups=1, bias=True, + pad_type='zero', norm_type=None, act_type='relu', mode='CNA', convtype='Conv2D', + spectral_norm=False): + """ Conv layer with padding, normalization, activation """ + assert mode in ['CNA', 'NAC', 'CNAC'], 'Wrong conv mode [{:s}]'.format(mode) + padding = get_valid_padding(kernel_size, dilation) + p = pad(pad_type, padding) if pad_type and pad_type != 'zero' else None + padding = padding if pad_type == 'zero' else 0 + + if convtype=='PartialConv2D': + c = PartialConv2d(in_nc, out_nc, kernel_size=kernel_size, stride=stride, padding=padding, + dilation=dilation, bias=bias, groups=groups) + elif convtype=='DeformConv2D': + c = DeformConv2d(in_nc, out_nc, kernel_size=kernel_size, stride=stride, padding=padding, + dilation=dilation, bias=bias, groups=groups) + elif convtype=='Conv3D': + c = nn.Conv3d(in_nc, out_nc, kernel_size=kernel_size, stride=stride, padding=padding, + dilation=dilation, bias=bias, groups=groups) + else: + c = nn.Conv2d(in_nc, out_nc, kernel_size=kernel_size, stride=stride, padding=padding, + dilation=dilation, bias=bias, groups=groups) + + if spectral_norm: + c = nn.utils.spectral_norm(c) + + a = act(act_type) if act_type else None + if 'CNA' in mode: + n = norm(norm_type, out_nc) if norm_type else None + return sequential(p, c, n, a) + elif mode == 'NAC': + if norm_type is None and act_type is not None: + a = act(act_type, inplace=False) + n = norm(norm_type, in_nc) if norm_type else None + return sequential(n, a, p, c) From bb57f30c2de46cfca5419ad01738a41705f96cc3 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Fri, 14 Oct 2022 10:56:41 +0200 Subject: [PATCH 0009/1118] init --- README.md | 1 + aesthetic_embeddings/insert_embs_here.txt | 0 modules/processing.py | 17 +++- modules/sd_hijack.py | 80 ++++++++++++++++++- modules/shared.py | 5 ++ modules/textual_inversion/dataset.py | 2 +- .../textual_inversion/textual_inversion.py | 35 +++++--- modules/txt2img.py | 11 ++- modules/ui.py | 59 +++++++++----- 9 files changed, 172 insertions(+), 38 deletions(-) create mode 100644 aesthetic_embeddings/insert_embs_here.txt diff --git a/README.md b/README.md index 859a91b6..7b8d018b 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - No token limit for prompts (original stable diffusion lets you use up to 75 tokens) - DeepDanbooru integration, creates danbooru style tags for anime prompts (add --deepdanbooru to commandline args) - [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add --xformers to commandline args) +- Aesthetic, a way to generate images with a specific aesthetic by using clip images embds (implementation of https://github.com/vicgalle/stable-diffusion-aesthetic-gradients) ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. diff --git a/aesthetic_embeddings/insert_embs_here.txt b/aesthetic_embeddings/insert_embs_here.txt new file mode 100644 index 00000000..e69de29b diff --git a/modules/processing.py b/modules/processing.py index d5172f00..9a033759 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -316,11 +316,16 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration return f"{all_prompts[index]}{negative_prompt_text}\n{generation_params_text}".strip() -def process_images(p: StableDiffusionProcessing) -> Processed: +def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, + aesthetic_imgs=None,aesthetic_slerp=False) -> Processed: """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""" + aesthetic_lr = float(aesthetic_lr) + aesthetic_weight = float(aesthetic_weight) + aesthetic_steps = int(aesthetic_steps) + if type(p.prompt) == list: - assert(len(p.prompt) > 0) + assert (len(p.prompt) > 0) else: assert p.prompt is not None @@ -394,7 +399,13 @@ def process_images(p: StableDiffusionProcessing) -> Processed: #uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt]) #c = p.sd_model.get_learned_conditioning(prompts) with devices.autocast(): - uc = prompt_parser.get_learned_conditioning(shared.sd_model, len(prompts) * [p.negative_prompt], p.steps) + if hasattr(shared.sd_model.cond_stage_model, "set_aesthetic_params"): + shared.sd_model.cond_stage_model.set_aesthetic_params(0, 0, 0) + uc = prompt_parser.get_learned_conditioning(shared.sd_model, len(prompts) * [p.negative_prompt], + p.steps) + if hasattr(shared.sd_model.cond_stage_model, "set_aesthetic_params"): + shared.sd_model.cond_stage_model.set_aesthetic_params(aesthetic_lr, aesthetic_weight, + aesthetic_steps, aesthetic_imgs,aesthetic_slerp) c = prompt_parser.get_multicond_learned_conditioning(shared.sd_model, prompts, p.steps) if len(model_hijack.comments) > 0: diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index c81722a0..6d5196fe 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -9,11 +9,14 @@ from torch.nn.functional import silu import modules.textual_inversion.textual_inversion from modules import prompt_parser, devices, sd_hijack_optimizations, shared -from modules.shared import opts, device, cmd_opts +from modules.shared import opts, device, cmd_opts, aesthetic_embeddings from modules.sd_hijack_optimizations import invokeAI_mps_available import ldm.modules.attention import ldm.modules.diffusionmodules.model +from transformers import CLIPVisionModel, CLIPModel +import torch.optim as optim +import copy attention_CrossAttention_forward = ldm.modules.attention.CrossAttention.forward diffusionmodules_model_nonlinearity = ldm.modules.diffusionmodules.model.nonlinearity @@ -109,13 +112,29 @@ class StableDiffusionModelHijack: _, remade_batch_tokens, _, _, _, token_count = self.clip.process_text([text]) return remade_batch_tokens[0], token_count, get_target_prompt_token_count(token_count) +def slerp(low, high, val): + low_norm = low/torch.norm(low, dim=1, keepdim=True) + high_norm = high/torch.norm(high, dim=1, keepdim=True) + omega = torch.acos((low_norm*high_norm).sum(1)) + so = torch.sin(omega) + res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high + return res class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): def __init__(self, wrapped, hijack): super().__init__() self.wrapped = wrapped + self.clipModel = CLIPModel.from_pretrained( + self.wrapped.transformer.name_or_path + ) + del self.clipModel.vision_model self.hijack: StableDiffusionModelHijack = hijack self.tokenizer = wrapped.tokenizer + # self.vision = CLIPVisionModel.from_pretrained(self.wrapped.transformer.name_or_path).eval() + self.image_embs_name = None + self.image_embs = None + self.load_image_embs(None) + self.token_mults = {} self.comma_token = [v for k, v in self.tokenizer.get_vocab().items() if k == ','][0] @@ -136,6 +155,23 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): if mult != 1.0: self.token_mults[ident] = mult + def set_aesthetic_params(self, aesthetic_lr, aesthetic_weight, aesthetic_steps, image_embs_name=None, + aesthetic_slerp=True): + self.slerp = aesthetic_slerp + self.aesthetic_lr = aesthetic_lr + self.aesthetic_weight = aesthetic_weight + self.aesthetic_steps = aesthetic_steps + self.load_image_embs(image_embs_name) + + def load_image_embs(self, image_embs_name): + if image_embs_name is None or len(image_embs_name) == 0: + image_embs_name = None + if image_embs_name is not None and self.image_embs_name != image_embs_name: + self.image_embs_name = image_embs_name + self.image_embs = torch.load(aesthetic_embeddings[self.image_embs_name], map_location=device) + self.image_embs /= self.image_embs.norm(dim=-1, keepdim=True) + self.image_embs.requires_grad_(False) + def tokenize_line(self, line, used_custom_terms, hijack_comments): id_end = self.wrapped.tokenizer.eos_token_id @@ -333,7 +369,47 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): z1 = self.process_tokens(tokens, multipliers) z = z1 if z is None else torch.cat((z, z1), axis=-2) - + + if len(text[ + 0]) != 0 and self.aesthetic_steps != 0 and self.aesthetic_lr != 0 and self.aesthetic_weight != 0 and self.image_embs_name != None: + if not opts.use_old_emphasis_implementation: + remade_batch_tokens = [ + [self.wrapped.tokenizer.bos_token_id] + x[:75] + [self.wrapped.tokenizer.eos_token_id] for x in + remade_batch_tokens] + + tokens = torch.asarray(remade_batch_tokens).to(device) + with torch.enable_grad(): + model = copy.deepcopy(self.clipModel).to(device) + model.requires_grad_(True) + + # We optimize the model to maximize the similarity + optimizer = optim.Adam( + model.text_model.parameters(), lr=self.aesthetic_lr + ) + + for i in range(self.aesthetic_steps): + text_embs = model.get_text_features(input_ids=tokens) + text_embs = text_embs / text_embs.norm(dim=-1, keepdim=True) + sim = text_embs @ self.image_embs.T + loss = -sim + optimizer.zero_grad() + loss.mean().backward() + optimizer.step() + + zn = model.text_model(input_ids=tokens, output_hidden_states=-opts.CLIP_stop_at_last_layers) + if opts.CLIP_stop_at_last_layers > 1: + zn = zn.hidden_states[-opts.CLIP_stop_at_last_layers] + zn = model.text_model.final_layer_norm(zn) + else: + zn = zn.last_hidden_state + model.cpu() + del model + + if self.slerp: + z = slerp(z, zn, self.aesthetic_weight) + else: + z = z * (1 - self.aesthetic_weight) + zn * self.aesthetic_weight + remade_batch_tokens = rem_tokens batch_multipliers = rem_multipliers i += 1 diff --git a/modules/shared.py b/modules/shared.py index 5901e605..cf13a10d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -30,6 +30,8 @@ parser.add_argument("--no-half-vae", action='store_true', help="do not switch th 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 acceleration in browser)") parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI") parser.add_argument("--embeddings-dir", type=str, default=os.path.join(script_path, 'embeddings'), help="embeddings directory for textual inversion (default: embeddings)") +parser.add_argument("--aesthetic_embeddings-dir", type=str, default=os.path.join(script_path, 'aesthetic_embeddings'), + help="aesthetic_embeddings directory(default: aesthetic_embeddings)") parser.add_argument("--hypernetwork-dir", type=str, default=os.path.join(models_path, 'hypernetworks'), help="hypernetwork directory") parser.add_argument("--allow-code", action='store_true', help="allow custom script execution from webui") parser.add_argument("--medvram", action='store_true', help="enable stable diffusion model optimizations for sacrificing a little speed for low VRM usage") @@ -90,6 +92,9 @@ os.makedirs(cmd_opts.hypernetwork_dir, exist_ok=True) hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir) loaded_hypernetwork = None +aesthetic_embeddings = {f.replace(".pt",""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in + os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} + def reload_hypernetworks(): global hypernetworks diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 67e90afe..59b2b021 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -48,7 +48,7 @@ class PersonalizedBase(Dataset): print("Preparing dataset...") for path in tqdm.tqdm(self.image_paths): try: - image = Image.open(path).convert('RGB').resize((self.width, self.height), PIL.Image.BICUBIC) + image = Image.open(path).convert('RGB').resize((self.width, self.height), PIL.Image.Resampling.BICUBIC) except Exception: continue diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index fa0e33a2..b12a8e6d 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -172,7 +172,15 @@ def create_embedding(name, num_vectors_per_token, init_text='*'): return fn -def train_embedding(embedding_name, learn_rate, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_image_prompt): +def batched(dataset, total, n=1): + for ndx in range(0, total, n): + yield [dataset.__getitem__(i) for i in range(ndx, min(ndx + n, total))] + + +def train_embedding(embedding_name, learn_rate, data_root, log_directory, training_width, training_height, steps, + create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, + preview_image_prompt, batch_size=1, + gradient_accumulation=1): assert embedding_name, 'embedding not selected' shared.state.textinfo = "Initializing textual inversion training..." @@ -204,7 +212,11 @@ def train_embedding(embedding_name, learn_rate, data_root, log_directory, traini shared.state.textinfo = f"Preparing dataset from {html.escape(data_root)}..." with torch.autocast("cuda"): - ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, device=devices.device, template_file=template_file) + ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, + height=training_height, + repeats=shared.opts.training_image_repeats_per_epoch, + placeholder_token=embedding_name, model=shared.sd_model, + device=devices.device, template_file=template_file) hijack = sd_hijack.model_hijack @@ -223,7 +235,7 @@ def train_embedding(embedding_name, learn_rate, data_root, log_directory, traini scheduler = LearnRateScheduler(learn_rate, steps, ititial_step) optimizer = torch.optim.AdamW([embedding.vec], lr=scheduler.learn_rate) - pbar = tqdm.tqdm(enumerate(ds), total=steps-ititial_step) + pbar = tqdm.tqdm(enumerate(batched(ds, steps - ititial_step, batch_size)), total=steps - ititial_step) for i, entry in pbar: embedding.step = i + ititial_step @@ -235,17 +247,20 @@ def train_embedding(embedding_name, learn_rate, data_root, log_directory, traini break with torch.autocast("cuda"): - c = cond_model([entry.cond_text]) + c = cond_model([e.cond_text for e in entry]) + + x = torch.stack([e.latent for e in entry]).to(devices.device) + loss = shared.sd_model(x, c)[0] - x = entry.latent.to(devices.device) - loss = shared.sd_model(x.unsqueeze(0), c)[0] del x losses[embedding.step % losses.shape[0]] = loss.item() - optimizer.zero_grad() loss.backward() - optimizer.step() + if ((i + 1) % gradient_accumulation == 0) or (i + 1 == steps - ititial_step): + optimizer.step() + optimizer.zero_grad() + epoch_num = embedding.step // len(ds) epoch_step = embedding.step - (epoch_num * len(ds)) + 1 @@ -259,7 +274,7 @@ def train_embedding(embedding_name, learn_rate, data_root, log_directory, traini if embedding.step > 0 and images_dir is not None and embedding.step % create_image_every == 0: last_saved_image = os.path.join(images_dir, f'{embedding_name}-{embedding.step}.png') - preview_text = entry.cond_text if preview_image_prompt == "" else preview_image_prompt + preview_text = entry[0].cond_text if preview_image_prompt == "" else preview_image_prompt p = processing.StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, @@ -305,7 +320,7 @@ def train_embedding(embedding_name, learn_rate, data_root, log_directory, traini

Loss: {losses.mean():.7f}
Step: {embedding.step}
-Last prompt: {html.escape(entry.cond_text)}
+Last prompt: {html.escape(entry[-1].cond_text)}
Last saved embedding: {html.escape(last_saved_file)}
Last saved image: {html.escape(last_saved_image)}

diff --git a/modules/txt2img.py b/modules/txt2img.py index e985242b..78342024 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -6,7 +6,14 @@ import modules.processing as processing from modules.ui import plaintext_to_html -def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, scale_latent: bool, denoising_strength: float, *args): +def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, + restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, + subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, + height: int, width: int, enable_hr: bool, scale_latent: bool, denoising_strength: float, + aesthetic_lr=0, + aesthetic_weight=0, aesthetic_steps=0, + aesthetic_imgs=None, + aesthetic_slerp=False, *args): p = StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, @@ -40,7 +47,7 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: processed = modules.scripts.scripts_txt2img.run(p, *args) if processed is None: - processed = process_images(p) + processed = process_images(p, aesthetic_lr, aesthetic_weight, aesthetic_steps, aesthetic_imgs, aesthetic_slerp) shared.total_tqdm.clear() diff --git a/modules/ui.py b/modules/ui.py index 220fb80b..d961d126 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -24,7 +24,8 @@ import gradio.routes from modules import sd_hijack from modules.paths import script_path -from modules.shared import opts, cmd_opts +from modules.shared import opts, cmd_opts,aesthetic_embeddings + if cmd_opts.deepdanbooru: from modules.deepbooru import get_deepbooru_tags import modules.shared as shared @@ -534,6 +535,14 @@ def create_ui(wrap_gradio_gpu_call): width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) + with gr.Group(): + aesthetic_lr = gr.Textbox(label='Learning rate', placeholder="Learning rate", value="0.005") + aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.7) + aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=50) + + aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Imgs embedding", value=sorted(aesthetic_embeddings.keys())[0] if len(aesthetic_embeddings) > 0 else None) + aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) + with gr.Row(): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1) tiling = gr.Checkbox(label='Tiling', value=False) @@ -586,25 +595,30 @@ def create_ui(wrap_gradio_gpu_call): fn=wrap_gradio_gpu_call(modules.txt2img.txt2img), _js="submit", inputs=[ - txt2img_prompt, - txt2img_negative_prompt, - txt2img_prompt_style, - txt2img_prompt_style2, - steps, - sampler_index, - restore_faces, - tiling, - batch_count, - batch_size, - cfg_scale, - seed, - subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox, - height, - width, - enable_hr, - scale_latent, - denoising_strength, - ] + custom_inputs, + txt2img_prompt, + txt2img_negative_prompt, + txt2img_prompt_style, + txt2img_prompt_style2, + steps, + sampler_index, + restore_faces, + tiling, + batch_count, + batch_size, + cfg_scale, + seed, + subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox, + height, + width, + enable_hr, + scale_latent, + denoising_strength, + aesthetic_lr, + aesthetic_weight, + aesthetic_steps, + aesthetic_imgs, + aesthetic_slerp + ] + custom_inputs, outputs=[ txt2img_gallery, generation_info, @@ -1097,6 +1111,9 @@ def create_ui(wrap_gradio_gpu_call): template_file = gr.Textbox(label='Prompt template file', value=os.path.join(script_path, "textual_inversion_templates", "style_filewords.txt")) training_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) training_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) + batch_size = gr.Slider(minimum=1, maximum=64, step=1, label="Batch Size", value=4) + gradient_accumulation = gr.Slider(minimum=1, maximum=256, step=1, label="Gradient accumulation", + value=1) steps = gr.Number(label='Max steps', value=100000, precision=0) create_image_every = gr.Number(label='Save an image to log directory every N steps, 0 to disable', value=500, precision=0) save_embedding_every = gr.Number(label='Save a copy of embedding to log directory every N steps, 0 to disable', value=500, precision=0) @@ -1180,6 +1197,8 @@ def create_ui(wrap_gradio_gpu_call): template_file, save_image_with_stored_embedding, preview_image_prompt, + batch_size, + gradient_accumulation ], outputs=[ ti_output, From 37d7ffb415cd8c69b3c0bb5f61844dde0b169f78 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sat, 15 Oct 2022 15:59:37 +0200 Subject: [PATCH 0010/1118] fix to tokens lenght, addend embs generator, add new features to edit the embedding before the generation using text --- modules/aesthetic_clip.py | 78 ++++++++++++++++++++ modules/processing.py | 148 +++++++++++++++++++++++++------------- modules/sd_hijack.py | 111 ++++++++++++++++++---------- modules/shared.py | 4 ++ modules/txt2img.py | 10 ++- modules/ui.py | 47 ++++++++++-- 6 files changed, 302 insertions(+), 96 deletions(-) create mode 100644 modules/aesthetic_clip.py diff --git a/modules/aesthetic_clip.py b/modules/aesthetic_clip.py new file mode 100644 index 00000000..f15cfd47 --- /dev/null +++ b/modules/aesthetic_clip.py @@ -0,0 +1,78 @@ +import itertools +import os +from pathlib import Path +import html +import gc + +import gradio as gr +import torch +from PIL import Image +from modules import shared +from modules.shared import device, aesthetic_embeddings +from transformers import CLIPModel, CLIPProcessor + +from tqdm.auto import tqdm + + +def get_all_images_in_folder(folder): + return [os.path.join(folder, f) for f in os.listdir(folder) if + os.path.isfile(os.path.join(folder, f)) and check_is_valid_image_file(f)] + + +def check_is_valid_image_file(filename): + return filename.lower().endswith(('.png', '.jpg', '.jpeg')) + + +def batched(dataset, total, n=1): + for ndx in range(0, total, n): + yield [dataset.__getitem__(i) for i in range(ndx, min(ndx + n, total))] + + +def iter_to_batched(iterable, n=1): + it = iter(iterable) + while True: + chunk = tuple(itertools.islice(it, n)) + if not chunk: + return + yield chunk + + +def generate_imgs_embd(name, folder, batch_size): + # clipModel = CLIPModel.from_pretrained( + # shared.sd_model.cond_stage_model.clipModel.name_or_path + # ) + model = CLIPModel.from_pretrained(shared.sd_model.cond_stage_model.clipModel.name_or_path).to(device) + processor = CLIPProcessor.from_pretrained(shared.sd_model.cond_stage_model.clipModel.name_or_path) + + with torch.no_grad(): + embs = [] + for paths in tqdm(iter_to_batched(get_all_images_in_folder(folder), batch_size), + desc=f"Generating embeddings for {name}"): + if shared.state.interrupted: + break + inputs = processor(images=[Image.open(path) for path in paths], return_tensors="pt").to(device) + outputs = model.get_image_features(**inputs).cpu() + embs.append(torch.clone(outputs)) + inputs.to("cpu") + del inputs, outputs + + embs = torch.cat(embs, dim=0).mean(dim=0, keepdim=True) + + # The generated embedding will be located here + path = str(Path(shared.cmd_opts.aesthetic_embeddings_dir) / f"{name}.pt") + torch.save(embs, path) + + model = model.cpu() + del model + del processor + del embs + gc.collect() + torch.cuda.empty_cache() + res = f""" + Done generating embedding for {name}! + Hypernetwork saved to {html.escape(path)} + """ + shared.update_aesthetic_embeddings() + return gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Imgs embedding", + value=sorted(aesthetic_embeddings.keys())[0] if len( + aesthetic_embeddings) > 0 else None), res, "" diff --git a/modules/processing.py b/modules/processing.py index 9a033759..ab68d63a 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -20,7 +20,6 @@ import modules.images as images import modules.styles import logging - # some of those options should not be changed at all because they would break the model, so I removed them from options. opt_C = 4 opt_f = 8 @@ -52,8 +51,13 @@ def get_correct_sampler(p): elif isinstance(p, modules.processing.StableDiffusionProcessingImg2Img): return sd_samplers.samplers_for_img2img + class StableDiffusionProcessing: - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, seed_enable_extras=True, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None, eta=None): + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, + subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, seed_enable_extras=True, + sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, + restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, + extra_generation_params=None, overlay_images=None, negative_prompt=None, eta=None): self.sd_model = sd_model self.outpath_samples: str = outpath_samples self.outpath_grids: str = outpath_grids @@ -104,7 +108,8 @@ class StableDiffusionProcessing: class Processed: - def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", subseed=None, all_prompts=None, all_seeds=None, all_subseeds=None, index_of_first_image=0, infotexts=None): + def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", subseed=None, all_prompts=None, + all_seeds=None, all_subseeds=None, index_of_first_image=0, infotexts=None): self.images = images_list self.prompt = p.prompt self.negative_prompt = p.negative_prompt @@ -141,7 +146,8 @@ class Processed: self.prompt = self.prompt if type(self.prompt) != list else self.prompt[0] self.negative_prompt = self.negative_prompt if type(self.negative_prompt) != list else self.negative_prompt[0] self.seed = int(self.seed if type(self.seed) != list else self.seed[0]) - self.subseed = int(self.subseed if type(self.subseed) != list else self.subseed[0]) if self.subseed is not None else -1 + self.subseed = int( + self.subseed if type(self.subseed) != list else self.subseed[0]) if self.subseed is not None else -1 self.all_prompts = all_prompts or [self.prompt] self.all_seeds = all_seeds or [self.seed] @@ -181,39 +187,43 @@ class Processed: return json.dumps(obj) - def infotext(self, p: StableDiffusionProcessing, index): - return create_infotext(p, self.all_prompts, self.all_seeds, self.all_subseeds, comments=[], position_in_batch=index % self.batch_size, iteration=index // self.batch_size) + def infotext(self, p: StableDiffusionProcessing, index): + return create_infotext(p, self.all_prompts, self.all_seeds, self.all_subseeds, comments=[], + position_in_batch=index % self.batch_size, iteration=index // self.batch_size) # from https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3 def slerp(val, low, high): - low_norm = low/torch.norm(low, dim=1, keepdim=True) - high_norm = high/torch.norm(high, dim=1, keepdim=True) - dot = (low_norm*high_norm).sum(1) + low_norm = low / torch.norm(low, dim=1, keepdim=True) + high_norm = high / torch.norm(high, dim=1, keepdim=True) + dot = (low_norm * high_norm).sum(1) if dot.mean() > 0.9995: return low * val + high * (1 - val) omega = torch.acos(dot) so = torch.sin(omega) - res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high + res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze(1) * high return res -def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, seed_resize_from_h=0, seed_resize_from_w=0, p=None): +def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, seed_resize_from_h=0, seed_resize_from_w=0, + p=None): xs = [] # if we have multiple seeds, this means we are working with batch size>1; this then # enables the generation of additional tensors with noise that the sampler will use during its processing. # Using those pre-generated tensors instead of simple torch.randn allows a batch with seeds [100, 101] to # produce the same images as with two batches [100], [101]. - if p is not None and p.sampler is not None and (len(seeds) > 1 and opts.enable_batch_seeds or opts.eta_noise_seed_delta > 0): + if p is not None and p.sampler is not None and ( + len(seeds) > 1 and opts.enable_batch_seeds or opts.eta_noise_seed_delta > 0): sampler_noises = [[] for _ in range(p.sampler.number_of_needed_noises(p))] else: sampler_noises = None for i, seed in enumerate(seeds): - noise_shape = shape if seed_resize_from_h <= 0 or seed_resize_from_w <= 0 else (shape[0], seed_resize_from_h//8, seed_resize_from_w//8) + noise_shape = shape if seed_resize_from_h <= 0 or seed_resize_from_w <= 0 else ( + shape[0], seed_resize_from_h // 8, seed_resize_from_w // 8) subnoise = None if subseeds is not None: @@ -241,7 +251,7 @@ def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, see dx = max(-dx, 0) dy = max(-dy, 0) - x[:, ty:ty+h, tx:tx+w] = noise[:, dy:dy+h, dx:dx+w] + x[:, ty:ty + h, tx:tx + w] = noise[:, dy:dy + h, dx:dx + w] noise = x if sampler_noises is not None: @@ -293,14 +303,20 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration "Seed": all_seeds[index], "Face restoration": (opts.face_restoration_model if p.restore_faces else None), "Size": f"{p.width}x{p.height}", - "Model hash": getattr(p, 'sd_model_hash', None if not opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), - "Model": (None if not opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', '')), - "Hypernet": (None if shared.loaded_hypernetwork is None else shared.loaded_hypernetwork.name.replace(',', '').replace(':', '')), + "Model hash": getattr(p, 'sd_model_hash', + None if not opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), + "Model": ( + None if not opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace( + ',', '').replace(':', '')), + "Hypernet": ( + None if shared.loaded_hypernetwork is None else shared.loaded_hypernetwork.name.replace(',', '').replace( + ':', '')), "Batch size": (None if p.batch_size < 2 else p.batch_size), "Batch pos": (None if p.batch_size < 2 else position_in_batch), "Variation seed": (None if p.subseed_strength == 0 else all_subseeds[index]), "Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength), - "Seed resize from": (None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"), + "Seed resize from": ( + None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"), "Denoising strength": getattr(p, 'denoising_strength', None), "Eta": (None if p.sampler is None or p.sampler.eta == p.sampler.default_eta else p.sampler.eta), "Clip skip": None if clip_skip <= 1 else clip_skip, @@ -309,7 +325,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration generation_params.update(p.extra_generation_params) - generation_params_text = ", ".join([k if k == v else f'{k}: {v}' for k, v in generation_params.items() if v is not None]) + generation_params_text = ", ".join( + [k if k == v else f'{k}: {v}' for k, v in generation_params.items() if v is not None]) negative_prompt_text = "\nNegative prompt: " + p.negative_prompt if p.negative_prompt else "" @@ -317,7 +334,9 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, - aesthetic_imgs=None,aesthetic_slerp=False) -> Processed: + aesthetic_imgs=None, aesthetic_slerp=False, aesthetic_imgs_text="", + aesthetic_slerp_angle=0.15, + aesthetic_text_negative=False) -> Processed: """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""" aesthetic_lr = float(aesthetic_lr) @@ -385,7 +404,7 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh for n in range(p.n_iter): if state.skipped: state.skipped = False - + if state.interrupted: break @@ -396,16 +415,19 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh if (len(prompts) == 0): break - #uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt]) - #c = p.sd_model.get_learned_conditioning(prompts) + # uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt]) + # c = p.sd_model.get_learned_conditioning(prompts) with devices.autocast(): if hasattr(shared.sd_model.cond_stage_model, "set_aesthetic_params"): - shared.sd_model.cond_stage_model.set_aesthetic_params(0, 0, 0) + shared.sd_model.cond_stage_model.set_aesthetic_params() uc = prompt_parser.get_learned_conditioning(shared.sd_model, len(prompts) * [p.negative_prompt], p.steps) if hasattr(shared.sd_model.cond_stage_model, "set_aesthetic_params"): shared.sd_model.cond_stage_model.set_aesthetic_params(aesthetic_lr, aesthetic_weight, - aesthetic_steps, aesthetic_imgs,aesthetic_slerp) + aesthetic_steps, aesthetic_imgs, + aesthetic_slerp, aesthetic_imgs_text, + aesthetic_slerp_angle, + aesthetic_text_negative) c = prompt_parser.get_multicond_learned_conditioning(shared.sd_model, prompts, p.steps) if len(model_hijack.comments) > 0: @@ -413,13 +435,13 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh comments[comment] = 1 if p.n_iter > 1: - shared.state.job = f"Batch {n+1} out of {p.n_iter}" + shared.state.job = f"Batch {n + 1} out of {p.n_iter}" with devices.autocast(): - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength) + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, + subseed_strength=p.subseed_strength) if state.interrupted or state.skipped: - # if we are interrupted, sample returns just noise # use the image collected previously in sampler loop samples_ddim = shared.state.current_latent @@ -445,7 +467,9 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh if p.restore_faces: if opts.save and not p.do_not_save_samples and opts.save_images_before_face_restoration: - images.save_image(Image.fromarray(x_sample), p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-face-restoration") + images.save_image(Image.fromarray(x_sample), p.outpath_samples, "", seeds[i], prompts[i], + opts.samples_format, info=infotext(n, i), p=p, + suffix="-before-face-restoration") devices.torch_gc() @@ -456,7 +480,8 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh if p.color_corrections is not None and i < len(p.color_corrections): if opts.save and not p.do_not_save_samples and opts.save_images_before_color_correction: - images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-color-correction") + images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, + info=infotext(n, i), p=p, suffix="-before-color-correction") image = apply_color_correction(p.color_corrections[i], image) if p.overlay_images is not None and i < len(p.overlay_images): @@ -474,7 +499,8 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh image = image.convert('RGB') if opts.samples_save and not p.do_not_save_samples: - images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p) + images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, + info=infotext(n, i), p=p) text = infotext(n, i) infotexts.append(text) @@ -482,7 +508,7 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh image.info["parameters"] = text output_images.append(image) - del x_samples_ddim + del x_samples_ddim devices.torch_gc() @@ -504,10 +530,13 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh index_of_first_image = 1 if opts.grid_save: - images.save_image(grid, p.outpath_grids, "grid", all_seeds[0], all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) + images.save_image(grid, p.outpath_grids, "grid", all_seeds[0], all_prompts[0], opts.grid_format, + info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) devices.torch_gc() - return Processed(p, output_images, all_seeds[0], infotext() + "".join(["\n\n" + x for x in comments]), subseed=all_subseeds[0], all_prompts=all_prompts, all_seeds=all_seeds, all_subseeds=all_subseeds, index_of_first_image=index_of_first_image, infotexts=infotexts) + return Processed(p, output_images, all_seeds[0], infotext() + "".join(["\n\n" + x for x in comments]), + subseed=all_subseeds[0], all_prompts=all_prompts, all_seeds=all_seeds, all_subseeds=all_subseeds, + index_of_first_image=index_of_first_image, infotexts=infotexts) class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): @@ -543,25 +572,34 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers, self.sampler_index, self.sd_model) if not self.enable_hr: - x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) + x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, + subseeds=subseeds, subseed_strength=self.subseed_strength, + seed_resize_from_h=self.seed_resize_from_h, + seed_resize_from_w=self.seed_resize_from_w, p=self) samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning) return samples - x = create_random_tensors([opt_C, self.firstphase_height // opt_f, self.firstphase_width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) + x = create_random_tensors([opt_C, self.firstphase_height // opt_f, self.firstphase_width // opt_f], seeds=seeds, + subseeds=subseeds, subseed_strength=self.subseed_strength, + seed_resize_from_h=self.seed_resize_from_h, + seed_resize_from_w=self.seed_resize_from_w, p=self) samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning) truncate_x = (self.firstphase_width - self.firstphase_width_truncated) // opt_f truncate_y = (self.firstphase_height - self.firstphase_height_truncated) // opt_f - samples = samples[:, :, truncate_y//2:samples.shape[2]-truncate_y//2, truncate_x//2:samples.shape[3]-truncate_x//2] + samples = samples[:, :, truncate_y // 2:samples.shape[2] - truncate_y // 2, + truncate_x // 2:samples.shape[3] - truncate_x // 2] if self.scale_latent: - samples = torch.nn.functional.interpolate(samples, size=(self.height // opt_f, self.width // opt_f), mode="bilinear") + samples = torch.nn.functional.interpolate(samples, size=(self.height // opt_f, self.width // opt_f), + mode="bilinear") else: decoded_samples = decode_first_stage(self.sd_model, samples) if opts.upscaler_for_img2img is None or opts.upscaler_for_img2img == "None": - decoded_samples = torch.nn.functional.interpolate(decoded_samples, size=(self.height, self.width), mode="bilinear") + decoded_samples = torch.nn.functional.interpolate(decoded_samples, size=(self.height, self.width), + mode="bilinear") else: lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0) @@ -585,13 +623,16 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers, self.sampler_index, self.sd_model) - noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) + noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, + subseed_strength=subseed_strength, seed_resize_from_h=self.seed_resize_from_h, + seed_resize_from_w=self.seed_resize_from_w, p=self) # GC now before running the next img2img to prevent running out of memory x = None devices.torch_gc() - samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps) + samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, + steps=self.steps) return samples @@ -599,7 +640,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): sampler = None - def __init__(self, init_images=None, resize_mode=0, denoising_strength=0.75, mask=None, mask_blur=4, inpainting_fill=0, inpaint_full_res=True, inpaint_full_res_padding=0, inpainting_mask_invert=0, **kwargs): + def __init__(self, init_images=None, resize_mode=0, denoising_strength=0.75, mask=None, mask_blur=4, + inpainting_fill=0, inpaint_full_res=True, inpaint_full_res_padding=0, inpainting_mask_invert=0, + **kwargs): super().__init__(**kwargs) self.init_images = init_images @@ -607,7 +650,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.denoising_strength: float = denoising_strength self.init_latent = None self.image_mask = mask - #self.image_unblurred_mask = None + # self.image_unblurred_mask = None self.latent_mask = None self.mask_for_overlay = None self.mask_blur = mask_blur @@ -619,7 +662,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.nmask = None def init(self, all_prompts, all_seeds, all_subseeds): - self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers_for_img2img, self.sampler_index, self.sd_model) + self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers_for_img2img, self.sampler_index, + self.sd_model) crop_region = None if self.image_mask is not None: @@ -628,7 +672,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if self.inpainting_mask_invert: self.image_mask = ImageOps.invert(self.image_mask) - #self.image_unblurred_mask = self.image_mask + # self.image_unblurred_mask = self.image_mask if self.mask_blur > 0: self.image_mask = self.image_mask.filter(ImageFilter.GaussianBlur(self.mask_blur)) @@ -642,7 +686,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): mask = mask.crop(crop_region) self.image_mask = images.resize_image(2, mask, self.width, self.height) - self.paste_to = (x1, y1, x2-x1, y2-y1) + self.paste_to = (x1, y1, x2 - x1, y2 - y1) else: self.image_mask = images.resize_image(self.resize_mode, self.image_mask, self.width, self.height) np_mask = np.array(self.image_mask) @@ -665,7 +709,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if self.image_mask is not None: image_masked = Image.new('RGBa', (image.width, image.height)) - image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L'))) + image_masked.paste(image.convert("RGBA").convert("RGBa"), + mask=ImageOps.invert(self.mask_for_overlay.convert('L'))) self.overlay_images.append(image_masked.convert('RGBA')) @@ -714,12 +759,17 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): # this needs to be fixed to be done in sample() using actual seeds for batches if self.inpainting_fill == 2: - self.init_latent = self.init_latent * self.mask + create_random_tensors(self.init_latent.shape[1:], all_seeds[0:self.init_latent.shape[0]]) * self.nmask + self.init_latent = self.init_latent * self.mask + create_random_tensors(self.init_latent.shape[1:], + all_seeds[ + 0:self.init_latent.shape[ + 0]]) * self.nmask elif self.inpainting_fill == 3: self.init_latent = self.init_latent * self.mask def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): - x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) + x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, + subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, + seed_resize_from_w=self.seed_resize_from_w, p=self) samples = self.sampler.sample_img2img(self, self.init_latent, x, conditioning, unconditional_conditioning) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 6d5196fe..192883b2 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -14,7 +14,8 @@ from modules.sd_hijack_optimizations import invokeAI_mps_available import ldm.modules.attention import ldm.modules.diffusionmodules.model -from transformers import CLIPVisionModel, CLIPModel +from tqdm import trange +from transformers import CLIPVisionModel, CLIPModel, CLIPTokenizer import torch.optim as optim import copy @@ -22,21 +23,25 @@ attention_CrossAttention_forward = ldm.modules.attention.CrossAttention.forward diffusionmodules_model_nonlinearity = ldm.modules.diffusionmodules.model.nonlinearity diffusionmodules_model_AttnBlock_forward = ldm.modules.diffusionmodules.model.AttnBlock.forward + def apply_optimizations(): undo_optimizations() ldm.modules.diffusionmodules.model.nonlinearity = silu - if cmd_opts.force_enable_xformers or (cmd_opts.xformers and shared.xformers_available and torch.version.cuda and (6, 0) <= torch.cuda.get_device_capability(shared.device) <= (8, 6)): + if cmd_opts.force_enable_xformers or (cmd_opts.xformers and shared.xformers_available and torch.version.cuda and ( + 6, 0) <= torch.cuda.get_device_capability(shared.device) <= (8, 6)): print("Applying xformers cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.xformers_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward elif cmd_opts.opt_split_attention_v1: print("Applying v1 cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_v1 - elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention_invokeai or not torch.cuda.is_available()): + elif not cmd_opts.disable_opt_split_attention and ( + cmd_opts.opt_split_attention_invokeai or not torch.cuda.is_available()): if not invokeAI_mps_available and shared.device.type == 'mps': - print("The InvokeAI cross attention optimization for MPS requires the psutil package which is not installed.") + print( + "The InvokeAI cross attention optimization for MPS requires the psutil package which is not installed.") print("Applying v1 cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_v1 else: @@ -112,14 +117,16 @@ class StableDiffusionModelHijack: _, remade_batch_tokens, _, _, _, token_count = self.clip.process_text([text]) return remade_batch_tokens[0], token_count, get_target_prompt_token_count(token_count) + def slerp(low, high, val): - low_norm = low/torch.norm(low, dim=1, keepdim=True) - high_norm = high/torch.norm(high, dim=1, keepdim=True) - omega = torch.acos((low_norm*high_norm).sum(1)) + low_norm = low / torch.norm(low, dim=1, keepdim=True) + high_norm = high / torch.norm(high, dim=1, keepdim=True) + omega = torch.acos((low_norm * high_norm).sum(1)) so = torch.sin(omega) - res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high + res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze(1) * high return res + class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): def __init__(self, wrapped, hijack): super().__init__() @@ -128,6 +135,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): self.wrapped.transformer.name_or_path ) del self.clipModel.vision_model + self.tokenizer = CLIPTokenizer.from_pretrained(self.wrapped.transformer.name_or_path) self.hijack: StableDiffusionModelHijack = hijack self.tokenizer = wrapped.tokenizer # self.vision = CLIPVisionModel.from_pretrained(self.wrapped.transformer.name_or_path).eval() @@ -139,7 +147,8 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): self.comma_token = [v for k, v in self.tokenizer.get_vocab().items() if k == ','][0] - tokens_with_parens = [(k, v) for k, v in self.tokenizer.get_vocab().items() if '(' in k or ')' in k or '[' in k or ']' in k] + tokens_with_parens = [(k, v) for k, v in self.tokenizer.get_vocab().items() if + '(' in k or ')' in k or '[' in k or ']' in k] for text, ident in tokens_with_parens: mult = 1.0 for c in text: @@ -155,8 +164,13 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): if mult != 1.0: self.token_mults[ident] = mult - def set_aesthetic_params(self, aesthetic_lr, aesthetic_weight, aesthetic_steps, image_embs_name=None, - aesthetic_slerp=True): + def set_aesthetic_params(self, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, image_embs_name=None, + aesthetic_slerp=True, aesthetic_imgs_text="", + aesthetic_slerp_angle=0.15, + aesthetic_text_negative=False): + self.aesthetic_imgs_text = aesthetic_imgs_text + self.aesthetic_slerp_angle = aesthetic_slerp_angle + self.aesthetic_text_negative = aesthetic_text_negative self.slerp = aesthetic_slerp self.aesthetic_lr = aesthetic_lr self.aesthetic_weight = aesthetic_weight @@ -180,7 +194,8 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): else: parsed = [[line, 1.0]] - tokenized = self.wrapped.tokenizer([text for text, _ in parsed], truncation=False, add_special_tokens=False)["input_ids"] + tokenized = self.wrapped.tokenizer([text for text, _ in parsed], truncation=False, add_special_tokens=False)[ + "input_ids"] fixes = [] remade_tokens = [] @@ -196,18 +211,20 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): if token == self.comma_token: last_comma = len(remade_tokens) - elif opts.comma_padding_backtrack != 0 and max(len(remade_tokens), 1) % 75 == 0 and last_comma != -1 and len(remade_tokens) - last_comma <= opts.comma_padding_backtrack: + elif opts.comma_padding_backtrack != 0 and max(len(remade_tokens), + 1) % 75 == 0 and last_comma != -1 and len( + remade_tokens) - last_comma <= opts.comma_padding_backtrack: last_comma += 1 reloc_tokens = remade_tokens[last_comma:] reloc_mults = multipliers[last_comma:] remade_tokens = remade_tokens[:last_comma] length = len(remade_tokens) - + rem = int(math.ceil(length / 75)) * 75 - length remade_tokens += [id_end] * rem + reloc_tokens multipliers = multipliers[:last_comma] + [1.0] * rem + reloc_mults - + if embedding is None: remade_tokens.append(token) multipliers.append(weight) @@ -248,7 +265,8 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): if line in cache: remade_tokens, fixes, multipliers = cache[line] else: - remade_tokens, fixes, multipliers, current_token_count = self.tokenize_line(line, used_custom_terms, hijack_comments) + remade_tokens, fixes, multipliers, current_token_count = self.tokenize_line(line, used_custom_terms, + hijack_comments) token_count = max(current_token_count, token_count) cache[line] = (remade_tokens, fixes, multipliers) @@ -259,7 +277,6 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count - def process_text_old(self, text): id_start = self.wrapped.tokenizer.bos_token_id id_end = self.wrapped.tokenizer.eos_token_id @@ -289,7 +306,8 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): while i < len(tokens): token = tokens[i] - embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, i) + embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, + i) mult_change = self.token_mults.get(token) if opts.enable_emphasis else None if mult_change is not None: @@ -312,11 +330,12 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): ovf = remade_tokens[maxlen - 2:] overflowing_words = [vocab.get(int(x), "") for x in ovf] overflowing_text = self.wrapped.tokenizer.convert_tokens_to_string(''.join(overflowing_words)) - hijack_comments.append(f"Warning: too many input tokens; some ({len(overflowing_words)}) have been truncated:\n{overflowing_text}\n") + hijack_comments.append( + f"Warning: too many input tokens; some ({len(overflowing_words)}) have been truncated:\n{overflowing_text}\n") token_count = len(remade_tokens) remade_tokens = remade_tokens + [id_end] * (maxlen - 2 - len(remade_tokens)) - remade_tokens = [id_start] + remade_tokens[0:maxlen-2] + [id_end] + remade_tokens = [id_start] + remade_tokens[0:maxlen - 2] + [id_end] cache[tuple_tokens] = (remade_tokens, fixes, multipliers) multipliers = multipliers + [1.0] * (maxlen - 2 - len(multipliers)) @@ -326,23 +345,26 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): hijack_fixes.append(fixes) batch_multipliers.append(multipliers) return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count - + def forward(self, text): use_old = opts.use_old_emphasis_implementation if use_old: - batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text_old(text) + batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text_old( + text) else: - batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text(text) + batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text( + text) self.hijack.comments += hijack_comments if len(used_custom_terms) > 0: - self.hijack.comments.append("Used embeddings: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms])) - + self.hijack.comments.append( + "Used embeddings: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms])) + if use_old: self.hijack.fixes = hijack_fixes return self.process_tokens(remade_batch_tokens, batch_multipliers) - + z = None i = 0 while max(map(len, remade_batch_tokens)) != 0: @@ -356,7 +378,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): if fix[0] == i: fixes.append(fix[1]) self.hijack.fixes.append(fixes) - + tokens = [] multipliers = [] for j in range(len(remade_batch_tokens)): @@ -378,19 +400,30 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): remade_batch_tokens] tokens = torch.asarray(remade_batch_tokens).to(device) + + model = copy.deepcopy(self.clipModel).to(device) + model.requires_grad_(True) + if self.aesthetic_imgs_text is not None and len(self.aesthetic_imgs_text) > 0: + text_embs_2 = model.get_text_features( + **self.tokenizer([self.aesthetic_imgs_text], padding=True, return_tensors="pt").to(device)) + if self.aesthetic_text_negative: + text_embs_2 = self.image_embs - text_embs_2 + text_embs_2 /= text_embs_2.norm(dim=-1, keepdim=True) + img_embs = slerp(self.image_embs, text_embs_2, self.aesthetic_slerp_angle) + else: + img_embs = self.image_embs + with torch.enable_grad(): - model = copy.deepcopy(self.clipModel).to(device) - model.requires_grad_(True) # We optimize the model to maximize the similarity optimizer = optim.Adam( model.text_model.parameters(), lr=self.aesthetic_lr ) - for i in range(self.aesthetic_steps): + for i in trange(self.aesthetic_steps, desc="Aesthetic optimization"): text_embs = model.get_text_features(input_ids=tokens) text_embs = text_embs / text_embs.norm(dim=-1, keepdim=True) - sim = text_embs @ self.image_embs.T + sim = text_embs @ img_embs.T loss = -sim optimizer.zero_grad() loss.mean().backward() @@ -405,6 +438,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): model.cpu() del model + zn = torch.concat([zn for i in range(z.shape[1] // 77)], 1) if self.slerp: z = slerp(z, zn, self.aesthetic_weight) else: @@ -413,15 +447,16 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): remade_batch_tokens = rem_tokens batch_multipliers = rem_multipliers i += 1 - + return z - - + def process_tokens(self, remade_batch_tokens, batch_multipliers): if not opts.use_old_emphasis_implementation: - remade_batch_tokens = [[self.wrapped.tokenizer.bos_token_id] + x[:75] + [self.wrapped.tokenizer.eos_token_id] for x in remade_batch_tokens] + remade_batch_tokens = [ + [self.wrapped.tokenizer.bos_token_id] + x[:75] + [self.wrapped.tokenizer.eos_token_id] for x in + remade_batch_tokens] batch_multipliers = [[1.0] + x[:75] + [1.0] for x in batch_multipliers] - + tokens = torch.asarray(remade_batch_tokens).to(device) outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=-opts.CLIP_stop_at_last_layers) @@ -461,8 +496,8 @@ class EmbeddingsWithFixes(torch.nn.Module): for fixes, tensor in zip(batch_fixes, inputs_embeds): for offset, embedding in fixes: emb = embedding.vec - emb_len = min(tensor.shape[0]-offset-1, emb.shape[0]) - tensor = torch.cat([tensor[0:offset+1], emb[0:emb_len], tensor[offset+1+emb_len:]]) + emb_len = min(tensor.shape[0] - offset - 1, emb.shape[0]) + tensor = torch.cat([tensor[0:offset + 1], emb[0:emb_len], tensor[offset + 1 + emb_len:]]) vecs.append(tensor) diff --git a/modules/shared.py b/modules/shared.py index cf13a10d..7cd608ca 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -95,6 +95,10 @@ loaded_hypernetwork = None aesthetic_embeddings = {f.replace(".pt",""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} +def update_aesthetic_embeddings(): + global aesthetic_embeddings + aesthetic_embeddings = {f.replace(".pt",""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in + os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} def reload_hypernetworks(): global hypernetworks diff --git a/modules/txt2img.py b/modules/txt2img.py index 78342024..eedcdfe0 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -13,7 +13,11 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, aesthetic_imgs=None, - aesthetic_slerp=False, *args): + aesthetic_slerp=False, + aesthetic_imgs_text="", + aesthetic_slerp_angle=0.15, + aesthetic_text_negative=False, + *args): p = StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, @@ -47,7 +51,9 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: processed = modules.scripts.scripts_txt2img.run(p, *args) if processed is None: - processed = process_images(p, aesthetic_lr, aesthetic_weight, aesthetic_steps, aesthetic_imgs, aesthetic_slerp) + processed = process_images(p, aesthetic_lr, aesthetic_weight, aesthetic_steps, aesthetic_imgs, aesthetic_slerp,aesthetic_imgs_text, + aesthetic_slerp_angle, + aesthetic_text_negative) shared.total_tqdm.clear() diff --git a/modules/ui.py b/modules/ui.py index d961d126..e98e2113 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -41,6 +41,7 @@ from modules import prompt_parser from modules.images import save_image import modules.textual_inversion.ui import modules.hypernetworks.ui +import modules.aesthetic_clip # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() @@ -449,7 +450,7 @@ def create_toprow(is_img2img): with gr.Row(): negative_prompt = gr.Textbox(label="Negative prompt", elem_id="negative_prompt", show_label=False, placeholder="Negative prompt", lines=2) with gr.Column(scale=1, elem_id="roll_col"): - sh = gr.Button(elem_id="sh", visible=True) + sh = gr.Button(elem_id="sh", visible=True) with gr.Column(scale=1, elem_id="style_neg_col"): prompt_style2 = gr.Dropdown(label="Style 2", elem_id=f"{id_part}_style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1) @@ -536,9 +537,13 @@ def create_ui(wrap_gradio_gpu_call): height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) with gr.Group(): - aesthetic_lr = gr.Textbox(label='Learning rate', placeholder="Learning rate", value="0.005") - aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.7) - aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=50) + aesthetic_lr = gr.Textbox(label='Learning rate', placeholder="Learning rate", value="0.0001") + aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.9) + aesthetic_steps = gr.Slider(minimum=0, maximum=256, step=1, label="Aesthetic steps", value=5) + with gr.Row(): + aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', placeholder="This text is used to rotate the feature space of the imgs embs", value="") + aesthetic_slerp_angle = gr.Slider(label='Slerp angle',minimum=0, maximum=1, step=0.01, value=0.1) + aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Imgs embedding", value=sorted(aesthetic_embeddings.keys())[0] if len(aesthetic_embeddings) > 0 else None) aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) @@ -617,7 +622,10 @@ def create_ui(wrap_gradio_gpu_call): aesthetic_weight, aesthetic_steps, aesthetic_imgs, - aesthetic_slerp + aesthetic_slerp, + aesthetic_imgs_text, + aesthetic_slerp_angle, + aesthetic_text_negative ] + custom_inputs, outputs=[ txt2img_gallery, @@ -721,7 +729,7 @@ def create_ui(wrap_gradio_gpu_call): with gr.Row(): inpaint_full_res = gr.Checkbox(label='Inpaint at full resolution', value=False) - inpaint_full_res_padding = gr.Slider(label='Inpaint at full resolution padding, pixels', minimum=0, maximum=256, step=4, value=32) + inpaint_full_res_padding = gr.Slider(label='Inpaint at full resolution padding, pixels', minimum=0, maximum=1024, step=4, value=32) with gr.TabItem('Batch img2img', id='batch'): hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' @@ -1071,6 +1079,17 @@ def create_ui(wrap_gradio_gpu_call): with gr.Column(): create_embedding = gr.Button(value="Create embedding", variant='primary') + with gr.Tab(label="Create images embedding"): + new_embedding_name_ae = gr.Textbox(label="Name") + process_src_ae = gr.Textbox(label='Source directory') + batch_ae = gr.Slider(minimum=1, maximum=1024, step=1, label="Batch size", value=256) + with gr.Row(): + with gr.Column(scale=3): + gr.HTML(value="") + + with gr.Column(): + create_embedding_ae = gr.Button(value="Create images embedding", variant='primary') + with gr.Tab(label="Create hypernetwork"): new_hypernetwork_name = gr.Textbox(label="Name") new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "320", "640", "1280"]) @@ -1139,7 +1158,7 @@ def create_ui(wrap_gradio_gpu_call): fn=modules.textual_inversion.ui.create_embedding, inputs=[ new_embedding_name, - initialization_text, + process_src, nvpt, ], outputs=[ @@ -1149,6 +1168,20 @@ def create_ui(wrap_gradio_gpu_call): ] ) + create_embedding_ae.click( + fn=modules.aesthetic_clip.generate_imgs_embd, + inputs=[ + new_embedding_name_ae, + process_src_ae, + batch_ae + ], + outputs=[ + aesthetic_imgs, + ti_output, + ti_outcome, + ] + ) + create_hypernetwork.click( fn=modules.hypernetworks.ui.create_hypernetwork, inputs=[ From 6e4f5566b58e36aede83427df6c69eba8517af28 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sat, 15 Oct 2022 23:53:49 +0800 Subject: [PATCH 0011/1118] sorting files --- javascript/images_history.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/images_history.js b/javascript/images_history.js index f7d052c3..7f0d8f42 100644 --- a/javascript/images_history.js +++ b/javascript/images_history.js @@ -96,7 +96,7 @@ function images_history_get_current_img(tabname, image_path, files){ ]; } -function images_history_delete(del_num, tabname, img_path, img_file_name, page_index, filenames, image_index){ +function images_history_delete(del_num, tabname, img_file_name, page_index, filenames, image_index){ image_index = parseInt(image_index); var tab = gradioApp().getElementById(tabname + '_images_history'); var set_btn = tab.querySelector(".images_history_set_index"); @@ -132,12 +132,12 @@ function images_history_delete(del_num, tabname, img_path, img_file_name, page_i return [del_num, tabname, img_path, img_file_name, page_index, filenames, image_index]; } -function images_history_turnpage(img_path, page_index, image_index, tabname){ +function images_history_turnpage(img_path, page_index, image_index, tabname, date_from, date_to){ var buttons = gradioApp().getElementById(tabname + '_images_history').querySelectorAll(".gallery-item"); buttons.forEach(function(elem) { elem.style.display = 'block'; }) - return [img_path, page_index, image_index, tabname]; + return [img_path, page_index, image_index, tabname, date_from, date_to]; } function images_history_enable_del_buttons(){ From 4387e4fe6479c08f7bc7e42924c3a1093e3a1872 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sat, 15 Oct 2022 18:39:29 +0200 Subject: [PATCH 0012/1118] Update modules/ui.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Gallego --- modules/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index d0696101..5bb961b2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -599,7 +599,8 @@ def create_ui(wrap_gradio_gpu_call): with gr.Group(): aesthetic_lr = gr.Textbox(label='Learning rate', placeholder="Learning rate", value="0.0001") aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.9) - aesthetic_steps = gr.Slider(minimum=0, maximum=256, step=1, label="Aesthetic steps", value=5) + aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) + with gr.Row(): aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', placeholder="This text is used to rotate the feature space of the imgs embs", value="") aesthetic_slerp_angle = gr.Slider(label='Slerp angle',minimum=0, maximum=1, step=0.01, value=0.1) From f7df06a98180a2a8769b3ceebf7b6a35eca8ffc5 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sat, 15 Oct 2022 18:40:06 +0200 Subject: [PATCH 0013/1118] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Gallego --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b8d018b..40104833 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - No token limit for prompts (original stable diffusion lets you use up to 75 tokens) - DeepDanbooru integration, creates danbooru style tags for anime prompts (add --deepdanbooru to commandline args) - [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add --xformers to commandline args) -- Aesthetic, a way to generate images with a specific aesthetic by using clip images embds (implementation of https://github.com/vicgalle/stable-diffusion-aesthetic-gradients) +- Aesthetic Gradients, a way to generate images with a specific aesthetic by using clip images embds (implementation of [https://github.com/vicgalle/stable-diffusion-aesthetic-gradients](https://github.com/vicgalle/stable-diffusion-aesthetic-gradients)) + ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. From 9b7705e0573bddde26df4575c71f994d73a4d519 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sat, 15 Oct 2022 18:40:34 +0200 Subject: [PATCH 0014/1118] Update modules/aesthetic_clip.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Gallego --- modules/aesthetic_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aesthetic_clip.py b/modules/aesthetic_clip.py index f15cfd47..bcf2b073 100644 --- a/modules/aesthetic_clip.py +++ b/modules/aesthetic_clip.py @@ -70,7 +70,7 @@ def generate_imgs_embd(name, folder, batch_size): torch.cuda.empty_cache() res = f""" Done generating embedding for {name}! - Hypernetwork saved to {html.escape(path)} + Aesthetic embedding saved to {html.escape(path)} """ shared.update_aesthetic_embeddings() return gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Imgs embedding", From 0d4f5db235357aeb4c7a8738179ba33aaf5a6b75 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sat, 15 Oct 2022 18:40:58 +0200 Subject: [PATCH 0015/1118] Update modules/ui.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Gallego --- modules/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 5bb961b2..25eba548 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -597,7 +597,8 @@ def create_ui(wrap_gradio_gpu_call): height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) with gr.Group(): - aesthetic_lr = gr.Textbox(label='Learning rate', placeholder="Learning rate", value="0.0001") + aesthetic_lr = gr.Textbox(label='Aesthetic learning rate', placeholder="Aesthetic learning rate", value="0.0001") + aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.9) aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) From ad9bc604a8fadcfebe72be37f66cec51e7e87fb5 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sat, 15 Oct 2022 18:41:18 +0200 Subject: [PATCH 0016/1118] Update modules/ui.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Gallego --- modules/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 25eba548..3b28b69c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -607,7 +607,8 @@ def create_ui(wrap_gradio_gpu_call): aesthetic_slerp_angle = gr.Slider(label='Slerp angle',minimum=0, maximum=1, step=0.01, value=0.1) aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) - aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Imgs embedding", value=sorted(aesthetic_embeddings.keys())[0] if len(aesthetic_embeddings) > 0 else None) + aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Aesthetic imgs embedding", value=sorted(aesthetic_embeddings.keys())[0] if len(aesthetic_embeddings) > 0 else None) + aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) with gr.Row(): From 3f5c3b981e46c16bb10948d012575b25170efb3b Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sat, 15 Oct 2022 18:41:46 +0200 Subject: [PATCH 0017/1118] Update modules/ui.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Gallego --- modules/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 3b28b69c..1f6fcdc9 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1190,7 +1190,8 @@ def create_ui(wrap_gradio_gpu_call): with gr.Column(): create_embedding = gr.Button(value="Create embedding", variant='primary') - with gr.Tab(label="Create images embedding"): + with gr.Tab(label="Create aesthetic images embedding"): + new_embedding_name_ae = gr.Textbox(label="Name") process_src_ae = gr.Textbox(label='Source directory') batch_ae = gr.Slider(minimum=1, maximum=1024, step=1, label="Batch size", value=256) From 3d21684ee30ca5734126b8d08c05b3a0f513fe75 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sun, 16 Oct 2022 00:01:00 +0200 Subject: [PATCH 0018/1118] Add support to other img format, fixed dropbox update --- modules/aesthetic_clip.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/aesthetic_clip.py b/modules/aesthetic_clip.py index bcf2b073..68264284 100644 --- a/modules/aesthetic_clip.py +++ b/modules/aesthetic_clip.py @@ -8,7 +8,7 @@ import gradio as gr import torch from PIL import Image from modules import shared -from modules.shared import device, aesthetic_embeddings +from modules.shared import device from transformers import CLIPModel, CLIPProcessor from tqdm.auto import tqdm @@ -20,7 +20,7 @@ def get_all_images_in_folder(folder): def check_is_valid_image_file(filename): - return filename.lower().endswith(('.png', '.jpg', '.jpeg')) + return filename.lower().endswith(('.png', '.jpg', '.jpeg', ".gif", ".tiff", ".webp")) def batched(dataset, total, n=1): @@ -73,6 +73,6 @@ def generate_imgs_embd(name, folder, batch_size): Aesthetic embedding saved to {html.escape(path)} """ shared.update_aesthetic_embeddings() - return gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Imgs embedding", - value=sorted(aesthetic_embeddings.keys())[0] if len( - aesthetic_embeddings) > 0 else None), res, "" + return gr.Dropdown.update(choices=sorted(shared.aesthetic_embeddings.keys()), label="Imgs embedding", + value=sorted(shared.aesthetic_embeddings.keys())[0] if len( + shared.aesthetic_embeddings) > 0 else None), res, "" From 9325c85f780c569d1823e422eaf51b2e497e0d3e Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sun, 16 Oct 2022 00:23:47 +0200 Subject: [PATCH 0019/1118] fixed dropbox update --- modules/sd_hijack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 192883b2..491312b4 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -9,7 +9,7 @@ from torch.nn.functional import silu import modules.textual_inversion.textual_inversion from modules import prompt_parser, devices, sd_hijack_optimizations, shared -from modules.shared import opts, device, cmd_opts, aesthetic_embeddings +from modules.shared import opts, device, cmd_opts from modules.sd_hijack_optimizations import invokeAI_mps_available import ldm.modules.attention @@ -182,7 +182,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): image_embs_name = None if image_embs_name is not None and self.image_embs_name != image_embs_name: self.image_embs_name = image_embs_name - self.image_embs = torch.load(aesthetic_embeddings[self.image_embs_name], map_location=device) + self.image_embs = torch.load(shared.aesthetic_embeddings[self.image_embs_name], map_location=device) self.image_embs /= self.image_embs.norm(dim=-1, keepdim=True) self.image_embs.requires_grad_(False) From 763b893f319cee280b86e63025eb55e7c16b02e7 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sun, 16 Oct 2022 10:03:09 +0800 Subject: [PATCH 0020/1118] images history sorting files by date --- javascript/images_history.js | 12 +- modules/images_history.py | 261 ++++++++++++++++++++++++++--------- 2 files changed, 202 insertions(+), 71 deletions(-) diff --git a/javascript/images_history.js b/javascript/images_history.js index 7f0d8f42..ac5834c7 100644 --- a/javascript/images_history.js +++ b/javascript/images_history.js @@ -88,10 +88,10 @@ function images_history_set_image_info(button){ } -function images_history_get_current_img(tabname, image_path, files){ +function images_history_get_current_img(tabname, img_index, files){ return [ - gradioApp().getElementById(tabname + '_images_history_set_index').getAttribute("img_index"), - image_path, + tabname, + gradioApp().getElementById(tabname + '_images_history_set_index').getAttribute("img_index"), files ]; } @@ -129,7 +129,7 @@ function images_history_delete(del_num, tabname, img_file_name, page_index, file setTimeout(function(btn){btn.click()}, 30, btn); } images_history_disabled_del(); - return [del_num, tabname, img_path, img_file_name, page_index, filenames, image_index]; + return [del_num, tabname, img_file_name, page_index, filenames, image_index]; } function images_history_turnpage(img_path, page_index, image_index, tabname, date_from, date_to){ @@ -170,8 +170,8 @@ function images_history_init(){ } tabs_box.classList.add(images_history_tab_list[0]); - // same as above, at page load - //load_txt2img_button.click(); + // same as above, at page load-- load very fast now + load_txt2img_button.click(); } else { setTimeout(images_history_init, 500); } diff --git a/modules/images_history.py b/modules/images_history.py index f5ef44fe..533cf51b 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -1,33 +1,74 @@ import os import shutil +import time +import hashlib +import gradio +show_max_dates_num = 3 +system_bak_path = "webui_log_and_bak" +def is_valid_date(date): + try: + time.strptime(date, "%Y%m%d") + return True + except: + return False +def reduplicative_file_move(src, dst): + def same_name_file(basename, path): + name, ext = os.path.splitext(basename) + f_list = os.listdir(path) + max_num = 0 + for f in f_list: + if len(f) <= len(basename): + continue + f_ext = f[-len(ext):] if len(ext) > 0 else "" + if f[:len(name)] == name and f_ext == ext: + if f[len(name)] == "(" and f[-len(ext)-1] == ")": + number = f[len(name)+1:-len(ext)-1] + if number.isdigit(): + if int(number) > max_num: + max_num = int(number) + return f"{name}({max_num + 1}){ext}" + name = os.path.basename(src) + save_name = os.path.join(dst, name) + if not os.path.exists(save_name): + shutil.move(src, dst) + else: + name = same_name_file(name, dst) + shutil.move(src, os.path.join(dst, name)) -def traverse_all_files(output_dir, image_list, curr_dir=None): - curr_path = output_dir if curr_dir is None else os.path.join(output_dir, curr_dir) +def traverse_all_files(curr_path, image_list, all_type=False): try: f_list = os.listdir(curr_path) except: - if curr_dir[-10:].rfind(".") > 0 and curr_dir[-4:] != ".txt": - image_list.append(curr_dir) + if all_type or curr_path[-10:].rfind(".") > 0 and curr_path[-4:] != ".txt": + image_list.append(curr_path) return image_list for file in f_list: - file = file if curr_dir is None else os.path.join(curr_dir, file) - file_path = os.path.join(curr_path, file) - if file[-4:] == ".txt": + file = os.path.join(curr_path, file) + if (not all_type) and file[-4:] == ".txt": pass - elif os.path.isfile(file_path) and file[-10:].rfind(".") > 0: + elif os.path.isfile(file) and file[-10:].rfind(".") > 0: image_list.append(file) else: - image_list = traverse_all_files(output_dir, image_list, file) + image_list = traverse_all_files(file, image_list) return image_list - -def get_recent_images(dir_name, page_index, step, image_index, tabname): - page_index = int(page_index) - f_list = os.listdir(dir_name) +def get_recent_images(dir_name, page_index, step, image_index, tabname, date_from, date_to): + #print(f"turn_page {page_index}",date_from) + if date_from is None or date_from == "": + return None, 1, None, "" image_list = [] - image_list = traverse_all_files(dir_name, image_list) - image_list = sorted(image_list, key=lambda file: -os.path.getctime(os.path.join(dir_name, file))) + date_list = auto_sorting(dir_name) + page_index = int(page_index) + today = time.strftime("%Y%m%d",time.localtime(time.time())) + for date in date_list: + if date >= date_from and date <= date_to: + path = os.path.join(dir_name, date) + if date == today and not os.path.exists(path): + continue + image_list = traverse_all_files(path, image_list) + + image_list = sorted(image_list, key=lambda file: -os.path.getctime(file)) num = 48 if tabname != "extras" else 12 max_page_index = len(image_list) // num + 1 page_index = max_page_index if page_index == -1 else page_index + step @@ -38,40 +79,101 @@ def get_recent_images(dir_name, page_index, step, image_index, tabname): image_index = int(image_index) if image_index < 0 or image_index > len(image_list) - 1: current_file = None - hidden = None else: - current_file = image_list[int(image_index)] - hidden = os.path.join(dir_name, current_file) - return [os.path.join(dir_name, file) for file in image_list], page_index, image_list, current_file, hidden, "" + current_file = image_list[image_index] + return image_list, page_index, image_list, "" + +def auto_sorting(dir_name): + #print(f"auto sorting") + bak_path = os.path.join(dir_name, system_bak_path) + if not os.path.exists(bak_path): + os.mkdir(bak_path) + log_file = None + files_list = [] + f_list = os.listdir(dir_name) + for file in f_list: + if file == system_bak_path: + continue + file_path = os.path.join(dir_name, file) + if not is_valid_date(file): + if file[-10:].rfind(".") > 0: + files_list.append(file_path) + else: + files_list = traverse_all_files(file_path, files_list, all_type=True) + + for file in files_list: + date_str = time.strftime("%Y%m%d",time.localtime(os.path.getctime(file))) + file_path = os.path.dirname(file) + hash_path = hashlib.md5(file_path.encode()).hexdigest() + path = os.path.join(dir_name, date_str, hash_path) + if not os.path.exists(path): + os.makedirs(path) + if log_file is None: + log_file = open(os.path.join(bak_path,"path_mapping.csv"),"a") + log_file.write(f"{hash_path},{file_path}\n") + reduplicative_file_move(file, path) + + date_list = [] + f_list = os.listdir(dir_name) + for f in f_list: + if is_valid_date(f): + date_list.append(f) + elif f == system_bak_path: + continue + else: + reduplicative_file_move(os.path.join(dir_name, f), bak_path) + + today = time.strftime("%Y%m%d",time.localtime(time.time())) + if today not in date_list: + date_list.append(today) + return sorted(date_list) -def first_page_click(dir_name, page_index, image_index, tabname): - return get_recent_images(dir_name, 1, 0, image_index, tabname) + +def archive_images(dir_name): + date_list = auto_sorting(dir_name) + date_from = date_list[-show_max_dates_num] if len(date_list) > show_max_dates_num else date_list[0] + return ( + gradio.update(visible=False), + gradio.update(visible=True), + gradio.Dropdown.update(choices=date_list, value=date_list[-1]), + gradio.Dropdown.update(choices=date_list, value=date_from) + ) + +def date_to_change(dir_name, page_index, image_index, tabname, date_from, date_to): + #print("date_to", date_to) + date_list = auto_sorting(dir_name) + date_from_list = [date for date in date_list if date <= date_to] + date_from = date_from_list[0] if len(date_from_list) < show_max_dates_num else date_from_list[-show_max_dates_num] + image_list, page_index, image_list, _ =get_recent_images(dir_name, 1, 0, image_index, tabname, date_from, date_to) + return image_list, page_index, image_list, _, gradio.Dropdown.update(choices=date_from_list, value=date_from) + +def first_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): + return get_recent_images(dir_name, 1, 0, image_index, tabname, date_from, date_to) -def end_page_click(dir_name, page_index, image_index, tabname): - return get_recent_images(dir_name, -1, 0, image_index, tabname) +def end_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): + return get_recent_images(dir_name, -1, 0, image_index, tabname, date_from, date_to) -def prev_page_click(dir_name, page_index, image_index, tabname): - return get_recent_images(dir_name, page_index, -1, image_index, tabname) +def prev_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): + return get_recent_images(dir_name, page_index, -1, image_index, tabname, date_from, date_to) -def next_page_click(dir_name, page_index, image_index, tabname): - return get_recent_images(dir_name, page_index, 1, image_index, tabname) +def next_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): + return get_recent_images(dir_name, page_index, 1, image_index, tabname, date_from, date_to) -def page_index_change(dir_name, page_index, image_index, tabname): - return get_recent_images(dir_name, page_index, 0, image_index, tabname) +def page_index_change(dir_name, page_index, image_index, tabname, date_from, date_to): + return get_recent_images(dir_name, page_index, 0, image_index, tabname, date_from, date_to) -def show_image_info(num, image_path, filenames): - # print(f"select image {num}") +def show_image_info(tabname_box, num, filenames): + # #print(f"select image {num}") file = filenames[int(num)] - return file, num, os.path.join(image_path, file) + return file, num, file - -def delete_image(delete_num, tabname, dir_name, name, page_index, filenames, image_index): +def delete_image(delete_num, tabname, name, page_index, filenames, image_index): if name == "": return filenames, delete_num else: @@ -81,21 +183,19 @@ def delete_image(delete_num, tabname, dir_name, name, page_index, filenames, ima new_file_list = [] for name in filenames: if i >= index and i < index + delete_num: - path = os.path.join(dir_name, name) - if os.path.exists(path): - print(f"Delete file {path}") - os.remove(path) - txt_file = os.path.splitext(path)[0] + ".txt" + if os.path.exists(name): + #print(f"Delete file {name}") + os.remove(name) + txt_file = os.path.splitext(name)[0] + ".txt" if os.path.exists(txt_file): os.remove(txt_file) else: - print(f"Not exists file {path}") + #print(f"Not exists file {name}") else: new_file_list.append(name) i += 1 return new_file_list, 1 - def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): if tabname == "txt2img": dir_name = opts.outdir_txt2img_samples @@ -107,16 +207,32 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): dir_name = d[0] for p in d[1:]: dir_name = os.path.join(dir_name, p) - with gr.Row(): - renew_page = gr.Button('Renew Page', elem_id=tabname + "_images_history_renew_page") - first_page = gr.Button('First Page') - prev_page = gr.Button('Prev Page') - page_index = gr.Number(value=1, label="Page Index") - next_page = gr.Button('Next Page') - end_page = gr.Button('End Page') - with gr.Row(elem_id=tabname + "_images_history"): + + f_list = os.listdir(dir_name) + sorted_flag = os.path.exists(os.path.join(dir_name, system_bak_path)) or len(f_list) == 0 + date_list, date_from, date_to = None, None, None + if sorted_flag: + #print(sorted_flag) + date_list = auto_sorting(dir_name) + date_to = date_list[-1] + date_from = date_list[-show_max_dates_num] if len(date_list) > show_max_dates_num else date_list[0] + + with gr.Column(visible=sorted_flag) as page_panel: with gr.Row(): + renew_page = gr.Button('Refresh', elem_id=tabname + "_images_history_renew_page", interactive=sorted_flag) + first_page = gr.Button('First Page') + prev_page = gr.Button('Prev Page') + page_index = gr.Number(value=1, label="Page Index") + next_page = gr.Button('Next Page') + end_page = gr.Button('End Page') + + with gr.Row(elem_id=tabname + "_images_history"): with gr.Column(scale=2): + with gr.Row(): + newest = gr.Button('Newest') + date_to = gr.Dropdown(choices=date_list, value=date_to, label="Date to") + date_from = gr.Dropdown(choices=date_list, value=date_from, label="Date from") + history_gallery = gr.Gallery(show_label=False, elem_id=tabname + "_images_history_gallery").style(grid=6) with gr.Row(): delete_num = gr.Number(value=1, interactive=True, label="number of images to delete consecutively next") @@ -128,22 +244,31 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): with gr.Row(): with gr.Column(): img_file_info = gr.Textbox(label="Generate Info", interactive=False) - img_file_name = gr.Textbox(label="File Name", interactive=False) - with gr.Row(): + img_file_name = gr.Textbox(value="", label="File Name", interactive=False) # hiden items + with gr.Row(visible=False): + img_path = gr.Textbox(dir_name) + tabname_box = gr.Textbox(tabname) + image_index = gr.Textbox(value=-1) + set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index") + filenames = gr.State() + hidden = gr.Image(type="pil") + info1 = gr.Textbox() + info2 = gr.Textbox() + with gr.Column(visible=not sorted_flag) as init_warning: + with gr.Row(): + gr.Textbox("The system needs to archive the files according to the date. This requires changing the directory structure of the files", + label="Waring", + css="") + with gr.Row(): + sorted_button = gr.Button('Confirme') - img_path = gr.Textbox(dir_name.rstrip("/"), visible=False) - tabname_box = gr.Textbox(tabname, visible=False) - image_index = gr.Textbox(value=-1, visible=False) - set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index", visible=False) - filenames = gr.State() - hidden = gr.Image(type="pil", visible=False) - info1 = gr.Textbox(visible=False) - info2 = gr.Textbox(visible=False) - + + + # turn pages - gallery_inputs = [img_path, page_index, image_index, tabname_box] - gallery_outputs = [history_gallery, page_index, filenames, img_file_name, hidden, img_file_name] + gallery_inputs = [img_path, page_index, image_index, tabname_box, date_from, date_to] + gallery_outputs = [history_gallery, page_index, filenames, img_file_name] first_page.click(first_page_click, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) next_page.click(next_page_click, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) @@ -154,15 +279,21 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): # page_index.change(page_index_change, inputs=[tabname_box, img_path, page_index], outputs=[history_gallery, page_index]) # other funcitons - set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, img_path, filenames], outputs=[img_file_name, image_index, hidden]) + set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, image_index, filenames], outputs=[img_file_name, image_index, hidden]) img_file_name.change(fn=None, _js="images_history_enable_del_buttons", inputs=None, outputs=None) - delete.click(delete_image, _js="images_history_delete", inputs=[delete_num, tabname_box, img_path, img_file_name, page_index, filenames, image_index], outputs=[filenames, delete_num]) + delete.click(delete_image, _js="images_history_delete", inputs=[delete_num, tabname_box, img_file_name, page_index, filenames, image_index], outputs=[filenames, delete_num]) hidden.change(fn=run_pnginfo, inputs=[hidden], outputs=[info1, img_file_info, info2]) - + date_to.change(date_to_change, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs + [date_from]) # pnginfo.click(fn=run_pnginfo, inputs=[hidden], outputs=[info1, img_file_info, info2]) switch_dict["fn"](pnginfo_send_to_txt2img, switch_dict["t2i"], img_file_info, 'switch_to_txt2img') switch_dict["fn"](pnginfo_send_to_img2img, switch_dict["i2i"], img_file_info, 'switch_to_img2img_img2img') + sorted_button.click(archive_images, inputs=[img_path], outputs=[init_warning, page_panel, date_to, date_from]) + newest.click(archive_images, inputs=[img_path], outputs=[init_warning, page_panel, date_to, date_from]) + + + + def create_history_tabs(gr, opts, run_pnginfo, switch_dict): with gr.Blocks(analytics_enabled=False) as images_history: From 523140d7805c644700009b8a2483ff4eb4a22304 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sun, 16 Oct 2022 10:23:30 +0200 Subject: [PATCH 0021/1118] ui fix --- modules/aesthetic_clip.py | 3 +-- modules/sd_hijack.py | 3 +-- modules/shared.py | 2 ++ modules/ui.py | 24 ++++++++++++++---------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/modules/aesthetic_clip.py b/modules/aesthetic_clip.py index 68264284..ccb35c73 100644 --- a/modules/aesthetic_clip.py +++ b/modules/aesthetic_clip.py @@ -74,5 +74,4 @@ def generate_imgs_embd(name, folder, batch_size): """ shared.update_aesthetic_embeddings() return gr.Dropdown.update(choices=sorted(shared.aesthetic_embeddings.keys()), label="Imgs embedding", - value=sorted(shared.aesthetic_embeddings.keys())[0] if len( - shared.aesthetic_embeddings) > 0 else None), res, "" + value="None"), res, "" diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 01fcb78f..2de2eed5 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -392,8 +392,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): z1 = self.process_tokens(tokens, multipliers) z = z1 if z is None else torch.cat((z, z1), axis=-2) - if len(text[ - 0]) != 0 and self.aesthetic_steps != 0 and self.aesthetic_lr != 0 and self.aesthetic_weight != 0 and self.image_embs_name != None: + if self.aesthetic_steps != 0 and self.aesthetic_lr != 0 and self.aesthetic_weight != 0 and self.image_embs_name != None: if not opts.use_old_emphasis_implementation: remade_batch_tokens = [ [self.wrapped.tokenizer.bos_token_id] + x[:75] + [self.wrapped.tokenizer.eos_token_id] for x in diff --git a/modules/shared.py b/modules/shared.py index 3c5ffef1..e2c98b2d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -96,11 +96,13 @@ loaded_hypernetwork = None aesthetic_embeddings = {f.replace(".pt",""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} +aesthetic_embeddings = aesthetic_embeddings | {"None": None} def update_aesthetic_embeddings(): global aesthetic_embeddings aesthetic_embeddings = {f.replace(".pt",""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} + aesthetic_embeddings = aesthetic_embeddings | {"None": None} def reload_hypernetworks(): global hypernetworks diff --git a/modules/ui.py b/modules/ui.py index 13ba3142..4069f0d2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -594,19 +594,23 @@ def create_ui(wrap_gradio_gpu_call): height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) with gr.Group(): - aesthetic_lr = gr.Textbox(label='Aesthetic learning rate', placeholder="Aesthetic learning rate", value="0.0001") + with gr.Accordion("Open for Clip Aesthetic!",open=False): + with gr.Row(): + aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.9) + aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) - aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.9) - aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) + with gr.Row(): + aesthetic_lr = gr.Textbox(label='Aesthetic learning rate', placeholder="Aesthetic learning rate", value="0.0001") + aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) + aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), + label="Aesthetic imgs embedding", + value="None") - with gr.Row(): - aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', placeholder="This text is used to rotate the feature space of the imgs embs", value="") - aesthetic_slerp_angle = gr.Slider(label='Slerp angle',minimum=0, maximum=1, step=0.01, value=0.1) - aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) + with gr.Row(): + aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', placeholder="This text is used to rotate the feature space of the imgs embs", value="") + aesthetic_slerp_angle = gr.Slider(label='Slerp angle',minimum=0, maximum=1, step=0.01, value=0.1) + aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) - aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), label="Aesthetic imgs embedding", value=sorted(aesthetic_embeddings.keys())[0] if len(aesthetic_embeddings) > 0 else None) - - aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) with gr.Row(): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1) From e4f8b5f00dd33b7547cc6b76fbed26bb83b37a64 Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sun, 16 Oct 2022 10:28:21 +0200 Subject: [PATCH 0022/1118] ui fix --- modules/sd_hijack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 2de2eed5..5d0590af 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -178,7 +178,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): self.load_image_embs(image_embs_name) def load_image_embs(self, image_embs_name): - if image_embs_name is None or len(image_embs_name) == 0: + if image_embs_name is None or len(image_embs_name) == 0 or image_embs_name == "None": image_embs_name = None if image_embs_name is not None and self.image_embs_name != image_embs_name: self.image_embs_name = image_embs_name From f62905fdf928b54aa76765e5cbde8d538d494e49 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sun, 16 Oct 2022 21:22:38 +0800 Subject: [PATCH 0023/1118] images history speed up --- javascript/images_history.js | 39 +++--- modules/images_history.py | 248 ++++++++++++++++++----------------- 2 files changed, 146 insertions(+), 141 deletions(-) diff --git a/javascript/images_history.js b/javascript/images_history.js index ac5834c7..fb1356d9 100644 --- a/javascript/images_history.js +++ b/javascript/images_history.js @@ -20,7 +20,7 @@ var images_history_click_image = function(){ var images_history_click_tab = function(){ var tabs_box = gradioApp().getElementById("images_history_tab"); if (!tabs_box.classList.contains(this.getAttribute("tabname"))) { - gradioApp().getElementById(this.getAttribute("tabname") + "_images_history_renew_page").click(); + gradioApp().getElementById(this.getAttribute("tabname") + "_images_history_start").click(); tabs_box.classList.add(this.getAttribute("tabname")) } } @@ -96,7 +96,7 @@ function images_history_get_current_img(tabname, img_index, files){ ]; } -function images_history_delete(del_num, tabname, img_file_name, page_index, filenames, image_index){ +function images_history_delete(del_num, tabname, image_index){ image_index = parseInt(image_index); var tab = gradioApp().getElementById(tabname + '_images_history'); var set_btn = tab.querySelector(".images_history_set_index"); @@ -107,6 +107,7 @@ function images_history_delete(del_num, tabname, img_file_name, page_index, file } }); var img_num = buttons.length / 2; + del_num = Math.min(img_num - image_index, del_num) if (img_num <= del_num){ setTimeout(function(tabname){ gradioApp().getElementById(tabname + '_images_history_renew_page').click(); @@ -114,30 +115,29 @@ function images_history_delete(del_num, tabname, img_file_name, page_index, file } else { var next_img for (var i = 0; i < del_num; i++){ - if (image_index + i < image_index + img_num){ - buttons[image_index + i].style.display = 'none'; - buttons[image_index + img_num + 1].style.display = 'none'; - next_img = image_index + i + 1 - } + buttons[image_index + i].style.display = 'none'; + buttons[image_index + i + img_num].style.display = 'none'; + next_img = image_index + i + 1 } var bnt; if (next_img >= img_num){ - btn = buttons[image_index - del_num]; + btn = buttons[image_index - 1]; } else { btn = buttons[next_img]; } setTimeout(function(btn){btn.click()}, 30, btn); } images_history_disabled_del(); - return [del_num, tabname, img_file_name, page_index, filenames, image_index]; + } -function images_history_turnpage(img_path, page_index, image_index, tabname, date_from, date_to){ +function images_history_turnpage(tabname){ + console.log("del_button") + gradioApp().getElementById(tabname + '_images_history_del_button').setAttribute('disabled','disabled'); var buttons = gradioApp().getElementById(tabname + '_images_history').querySelectorAll(".gallery-item"); buttons.forEach(function(elem) { elem.style.display = 'block'; - }) - return [img_path, page_index, image_index, tabname, date_from, date_to]; + }) } function images_history_enable_del_buttons(){ @@ -147,7 +147,7 @@ function images_history_enable_del_buttons(){ } function images_history_init(){ - var load_txt2img_button = gradioApp().getElementById('txt2img_images_history_renew_page') + var load_txt2img_button = gradioApp().getElementById('saved_images_history_start') if (load_txt2img_button){ for (var i in images_history_tab_list ){ tab = images_history_tab_list[i]; @@ -166,7 +166,8 @@ function images_history_init(){ // this refreshes history upon tab switch // until the history is known to work well, which is not the case now, we do not do this at startup - //tab_btns[i].addEventListener('click', images_history_click_tab); + // -- load page very fast now, so better user experience by automatically activating pages + tab_btns[i].addEventListener('click', images_history_click_tab); } tabs_box.classList.add(images_history_tab_list[0]); @@ -177,7 +178,7 @@ function images_history_init(){ } } -var images_history_tab_list = ["txt2img", "img2img", "extras"]; +var images_history_tab_list = ["saved", "txt2img", "img2img", "extras"]; setTimeout(images_history_init, 500); document.addEventListener("DOMContentLoaded", function() { var mutationObserver = new MutationObserver(function(m){ @@ -188,18 +189,16 @@ document.addEventListener("DOMContentLoaded", function() { bnt.addEventListener('click', images_history_click_image, true); }); - // same as load_txt2img_button.click() above - /* var cls_btn = gradioApp().getElementById(tabname + '_images_history_gallery').querySelector("svg"); if (cls_btn){ cls_btn.addEventListener('click', function(){ - gradioApp().getElementById(tabname + '_images_history_renew_page').click(); + gradioApp().getElementById(tabname + '_images_history_del_button').setAttribute('disabled','disabled'); }, false); - }*/ + } } }); - mutationObserver.observe( gradioApp(), { childList:true, subtree:true }); + mutationObserver.observe(gradioApp(), { childList:true, subtree:true }); }); diff --git a/modules/images_history.py b/modules/images_history.py index 7fd75005..ae0b4e40 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -3,8 +3,10 @@ import shutil import time import hashlib import gradio -show_max_dates_num = 3 + system_bak_path = "webui_log_and_bak" +loads_files_num = 216 +num_of_imgs_per_page = 36 def is_valid_date(date): try: time.strptime(date, "%Y%m%d") @@ -53,38 +55,7 @@ def traverse_all_files(curr_path, image_list, all_type=False): image_list = traverse_all_files(file, image_list) return image_list -def get_recent_images(dir_name, page_index, step, image_index, tabname, date_from, date_to): - #print(f"turn_page {page_index}",date_from) - if date_from is None or date_from == "": - return None, 1, None, "" - image_list = [] - date_list = auto_sorting(dir_name) - page_index = int(page_index) - today = time.strftime("%Y%m%d",time.localtime(time.time())) - for date in date_list: - if date >= date_from and date <= date_to: - path = os.path.join(dir_name, date) - if date == today and not os.path.exists(path): - continue - image_list = traverse_all_files(path, image_list) - - image_list = sorted(image_list, key=lambda file: -os.path.getctime(file)) - num = 48 if tabname != "extras" else 12 - max_page_index = len(image_list) // num + 1 - page_index = max_page_index if page_index == -1 else page_index + step - page_index = 1 if page_index < 1 else page_index - page_index = max_page_index if page_index > max_page_index else page_index - idx_frm = (page_index - 1) * num - image_list = image_list[idx_frm:idx_frm + num] - image_index = int(image_index) - if image_index < 0 or image_index > len(image_list) - 1: - current_file = None - else: - current_file = image_list[image_index] - return image_list, page_index, image_list, "" - -def auto_sorting(dir_name): - #print(f"auto sorting") +def auto_sorting(dir_name): bak_path = os.path.join(dir_name, system_bak_path) if not os.path.exists(bak_path): os.mkdir(bak_path) @@ -126,102 +97,131 @@ def auto_sorting(dir_name): today = time.strftime("%Y%m%d",time.localtime(time.time())) if today not in date_list: date_list.append(today) - return sorted(date_list) + return sorted(date_list, reverse=True) -def archive_images(dir_name): +def archive_images(dir_name, date_to): date_list = auto_sorting(dir_name) - date_from = date_list[-show_max_dates_num] if len(date_list) > show_max_dates_num else date_list[0] + today = time.strftime("%Y%m%d",time.localtime(time.time())) + date_to = today if date_to is None or date_to == "" else date_to + filenames = [] + for date in date_list: + if date <= date_to: + path = os.path.join(dir_name, date) + if date == today and not os.path.exists(path): + continue + filenames = traverse_all_files(path, filenames) + if len(filenames) > loads_files_num: + break + filenames = sorted(filenames, key=lambda file: -os.path.getctime(file)) + _, image_list, _, visible_num = get_recent_images(1, 0, filenames) return ( gradio.update(visible=False), gradio.update(visible=True), - gradio.Dropdown.update(choices=date_list, value=date_list[-1]), - gradio.Dropdown.update(choices=date_list, value=date_from) + gradio.Dropdown.update(choices=date_list, value=date_to), + date, + filenames, + 1, + image_list, + "", + visible_num ) +def system_init(dir_name): + ret = [x for x in archive_images(dir_name, None)] + ret += [gradio.update(visible=False)] + return ret -def date_to_change(dir_name, page_index, image_index, tabname, date_from, date_to): - #print("date_to", date_to) - date_list = auto_sorting(dir_name) - date_from_list = [date for date in date_list if date <= date_to] - date_from = date_from_list[0] if len(date_from_list) < show_max_dates_num else date_from_list[-show_max_dates_num] - image_list, page_index, image_list, _ =get_recent_images(dir_name, 1, 0, image_index, tabname, date_from, date_to) - return image_list, page_index, image_list, _, gradio.Dropdown.update(choices=date_from_list, value=date_from) +def newest_click(dir_name, date_to): + if date_to == "start": + return True, False, "start", None, None, 1, None, "" + else: + return archive_images(dir_name, time.strftime("%Y%m%d",time.localtime(time.time()))) -def first_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): - return get_recent_images(dir_name, 1, 0, image_index, tabname, date_from, date_to) - - -def end_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): - return get_recent_images(dir_name, -1, 0, image_index, tabname, date_from, date_to) - - -def prev_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): - return get_recent_images(dir_name, page_index, -1, image_index, tabname, date_from, date_to) - - -def next_page_click(dir_name, page_index, image_index, tabname, date_from, date_to): - return get_recent_images(dir_name, page_index, 1, image_index, tabname, date_from, date_to) - - -def page_index_change(dir_name, page_index, image_index, tabname, date_from, date_to): - return get_recent_images(dir_name, page_index, 0, image_index, tabname, date_from, date_to) - - -def show_image_info(tabname_box, num, filenames): - # #print(f"select image {num}") - file = filenames[int(num)] - return file, num, file - -def delete_image(delete_num, tabname, name, page_index, filenames, image_index): +def delete_image(delete_num, name, filenames, image_index, visible_num): if name == "": return filenames, delete_num else: delete_num = int(delete_num) + visible_num = int(visible_num) + image_index = int(image_index) index = list(filenames).index(name) i = 0 new_file_list = [] for name in filenames: if i >= index and i < index + delete_num: if os.path.exists(name): - #print(f"Delete file {name}") + if visible_num == image_index: + new_file_list.append(name) + continue + print(f"Delete file {name}") os.remove(name) + visible_num -= 1 txt_file = os.path.splitext(name)[0] + ".txt" if os.path.exists(txt_file): os.remove(txt_file) else: - #print(f"Not exists file {name}") + print(f"Not exists file {name}") else: new_file_list.append(name) i += 1 - return new_file_list, 1 + return new_file_list, 1, visible_num + +def get_recent_images(page_index, step, filenames): + page_index = int(page_index) + max_page_index = len(filenames) // num_of_imgs_per_page + 1 + page_index = max_page_index if page_index == -1 else page_index + step + page_index = 1 if page_index < 1 else page_index + page_index = max_page_index if page_index > max_page_index else page_index + idx_frm = (page_index - 1) * num_of_imgs_per_page + image_list = filenames[idx_frm:idx_frm + num_of_imgs_per_page] + length = len(filenames) + visible_num = num_of_imgs_per_page if idx_frm + num_of_imgs_per_page <= length else length % num_of_imgs_per_page + visible_num = num_of_imgs_per_page if visible_num == 0 else visible_num + return page_index, image_list, "", visible_num + +def first_page_click(page_index, filenames): + return get_recent_images(1, 0, filenames) + +def end_page_click(page_index, filenames): + return get_recent_images(-1, 0, filenames) + +def prev_page_click(page_index, filenames): + return get_recent_images(page_index, -1, filenames) + +def next_page_click(page_index, filenames): + return get_recent_images(page_index, 1, filenames) + +def page_index_change(page_index, filenames): + return get_recent_images(page_index, 0, filenames) + +def show_image_info(tabname_box, num, page_index, filenames): + file = filenames[int(num) + int((page_index - 1) * num_of_imgs_per_page)] + return file, num, file def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): - if opts.outdir_samples != "": - dir_name = opts.outdir_samples - elif tabname == "txt2img": + if tabname == "txt2img": dir_name = opts.outdir_txt2img_samples elif tabname == "img2img": dir_name = opts.outdir_img2img_samples elif tabname == "extras": dir_name = opts.outdir_extras_samples + elif tabname == "saved": + dir_name = opts.outdir_save + if not os.path.exists(dir_name): + os.makedirs(dir_name) d = dir_name.split("/") - dir_name = "/" if dir_name.startswith("/") else d[0] + dir_name = d[0] for p in d[1:]: dir_name = os.path.join(dir_name, p) f_list = os.listdir(dir_name) sorted_flag = os.path.exists(os.path.join(dir_name, system_bak_path)) or len(f_list) == 0 date_list, date_from, date_to = None, None, None - if sorted_flag: - #print(sorted_flag) - date_list = auto_sorting(dir_name) - date_to = date_list[-1] - date_from = date_list[-show_max_dates_num] if len(date_list) > show_max_dates_num else date_list[0] with gr.Column(visible=sorted_flag) as page_panel: with gr.Row(): - renew_page = gr.Button('Refresh', elem_id=tabname + "_images_history_renew_page", interactive=sorted_flag) + #renew_page = gr.Button('Refresh') first_page = gr.Button('First Page') prev_page = gr.Button('Prev Page') page_index = gr.Number(value=1, label="Page Index") @@ -231,9 +231,9 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): with gr.Row(elem_id=tabname + "_images_history"): with gr.Column(scale=2): with gr.Row(): - newest = gr.Button('Newest') - date_to = gr.Dropdown(choices=date_list, value=date_to, label="Date to") - date_from = gr.Dropdown(choices=date_list, value=date_from, label="Date from") + newest = gr.Button('Refresh', elem_id=tabname + "_images_history_start") + date_from = gr.Textbox(label="Date from", interactive=False) + date_to = gr.Dropdown(value="start" if not sorted_flag else None, label="Date to") history_gallery = gr.Gallery(show_label=False, elem_id=tabname + "_images_history_gallery").style(grid=6) with gr.Row(): @@ -247,66 +247,72 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): with gr.Column(): img_file_info = gr.Textbox(label="Generate Info", interactive=False) img_file_name = gr.Textbox(value="", label="File Name", interactive=False) + # hiden items - with gr.Row(visible=False): + with gr.Row(visible=False): + visible_img_num = gr.Number() img_path = gr.Textbox(dir_name) tabname_box = gr.Textbox(tabname) image_index = gr.Textbox(value=-1) set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index") filenames = gr.State() + all_images_list = gr.State() hidden = gr.Image(type="pil") info1 = gr.Textbox() info2 = gr.Textbox() + with gr.Column(visible=not sorted_flag) as init_warning: with gr.Row(): - gr.Textbox("The system needs to archive the files according to the date. This requires changing the directory structure of the files", - label="Waring", - css="") + warning = gr.Textbox( + label="Waring", + value=f"The system needs to archive the files according to the date. This requires changing the directory structure of the files.If you have doubts about this operation, you can first back up the files in the '{dir_name}' directory" + ) + warning.style(height=100, width=50) with gr.Row(): sorted_button = gr.Button('Confirme') - - + change_date_output = [init_warning, page_panel, date_to, date_from, filenames, page_index, history_gallery, img_file_name, visible_img_num] + sorted_button.click(system_init, inputs=[img_path], outputs=change_date_output + [sorted_button]) + newest.click(newest_click, inputs=[img_path, date_to], outputs=change_date_output) + date_to.change(archive_images, inputs=[img_path, date_to], outputs=change_date_output) + date_to.change(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + newest.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + + delete.click(delete_image, inputs=[delete_num, img_file_name, filenames, image_index, visible_img_num], outputs=[filenames, delete_num, visible_img_num]) + delete.click(fn=None, _js="images_history_delete", inputs=[delete_num, tabname_box, image_index], outputs=None) + # turn pages - gallery_inputs = [img_path, page_index, image_index, tabname_box, date_from, date_to] - gallery_outputs = [history_gallery, page_index, filenames, img_file_name] + gallery_inputs = [page_index, filenames] + gallery_outputs = [page_index, history_gallery, img_file_name, visible_img_num] - first_page.click(first_page_click, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) - next_page.click(next_page_click, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) - prev_page.click(prev_page_click, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) - end_page.click(end_page_click, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) - page_index.submit(page_index_change, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) - renew_page.click(page_index_change, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs) - # page_index.change(page_index_change, inputs=[tabname_box, img_path, page_index], outputs=[history_gallery, page_index]) + first_page.click(first_page_click, inputs=gallery_inputs, outputs=gallery_outputs) + next_page.click(next_page_click, inputs=gallery_inputs, outputs=gallery_outputs) + prev_page.click(prev_page_click, inputs=gallery_inputs, outputs=gallery_outputs) + end_page.click(end_page_click, inputs=gallery_inputs, outputs=gallery_outputs) + page_index.submit(page_index_change, inputs=gallery_inputs, outputs=gallery_outputs) + + first_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + next_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + prev_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + end_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + page_index.submit(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") # other funcitons - set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, image_index, filenames], outputs=[img_file_name, image_index, hidden]) - img_file_name.change(fn=None, _js="images_history_enable_del_buttons", inputs=None, outputs=None) - delete.click(delete_image, _js="images_history_delete", inputs=[delete_num, tabname_box, img_file_name, page_index, filenames, image_index], outputs=[filenames, delete_num]) + set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, image_index, page_index, filenames], outputs=[img_file_name, image_index, hidden]) + img_file_name.change(fn=None, _js="images_history_enable_del_buttons", inputs=None, outputs=None) hidden.change(fn=run_pnginfo, inputs=[hidden], outputs=[info1, img_file_info, info2]) - date_to.change(date_to_change, _js="images_history_turnpage", inputs=gallery_inputs, outputs=gallery_outputs + [date_from]) - # pnginfo.click(fn=run_pnginfo, inputs=[hidden], outputs=[info1, img_file_info, info2]) + switch_dict["fn"](pnginfo_send_to_txt2img, switch_dict["t2i"], img_file_info, 'switch_to_txt2img') switch_dict["fn"](pnginfo_send_to_img2img, switch_dict["i2i"], img_file_info, 'switch_to_img2img_img2img') - sorted_button.click(archive_images, inputs=[img_path], outputs=[init_warning, page_panel, date_to, date_from]) - newest.click(archive_images, inputs=[img_path], outputs=[init_warning, page_panel, date_to, date_from]) - - - def create_history_tabs(gr, opts, run_pnginfo, switch_dict): with gr.Blocks(analytics_enabled=False) as images_history: with gr.Tabs() as tabs: - with gr.Tab("txt2img history"): - with gr.Blocks(analytics_enabled=False) as images_history_txt2img: - show_images_history(gr, opts, "txt2img", run_pnginfo, switch_dict) - with gr.Tab("img2img history"): - with gr.Blocks(analytics_enabled=False) as images_history_img2img: - show_images_history(gr, opts, "img2img", run_pnginfo, switch_dict) - with gr.Tab("extras history"): - with gr.Blocks(analytics_enabled=False) as images_history_img2img: - show_images_history(gr, opts, "extras", run_pnginfo, switch_dict) + for tab in ["saved", "txt2img", "img2img", "extras"]: + with gr.Tab(tab): + with gr.Blocks(analytics_enabled=False) as images_history_img2img: + show_images_history(gr, opts, tab, run_pnginfo, switch_dict) return images_history From a4de699e3c235d83b5a957d08779cb41cb0781bc Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sun, 16 Oct 2022 22:37:12 +0800 Subject: [PATCH 0024/1118] Images history speed up --- javascript/images_history.js | 1 + modules/images_history.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/images_history.js b/javascript/images_history.js index fb1356d9..9d9d04fb 100644 --- a/javascript/images_history.js +++ b/javascript/images_history.js @@ -108,6 +108,7 @@ function images_history_delete(del_num, tabname, image_index){ }); var img_num = buttons.length / 2; del_num = Math.min(img_num - image_index, del_num) + console.log(del_num, img_num) if (img_num <= del_num){ setTimeout(function(tabname){ gradioApp().getElementById(tabname + '_images_history_renew_page').click(); diff --git a/modules/images_history.py b/modules/images_history.py index ae0b4e40..94bd16a8 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -153,6 +153,7 @@ def delete_image(delete_num, name, filenames, image_index, visible_num): if os.path.exists(name): if visible_num == image_index: new_file_list.append(name) + i += 1 continue print(f"Delete file {name}") os.remove(name) @@ -221,7 +222,7 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): with gr.Column(visible=sorted_flag) as page_panel: with gr.Row(): - #renew_page = gr.Button('Refresh') + renew_page = gr.Button('Refresh page', elem_id=tabname + "_images_history_renew_page") first_page = gr.Button('First Page') prev_page = gr.Button('Prev Page') page_index = gr.Number(value=1, label="Page Index") @@ -231,7 +232,7 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): with gr.Row(elem_id=tabname + "_images_history"): with gr.Column(scale=2): with gr.Row(): - newest = gr.Button('Refresh', elem_id=tabname + "_images_history_start") + newest = gr.Button('Reload', elem_id=tabname + "_images_history_start") date_from = gr.Textbox(label="Date from", interactive=False) date_to = gr.Dropdown(value="start" if not sorted_flag else None, label="Date to") @@ -291,12 +292,14 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): prev_page.click(prev_page_click, inputs=gallery_inputs, outputs=gallery_outputs) end_page.click(end_page_click, inputs=gallery_inputs, outputs=gallery_outputs) page_index.submit(page_index_change, inputs=gallery_inputs, outputs=gallery_outputs) + renew_page.click(page_index_change, inputs=gallery_inputs, outputs=gallery_outputs) first_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") next_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") prev_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") end_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") page_index.submit(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + renew_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") # other funcitons set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, image_index, page_index, filenames], outputs=[img_file_name, image_index, hidden]) From 9324cdaa3199d65c182858785dd1eca42b192b8e Mon Sep 17 00:00:00 2001 From: MalumaDev Date: Sun, 16 Oct 2022 17:53:56 +0200 Subject: [PATCH 0025/1118] ui fix, re organization of the code --- modules/aesthetic_clip.py | 154 +++++++++++++++++++++++++-- modules/img2img.py | 14 ++- modules/processing.py | 29 ++--- modules/sd_hijack.py | 102 +----------------- modules/sd_models.py | 5 +- modules/shared.py | 14 ++- modules/textual_inversion/dataset.py | 2 +- modules/txt2img.py | 18 ++-- modules/ui.py | 50 +++++---- 9 files changed, 232 insertions(+), 156 deletions(-) diff --git a/modules/aesthetic_clip.py b/modules/aesthetic_clip.py index ccb35c73..34efa931 100644 --- a/modules/aesthetic_clip.py +++ b/modules/aesthetic_clip.py @@ -1,3 +1,4 @@ +import copy import itertools import os from pathlib import Path @@ -7,11 +8,12 @@ import gc import gradio as gr import torch from PIL import Image -from modules import shared -from modules.shared import device -from transformers import CLIPModel, CLIPProcessor +from torch import optim -from tqdm.auto import tqdm +from modules import shared +from transformers import CLIPModel, CLIPProcessor, CLIPTokenizer +from tqdm.auto import tqdm, trange +from modules.shared import opts, device def get_all_images_in_folder(folder): @@ -37,12 +39,39 @@ def iter_to_batched(iterable, n=1): yield chunk +def create_ui(): + with gr.Group(): + with gr.Accordion("Open for Clip Aesthetic!", open=False): + with gr.Row(): + aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", + value=0.9) + aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) + + with gr.Row(): + aesthetic_lr = gr.Textbox(label='Aesthetic learning rate', + placeholder="Aesthetic learning rate", value="0.0001") + aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) + aesthetic_imgs = gr.Dropdown(sorted(shared.aesthetic_embeddings.keys()), + label="Aesthetic imgs embedding", + value="None") + + with gr.Row(): + aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', + placeholder="This text is used to rotate the feature space of the imgs embs", + value="") + aesthetic_slerp_angle = gr.Slider(label='Slerp angle', minimum=0, maximum=1, step=0.01, + value=0.1) + aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) + + return aesthetic_weight, aesthetic_steps, aesthetic_lr, aesthetic_slerp, aesthetic_imgs, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative + + def generate_imgs_embd(name, folder, batch_size): # clipModel = CLIPModel.from_pretrained( # shared.sd_model.cond_stage_model.clipModel.name_or_path # ) - model = CLIPModel.from_pretrained(shared.sd_model.cond_stage_model.clipModel.name_or_path).to(device) - processor = CLIPProcessor.from_pretrained(shared.sd_model.cond_stage_model.clipModel.name_or_path) + model = shared.clip_model.to(device) + processor = CLIPProcessor.from_pretrained(model.name_or_path) with torch.no_grad(): embs = [] @@ -63,7 +92,6 @@ def generate_imgs_embd(name, folder, batch_size): torch.save(embs, path) model = model.cpu() - del model del processor del embs gc.collect() @@ -74,4 +102,114 @@ def generate_imgs_embd(name, folder, batch_size): """ shared.update_aesthetic_embeddings() return gr.Dropdown.update(choices=sorted(shared.aesthetic_embeddings.keys()), label="Imgs embedding", - value="None"), res, "" + value="None"), \ + gr.Dropdown.update(choices=sorted(shared.aesthetic_embeddings.keys()), + label="Imgs embedding", + value="None"), res, "" + + +def slerp(low, high, val): + low_norm = low / torch.norm(low, dim=1, keepdim=True) + high_norm = high / torch.norm(high, dim=1, keepdim=True) + omega = torch.acos((low_norm * high_norm).sum(1)) + so = torch.sin(omega) + res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze(1) * high + return res + + +class AestheticCLIP: + def __init__(self): + self.skip = False + self.aesthetic_steps = 0 + self.aesthetic_weight = 0 + self.aesthetic_lr = 0 + self.slerp = False + self.aesthetic_text_negative = "" + self.aesthetic_slerp_angle = 0 + self.aesthetic_imgs_text = "" + + self.image_embs_name = None + self.image_embs = None + self.load_image_embs(None) + + def set_aesthetic_params(self, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, image_embs_name=None, + aesthetic_slerp=True, aesthetic_imgs_text="", + aesthetic_slerp_angle=0.15, + aesthetic_text_negative=False): + self.aesthetic_imgs_text = aesthetic_imgs_text + self.aesthetic_slerp_angle = aesthetic_slerp_angle + self.aesthetic_text_negative = aesthetic_text_negative + self.slerp = aesthetic_slerp + self.aesthetic_lr = aesthetic_lr + self.aesthetic_weight = aesthetic_weight + self.aesthetic_steps = aesthetic_steps + self.load_image_embs(image_embs_name) + + def set_skip(self, skip): + self.skip = skip + + def load_image_embs(self, image_embs_name): + if image_embs_name is None or len(image_embs_name) == 0 or image_embs_name == "None": + image_embs_name = None + self.image_embs_name = None + if image_embs_name is not None and self.image_embs_name != image_embs_name: + self.image_embs_name = image_embs_name + self.image_embs = torch.load(shared.aesthetic_embeddings[self.image_embs_name], map_location=device) + self.image_embs /= self.image_embs.norm(dim=-1, keepdim=True) + self.image_embs.requires_grad_(False) + + def __call__(self, z, remade_batch_tokens): + if not self.skip and self.aesthetic_steps != 0 and self.aesthetic_lr != 0 and self.aesthetic_weight != 0 and self.image_embs_name is not None: + tokenizer = shared.sd_model.cond_stage_model.tokenizer + if not opts.use_old_emphasis_implementation: + remade_batch_tokens = [ + [tokenizer.bos_token_id] + x[:75] + [tokenizer.eos_token_id] for x in + remade_batch_tokens] + + tokens = torch.asarray(remade_batch_tokens).to(device) + + model = copy.deepcopy(shared.clip_model).to(device) + model.requires_grad_(True) + if self.aesthetic_imgs_text is not None and len(self.aesthetic_imgs_text) > 0: + text_embs_2 = model.get_text_features( + **tokenizer([self.aesthetic_imgs_text], padding=True, return_tensors="pt").to(device)) + if self.aesthetic_text_negative: + text_embs_2 = self.image_embs - text_embs_2 + text_embs_2 /= text_embs_2.norm(dim=-1, keepdim=True) + img_embs = slerp(self.image_embs, text_embs_2, self.aesthetic_slerp_angle) + else: + img_embs = self.image_embs + + with torch.enable_grad(): + + # We optimize the model to maximize the similarity + optimizer = optim.Adam( + model.text_model.parameters(), lr=self.aesthetic_lr + ) + + for _ in trange(self.aesthetic_steps, desc="Aesthetic optimization"): + text_embs = model.get_text_features(input_ids=tokens) + text_embs = text_embs / text_embs.norm(dim=-1, keepdim=True) + sim = text_embs @ img_embs.T + loss = -sim + optimizer.zero_grad() + loss.mean().backward() + optimizer.step() + + zn = model.text_model(input_ids=tokens, output_hidden_states=-opts.CLIP_stop_at_last_layers) + if opts.CLIP_stop_at_last_layers > 1: + zn = zn.hidden_states[-opts.CLIP_stop_at_last_layers] + zn = model.text_model.final_layer_norm(zn) + else: + zn = zn.last_hidden_state + model.cpu() + del model + gc.collect() + torch.cuda.empty_cache() + zn = torch.concat([zn[77 * i:77 * (i + 1)] for i in range(max(z.shape[1] // 77, 1))], 1) + if self.slerp: + z = slerp(z, zn, self.aesthetic_weight) + else: + z = z * (1 - self.aesthetic_weight) + zn * self.aesthetic_weight + + return z diff --git a/modules/img2img.py b/modules/img2img.py index 24126774..4ed80c4b 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -56,7 +56,14 @@ def process_batch(p, input_dir, output_dir, args): processed_image.save(os.path.join(output_dir, filename)) -def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_img_inpaint, init_mask_inpaint, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, *args): +def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_img_inpaint, init_mask_inpaint, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, + aesthetic_lr=0, + aesthetic_weight=0, aesthetic_steps=0, + aesthetic_imgs=None, + aesthetic_slerp=False, + aesthetic_imgs_text="", + aesthetic_slerp_angle=0.15, + aesthetic_text_negative=False, *args): is_inpaint = mode == 1 is_batch = mode == 2 @@ -109,6 +116,11 @@ def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, pro inpainting_mask_invert=inpainting_mask_invert, ) + shared.aesthetic_clip.set_aesthetic_params(float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), + aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, + aesthetic_slerp_angle, + aesthetic_text_negative) + if shared.cmd_opts.enable_console_prompts: print(f"\nimg2img: {prompt}", file=shared.progress_print_out) diff --git a/modules/processing.py b/modules/processing.py index 1db26c3e..685f9fcd 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -146,7 +146,8 @@ class Processed: self.prompt = self.prompt if type(self.prompt) != list else self.prompt[0] self.negative_prompt = self.negative_prompt if type(self.negative_prompt) != list else self.negative_prompt[0] self.seed = int(self.seed if type(self.seed) != list else self.seed[0]) if self.seed is not None else -1 - self.subseed = int(self.subseed if type(self.subseed) != list else self.subseed[0]) if self.subseed is not None else -1 + self.subseed = int( + self.subseed if type(self.subseed) != list else self.subseed[0]) if self.subseed is not None else -1 self.all_prompts = all_prompts or [self.prompt] self.all_seeds = all_seeds or [self.seed] @@ -332,16 +333,9 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration return f"{all_prompts[index]}{negative_prompt_text}\n{generation_params_text}".strip() -def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, - aesthetic_imgs=None, aesthetic_slerp=False, aesthetic_imgs_text="", - aesthetic_slerp_angle=0.15, - aesthetic_text_negative=False) -> Processed: +def process_images(p: StableDiffusionProcessing) -> Processed: """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""" - aesthetic_lr = float(aesthetic_lr) - aesthetic_weight = float(aesthetic_weight) - aesthetic_steps = int(aesthetic_steps) - if type(p.prompt) == list: assert (len(p.prompt) > 0) else: @@ -417,16 +411,10 @@ def process_images(p: StableDiffusionProcessing, aesthetic_lr=0, aesthetic_weigh # uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt]) # c = p.sd_model.get_learned_conditioning(prompts) with devices.autocast(): - if hasattr(shared.sd_model.cond_stage_model, "set_aesthetic_params"): - shared.sd_model.cond_stage_model.set_aesthetic_params() + shared.aesthetic_clip.set_skip(True) uc = prompt_parser.get_learned_conditioning(shared.sd_model, len(prompts) * [p.negative_prompt], p.steps) - if hasattr(shared.sd_model.cond_stage_model, "set_aesthetic_params"): - shared.sd_model.cond_stage_model.set_aesthetic_params(aesthetic_lr, aesthetic_weight, - aesthetic_steps, aesthetic_imgs, - aesthetic_slerp, aesthetic_imgs_text, - aesthetic_slerp_angle, - aesthetic_text_negative) + shared.aesthetic_clip.set_skip(False) c = prompt_parser.get_multicond_learned_conditioning(shared.sd_model, prompts, p.steps) if len(model_hijack.comments) > 0: @@ -582,7 +570,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.truncate_x = int(self.firstphase_width - firstphase_width_truncated) // opt_f self.truncate_y = int(self.firstphase_height - firstphase_height_truncated) // opt_f - def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers, self.sampler_index, self.sd_model) @@ -600,10 +587,12 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): seed_resize_from_w=self.seed_resize_from_w, p=self) samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning) - samples = samples[:, :, self.truncate_y//2:samples.shape[2]-self.truncate_y//2, self.truncate_x//2:samples.shape[3]-self.truncate_x//2] + samples = samples[:, :, self.truncate_y // 2:samples.shape[2] - self.truncate_y // 2, + self.truncate_x // 2:samples.shape[3] - self.truncate_x // 2] if opts.use_scale_latent_for_hires_fix: - samples = torch.nn.functional.interpolate(samples, size=(self.height // opt_f, self.width // opt_f), mode="bilinear") + samples = torch.nn.functional.interpolate(samples, size=(self.height // opt_f, self.width // opt_f), + mode="bilinear") else: decoded_samples = decode_first_stage(self.sd_model, samples) lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 5d0590af..227e7670 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -29,8 +29,8 @@ def apply_optimizations(): ldm.modules.diffusionmodules.model.nonlinearity = silu - - if cmd_opts.force_enable_xformers or (cmd_opts.xformers and shared.xformers_available and torch.version.cuda and (6, 0) <= torch.cuda.get_device_capability(shared.device) <= (9, 0)): + if cmd_opts.force_enable_xformers or (cmd_opts.xformers and shared.xformers_available and torch.version.cuda and ( + 6, 0) <= torch.cuda.get_device_capability(shared.device) <= (9, 0)): print("Applying xformers cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.xformers_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward @@ -118,33 +118,14 @@ class StableDiffusionModelHijack: return remade_batch_tokens[0], token_count, get_target_prompt_token_count(token_count) -def slerp(low, high, val): - low_norm = low / torch.norm(low, dim=1, keepdim=True) - high_norm = high / torch.norm(high, dim=1, keepdim=True) - omega = torch.acos((low_norm * high_norm).sum(1)) - so = torch.sin(omega) - res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze(1) * high - return res - - class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): def __init__(self, wrapped, hijack): super().__init__() self.wrapped = wrapped - self.clipModel = CLIPModel.from_pretrained( - self.wrapped.transformer.name_or_path - ) - del self.clipModel.vision_model - self.tokenizer = CLIPTokenizer.from_pretrained(self.wrapped.transformer.name_or_path) - self.hijack: StableDiffusionModelHijack = hijack - self.tokenizer = wrapped.tokenizer - # self.vision = CLIPVisionModel.from_pretrained(self.wrapped.transformer.name_or_path).eval() - self.image_embs_name = None - self.image_embs = None - self.load_image_embs(None) self.token_mults = {} - + self.hijack: StableDiffusionModelHijack = hijack + self.tokenizer = wrapped.tokenizer self.comma_token = [v for k, v in self.tokenizer.get_vocab().items() if k == ','][0] tokens_with_parens = [(k, v) for k, v in self.tokenizer.get_vocab().items() if @@ -164,28 +145,6 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): if mult != 1.0: self.token_mults[ident] = mult - def set_aesthetic_params(self, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, image_embs_name=None, - aesthetic_slerp=True, aesthetic_imgs_text="", - aesthetic_slerp_angle=0.15, - aesthetic_text_negative=False): - self.aesthetic_imgs_text = aesthetic_imgs_text - self.aesthetic_slerp_angle = aesthetic_slerp_angle - self.aesthetic_text_negative = aesthetic_text_negative - self.slerp = aesthetic_slerp - self.aesthetic_lr = aesthetic_lr - self.aesthetic_weight = aesthetic_weight - self.aesthetic_steps = aesthetic_steps - self.load_image_embs(image_embs_name) - - def load_image_embs(self, image_embs_name): - if image_embs_name is None or len(image_embs_name) == 0 or image_embs_name == "None": - image_embs_name = None - if image_embs_name is not None and self.image_embs_name != image_embs_name: - self.image_embs_name = image_embs_name - self.image_embs = torch.load(shared.aesthetic_embeddings[self.image_embs_name], map_location=device) - self.image_embs /= self.image_embs.norm(dim=-1, keepdim=True) - self.image_embs.requires_grad_(False) - def tokenize_line(self, line, used_custom_terms, hijack_comments): id_end = self.wrapped.tokenizer.eos_token_id @@ -391,58 +350,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): z1 = self.process_tokens(tokens, multipliers) z = z1 if z is None else torch.cat((z, z1), axis=-2) - - if self.aesthetic_steps != 0 and self.aesthetic_lr != 0 and self.aesthetic_weight != 0 and self.image_embs_name != None: - if not opts.use_old_emphasis_implementation: - remade_batch_tokens = [ - [self.wrapped.tokenizer.bos_token_id] + x[:75] + [self.wrapped.tokenizer.eos_token_id] for x in - remade_batch_tokens] - - tokens = torch.asarray(remade_batch_tokens).to(device) - - model = copy.deepcopy(self.clipModel).to(device) - model.requires_grad_(True) - if self.aesthetic_imgs_text is not None and len(self.aesthetic_imgs_text) > 0: - text_embs_2 = model.get_text_features( - **self.tokenizer([self.aesthetic_imgs_text], padding=True, return_tensors="pt").to(device)) - if self.aesthetic_text_negative: - text_embs_2 = self.image_embs - text_embs_2 - text_embs_2 /= text_embs_2.norm(dim=-1, keepdim=True) - img_embs = slerp(self.image_embs, text_embs_2, self.aesthetic_slerp_angle) - else: - img_embs = self.image_embs - - with torch.enable_grad(): - - # We optimize the model to maximize the similarity - optimizer = optim.Adam( - model.text_model.parameters(), lr=self.aesthetic_lr - ) - - for i in trange(self.aesthetic_steps, desc="Aesthetic optimization"): - text_embs = model.get_text_features(input_ids=tokens) - text_embs = text_embs / text_embs.norm(dim=-1, keepdim=True) - sim = text_embs @ img_embs.T - loss = -sim - optimizer.zero_grad() - loss.mean().backward() - optimizer.step() - - zn = model.text_model(input_ids=tokens, output_hidden_states=-opts.CLIP_stop_at_last_layers) - if opts.CLIP_stop_at_last_layers > 1: - zn = zn.hidden_states[-opts.CLIP_stop_at_last_layers] - zn = model.text_model.final_layer_norm(zn) - else: - zn = zn.last_hidden_state - model.cpu() - del model - - zn = torch.concat([zn for i in range(z.shape[1] // 77)], 1) - if self.slerp: - z = slerp(z, zn, self.aesthetic_weight) - else: - z = z * (1 - self.aesthetic_weight) + zn * self.aesthetic_weight - + z = shared.aesthetic_clip(z, remade_batch_tokens) remade_batch_tokens = rem_tokens batch_multipliers = rem_multipliers i += 1 diff --git a/modules/sd_models.py b/modules/sd_models.py index 3aa21ec1..8e4ee435 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -20,7 +20,7 @@ checkpoints_loaded = collections.OrderedDict() try: # this silences the annoying "Some weights of the model checkpoint were not used when initializing..." message at start. - from transformers import logging + from transformers import logging, CLIPModel logging.set_verbosity_error() except Exception: @@ -196,6 +196,9 @@ def load_model(): sd_hijack.model_hijack.hijack(sd_model) + if shared.clip_model is None or shared.clip_model.transformer.name_or_path != sd_model.cond_stage_model.wrapped.transformer.name_or_path: + shared.clip_model = CLIPModel.from_pretrained(sd_model.cond_stage_model.wrapped.transformer.name_or_path) + sd_model.eval() print(f"Model loaded.") diff --git a/modules/shared.py b/modules/shared.py index e2c98b2d..e19ca779 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -3,6 +3,7 @@ import datetime import json import os import sys +from collections import OrderedDict import gradio as gr import tqdm @@ -94,15 +95,15 @@ os.makedirs(cmd_opts.hypernetwork_dir, exist_ok=True) hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir) loaded_hypernetwork = None -aesthetic_embeddings = {f.replace(".pt",""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in - os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} -aesthetic_embeddings = aesthetic_embeddings | {"None": None} +aesthetic_embeddings = {} def update_aesthetic_embeddings(): global aesthetic_embeddings aesthetic_embeddings = {f.replace(".pt",""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} - aesthetic_embeddings = aesthetic_embeddings | {"None": None} + aesthetic_embeddings = OrderedDict(**{"None": None}, **aesthetic_embeddings) + +update_aesthetic_embeddings() def reload_hypernetworks(): global hypernetworks @@ -381,6 +382,11 @@ sd_upscalers = [] sd_model = None +clip_model = None + +from modules.aesthetic_clip import AestheticCLIP +aesthetic_clip = AestheticCLIP() + progress_print_out = sys.stdout diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 68ceffe3..23bb4b6a 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -49,7 +49,7 @@ class PersonalizedBase(Dataset): print("Preparing dataset...") for path in tqdm.tqdm(self.image_paths): try: - image = Image.open(path).convert('RGB').resize((self.width, self.height), PIL.Image.Resampling.BICUBIC) + image = Image.open(path).convert('RGB').resize((self.width, self.height), PIL.Image.BICUBIC) except Exception: continue diff --git a/modules/txt2img.py b/modules/txt2img.py index 8f394d05..6cbc50fc 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -1,12 +1,17 @@ import modules.scripts -from modules.processing import StableDiffusionProcessing, Processed, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images +from modules.processing import StableDiffusionProcessing, Processed, StableDiffusionProcessingTxt2Img, \ + StableDiffusionProcessingImg2Img, process_images from modules.shared import opts, cmd_opts import modules.shared as shared import modules.processing as processing from modules.ui import plaintext_to_html -def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, firstphase_width: int, firstphase_height: int,aesthetic_lr=0, +def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, + restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, + subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, + height: int, width: int, enable_hr: bool, denoising_strength: float, firstphase_width: int, + firstphase_height: int, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, aesthetic_imgs=None, aesthetic_slerp=False, @@ -41,15 +46,17 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: firstphase_height=firstphase_height if enable_hr else None, ) + shared.aesthetic_clip.set_aesthetic_params(float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), + aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, aesthetic_slerp_angle, + aesthetic_text_negative) + if cmd_opts.enable_console_prompts: print(f"\ntxt2img: {prompt}", file=shared.progress_print_out) processed = modules.scripts.scripts_txt2img.run(p, *args) if processed is None: - processed = process_images(p, aesthetic_lr, aesthetic_weight, aesthetic_steps, aesthetic_imgs, aesthetic_slerp,aesthetic_imgs_text, - aesthetic_slerp_angle, - aesthetic_text_negative) + processed = process_images(p) shared.total_tqdm.clear() @@ -61,4 +68,3 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: processed.images = [] return processed.images, generation_info_js, plaintext_to_html(processed.info) - diff --git a/modules/ui.py b/modules/ui.py index 4069f0d2..0e5d73f0 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -43,7 +43,7 @@ from modules.images import save_image import modules.textual_inversion.ui import modules.hypernetworks.ui -import modules.aesthetic_clip +import modules.aesthetic_clip as aesthetic_clip import modules.images_history as img_his @@ -593,23 +593,25 @@ def create_ui(wrap_gradio_gpu_call): width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) - with gr.Group(): - with gr.Accordion("Open for Clip Aesthetic!",open=False): - with gr.Row(): - aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.9) - aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) + # with gr.Group(): + # with gr.Accordion("Open for Clip Aesthetic!",open=False): + # with gr.Row(): + # aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", value=0.9) + # aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) + # + # with gr.Row(): + # aesthetic_lr = gr.Textbox(label='Aesthetic learning rate', placeholder="Aesthetic learning rate", value="0.0001") + # aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) + # aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), + # label="Aesthetic imgs embedding", + # value="None") + # + # with gr.Row(): + # aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', placeholder="This text is used to rotate the feature space of the imgs embs", value="") + # aesthetic_slerp_angle = gr.Slider(label='Slerp angle',minimum=0, maximum=1, step=0.01, value=0.1) + # aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) - with gr.Row(): - aesthetic_lr = gr.Textbox(label='Aesthetic learning rate', placeholder="Aesthetic learning rate", value="0.0001") - aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) - aesthetic_imgs = gr.Dropdown(sorted(aesthetic_embeddings.keys()), - label="Aesthetic imgs embedding", - value="None") - - with gr.Row(): - aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', placeholder="This text is used to rotate the feature space of the imgs embs", value="") - aesthetic_slerp_angle = gr.Slider(label='Slerp angle',minimum=0, maximum=1, step=0.01, value=0.1) - aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) + aesthetic_weight, aesthetic_steps, aesthetic_lr, aesthetic_slerp, aesthetic_imgs, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative = aesthetic_clip.create_ui() with gr.Row(): @@ -840,6 +842,9 @@ def create_ui(wrap_gradio_gpu_call): width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) + aesthetic_weight_im, aesthetic_steps_im, aesthetic_lr_im, aesthetic_slerp_im, aesthetic_imgs_im, aesthetic_imgs_text_im, aesthetic_slerp_angle_im, aesthetic_text_negative_im = aesthetic_clip.create_ui() + + with gr.Row(): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1) tiling = gr.Checkbox(label='Tiling', value=False) @@ -944,6 +949,14 @@ def create_ui(wrap_gradio_gpu_call): inpainting_mask_invert, img2img_batch_input_dir, img2img_batch_output_dir, + aesthetic_lr_im, + aesthetic_weight_im, + aesthetic_steps_im, + aesthetic_imgs_im, + aesthetic_slerp_im, + aesthetic_imgs_text_im, + aesthetic_slerp_angle_im, + aesthetic_text_negative_im, ] + custom_inputs, outputs=[ img2img_gallery, @@ -1283,7 +1296,7 @@ def create_ui(wrap_gradio_gpu_call): ) create_embedding_ae.click( - fn=modules.aesthetic_clip.generate_imgs_embd, + fn=aesthetic_clip.generate_imgs_embd, inputs=[ new_embedding_name_ae, process_src_ae, @@ -1291,6 +1304,7 @@ def create_ui(wrap_gradio_gpu_call): ], outputs=[ aesthetic_imgs, + aesthetic_imgs_im, ti_output, ti_outcome, ] From 9d702b16f01795c3af900e0ebd70faf4b25200f6 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 17 Oct 2022 16:11:03 +0800 Subject: [PATCH 0026/1118] fix two little bug --- modules/images_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/images_history.py b/modules/images_history.py index 23045df1..1ae168ca 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -133,7 +133,7 @@ def archive_images(dir_name, date_to): date = sort_array[loads_num][2] filenames = [x[1] for x in sort_array] else: - date = sort_array[loads_num][2] + date = sort_array[-1][2] filenames = [x[1] for x in sort_array] filenames = [x[1] for x in sort_array if x[2]>= date] _, image_list, _, visible_num = get_recent_images(1, 0, filenames) @@ -334,6 +334,6 @@ def create_history_tabs(gr, sys_opts, run_pnginfo, switch_dict): with gr.Tab(tab): with gr.Blocks(analytics_enabled=False) as images_history_img2img: show_images_history(gr, opts, tab, run_pnginfo, switch_dict) - gradio.Checkbox(opts.images_history_reconstruct_directory, elem_id="images_history_reconstruct_directory") #, visible=False) + gradio.Checkbox(opts.images_history_reconstruct_directory, elem_id="images_history_reconstruct_directory", visible=False) return images_history From c408a0b41cfffde184cad35b2d97346342947d83 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 17 Oct 2022 22:28:43 +0800 Subject: [PATCH 0027/1118] fix two bug --- launch.py | 1 - modules/images_history.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/launch.py b/launch.py index 7520cfee..088eada1 100644 --- a/launch.py +++ b/launch.py @@ -11,7 +11,6 @@ python = sys.executable git = os.environ.get('GIT', "git") index_url = os.environ.get('INDEX_URL', "") - def extract_arg(args, name): return [x for x in args if x != name], name in args diff --git a/modules/images_history.py b/modules/images_history.py index 1ae168ca..10e5b970 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -181,7 +181,8 @@ def delete_image(delete_num, name, filenames, image_index, visible_num): return new_file_list, 1, visible_num def save_image(file_name): - shutil.copy2(file_name, opts.outdir_save) + if file_name is not None and os.path.exists(file_name): + shutil.copy2(file_name, opts.outdir_save) def get_recent_images(page_index, step, filenames): page_index = int(page_index) @@ -327,7 +328,6 @@ def create_history_tabs(gr, sys_opts, run_pnginfo, switch_dict): opts = sys_opts loads_files_num = int(opts.images_history_num_per_page) num_of_imgs_per_page = int(opts.images_history_num_per_page * opts.images_history_pages_num) - backup_flag = opts.images_history_backup with gr.Blocks(analytics_enabled=False) as images_history: with gr.Tabs() as tabs: for tab in ["txt2img", "img2img", "extras", "saved"]: From de179cf8fd8191e1a6d288e7c29a16f53da1be88 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 17 Oct 2022 22:38:46 +0800 Subject: [PATCH 0028/1118] fix two bug --- launch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/launch.py b/launch.py index 088eada1..7520cfee 100644 --- a/launch.py +++ b/launch.py @@ -11,6 +11,7 @@ python = sys.executable git = os.environ.get('GIT', "git") index_url = os.environ.get('INDEX_URL', "") + def extract_arg(args, name): return [x for x in args if x != name], name in args From 2272cf2f35fafd5cd486bfb4ee89df5bbc625b97 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 17 Oct 2022 23:04:42 +0800 Subject: [PATCH 0029/1118] fix two bug --- modules/images_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images_history.py b/modules/images_history.py index 10e5b970..1c1790a4 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -133,7 +133,7 @@ def archive_images(dir_name, date_to): date = sort_array[loads_num][2] filenames = [x[1] for x in sort_array] else: - date = sort_array[-1][2] + date = None if len(sort_array) == 0 else sort_array[-1][2] filenames = [x[1] for x in sort_array] filenames = [x[1] for x in sort_array if x[2]>= date] _, image_list, _, visible_num = get_recent_images(1, 0, filenames) From 2b5b62e768d892773a7ec1d5e8d8cea23aae1254 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 17 Oct 2022 23:14:03 +0800 Subject: [PATCH 0030/1118] fix two bug --- modules/images_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/images_history.py b/modules/images_history.py index 1c1790a4..20324557 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -44,7 +44,7 @@ def traverse_all_files(curr_path, image_list, all_type=False): return image_list for file in f_list: file = os.path.join(curr_path, file) - if (not all_type) and file[-4:] == ".txt": + if (not all_type) and (file[-4:] == ".txt" or file[-4:] == ".csv"): pass elif os.path.isfile(file) and file[-10:].rfind(".") > 0: image_list.append(file) @@ -182,7 +182,7 @@ def delete_image(delete_num, name, filenames, image_index, visible_num): def save_image(file_name): if file_name is not None and os.path.exists(file_name): - shutil.copy2(file_name, opts.outdir_save) + shutil.copy(file_name, opts.outdir_save) def get_recent_images(page_index, step, filenames): page_index = int(page_index) From ca023f8a459717be19d333513a0e388cf3944e74 Mon Sep 17 00:00:00 2001 From: ClashSAN <98228077+ClashSAN@users.noreply.github.com> Date: Tue, 18 Oct 2022 08:57:05 +0000 Subject: [PATCH 0031/1118] Update README.md --- README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 859a91b6..a89593bf 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - One click install and run script (but you still must install python and git) - Outpainting - Inpainting +- Color Sketch - Prompt Matrix - Stable Diffusion Upscale - Attention, specify parts of text that the model should pay more attention to @@ -37,14 +38,14 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - Interrupt processing at any time - 4GB video card support (also reports of 2GB working) - Correct seeds for batches -- Prompt length validation - - get length of prompt in tokens as you type - - get a warning after generation if some text was truncated +- Live prompt token length validation - Generation parameters - parameters you used to generate images are saved with that image - in PNG chunks for PNG, in EXIF for JPEG - can drag the image to PNG info tab to restore generation parameters and automatically copy them into UI - can be disabled in settings + - drag and drop an image/text-parameters to promptbox +- Read Generation Parameters Button, loads parameters in promptbox to UI - Settings page - Running arbitrary python code from UI (must run with --allow-code to enable) - Mouseover hints for most UI elements @@ -62,7 +63,7 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - Img2img Alternative - Highres Fix, a convenience option to produce high resolution pictures in one click without usual distortions - Reloading checkpoints on the fly -- Checkpoint Merger, a tab that allows you to merge two checkpoints into one +- Checkpoint Merger, a tab that allows you to merge up to 3 checkpoints into one - [Custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Scripts) with many extensions from community - [Composable-Diffusion](https://energy-based-model.github.io/Compositional-Visual-Generation-with-Composable-Diffusion-Models/), a way to use multiple prompts at once - separate prompts using uppercase `AND` @@ -70,14 +71,19 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - No token limit for prompts (original stable diffusion lets you use up to 75 tokens) - DeepDanbooru integration, creates danbooru style tags for anime prompts (add --deepdanbooru to commandline args) - [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add --xformers to commandline args) +- History tab: view, direct and delete images conveniently within the UI +- Generate forever option +- Training Tab +- Preprocessing Image Datasets: cropping, mirroring, autotagging using BLIP or deepdanbooru (for anime) + + ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. -Alternatively, use Google Colab: +Alternatively, use online services(like Google Colab): -- [Colab, maintained by Akaibu](https://colab.research.google.com/drive/1kw3egmSn-KgWsikYvOMjJkVDsPLjEMzl) -- [Colab, original by me, outdated](https://colab.research.google.com/drive/1Iy-xW9t1-OQWhb0hNxueGij8phCyluOh). +- [List of Online Services](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Online-Services) ### Automatic Installation on Windows 1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH" From 7651b84968f66dd0a5c3346520aad8dac6c4464e Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 19:07:17 +0900 Subject: [PATCH 0032/1118] Initial KR support - WIP Localization WIP --- ko-KR.json | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 ko-KR.json diff --git a/ko-KR.json b/ko-KR.json new file mode 100644 index 00000000..f93b3e16 --- /dev/null +++ b/ko-KR.json @@ -0,0 +1,76 @@ +{ + "txt2img": "텍스트→이미지", + "img2img": "이미지→이미지", + "Extras": "부가기능", + "PNG Info": "PNG 정보", + "History": "기록", + "Checkpoint Merger": "체크포인트 병합", + "Train": "훈련", + "Settings": "설정", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Hypernetwork": "하이퍼네트워크", + "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", + "Generate": "생성", + "Style 1": "스타일 1", + "Style 2": "스타일 2", + "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", + "Save style": "스타일 저장", + "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", + "Do not do anything special": "아무것도 하지 않기", + "Generate forever": "반복 생성", + "Cancel generate forever": "반복 생성 취소", + "Interrupt": "중단", + "Skip": "건너뛰기", + "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", + "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", + "Prompt": "프롬프트", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Negative prompt": "네거티브 프롬프트", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Sampling Steps": "샘플링 스텝 수", + "Sampling method": "샘플링 방법", + "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", + "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Width": "가로", + "Height": "세로", + "Restore faces": "얼굴 보정", + "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", + "Tiling": "타일링", + "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Highres. fix": "고해상도 보정", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Firstpass width": "초기 가로길이", + "Firstpass height": "초기 세로길이", + "Denoising strength": "디노이즈 강도", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Batch count": "배치 수", + "Batch size": "배치 크기", + "How many batches of images to create": "생성할 이미지 배치 수", + "How many image to create in a single batch": "한 배치당 이미지 수", + "CFG Scale": "CFG 스케일", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Seed": "시드", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", + "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", + "Extra": "고급", + "Variation seed": "바리에이션 시드", + "Variation strength": "바리에이션 강도", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", + "Resize seed from height": "시드 리사이징 가로길이", + "Resize seed from width": "시드 리사이징 세로길이", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Script": "스크립트", + "Save": "저장", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "Send to img2img": "이미지→이미지로 전송", + "Send to inpaint": "인페인트로 전송", + "Send to extras": "부가기능으로 전송", + "Open images output directory": "이미지 저장 경로 열기", + "Make Zip when Save?": "저장 시 Zip 생성하기", + "Always save all generated images": "생성된 이미지 항상 저장하기" +} \ No newline at end of file From 50e34cf194b3e3085bc99aeea4dbfd7758dc79c8 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 20:11:17 +0900 Subject: [PATCH 0033/1118] Update ko-KR.json --- localizations/ko-KR.json | 85 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 localizations/ko-KR.json diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json new file mode 100644 index 00000000..a4367dc5 --- /dev/null +++ b/localizations/ko-KR.json @@ -0,0 +1,85 @@ +{ + "⤡": "⤡", + "⊞": "⊞", + "×": "×", + "❮": "❮", + "❯": "❯", + "Loading...": "로딩중...", + "view": "", + "api": "api", + "•": "•", + "txt2img": "텍스트→이미지", + "img2img": "이미지→이미지", + "Extras": "부가기능", + "PNG Info": "PNG 정보", + "History": "기록", + "Checkpoint Merger": "체크포인트 병합", + "Train": "훈련", + "Settings": "설정", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Hypernetwork": "하이퍼네트워크", + "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", + "Generate": "생성", + "Style 1": "스타일 1", + "Style 2": "스타일 2", + "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", + "Save style": "스타일 저장", + "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", + "Do not do anything special": "아무것도 하지 않기", + "Generate forever": "반복 생성", + "Cancel generate forever": "반복 생성 취소", + "Interrupt": "중단", + "Skip": "건너뛰기", + "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", + "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", + "Prompt": "프롬프트", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Negative prompt": "네거티브 프롬프트", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Sampling Steps": "샘플링 스텝 수", + "Sampling method": "샘플링 방법", + "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", + "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Width": "가로", + "Height": "세로", + "Restore faces": "얼굴 보정", + "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", + "Tiling": "타일링", + "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Highres. fix": "고해상도 보정", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Firstpass width": "초기 가로길이", + "Firstpass height": "초기 세로길이", + "Denoising strength": "디노이즈 강도", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Batch count": "배치 수", + "Batch size": "배치 크기", + "How many batches of images to create": "생성할 이미지 배치 수", + "How many image to create in a single batch": "한 배치당 이미지 수", + "CFG Scale": "CFG 스케일", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Seed": "시드", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", + "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", + "Extra": "고급", + "Variation seed": "바리에이션 시드", + "Variation strength": "바리에이션 강도", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", + "Resize seed from height": "시드 리사이징 가로길이", + "Resize seed from width": "시드 리사이징 세로길이", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Script": "스크립트", + "Save": "저장", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "Send to img2img": "이미지→이미지로 전송", + "Send to inpaint": "인페인트로 전송", + "Send to extras": "부가기능으로 전송", + "Open images output directory": "이미지 저장 경로 열기", + "Make Zip when Save?": "저장 시 Zip 생성하기", + "Always save all generated images": "생성된 이미지 항상 저장하기" +} \ No newline at end of file From 0530f07da3c77ed4bfa02f37de5c84562a37f470 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 20:12:54 +0900 Subject: [PATCH 0034/1118] Move ko-KR.json --- ko-KR.json | 76 ------------------------------------------------------ 1 file changed, 76 deletions(-) delete mode 100644 ko-KR.json diff --git a/ko-KR.json b/ko-KR.json deleted file mode 100644 index f93b3e16..00000000 --- a/ko-KR.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "txt2img": "텍스트→이미지", - "img2img": "이미지→이미지", - "Extras": "부가기능", - "PNG Info": "PNG 정보", - "History": "기록", - "Checkpoint Merger": "체크포인트 병합", - "Train": "훈련", - "Settings": "설정", - "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", - "Hypernetwork": "하이퍼네트워크", - "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", - "Generate": "생성", - "Style 1": "스타일 1", - "Style 2": "스타일 2", - "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", - "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", - "Save style": "스타일 저장", - "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", - "Do not do anything special": "아무것도 하지 않기", - "Generate forever": "반복 생성", - "Cancel generate forever": "반복 생성 취소", - "Interrupt": "중단", - "Skip": "건너뛰기", - "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", - "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", - "Prompt": "프롬프트", - "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Negative prompt": "네거티브 프롬프트", - "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Sampling Steps": "샘플링 스텝 수", - "Sampling method": "샘플링 방법", - "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", - "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", - "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", - "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", - "Width": "가로", - "Height": "세로", - "Restore faces": "얼굴 보정", - "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", - "Tiling": "타일링", - "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", - "Highres. fix": "고해상도 보정", - "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", - "Firstpass width": "초기 가로길이", - "Firstpass height": "초기 세로길이", - "Denoising strength": "디노이즈 강도", - "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", - "Batch count": "배치 수", - "Batch size": "배치 크기", - "How many batches of images to create": "생성할 이미지 배치 수", - "How many image to create in a single batch": "한 배치당 이미지 수", - "CFG Scale": "CFG 스케일", - "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", - "Seed": "시드", - "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", - "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", - "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", - "Extra": "고급", - "Variation seed": "바리에이션 시드", - "Variation strength": "바리에이션 강도", - "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", - "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", - "Resize seed from height": "시드 리사이징 가로길이", - "Resize seed from width": "시드 리사이징 세로길이", - "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", - "Script": "스크립트", - "Save": "저장", - "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", - "Send to img2img": "이미지→이미지로 전송", - "Send to inpaint": "인페인트로 전송", - "Send to extras": "부가기능으로 전송", - "Open images output directory": "이미지 저장 경로 열기", - "Make Zip when Save?": "저장 시 Zip 생성하기", - "Always save all generated images": "생성된 이미지 항상 저장하기" -} \ No newline at end of file From eb299527b1e5d1f83a14641647fca72e8fb305ac Mon Sep 17 00:00:00 2001 From: yfszzx Date: Tue, 18 Oct 2022 20:14:11 +0800 Subject: [PATCH 0035/1118] Image browser --- javascript/images_history.js | 19 ++- modules/images_history.py | 223 +++++++++++++++++++++++------------ modules/shared.py | 7 +- modules/ui.py | 2 +- uitest.bat | 2 + uitest.py | 124 +++++++++++++++++++ 6 files changed, 287 insertions(+), 90 deletions(-) create mode 100644 uitest.bat create mode 100644 uitest.py diff --git a/javascript/images_history.js b/javascript/images_history.js index 3c028bc6..182d730b 100644 --- a/javascript/images_history.js +++ b/javascript/images_history.js @@ -145,9 +145,10 @@ function images_history_enable_del_buttons(){ } function images_history_init(){ - var loaded = gradioApp().getElementById("images_history_reconstruct_directory") - if (loaded){ - var init_status = loaded.querySelector("input").checked + // var loaded = gradioApp().getElementById("images_history_reconstruct_directory") + // if (loaded){ + // var init_status = loaded.querySelector("input").checked + if (gradioApp().getElementById("images_history_finish_render")){ for (var i in images_history_tab_list ){ tab = images_history_tab_list[i]; gradioApp().getElementById(tab + '_images_history').classList.add("images_history_cantainor"); @@ -163,19 +164,17 @@ function images_history_init(){ for (var i in images_history_tab_list){ var tabname = images_history_tab_list[i] tab_btns[i].setAttribute("tabname", tabname); - if (init_status){ - tab_btns[i].addEventListener('click', images_history_click_tab); - } - } - if (init_status){ - tab_btns[0].click(); + // if (!init_status){ + // tab_btns[i].addEventListener('click', images_history_click_tab); + // } + tab_btns[i].addEventListener('click', images_history_click_tab); } } else { setTimeout(images_history_init, 500); } } -var images_history_tab_list = ["txt2img", "img2img", "extras", "saved"]; +var images_history_tab_list = ["custom", "txt2img", "img2img", "extras", "saved"]; setTimeout(images_history_init, 500); document.addEventListener("DOMContentLoaded", function() { var mutationObserver = new MutationObserver(function(m){ diff --git a/modules/images_history.py b/modules/images_history.py index 20324557..d56f3a25 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -4,6 +4,7 @@ import time import hashlib import gradio system_bak_path = "webui_log_and_bak" +browser_tabname = "custom" def is_valid_date(date): try: time.strptime(date, "%Y%m%d") @@ -99,13 +100,15 @@ def auto_sorting(dir_name): date_list.append(today) return sorted(date_list, reverse=True) -def archive_images(dir_name, date_to): +def archive_images(dir_name, date_to): + filenames = [] loads_num =int(opts.images_history_num_per_page * opts.images_history_pages_num) + today = time.strftime("%Y%m%d",time.localtime(time.time())) + date_to = today if date_to is None or date_to == "" else date_to + date_to_bak = date_to if opts.images_history_reconstruct_directory: - date_list = auto_sorting(dir_name) - today = time.strftime("%Y%m%d",time.localtime(time.time())) - date_to = today if date_to is None or date_to == "" else date_to + date_list = auto_sorting(dir_name) for date in date_list: if date <= date_to: path = os.path.join(dir_name, date) @@ -120,7 +123,7 @@ def archive_images(dir_name, date_to): tmparray = [(os.path.getmtime(file), file) for file in filenames ] date_stamp = time.mktime(time.strptime(date_to, "%Y%m%d")) + 86400 filenames = [] - date_list = {} + date_list = {date_to:None} date = time.strftime("%Y%m%d",time.localtime(time.time())) for t, f in tmparray: date = time.strftime("%Y%m%d",time.localtime(t)) @@ -133,22 +136,29 @@ def archive_images(dir_name, date_to): date = sort_array[loads_num][2] filenames = [x[1] for x in sort_array] else: - date = None if len(sort_array) == 0 else sort_array[-1][2] + date = date_to if len(sort_array) == 0 else sort_array[-1][2] filenames = [x[1] for x in sort_array] - filenames = [x[1] for x in sort_array if x[2]>= date] - _, image_list, _, visible_num = get_recent_images(1, 0, filenames) + filenames = [x[1] for x in sort_array if x[2]>= date] + num = len(filenames) + last_date_from = date_to_bak if num == 0 else time.strftime("%Y%m%d", time.localtime(time.mktime(time.strptime(date, "%Y%m%d")) - 1000)) + date = date[:4] + "-" + date[4:6] + "-" + date[6:8] + date_to_bak = date_to_bak[:4] + "-" + date_to_bak[4:6] + "-" + date_to_bak[6:8] + load_info = f"Loaded {(num + 1) // opts.images_history_pages_num} pades, {num} images, during {date} - {date_to_bak}" + _, image_list, _, _, visible_num = get_recent_images(1, 0, filenames) return ( gradio.Dropdown.update(choices=date_list, value=date_to), - date, + load_info, filenames, 1, image_list, "", - visible_num + "", + visible_num, + last_date_from ) -def newest_click(dir_name, date_to): - return archive_images(dir_name, time.strftime("%Y%m%d",time.localtime(time.time()))) + + def delete_image(delete_num, name, filenames, image_index, visible_num): if name == "": @@ -196,7 +206,29 @@ def get_recent_images(page_index, step, filenames): length = len(filenames) visible_num = num_of_imgs_per_page if idx_frm + num_of_imgs_per_page <= length else length % num_of_imgs_per_page visible_num = num_of_imgs_per_page if visible_num == 0 else visible_num - return page_index, image_list, "", visible_num + return page_index, image_list, "", "", visible_num + +def newest_click(date_to): + if date_to is None: + return time.strftime("%Y%m%d",time.localtime(time.time())), [] + else: + return None, [] +def forward_click(last_date_from, date_to_recorder): + if len(date_to_recorder) == 0: + return None, [] + if last_date_from == date_to_recorder[-1]: + date_to_recorder = date_to_recorder[:-1] + if len(date_to_recorder) == 0: + return None, [] + return date_to_recorder[-1], date_to_recorder[:-1] + +def backward_click(last_date_from, date_to_recorder): + if last_date_from is None or last_date_from == "": + return time.strftime("%Y%m%d",time.localtime(time.time())), [] + if len(date_to_recorder) == 0 or last_date_from != date_to_recorder[-1]: + date_to_recorder.append(last_date_from) + return last_date_from, date_to_recorder + def first_page_click(page_index, filenames): return get_recent_images(1, 0, filenames) @@ -214,13 +246,33 @@ def page_index_change(page_index, filenames): return get_recent_images(page_index, 0, filenames) def show_image_info(tabname_box, num, page_index, filenames): - file = filenames[int(num) + int((page_index - 1) * int(opts.images_history_num_per_page))] - return file, num, file + file = filenames[int(num) + int((page_index - 1) * int(opts.images_history_num_per_page))] + tm = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(file))) + return file, tm, num, file def enable_page_buttons(): return gradio.update(visible=True) +def change_dir(img_dir, date_to): + warning = None + try: + if os.path.exists(img_dir): + try: + f = os.listdir(img_dir) + except: + warning = f"'{img_dir} is not a directory" + else: + warning = "The directory is not exist" + except: + warning = "The format of the directory is incorrect" + if warning is None: + today = time.strftime("%Y%m%d",time.localtime(time.time())) + return gradio.update(visible=False), gradio.update(visible=True), None, None if date_to != today else today + else: + return gradio.update(visible=True), gradio.update(visible=False), warning, date_to + def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): + custom_dir = False if tabname == "txt2img": dir_name = opts.outdir_txt2img_samples elif tabname == "img2img": @@ -229,69 +281,85 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): dir_name = opts.outdir_extras_samples elif tabname == "saved": dir_name = opts.outdir_save + else: + custom_dir = True + dir_name = None - d = dir_name.split("/") - dir_name = d[0] - for p in d[1:]: - dir_name = os.path.join(dir_name, p) - if not os.path.exists(dir_name): - os.makedirs(dir_name) + if not custom_dir: + d = dir_name.split("/") + dir_name = d[0] + for p in d[1:]: + dir_name = os.path.join(dir_name, p) + if not os.path.exists(dir_name): + os.makedirs(dir_name) - with gr.Column() as page_panel: - with gr.Row(visible=False) as turn_page_buttons: - renew_page = gr.Button('Refresh page', elem_id=tabname + "_images_history_renew_page") - first_page = gr.Button('First Page') - prev_page = gr.Button('Prev Page') - page_index = gr.Number(value=1, label="Page Index") - next_page = gr.Button('Next Page') - end_page = gr.Button('End Page') + with gr.Column() as page_panel: + with gr.Row(): + img_path = gr.Textbox(dir_name, label="Images directory", placeholder="Input images directory") + with gr.Row(visible=False) as warning: + warning_box = gr.Textbox("Message", interactive=False) - with gr.Row(elem_id=tabname + "_images_history"): - with gr.Column(scale=2): - with gr.Row(): - newest = gr.Button('Reload', elem_id=tabname + "_images_history_start") - date_from = gr.Textbox(label="Date from", interactive=False) - date_to = gr.Dropdown(label="Date to") - - history_gallery = gr.Gallery(show_label=False, elem_id=tabname + "_images_history_gallery").style(grid=6) - with gr.Row(): - delete_num = gr.Number(value=1, interactive=True, label="number of images to delete consecutively next") - delete = gr.Button('Delete', elem_id=tabname + "_images_history_del_button") - - with gr.Column(): - with gr.Row(): - if tabname != "saved": - save_btn = gr.Button('Save') - pnginfo_send_to_txt2img = gr.Button('Send to txt2img') - pnginfo_send_to_img2img = gr.Button('Send to img2img') - with gr.Row(): - with gr.Column(): - img_file_info = gr.Textbox(label="Generate Info", interactive=False) - img_file_name = gr.Textbox(value="", label="File Name", interactive=False) + with gr.Row(visible=not custom_dir, elem_id=tabname + "_images_history") as main_panel: + with gr.Column(scale=2): + with gr.Row(): + backward = gr.Button('Backward') + date_to = gr.Dropdown(label="Date to") + forward = gr.Button('Forward') + newest = gr.Button('Reload', elem_id=tabname + "_images_history_start") + with gr.Row(): + load_info = gr.Textbox(show_label=False, interactive=False) + with gr.Row(visible=False) as turn_page_buttons: + renew_page = gr.Button('Refresh page', elem_id=tabname + "_images_history_renew_page") + first_page = gr.Button('First Page') + prev_page = gr.Button('Prev Page') + page_index = gr.Number(value=1, label="Page Index") + next_page = gr.Button('Next Page') + end_page = gr.Button('End Page') + history_gallery = gr.Gallery(show_label=False, elem_id=tabname + "_images_history_gallery").style(grid=opts.images_history_grid_num) + with gr.Row(): + delete_num = gr.Number(value=1, interactive=True, label="number of images to delete consecutively next") + delete = gr.Button('Delete', elem_id=tabname + "_images_history_del_button") - # hiden items - with gr.Row(visible=False): - visible_img_num = gr.Number() - img_path = gr.Textbox(dir_name) - tabname_box = gr.Textbox(tabname) - image_index = gr.Textbox(value=-1) - set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index") - filenames = gr.State() - all_images_list = gr.State() - hidden = gr.Image(type="pil") - info1 = gr.Textbox() - info2 = gr.Textbox() + with gr.Column(): + with gr.Row(): + if tabname != "saved": + save_btn = gr.Button('Save') + pnginfo_send_to_txt2img = gr.Button('Send to txt2img') + pnginfo_send_to_img2img = gr.Button('Send to img2img') + with gr.Row(): + with gr.Column(): + img_file_info = gr.Textbox(label="Generate Info", interactive=False) + img_file_name = gr.Textbox(value="", label="File Name", interactive=False) + img_file_time= gr.Textbox(value="", label="Create Time", interactive=False) - + + # hiden items + with gr.Row(): #visible=False): + visible_img_num = gr.Number() + date_to_recorder = gr.State([]) + last_date_from = gr.Textbox() + tabname_box = gr.Textbox(tabname) + image_index = gr.Textbox(value=-1) + set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index") + filenames = gr.State() + all_images_list = gr.State() + hidden = gr.Image(type="pil") + info1 = gr.Textbox() + info2 = gr.Textbox() + + img_path.submit(change_dir, inputs=[img_path, date_to], outputs=[warning, main_panel, warning_box, date_to]) #change date - change_date_output = [date_to, date_from, filenames, page_index, history_gallery, img_file_name, visible_img_num] - newest.click(newest_click, inputs=[img_path, date_to], outputs=change_date_output) - date_to.change(archive_images, inputs=[img_path, date_to], outputs=change_date_output) - newest.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - date_to.change(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - date_to.change(enable_page_buttons, inputs=None, outputs=[turn_page_buttons]) - newest.click(enable_page_buttons, inputs=None, outputs=[turn_page_buttons]) + change_date_output = [date_to, load_info, filenames, page_index, history_gallery, img_file_name, img_file_time, visible_img_num, last_date_from] + + date_to.change(archive_images, inputs=[img_path, date_to], outputs=change_date_output) + date_to.change(enable_page_buttons, inputs=None, outputs=[turn_page_buttons]) + date_to.change(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + + newest.click(newest_click, inputs=[date_to], outputs=[date_to, date_to_recorder]) + forward.click(forward_click, inputs=[last_date_from, date_to_recorder], outputs=[date_to, date_to_recorder]) + backward.click(backward_click, inputs=[last_date_from, date_to_recorder], outputs=[date_to, date_to_recorder]) + #delete delete.click(delete_image, inputs=[delete_num, img_file_name, filenames, image_index, visible_img_num], outputs=[filenames, delete_num, visible_img_num]) @@ -301,7 +369,7 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): #turn page gallery_inputs = [page_index, filenames] - gallery_outputs = [page_index, history_gallery, img_file_name, visible_img_num] + gallery_outputs = [page_index, history_gallery, img_file_name, img_file_time, visible_img_num] first_page.click(first_page_click, inputs=gallery_inputs, outputs=gallery_outputs) next_page.click(next_page_click, inputs=gallery_inputs, outputs=gallery_outputs) prev_page.click(prev_page_click, inputs=gallery_inputs, outputs=gallery_outputs) @@ -317,12 +385,14 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): renew_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") # other funcitons - set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, image_index, page_index, filenames], outputs=[img_file_name, image_index, hidden]) + set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, image_index, page_index, filenames], outputs=[img_file_name, img_file_time, image_index, hidden]) img_file_name.change(fn=None, _js="images_history_enable_del_buttons", inputs=None, outputs=None) hidden.change(fn=run_pnginfo, inputs=[hidden], outputs=[info1, img_file_info, info2]) switch_dict["fn"](pnginfo_send_to_txt2img, switch_dict["t2i"], img_file_info, 'switch_to_txt2img') switch_dict["fn"](pnginfo_send_to_img2img, switch_dict["i2i"], img_file_info, 'switch_to_img2img_img2img') + + def create_history_tabs(gr, sys_opts, run_pnginfo, switch_dict): global opts; opts = sys_opts @@ -330,10 +400,11 @@ def create_history_tabs(gr, sys_opts, run_pnginfo, switch_dict): num_of_imgs_per_page = int(opts.images_history_num_per_page * opts.images_history_pages_num) with gr.Blocks(analytics_enabled=False) as images_history: with gr.Tabs() as tabs: - for tab in ["txt2img", "img2img", "extras", "saved"]: + for tab in [browser_tabname, "txt2img", "img2img", "extras", "saved"]: with gr.Tab(tab): - with gr.Blocks(analytics_enabled=False) as images_history_img2img: + with gr.Blocks(analytics_enabled=False) : show_images_history(gr, opts, tab, run_pnginfo, switch_dict) - gradio.Checkbox(opts.images_history_reconstruct_directory, elem_id="images_history_reconstruct_directory", visible=False) + #gradio.Checkbox(opts.images_history_reconstruct_directory, elem_id="images_history_reconstruct_directory", visible=False) + gradio.Checkbox(opts.images_history_reconstruct_directory, elem_id="images_history_finish_render", visible=False) return images_history diff --git a/modules/shared.py b/modules/shared.py index c2ea4186..1811018d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -309,10 +309,11 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}), })) -options_templates.update(options_section(('images-history', "Images history"), { - "images_history_reconstruct_directory": OptionInfo(False, "Reconstruct output directory structure.This can greatly improve the speed of loading , but will change the original output directory structure"), +options_templates.update(options_section(('images-history', "Images Browser"), { + #"images_history_reconstruct_directory": OptionInfo(False, "Reconstruct output directory structure.This can greatly improve the speed of loading , but will change the original output directory structure"), "images_history_num_per_page": OptionInfo(36, "Number of pictures displayed on each page"), - "images_history_pages_num": OptionInfo(6, "Maximum number of pages per load "), + "images_history_pages_num": OptionInfo(6, "Minimum number of pages per load "), + "images_history_grid_num": OptionInfo(6, "Number of grids in each row"), })) diff --git a/modules/ui.py b/modules/ui.py index 43dc88fc..85abac4d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1548,7 +1548,7 @@ Requested path was: {f} (img2img_interface, "img2img", "img2img"), (extras_interface, "Extras", "extras"), (pnginfo_interface, "PNG Info", "pnginfo"), - (images_history, "History", "images_history"), + (images_history, "Image Browser", "images_history"), (modelmerger_interface, "Checkpoint Merger", "modelmerger"), (train_interface, "Train", "ti"), (settings_interface, "Settings", "settings"), diff --git a/uitest.bat b/uitest.bat new file mode 100644 index 00000000..ae863af6 --- /dev/null +++ b/uitest.bat @@ -0,0 +1,2 @@ +venv\Scripts\python.exe uitest.py +pause diff --git a/uitest.py b/uitest.py new file mode 100644 index 00000000..393e2d81 --- /dev/null +++ b/uitest.py @@ -0,0 +1,124 @@ +import os +import threading +import time +import importlib +import signal +import threading + +from modules.paths import script_path + +from modules import devices, sd_samplers +import modules.codeformer_model as codeformer +import modules.extras +import modules.face_restoration +import modules.gfpgan_model as gfpgan +import modules.img2img + +import modules.lowvram +import modules.paths +import modules.scripts +import modules.sd_hijack +import modules.sd_models +import modules.shared as shared +import modules.txt2img + +import modules.ui +from modules import devices +from modules import modelloader +from modules.paths import script_path +from modules.shared import cmd_opts + +modelloader.cleanup_models() +modules.sd_models.setup_model() +codeformer.setup_model(cmd_opts.codeformer_models_path) +gfpgan.setup_model(cmd_opts.gfpgan_models_path) +shared.face_restorers.append(modules.face_restoration.FaceRestoration()) +modelloader.load_upscalers() +queue_lock = threading.Lock() + + +def wrap_queued_call(func): + def f(*args, **kwargs): + with queue_lock: + res = func(*args, **kwargs) + + return res + + return f + + +def wrap_gradio_gpu_call(func, extra_outputs=None): + def f(*args, **kwargs): + devices.torch_gc() + + shared.state.sampling_step = 0 + shared.state.job_count = -1 + shared.state.job_no = 0 + shared.state.job_timestamp = shared.state.get_job_timestamp() + shared.state.current_latent = None + shared.state.current_image = None + shared.state.current_image_sampling_step = 0 + shared.state.interrupted = False + shared.state.textinfo = None + + with queue_lock: + res = func(*args, **kwargs) + + shared.state.job = "" + shared.state.job_count = 0 + + devices.torch_gc() + + return res + + return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs) + + +modules.scripts.load_scripts(os.path.join(script_path, "scripts")) + +shared.sd_model = None #modules.sd_models.load_model() +#shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(shared.sd_model))) + + +def webui(): + # make the program just exit at ctrl+c without waiting for anything + def sigint_handler(sig, frame): + print(f'Interrupted with signal {sig} in {frame}') + os._exit(0) + + signal.signal(signal.SIGINT, sigint_handler) + + while 1: + + demo = modules.ui.create_ui(wrap_gradio_gpu_call=wrap_gradio_gpu_call) + + demo.launch( + share=cmd_opts.share, + server_name="0.0.0.0" if cmd_opts.listen else None, + server_port=cmd_opts.port, + debug=cmd_opts.gradio_debug, + auth=[tuple(cred.split(':')) for cred in cmd_opts.gradio_auth.strip('"').split(',')] if cmd_opts.gradio_auth else None, + inbrowser=cmd_opts.autolaunch, + prevent_thread_lock=True + ) + + while 1: + time.sleep(0.5) + if getattr(demo, 'do_restart', False): + time.sleep(0.5) + demo.close() + time.sleep(0.5) + break + + sd_samplers.set_samplers() + + print('Reloading Custom Scripts') + modules.scripts.reload_scripts(os.path.join(script_path, "scripts")) + print('Reloading modules: modules.ui') + importlib.reload(modules.ui) + print('Restarting Gradio') + + + +if __name__ == "__main__": + webui() \ No newline at end of file From c6f778d9b19d7116ffb82718f6ca0b867e2f4445 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Tue, 18 Oct 2022 20:15:08 +0800 Subject: [PATCH 0036/1118] Image browser --- uitest.bat | 2 - uitest.py | 124 ----------------------------------------------------- 2 files changed, 126 deletions(-) delete mode 100644 uitest.bat delete mode 100644 uitest.py diff --git a/uitest.bat b/uitest.bat deleted file mode 100644 index ae863af6..00000000 --- a/uitest.bat +++ /dev/null @@ -1,2 +0,0 @@ -venv\Scripts\python.exe uitest.py -pause diff --git a/uitest.py b/uitest.py deleted file mode 100644 index 393e2d81..00000000 --- a/uitest.py +++ /dev/null @@ -1,124 +0,0 @@ -import os -import threading -import time -import importlib -import signal -import threading - -from modules.paths import script_path - -from modules import devices, sd_samplers -import modules.codeformer_model as codeformer -import modules.extras -import modules.face_restoration -import modules.gfpgan_model as gfpgan -import modules.img2img - -import modules.lowvram -import modules.paths -import modules.scripts -import modules.sd_hijack -import modules.sd_models -import modules.shared as shared -import modules.txt2img - -import modules.ui -from modules import devices -from modules import modelloader -from modules.paths import script_path -from modules.shared import cmd_opts - -modelloader.cleanup_models() -modules.sd_models.setup_model() -codeformer.setup_model(cmd_opts.codeformer_models_path) -gfpgan.setup_model(cmd_opts.gfpgan_models_path) -shared.face_restorers.append(modules.face_restoration.FaceRestoration()) -modelloader.load_upscalers() -queue_lock = threading.Lock() - - -def wrap_queued_call(func): - def f(*args, **kwargs): - with queue_lock: - res = func(*args, **kwargs) - - return res - - return f - - -def wrap_gradio_gpu_call(func, extra_outputs=None): - def f(*args, **kwargs): - devices.torch_gc() - - shared.state.sampling_step = 0 - shared.state.job_count = -1 - shared.state.job_no = 0 - shared.state.job_timestamp = shared.state.get_job_timestamp() - shared.state.current_latent = None - shared.state.current_image = None - shared.state.current_image_sampling_step = 0 - shared.state.interrupted = False - shared.state.textinfo = None - - with queue_lock: - res = func(*args, **kwargs) - - shared.state.job = "" - shared.state.job_count = 0 - - devices.torch_gc() - - return res - - return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs) - - -modules.scripts.load_scripts(os.path.join(script_path, "scripts")) - -shared.sd_model = None #modules.sd_models.load_model() -#shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(shared.sd_model))) - - -def webui(): - # make the program just exit at ctrl+c without waiting for anything - def sigint_handler(sig, frame): - print(f'Interrupted with signal {sig} in {frame}') - os._exit(0) - - signal.signal(signal.SIGINT, sigint_handler) - - while 1: - - demo = modules.ui.create_ui(wrap_gradio_gpu_call=wrap_gradio_gpu_call) - - demo.launch( - share=cmd_opts.share, - server_name="0.0.0.0" if cmd_opts.listen else None, - server_port=cmd_opts.port, - debug=cmd_opts.gradio_debug, - auth=[tuple(cred.split(':')) for cred in cmd_opts.gradio_auth.strip('"').split(',')] if cmd_opts.gradio_auth else None, - inbrowser=cmd_opts.autolaunch, - prevent_thread_lock=True - ) - - while 1: - time.sleep(0.5) - if getattr(demo, 'do_restart', False): - time.sleep(0.5) - demo.close() - time.sleep(0.5) - break - - sd_samplers.set_samplers() - - print('Reloading Custom Scripts') - modules.scripts.reload_scripts(os.path.join(script_path, "scripts")) - print('Reloading modules: modules.ui') - importlib.reload(modules.ui) - print('Restarting Gradio') - - - -if __name__ == "__main__": - webui() \ No newline at end of file From 684a31c4da673961ce9e3a384132fda5d1111ab8 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 21:50:34 +0900 Subject: [PATCH 0037/1118] update ko-KR.json Translated all text on txt2img window, plus some extra --- localizations/ko-KR.json | 42 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json index a4367dc5..c6e55bb1 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko-KR.json @@ -4,9 +4,10 @@ "×": "×", "❮": "❮", "❯": "❯", - "Loading...": "로딩중...", - "view": "", - "api": "api", + "Loading...": "", + "view": "api 보이기", + "hide": "api 숨기기", + "api": "", "•": "•", "txt2img": "텍스트→이미지", "img2img": "이미지→이미지", @@ -50,7 +51,7 @@ "Tiling": "타일링", "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", "Highres. fix": "고해상도 보정", - "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", "Firstpass width": "초기 가로길이", "Firstpass height": "초기 세로길이", "Denoising strength": "디노이즈 강도", @@ -81,5 +82,38 @@ "Send to extras": "부가기능으로 전송", "Open images output directory": "이미지 저장 경로 열기", "Make Zip when Save?": "저장 시 Zip 생성하기", + "Prompt matrix": "프롬프트 매트릭스", + "Separate prompts into parts using vertical pipe character (|) and the script will create a picture for every combination of them (except for the first part, which will be present in all combinations)": "(|)를 이용해 프롬프트를 분리할 시 첫 프롬프트를 제외하고 모든 프롬프트의 조합마다 이미지를 생성합니다. 첫 프롬프트는 모든 조합에 포함되게 됩니다.", + "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", + "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", + "Show Textbox": "텍스트박스 보이기", + "File with inputs": "설정값 파일", + "Prompts": "프롬프트", + "X/Y plot": "X/Y 플롯", + "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", + "X type": "X축", + "Y type": "Y축", + "X values": "X 설정값", + "Y values": "Y 설정값", + "Separate values for X axis using commas.": "쉼표로 X축에 적용할 값 분리", + "Separate values for Y axis using commas.": "쉼표로 Y축에 적용할 값 분리", + "Draw legend": "범례 그리기", + "Include Separate Images": "분리된 이미지 포함하기", + "Keep -1 for seeds": "시드값 -1로 유지", + "Var. seed": "바리에이션 시드", + "Var. strength": "바리에이션 강도", + "Steps": "스텝 수", + "Prompt S/R": "프롬프트 스타일 변경", + "Prompt order": "프롬프트 순서", + "Sampler": "샘플러", + "Checkpoint name": "체크포인트 이름", + "Hypernet str.": "하이퍼네트워크 강도", + "Sigma Churn": "시그마 섞기", + "Sigma min": "시그마 최솟값", + "Sigma max": "시그마 최댓값", + "Sigma noise": "시그마 노이즈", + "Clip skip": "클립 건너뛰기", + "Denoising": "디노이징", + "Nothing": "없음", "Always save all generated images": "생성된 이미지 항상 저장하기" } \ No newline at end of file From 4f4e7fed7e4910b165c651e7618eb8e47c57ddb5 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 22:12:41 +0900 Subject: [PATCH 0038/1118] update ko-KR.json --- localizations/ko-KR.json | 1 + 1 file changed, 1 insertion(+) diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json index c6e55bb1..b263b13c 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko-KR.json @@ -115,5 +115,6 @@ "Clip skip": "클립 건너뛰기", "Denoising": "디노이징", "Nothing": "없음", + "Apply settings": "설정 적용하기", "Always save all generated images": "생성된 이미지 항상 저장하기" } \ No newline at end of file From b7e78ef692fe912916de6e54f6e2521b000d650c Mon Sep 17 00:00:00 2001 From: yfszzx Date: Tue, 18 Oct 2022 22:21:54 +0800 Subject: [PATCH 0039/1118] Image browser improve --- modules/images_history.py | 43 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/modules/images_history.py b/modules/images_history.py index d56f3a25..a40cdc0e 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -100,14 +100,15 @@ def auto_sorting(dir_name): date_list.append(today) return sorted(date_list, reverse=True) -def archive_images(dir_name, date_to): - +def archive_images(dir_name, date_to): filenames = [] - loads_num =int(opts.images_history_num_per_page * opts.images_history_pages_num) + batch_size =int(opts.images_history_num_per_page * opts.images_history_pages_num) + if batch_size <= 0: + batch_size = opts.images_history_num_per_page * 6 today = time.strftime("%Y%m%d",time.localtime(time.time())) date_to = today if date_to is None or date_to == "" else date_to date_to_bak = date_to - if opts.images_history_reconstruct_directory: + if False: #opts.images_history_reconstruct_directory: date_list = auto_sorting(dir_name) for date in date_list: if date <= date_to: @@ -115,11 +116,13 @@ def archive_images(dir_name, date_to): if date == today and not os.path.exists(path): continue filenames = traverse_all_files(path, filenames) - if len(filenames) > loads_num: + if len(filenames) > batch_size: break filenames = sorted(filenames, key=lambda file: -os.path.getmtime(file)) else: - filenames = traverse_all_files(dir_name, filenames) + filenames = traverse_all_files(dir_name, filenames) + total_num = len(filenames) + batch_count = len(filenames) + 1 // batch_size + 1 tmparray = [(os.path.getmtime(file), file) for file in filenames ] date_stamp = time.mktime(time.strptime(date_to, "%Y%m%d")) + 86400 filenames = [] @@ -132,8 +135,8 @@ def archive_images(dir_name, date_to): filenames.append((t, f ,date)) date_list = sorted(list(date_list.keys()), reverse=True) sort_array = sorted(filenames, key=lambda x:-x[0]) - if len(sort_array) > loads_num: - date = sort_array[loads_num][2] + if len(sort_array) > batch_size: + date = sort_array[batch_size][2] filenames = [x[1] for x in sort_array] else: date = date_to if len(sort_array) == 0 else sort_array[-1][2] @@ -141,9 +144,9 @@ def archive_images(dir_name, date_to): filenames = [x[1] for x in sort_array if x[2]>= date] num = len(filenames) last_date_from = date_to_bak if num == 0 else time.strftime("%Y%m%d", time.localtime(time.mktime(time.strptime(date, "%Y%m%d")) - 1000)) - date = date[:4] + "-" + date[4:6] + "-" + date[6:8] - date_to_bak = date_to_bak[:4] + "-" + date_to_bak[4:6] + "-" + date_to_bak[6:8] - load_info = f"Loaded {(num + 1) // opts.images_history_pages_num} pades, {num} images, during {date} - {date_to_bak}" + date = date[:4] + "/" + date[4:6] + "/" + date[6:8] + date_to_bak = date_to_bak[:4] + "/" + date_to_bak[4:6] + "/" + date_to_bak[6:8] + load_info = f"{total_num} images in this directory. Loaded {num} images during {date} - {date_to_bak}, divided into {int((num + 1) // opts.images_history_num_per_page + 1)} pages" _, image_list, _, _, visible_num = get_recent_images(1, 0, filenames) return ( gradio.Dropdown.update(choices=date_list, value=date_to), @@ -154,12 +157,10 @@ def archive_images(dir_name, date_to): "", "", visible_num, - last_date_from + last_date_from, + #gradio.update(visible=batch_count > 1) ) - - - def delete_image(delete_num, name, filenames, image_index, visible_num): if name == "": return filenames, delete_num @@ -295,16 +296,16 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): with gr.Column() as page_panel: with gr.Row(): - img_path = gr.Textbox(dir_name, label="Images directory", placeholder="Input images directory") + img_path = gr.Textbox(dir_name, label="Images directory", placeholder="Input images directory", interactive=custom_dir) with gr.Row(visible=False) as warning: warning_box = gr.Textbox("Message", interactive=False) with gr.Row(visible=not custom_dir, elem_id=tabname + "_images_history") as main_panel: with gr.Column(scale=2): - with gr.Row(): - backward = gr.Button('Backward') - date_to = gr.Dropdown(label="Date to") - forward = gr.Button('Forward') + with gr.Row() as batch_panel: + forward = gr.Button('Forward') + date_to = gr.Dropdown(label="Date to") + backward = gr.Button('Backward') newest = gr.Button('Reload', elem_id=tabname + "_images_history_start") with gr.Row(): load_info = gr.Textbox(show_label=False, interactive=False) @@ -335,7 +336,7 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): # hiden items - with gr.Row(): #visible=False): + with gr.Row(visible=False): visible_img_num = gr.Number() date_to_recorder = gr.State([]) last_date_from = gr.Textbox() From 538bc89c269743e56b07ef2b471d1ce0a39b6776 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Wed, 19 Oct 2022 11:27:51 +0800 Subject: [PATCH 0040/1118] Image browser improved --- javascript/images_history.js | 81 +++++++++++---------- modules/images_history.py | 133 +++++++++++++++++++---------------- modules/shared.py | 5 ++ modules/ui.py | 2 +- 4 files changed, 119 insertions(+), 102 deletions(-) diff --git a/javascript/images_history.js b/javascript/images_history.js index 182d730b..c9aa76f8 100644 --- a/javascript/images_history.js +++ b/javascript/images_history.js @@ -17,14 +17,6 @@ var images_history_click_image = function(){ images_history_set_image_info(this); } -var images_history_click_tab = function(){ - var tabs_box = gradioApp().getElementById("images_history_tab"); - if (!tabs_box.classList.contains(this.getAttribute("tabname"))) { - gradioApp().getElementById(this.getAttribute("tabname") + "_images_history_start").click(); - tabs_box.classList.add(this.getAttribute("tabname")) - } -} - function images_history_disabled_del(){ gradioApp().querySelectorAll(".images_history_del_button").forEach(function(btn){ btn.setAttribute('disabled','disabled'); @@ -145,57 +137,64 @@ function images_history_enable_del_buttons(){ } function images_history_init(){ - // var loaded = gradioApp().getElementById("images_history_reconstruct_directory") - // if (loaded){ - // var init_status = loaded.querySelector("input").checked - if (gradioApp().getElementById("images_history_finish_render")){ + var tabnames = gradioApp().getElementById("images_history_tabnames_list") + if (tabnames){ + images_history_tab_list = tabnames.querySelector("textarea").value.split(",") for (var i in images_history_tab_list ){ - tab = images_history_tab_list[i]; + var tab = images_history_tab_list[i]; gradioApp().getElementById(tab + '_images_history').classList.add("images_history_cantainor"); gradioApp().getElementById(tab + '_images_history_set_index').classList.add("images_history_set_index"); gradioApp().getElementById(tab + '_images_history_del_button').classList.add("images_history_del_button"); - gradioApp().getElementById(tab + '_images_history_gallery').classList.add("images_history_gallery"); - + gradioApp().getElementById(tab + '_images_history_gallery').classList.add("images_history_gallery"); + gradioApp().getElementById(tab + "_images_history_start").setAttribute("style","padding:20px;font-size:25px"); } - var tabs_box = gradioApp().getElementById("tab_images_history").querySelector("div").querySelector("div").querySelector("div"); - tabs_box.setAttribute("id", "images_history_tab"); - var tab_btns = tabs_box.querySelectorAll("button"); - for (var i in images_history_tab_list){ - var tabname = images_history_tab_list[i] - tab_btns[i].setAttribute("tabname", tabname); - // if (!init_status){ - // tab_btns[i].addEventListener('click', images_history_click_tab); - // } - tab_btns[i].addEventListener('click', images_history_click_tab); - } + //preload + if (gradioApp().getElementById("images_history_preload").querySelector("input").checked ){ + var tabs_box = gradioApp().getElementById("tab_images_history").querySelector("div").querySelector("div").querySelector("div"); + tabs_box.setAttribute("id", "images_history_tab"); + var tab_btns = tabs_box.querySelectorAll("button"); + for (var i in images_history_tab_list){ + var tabname = images_history_tab_list[i] + tab_btns[i].setAttribute("tabname", tabname); + tab_btns[i].addEventListener('click', function(){ + var tabs_box = gradioApp().getElementById("images_history_tab"); + if (!tabs_box.classList.contains(this.getAttribute("tabname"))) { + gradioApp().getElementById(this.getAttribute("tabname") + "_images_history_start").click(); + tabs_box.classList.add(this.getAttribute("tabname")) + } + }); + } + tab_btns[0].click() + } } else { setTimeout(images_history_init, 500); } } -var images_history_tab_list = ["custom", "txt2img", "img2img", "extras", "saved"]; +var images_history_tab_list = ""; setTimeout(images_history_init, 500); document.addEventListener("DOMContentLoaded", function() { var mutationObserver = new MutationObserver(function(m){ - for (var i in images_history_tab_list ){ - let tabname = images_history_tab_list[i] - var buttons = gradioApp().querySelectorAll('#' + tabname + '_images_history .gallery-item'); - buttons.forEach(function(bnt){ - bnt.addEventListener('click', images_history_click_image, true); - }); + if (images_history_tab_list != ""){ + for (var i in images_history_tab_list ){ + let tabname = images_history_tab_list[i] + var buttons = gradioApp().querySelectorAll('#' + tabname + '_images_history .gallery-item'); + buttons.forEach(function(bnt){ + bnt.addEventListener('click', images_history_click_image, true); + }); - var cls_btn = gradioApp().getElementById(tabname + '_images_history_gallery').querySelector("svg"); - if (cls_btn){ - cls_btn.addEventListener('click', function(){ - gradioApp().getElementById(tabname + '_images_history_del_button').setAttribute('disabled','disabled'); - }, false); - } + var cls_btn = gradioApp().getElementById(tabname + '_images_history_gallery').querySelector("svg"); + if (cls_btn){ + cls_btn.addEventListener('click', function(){ + gradioApp().getElementById(tabname + '_images_history_renew_page').click(); + }, false); + } - } + } + } }); mutationObserver.observe(gradioApp(), { childList:true, subtree:true }); - }); diff --git a/modules/images_history.py b/modules/images_history.py index a40cdc0e..78fd0543 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -4,7 +4,9 @@ import time import hashlib import gradio system_bak_path = "webui_log_and_bak" -browser_tabname = "custom" +custom_tab_name = "custom fold" +faverate_tab_name = "favorites" +tabs_list = ["txt2img", "img2img", "extras", faverate_tab_name] def is_valid_date(date): try: time.strptime(date, "%Y%m%d") @@ -122,7 +124,6 @@ def archive_images(dir_name, date_to): else: filenames = traverse_all_files(dir_name, filenames) total_num = len(filenames) - batch_count = len(filenames) + 1 // batch_size + 1 tmparray = [(os.path.getmtime(file), file) for file in filenames ] date_stamp = time.mktime(time.strptime(date_to, "%Y%m%d")) + 86400 filenames = [] @@ -146,10 +147,12 @@ def archive_images(dir_name, date_to): last_date_from = date_to_bak if num == 0 else time.strftime("%Y%m%d", time.localtime(time.mktime(time.strptime(date, "%Y%m%d")) - 1000)) date = date[:4] + "/" + date[4:6] + "/" + date[6:8] date_to_bak = date_to_bak[:4] + "/" + date_to_bak[4:6] + "/" + date_to_bak[6:8] - load_info = f"{total_num} images in this directory. Loaded {num} images during {date} - {date_to_bak}, divided into {int((num + 1) // opts.images_history_num_per_page + 1)} pages" + load_info = "
" + load_info += f"{total_num} images in this directory. Loaded {num} images during {date} - {date_to_bak}, divided into {int((num + 1) // opts.images_history_num_per_page + 1)} pages" + load_info += "
" _, image_list, _, _, visible_num = get_recent_images(1, 0, filenames) return ( - gradio.Dropdown.update(choices=date_list, value=date_to), + date_to, load_info, filenames, 1, @@ -158,7 +161,7 @@ def archive_images(dir_name, date_to): "", visible_num, last_date_from, - #gradio.update(visible=batch_count > 1) + gradio.update(visible=total_num > num) ) def delete_image(delete_num, name, filenames, image_index, visible_num): @@ -209,7 +212,7 @@ def get_recent_images(page_index, step, filenames): visible_num = num_of_imgs_per_page if visible_num == 0 else visible_num return page_index, image_list, "", "", visible_num -def newest_click(date_to): +def loac_batch_click(date_to): if date_to is None: return time.strftime("%Y%m%d",time.localtime(time.time())), [] else: @@ -248,7 +251,7 @@ def page_index_change(page_index, filenames): def show_image_info(tabname_box, num, page_index, filenames): file = filenames[int(num) + int((page_index - 1) * int(opts.images_history_num_per_page))] - tm = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(file))) + tm = "
" + time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(file))) + "
" return file, tm, num, file def enable_page_buttons(): @@ -268,9 +271,9 @@ def change_dir(img_dir, date_to): warning = "The format of the directory is incorrect" if warning is None: today = time.strftime("%Y%m%d",time.localtime(time.time())) - return gradio.update(visible=False), gradio.update(visible=True), None, None if date_to != today else today + return gradio.update(visible=False), gradio.update(visible=True), None, None if date_to != today else today, gradio.update(visible=True), gradio.update(visible=True) else: - return gradio.update(visible=True), gradio.update(visible=False), warning, date_to + return gradio.update(visible=True), gradio.update(visible=False), warning, date_to, gradio.update(visible=False), gradio.update(visible=False) def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): custom_dir = False @@ -280,7 +283,7 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): dir_name = opts.outdir_img2img_samples elif tabname == "extras": dir_name = opts.outdir_extras_samples - elif tabname == "saved": + elif tabname == faverate_tab_name: dir_name = opts.outdir_save else: custom_dir = True @@ -295,22 +298,26 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): os.makedirs(dir_name) with gr.Column() as page_panel: - with gr.Row(): - img_path = gr.Textbox(dir_name, label="Images directory", placeholder="Input images directory", interactive=custom_dir) + with gr.Row(): + with gr.Column(scale=1, visible=not custom_dir) as load_batch_box: + load_batch = gr.Button('Load', elem_id=tabname + "_images_history_start", full_width=True) + with gr.Column(scale=4): + with gr.Row(): + img_path = gr.Textbox(dir_name, label="Images directory", placeholder="Input images directory", interactive=custom_dir) + with gr.Row(): + with gr.Column(visible=False, scale=1) as batch_panel: + with gr.Row(): + forward = gr.Button('Prev batch') + backward = gr.Button('Next batch') + with gr.Column(scale=3): + load_info = gr.HTML(visible=not custom_dir) with gr.Row(visible=False) as warning: warning_box = gr.Textbox("Message", interactive=False) with gr.Row(visible=not custom_dir, elem_id=tabname + "_images_history") as main_panel: - with gr.Column(scale=2): - with gr.Row() as batch_panel: - forward = gr.Button('Forward') - date_to = gr.Dropdown(label="Date to") - backward = gr.Button('Backward') - newest = gr.Button('Reload', elem_id=tabname + "_images_history_start") - with gr.Row(): - load_info = gr.Textbox(show_label=False, interactive=False) - with gr.Row(visible=False) as turn_page_buttons: - renew_page = gr.Button('Refresh page', elem_id=tabname + "_images_history_renew_page") + with gr.Column(scale=2): + with gr.Row(visible=True) as turn_page_buttons: + #date_to = gr.Dropdown(label="Date to") first_page = gr.Button('First Page') prev_page = gr.Button('Prev Page') page_index = gr.Number(value=1, label="Page Index") @@ -322,50 +329,54 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): delete_num = gr.Number(value=1, interactive=True, label="number of images to delete consecutively next") delete = gr.Button('Delete', elem_id=tabname + "_images_history_del_button") - with gr.Column(): - with gr.Row(): - if tabname != "saved": - save_btn = gr.Button('Save') - pnginfo_send_to_txt2img = gr.Button('Send to txt2img') - pnginfo_send_to_img2img = gr.Button('Send to img2img') + with gr.Column(): with gr.Row(): with gr.Column(): - img_file_info = gr.Textbox(label="Generate Info", interactive=False) + img_file_info = gr.Textbox(label="Generate Info", interactive=False, lines=6) + gr.HTML("
") img_file_name = gr.Textbox(value="", label="File Name", interactive=False) - img_file_time= gr.Textbox(value="", label="Create Time", interactive=False) - + img_file_time= gr.HTML() + with gr.Row(): + if tabname != faverate_tab_name: + save_btn = gr.Button('Collect') + pnginfo_send_to_txt2img = gr.Button('Send to txt2img') + pnginfo_send_to_img2img = gr.Button('Send to img2img') + - # hiden items - with gr.Row(visible=False): - visible_img_num = gr.Number() - date_to_recorder = gr.State([]) - last_date_from = gr.Textbox() - tabname_box = gr.Textbox(tabname) - image_index = gr.Textbox(value=-1) - set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index") - filenames = gr.State() - all_images_list = gr.State() - hidden = gr.Image(type="pil") - info1 = gr.Textbox() - info2 = gr.Textbox() + # hiden items + with gr.Row(visible=False): + renew_page = gr.Button('Refresh page', elem_id=tabname + "_images_history_renew_page") + batch_date_to = gr.Textbox(label="Date to") + visible_img_num = gr.Number() + date_to_recorder = gr.State([]) + last_date_from = gr.Textbox() + tabname_box = gr.Textbox(tabname) + image_index = gr.Textbox(value=-1) + set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index") + filenames = gr.State() + all_images_list = gr.State() + hidden = gr.Image(type="pil") + info1 = gr.Textbox() + info2 = gr.Textbox() - img_path.submit(change_dir, inputs=[img_path, date_to], outputs=[warning, main_panel, warning_box, date_to]) - #change date - change_date_output = [date_to, load_info, filenames, page_index, history_gallery, img_file_name, img_file_time, visible_img_num, last_date_from] + img_path.submit(change_dir, inputs=[img_path, batch_date_to], outputs=[warning, main_panel, warning_box, batch_date_to, load_batch_box, load_info]) + + #change batch + change_date_output = [batch_date_to, load_info, filenames, page_index, history_gallery, img_file_name, img_file_time, visible_img_num, last_date_from, batch_panel] - date_to.change(archive_images, inputs=[img_path, date_to], outputs=change_date_output) - date_to.change(enable_page_buttons, inputs=None, outputs=[turn_page_buttons]) - date_to.change(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") + batch_date_to.change(archive_images, inputs=[img_path, batch_date_to], outputs=change_date_output) + batch_date_to.change(enable_page_buttons, inputs=None, outputs=[turn_page_buttons]) + batch_date_to.change(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - newest.click(newest_click, inputs=[date_to], outputs=[date_to, date_to_recorder]) - forward.click(forward_click, inputs=[last_date_from, date_to_recorder], outputs=[date_to, date_to_recorder]) - backward.click(backward_click, inputs=[last_date_from, date_to_recorder], outputs=[date_to, date_to_recorder]) + load_batch.click(loac_batch_click, inputs=[batch_date_to], outputs=[batch_date_to, date_to_recorder]) + forward.click(forward_click, inputs=[last_date_from, date_to_recorder], outputs=[batch_date_to, date_to_recorder]) + backward.click(backward_click, inputs=[last_date_from, date_to_recorder], outputs=[batch_date_to, date_to_recorder]) #delete delete.click(delete_image, inputs=[delete_num, img_file_name, filenames, image_index, visible_img_num], outputs=[filenames, delete_num, visible_img_num]) delete.click(fn=None, _js="images_history_delete", inputs=[delete_num, tabname_box, image_index], outputs=None) - if tabname != "saved": + if tabname != faverate_tab_name: save_btn.click(save_image, inputs=[img_file_name], outputs=None) #turn page @@ -394,18 +405,20 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): -def create_history_tabs(gr, sys_opts, run_pnginfo, switch_dict): +def create_history_tabs(gr, sys_opts, cmp_ops, run_pnginfo, switch_dict): global opts; opts = sys_opts loads_files_num = int(opts.images_history_num_per_page) num_of_imgs_per_page = int(opts.images_history_num_per_page * opts.images_history_pages_num) + if cmp_ops.browse_all_images: + tabs_list.append(custom_tab_name) with gr.Blocks(analytics_enabled=False) as images_history: with gr.Tabs() as tabs: - for tab in [browser_tabname, "txt2img", "img2img", "extras", "saved"]: + for tab in tabs_list: with gr.Tab(tab): with gr.Blocks(analytics_enabled=False) : - show_images_history(gr, opts, tab, run_pnginfo, switch_dict) - #gradio.Checkbox(opts.images_history_reconstruct_directory, elem_id="images_history_reconstruct_directory", visible=False) - gradio.Checkbox(opts.images_history_reconstruct_directory, elem_id="images_history_finish_render", visible=False) - + show_images_history(gr, opts, tab, run_pnginfo, switch_dict) + gradio.Checkbox(opts.images_history_preload, elem_id="images_history_preload", visible=False) + gradio.Textbox(",".join(tabs_list), elem_id="images_history_tabnames_list", visible=False) + return images_history diff --git a/modules/shared.py b/modules/shared.py index 1811018d..4d735414 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -74,6 +74,10 @@ parser.add_argument("--disable-console-progressbars", action='store_true', help= parser.add_argument("--enable-console-prompts", action='store_true', help="print prompts to console when generating with txt2img and img2img", default=False) parser.add_argument('--vae-path', type=str, help='Path to Variational Autoencoders model', default=None) parser.add_argument("--disable-safe-unpickle", action='store_true', help="disable checking pytorch models for malicious code", default=False) +parser.add_argument("--browse-all-images", action='store_true', help="Allow browsing all images by Image Browser", default=False) + + +cmd_opts = parser.parse_args() cmd_opts = parser.parse_args() @@ -311,6 +315,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" options_templates.update(options_section(('images-history', "Images Browser"), { #"images_history_reconstruct_directory": OptionInfo(False, "Reconstruct output directory structure.This can greatly improve the speed of loading , but will change the original output directory structure"), + "images_history_preload": OptionInfo(False, "Preload images at startup"), "images_history_num_per_page": OptionInfo(36, "Number of pictures displayed on each page"), "images_history_pages_num": OptionInfo(6, "Minimum number of pages per load "), "images_history_grid_num": OptionInfo(6, "Number of grids in each row"), diff --git a/modules/ui.py b/modules/ui.py index 85abac4d..88f46659 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1150,7 +1150,7 @@ def create_ui(wrap_gradio_gpu_call): "i2i":img2img_paste_fields } - images_history = img_his.create_history_tabs(gr, opts, wrap_gradio_call(modules.extras.run_pnginfo), images_history_switch_dict) + images_history = img_his.create_history_tabs(gr, opts, cmd_opts, wrap_gradio_call(modules.extras.run_pnginfo), images_history_switch_dict) with gr.Blocks() as modelmerger_interface: with gr.Row().style(equal_height=False): From abeec4b63029c2c4151a78fc395d312113881845 Mon Sep 17 00:00:00 2001 From: captin411 Date: Wed, 19 Oct 2022 03:18:26 -0700 Subject: [PATCH 0041/1118] Add auto focal point cropping to Preprocess images This algorithm plots a bunch of points of interest on the source image and averages their locations to find a center. Most points come from OpenCV. One point comes from an entropy model. OpenCV points account for 50% of the weight and the entropy based point is the other 50%. The center of all weighted points is calculated and a bounding box is drawn as close to centered over that point as possible. --- modules/textual_inversion/preprocess.py | 151 +++++++++++++++++++++++- 1 file changed, 146 insertions(+), 5 deletions(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 886cf0c3..168bfb09 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -1,5 +1,7 @@ import os -from PIL import Image, ImageOps +import cv2 +import numpy as np +from PIL import Image, ImageOps, ImageDraw import platform import sys import tqdm @@ -11,7 +13,7 @@ if cmd_opts.deepdanbooru: import modules.deepbooru as deepbooru -def preprocess(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False): +def preprocess(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False, process_entropy_focus=False): try: if process_caption: shared.interrogator.load() @@ -21,7 +23,7 @@ def preprocess(process_src, process_dst, process_width, process_height, process_ db_opts[deepbooru.OPT_INCLUDE_RANKS] = False deepbooru.create_deepbooru_process(opts.interrogate_deepbooru_score_threshold, db_opts) - preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru) + preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru, process_entropy_focus) finally: @@ -33,7 +35,7 @@ def preprocess(process_src, process_dst, process_width, process_height, process_ -def preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False): +def preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False, process_entropy_focus=False): width = process_width height = process_height src = os.path.abspath(process_src) @@ -93,6 +95,8 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro is_tall = ratio > 1.35 is_wide = ratio < 1 / 1.35 + processing_option_ran = False + if process_split and is_tall: img = img.resize((width, height * img.height // img.width)) @@ -101,6 +105,8 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro bot = img.crop((0, img.height - height, width, img.height)) save_pic(bot, index) + + processing_option_ran = True elif process_split and is_wide: img = img.resize((width * img.width // img.height, height)) @@ -109,8 +115,143 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro right = img.crop((img.width - width, 0, img.width, height)) save_pic(right, index) - else: + + processing_option_ran = True + + if process_entropy_focus and (is_tall or is_wide): + if is_tall: + img = img.resize((width, height * img.height // img.width)) + else: + img = img.resize((width * img.width // img.height, height)) + + x_focal_center, y_focal_center = image_central_focal_point(img, width, height) + + # take the focal point and turn it into crop coordinates that try to center over the focal + # point but then get adjusted back into the frame + y_half = int(height / 2) + x_half = int(width / 2) + + x1 = x_focal_center - x_half + if x1 < 0: + x1 = 0 + elif x1 + width > img.width: + x1 = img.width - width + + y1 = y_focal_center - y_half + if y1 < 0: + y1 = 0 + elif y1 + height > img.height: + y1 = img.height - height + + x2 = x1 + width + y2 = y1 + height + + crop = [x1, y1, x2, y2] + + focal = img.crop(tuple(crop)) + save_pic(focal, index) + + processing_option_ran = True + + if not processing_option_ran: img = images.resize_image(1, img, width, height) save_pic(img, index) shared.state.nextjob() + + +def image_central_focal_point(im, target_width, target_height): + focal_points = [] + + focal_points.extend( + image_focal_points(im) + ) + + fp_entropy = image_entropy_point(im, target_width, target_height) + fp_entropy['weight'] = len(focal_points) + 1 # about half of the weight to entropy + + focal_points.append(fp_entropy) + + weight = 0.0 + x = 0.0 + y = 0.0 + for focal_point in focal_points: + weight += focal_point['weight'] + x += focal_point['x'] * focal_point['weight'] + y += focal_point['y'] * focal_point['weight'] + avg_x = round(x // weight) + avg_y = round(y // weight) + + return avg_x, avg_y + + +def image_focal_points(im): + grayscale = im.convert("L") + + # naive attempt at preventing focal points from collecting at watermarks near the bottom + gd = ImageDraw.Draw(grayscale) + gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999") + + np_im = np.array(grayscale) + + points = cv2.goodFeaturesToTrack( + np_im, + maxCorners=50, + qualityLevel=0.04, + minDistance=min(grayscale.width, grayscale.height)*0.05, + useHarrisDetector=False, + ) + + if points is None: + return [] + + focal_points = [] + for point in points: + x, y = point.ravel() + focal_points.append({ + 'x': x, + 'y': y, + 'weight': 1.0 + }) + + return focal_points + + +def image_entropy_point(im, crop_width, crop_height): + img = im.copy() + # just make it easier to slide the test crop with images oriented the same way + if (img.size[0] < img.size[1]): + portrait = True + img = img.rotate(90, expand=1) + + e_max = 0 + crop_current = [0, 0, crop_width, crop_height] + crop_best = crop_current + while crop_current[2] < img.size[0]: + crop = img.crop(tuple(crop_current)) + e = image_entropy(crop) + + if (e_max < e): + e_max = e + crop_best = list(crop_current) + + crop_current[0] += 4 + crop_current[2] += 4 + + x_mid = int((crop_best[2] - crop_best[0])/2) + y_mid = int((crop_best[3] - crop_best[1])/2) + + return { + 'x': x_mid, + 'y': y_mid, + 'weight': 1.0 + } + + +def image_entropy(im): + # greyscale image entropy + band = np.asarray(im.convert("L")) + hist, _ = np.histogram(band, bins=range(0, 256)) + hist = hist[hist > 0] + return -np.log2(hist / hist.sum()).sum() + From 087609ee181a91a523647435ffffa6288a317e2f Mon Sep 17 00:00:00 2001 From: captin411 Date: Wed, 19 Oct 2022 03:19:35 -0700 Subject: [PATCH 0042/1118] UI changes for focal point image cropping --- modules/ui.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 1ff7eb4f..b6be713b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1234,6 +1234,7 @@ def create_ui(wrap_gradio_gpu_call): with gr.Row(): process_flip = gr.Checkbox(label='Create flipped copies') process_split = gr.Checkbox(label='Split oversized images into two') + process_entropy_focus = gr.Checkbox(label='Create auto focal point crop') process_caption = gr.Checkbox(label='Use BLIP for caption') process_caption_deepbooru = gr.Checkbox(label='Use deepbooru for caption', visible=True if cmd_opts.deepdanbooru else False) @@ -1318,7 +1319,8 @@ def create_ui(wrap_gradio_gpu_call): process_flip, process_split, process_caption, - process_caption_deepbooru + process_caption_deepbooru, + process_entropy_focus ], outputs=[ ti_output, From 019a3a88f07766f2d32c32fbe8e41625f28ecb5e Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:15:47 +0100 Subject: [PATCH 0043/1118] Update ui.py --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index d2e24880..1573ef82 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1247,7 +1247,7 @@ def create_ui(wrap_gradio_gpu_call): run_preprocess = gr.Button(value="Preprocess", variant='primary') with gr.Tab(label="Train"): - gr.HTML(value="

Train an embedding; must specify a directory with a set of 1:1 ratio images

") + gr.HTML(value="

Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images
Initial learning rates: 0.005 for an Embedding, 0.00001 for Hypernetwork wiki

") with gr.Row(): train_embedding_name = gr.Dropdown(label='Embedding', elem_id="train_embedding", choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())) create_refresh_button(train_embedding_name, sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings, lambda: {"choices": sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())}, "refresh_train_embedding_name") From 2ce52d32e41fb523d1494f45073fd18496e52d35 Mon Sep 17 00:00:00 2001 From: discus0434 Date: Wed, 19 Oct 2022 16:31:12 +0000 Subject: [PATCH 0044/1118] fix for #3086 failing to load any previous hypernet --- modules/hypernetworks/hypernetwork.py | 60 +++++++++++++-------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 7d519cd9..74300122 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -24,11 +24,10 @@ class HypernetworkModule(torch.nn.Module): def __init__(self, dim, state_dict=None, layer_structure=None, add_layer_norm=False): super().__init__() - if layer_structure is not None: - assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!" - assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!" - else: - layer_structure = parse_layer_structure(dim, state_dict) + + assert layer_structure is not None, "layer_structure mut not be None" + assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!" + assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!" linears = [] for i in range(len(layer_structure) - 1): @@ -39,23 +38,30 @@ class HypernetworkModule(torch.nn.Module): self.linear = torch.nn.Sequential(*linears) if state_dict is not None: - try: - self.load_state_dict(state_dict) - except RuntimeError: - self.try_load_previous(state_dict) + self.fix_old_state_dict(state_dict) + self.load_state_dict(state_dict) else: for layer in self.linear: - layer.weight.data.normal_(mean = 0.0, std = 0.01) + layer.weight.data.normal_(mean=0.0, std=0.01) layer.bias.data.zero_() self.to(devices.device) - def try_load_previous(self, state_dict): - states = self.state_dict() - states['linear.0.bias'].copy_(state_dict['linear1.bias']) - states['linear.0.weight'].copy_(state_dict['linear1.weight']) - states['linear.1.bias'].copy_(state_dict['linear2.bias']) - states['linear.1.weight'].copy_(state_dict['linear2.weight']) + def fix_old_state_dict(self, state_dict): + changes = { + 'linear1.bias': 'linear.0.bias', + 'linear1.weight': 'linear.0.weight', + 'linear2.bias': 'linear.1.bias', + 'linear2.weight': 'linear.1.weight', + } + + for fr, to in changes.items(): + x = state_dict.get(fr, None) + if x is None: + continue + + del state_dict[fr] + state_dict[to] = x def forward(self, x): return x + self.linear(x) * self.multiplier @@ -71,18 +77,6 @@ def apply_strength(value=None): HypernetworkModule.multiplier = value if value is not None else shared.opts.sd_hypernetwork_strength -def parse_layer_structure(dim, state_dict): - i = 0 - layer_structure = [1] - - while (key := "linear.{}.weight".format(i)) in state_dict: - weight = state_dict[key] - layer_structure.append(len(weight) // dim) - i += 1 - - return layer_structure - - class Hypernetwork: filename = None name = None @@ -135,17 +129,18 @@ class Hypernetwork: state_dict = torch.load(filename, map_location='cpu') + self.layer_structure = state_dict.get('layer_structure', [1, 2, 1]) + self.add_layer_norm = state_dict.get('is_layer_norm', False) + for size, sd in state_dict.items(): if type(size) == int: self.layers[size] = ( - HypernetworkModule(size, sd[0], state_dict["layer_structure"], state_dict["is_layer_norm"]), - HypernetworkModule(size, sd[1], state_dict["layer_structure"], state_dict["is_layer_norm"]), + HypernetworkModule(size, sd[0], self.layer_structure, self.add_layer_norm), + HypernetworkModule(size, sd[1], self.layer_structure, self.add_layer_norm), ) self.name = state_dict.get('name', self.name) self.step = state_dict.get('step', 0) - self.layer_structure = state_dict.get('layer_structure', None) - self.add_layer_norm = state_dict.get('is_layer_norm', False) self.sd_checkpoint = state_dict.get('sd_checkpoint', None) self.sd_checkpoint_name = state_dict.get('sd_checkpoint_name', None) @@ -244,6 +239,7 @@ def stack_conds(conds): return torch.stack(conds) + def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): assert hypernetwork_name, 'hypernetwork not selected' From 14c1c2b9351f16d43ba4e6b6c9062edad44a6bec Mon Sep 17 00:00:00 2001 From: Alexandre Simard Date: Wed, 19 Oct 2022 13:53:52 -0400 Subject: [PATCH 0045/1118] Show PB texts at same time and earlier For big tasks (1000+ steps), waiting 1 minute to see ETA is long and this changes it so the number of steps done plays a role in showing the text as well. --- modules/ui.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index a2dbd41e..0abd177a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -261,14 +261,14 @@ def wrap_gradio_call(func, extra_outputs=None): return f -def calc_time_left(progress, threshold, label, force_display): +def calc_time_left(progress, threshold, label, force_display, showTime): if progress == 0: return "" else: time_since_start = time.time() - shared.state.time_start eta = (time_since_start/progress) eta_relative = eta-time_since_start - if (eta_relative > threshold and progress > 0.02) or force_display: + if (eta_relative > threshold and showTime) or force_display: if eta_relative > 3600: return label + time.strftime('%H:%M:%S', time.gmtime(eta_relative)) elif eta_relative > 60: @@ -290,7 +290,10 @@ def check_progress_call(id_part): if shared.state.sampling_steps > 0: progress += 1 / shared.state.job_count * shared.state.sampling_step / shared.state.sampling_steps - time_left = calc_time_left( progress, 1, " ETA: ", shared.state.time_left_force_display ) + # Show progress percentage and time left at the same moment, and base it also on steps done + showPBText = progress >= 0.01 or shared.state.sampling_step >= 10 + + time_left = calc_time_left( progress, 1, " ETA: ", shared.state.time_left_force_display, showPBText ) if time_left != "": shared.state.time_left_force_display = True @@ -298,7 +301,7 @@ def check_progress_call(id_part): progressbar = "" if opts.show_progressbar: - progressbar = f"""
{" " * 2 + str(int(progress*100))+"%" + time_left if progress > 0.01 else ""}
""" + progressbar = f"""
{" " * 2 + str(int(progress*100))+"%" + time_left if showPBText else ""}
""" image = gr_show(False) preview_visibility = gr_show(False) From eb7ba4b713ac2fb960ecf6365b1de0c89451e583 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Wed, 19 Oct 2022 19:50:46 +0100 Subject: [PATCH 0046/1118] update training header text --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 1573ef82..93c0767c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1247,7 +1247,7 @@ def create_ui(wrap_gradio_gpu_call): run_preprocess = gr.Button(value="Preprocess", variant='primary') with gr.Tab(label="Train"): - gr.HTML(value="

Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images
Initial learning rates: 0.005 for an Embedding, 0.00001 for Hypernetwork wiki

") + gr.HTML(value="

Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images
Initial learning rates: 0.005 for an Embedding, 0.00001 for Hypernetwork [wiki]

") with gr.Row(): train_embedding_name = gr.Dropdown(label='Embedding', elem_id="train_embedding", choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())) create_refresh_button(train_embedding_name, sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings, lambda: {"choices": sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())}, "refresh_train_embedding_name") From 4fbdbddc18b21f712acae58bf41740d27023285f Mon Sep 17 00:00:00 2001 From: Alexandre Simard Date: Wed, 19 Oct 2022 15:21:36 -0400 Subject: [PATCH 0047/1118] Remove pad spaces from progress bar text --- javascript/progressbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 7a05726e..24ab4795 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -10,7 +10,7 @@ function check_progressbar(id_part, id_progressbar, id_progressbar_span, id_skip if(opts.show_progress_in_title && progressbar && progressbar.offsetParent){ if(progressbar.innerText){ - let newtitle = 'Stable Diffusion - ' + progressbar.innerText + let newtitle = 'Stable Diffusion - ' + progressbar.innerText.slice(2) if(document.title != newtitle){ document.title = newtitle; } From 4d663055ded968831ec97f047dfa8e94036cf1c1 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Wed, 19 Oct 2022 20:33:18 +0100 Subject: [PATCH 0048/1118] update ui with extra training options --- modules/ui.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 93c0767c..cdb9d335 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1206,6 +1206,7 @@ def create_ui(wrap_gradio_gpu_call): new_embedding_name = gr.Textbox(label="Name") initialization_text = gr.Textbox(label="Initialization text", value="*") nvpt = gr.Slider(label="Number of vectors per token", minimum=1, maximum=75, step=1, value=1) + overwrite_old_embedding = gr.Checkbox(value=False, label="Overwrite Old Embedding") with gr.Row(): with gr.Column(scale=3): @@ -1219,6 +1220,7 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "320", "640", "1280"]) new_hypernetwork_layer_structure = gr.Textbox("1, 2, 1", label="Enter hypernetwork layer structure", placeholder="1st and last digit must be 1. ex:'1, 2, 1'") new_hypernetwork_add_layer_norm = gr.Checkbox(label="Add layer normalization") + overwrite_old_hypernetwork = gr.Checkbox(value=False, label="Overwrite Old Hypernetwork") with gr.Row(): with gr.Column(scale=3): @@ -1247,14 +1249,17 @@ def create_ui(wrap_gradio_gpu_call): run_preprocess = gr.Button(value="Preprocess", variant='primary') with gr.Tab(label="Train"): - gr.HTML(value="

Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images
Initial learning rates: 0.005 for an Embedding, 0.00001 for Hypernetwork [wiki]

") + gr.HTML(value="

Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images [wiki]

") with gr.Row(): train_embedding_name = gr.Dropdown(label='Embedding', elem_id="train_embedding", choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())) create_refresh_button(train_embedding_name, sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings, lambda: {"choices": sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())}, "refresh_train_embedding_name") with gr.Row(): train_hypernetwork_name = gr.Dropdown(label='Hypernetwork', elem_id="train_hypernetwork", choices=[x for x in shared.hypernetworks.keys()]) create_refresh_button(train_hypernetwork_name, shared.reload_hypernetworks, lambda: {"choices": sorted([x for x in shared.hypernetworks.keys()])}, "refresh_train_hypernetwork_name") - learn_rate = gr.Textbox(label='Learning rate', placeholder="Learning rate", value="0.005") + with gr.Row(): + embedding_learn_rate = gr.Textbox(label='Embedding Learning rate', placeholder="Embedding Learning rate", value="0.005") + hypernetwork_learn_rate = gr.Textbox(label='Hypernetwork Learning rate', placeholder="Hypernetwork Learning rate", value="0.00001") + batch_size = gr.Number(label='Batch size', value=1, precision=0) dataset_directory = gr.Textbox(label='Dataset directory', placeholder="Path to directory with input images") log_directory = gr.Textbox(label='Log directory', placeholder="Path to directory where to write outputs", value="textual_inversion") @@ -1288,6 +1293,7 @@ def create_ui(wrap_gradio_gpu_call): new_embedding_name, initialization_text, nvpt, + overwrite_old_embedding, ], outputs=[ train_embedding_name, @@ -1303,6 +1309,7 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_sizes, new_hypernetwork_layer_structure, new_hypernetwork_add_layer_norm, + overwrite_old_hypernetwork, ], outputs=[ train_hypernetwork_name, From 41e3877be2c667316515c86037413763eb0ba4da Mon Sep 17 00:00:00 2001 From: captin411 Date: Wed, 19 Oct 2022 13:44:59 -0700 Subject: [PATCH 0049/1118] fix entropy point calculation --- modules/textual_inversion/preprocess.py | 34 ++++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 168bfb09..7c1a594e 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -196,9 +196,9 @@ def image_focal_points(im): points = cv2.goodFeaturesToTrack( np_im, - maxCorners=50, + maxCorners=100, qualityLevel=0.04, - minDistance=min(grayscale.width, grayscale.height)*0.05, + minDistance=min(grayscale.width, grayscale.height)*0.07, useHarrisDetector=False, ) @@ -218,28 +218,32 @@ def image_focal_points(im): def image_entropy_point(im, crop_width, crop_height): - img = im.copy() - # just make it easier to slide the test crop with images oriented the same way - if (img.size[0] < img.size[1]): - portrait = True - img = img.rotate(90, expand=1) + landscape = im.height < im.width + portrait = im.height > im.width + if landscape: + move_idx = [0, 2] + move_max = im.size[0] + elif portrait: + move_idx = [1, 3] + move_max = im.size[1] e_max = 0 crop_current = [0, 0, crop_width, crop_height] crop_best = crop_current - while crop_current[2] < img.size[0]: - crop = img.crop(tuple(crop_current)) + while crop_current[move_idx[1]] < move_max: + crop = im.crop(tuple(crop_current)) e = image_entropy(crop) - if (e_max < e): + if (e > e_max): e_max = e crop_best = list(crop_current) - crop_current[0] += 4 - crop_current[2] += 4 + crop_current[move_idx[0]] += 4 + crop_current[move_idx[1]] += 4 + + x_mid = int(crop_best[0] + crop_width/2) + y_mid = int(crop_best[1] + crop_height/2) - x_mid = int((crop_best[2] - crop_best[0])/2) - y_mid = int((crop_best[3] - crop_best[1])/2) return { 'x': x_mid, @@ -250,7 +254,7 @@ def image_entropy_point(im, crop_width, crop_height): def image_entropy(im): # greyscale image entropy - band = np.asarray(im.convert("L")) + band = np.asarray(im.convert("1")) hist, _ = np.histogram(band, bins=range(0, 256)) hist = hist[hist > 0] return -np.log2(hist / hist.sum()).sum() From 8e7097d06a6a261580d34375c9d2a9e4ffc63ffa Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Wed, 19 Oct 2022 13:47:45 -0700 Subject: [PATCH 0050/1118] Added support for RunwayML inpainting model --- modules/processing.py | 34 +++++- modules/sd_hijack_inpainting.py | 208 ++++++++++++++++++++++++++++++++ modules/sd_models.py | 16 ++- modules/sd_samplers.py | 50 ++++++-- 4 files changed, 293 insertions(+), 15 deletions(-) create mode 100644 modules/sd_hijack_inpainting.py diff --git a/modules/processing.py b/modules/processing.py index bcb0c32c..a6c308f9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -546,7 +546,16 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if not self.enable_hr: x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) - samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning) + + # The "masked-image" in this case will just be all zeros since the entire image is masked. + image_conditioning = torch.zeros(x.shape[0], 3, self.height, self.width, device=x.device) + image_conditioning = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(image_conditioning)) + + # Add the fake full 1s mask to the first dimension. + image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0) + image_conditioning = image_conditioning.to(x.dtype) + + samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=image_conditioning) return samples x = create_random_tensors([opt_C, self.firstphase_height // opt_f, self.firstphase_width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) @@ -714,10 +723,31 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): elif self.inpainting_fill == 3: self.init_latent = self.init_latent * self.mask + if self.image_mask is not None: + conditioning_mask = np.array(self.image_mask.convert("L")) + conditioning_mask = conditioning_mask.astype(np.float32) / 255.0 + conditioning_mask = torch.from_numpy(conditioning_mask[None, None]) + + # Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0 + conditioning_mask = torch.round(conditioning_mask) + else: + conditioning_mask = torch.ones(1, 1, *image.shape[-2:]) + + # Create another latent image, this time with a masked version of the original input. + conditioning_mask = conditioning_mask.to(image.device) + conditioning_image = image * (1.0 - conditioning_mask) + conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(conditioning_image)) + + # Create the concatenated conditioning tensor to be fed to `c_concat` + conditioning_mask = torch.nn.functional.interpolate(conditioning_mask, size=self.init_latent.shape[-2:]) + conditioning_mask = conditioning_mask.expand(conditioning_image.shape[0], -1, -1, -1) + self.image_conditioning = torch.cat([conditioning_mask, conditioning_image], dim=1) + self.image_conditioning = self.image_conditioning.to(shared.device).type(self.sd_model.dtype) + def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) - samples = self.sampler.sample_img2img(self, self.init_latent, x, conditioning, unconditional_conditioning) + samples = self.sampler.sample_img2img(self, self.init_latent, x, conditioning, unconditional_conditioning, image_conditioning=self.image_conditioning) if self.mask is not None: samples = samples * self.nmask + self.init_latent * self.mask diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py new file mode 100644 index 00000000..7e5670d6 --- /dev/null +++ b/modules/sd_hijack_inpainting.py @@ -0,0 +1,208 @@ +import torch +import numpy as np + +from tqdm import tqdm +from einops import rearrange, repeat +from omegaconf import ListConfig + +from types import MethodType + +import ldm.models.diffusion.ddpm +import ldm.models.diffusion.ddim + +from ldm.models.diffusion.ddpm import LatentDiffusion +from ldm.models.diffusion.ddim import DDIMSampler, noise_like + +# ================================================================================================= +# Monkey patch DDIMSampler methods from RunwayML repo directly. +# Adapted from: +# https://github.com/runwayml/stable-diffusion/blob/main/ldm/models/diffusion/ddim.py +# ================================================================================================= +@torch.no_grad() +def sample( + self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + ctmp = conditioning[list(conditioning.keys())[0]] + while isinstance(ctmp, list): + ctmp = elf.inpainting_fill == 2: + self.init_latent = self.init_latent * self.mask + create_random_tensors(self.init_latent.shape[1:], all_seeds[0:self.init_latent.shape[0]]) * self.nmask + elif self.inpainting_fill == 3: + self.init_latent = self.init_latent * self.mask + + if self.image_mask is not None: + conditioning_mask = np.array(self.image_mask.convert("L")) + conditioning_mask = conditioning_mask.astype(np.float32) / 255.0 + conditioning_mask = torch.from_numpy(conditioning_mask[None, None]) + + # Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0 + conditioning_mask = torch.round(conditioning_mask) + else: + conditioning_mask = torch.ones(1, 1, *image.shape[-2:]) + + # Create another latent image, this time with a masked version of the original input. + conditioning_mask = conditioning_mask.to(image.device) + conditioning_image = image * (1.0 - conditioning_mask) + conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(conditioning_image)) + + # Create the concatenated conditioning tensor to be fed to `c_concat` + conditioning_mask = torch.nn.functional.interpolate(conditioning_mask, size=self.init_latent.shape[-2:]) + conditioning_mask = conditioning_mask.expand(conditioning_image.shape[0], -1, -1, -1) + self.image_conditioning = torch.cat([conditioning_mask, conditioning_image], dim=1) + self.image_conditioning = self.image_conditioning.to(shared.device).type(self.sd_model.dtype) + + def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): + x = create_random_tensors([opctmp[0] + cbs = ctmp.shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose) + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + print(f'Data shape for DDIM sampling is {size}, eta {eta}') + + samples, intermediates = self.ddim_sampling(conditioning, size, + callback=callback, + img_callback=img_callback, + quantize_denoised=quantize_x0, + mask=mask, x0=x0, + ddim_use_original_steps=False, + noise_dropout=noise_dropout, + temperature=temperature, + score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + x_T=x_T, + log_every_t=log_every_t, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + ) + return samples, intermediates + + +@torch.no_grad() +def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None): + b, *_, device = *x.shape, x.device + + if unconditional_conditioning is None or unconditional_guidance_scale == 1.: + e_t = self.model.apply_model(x, t, c) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t] * 2) + if isinstance(c, dict): + assert isinstance(unconditional_conditioning, dict) + c_in = dict() + for k in c: + if isinstance(c[k], list): + c_in[k] = [ + torch.cat([unconditional_conditioning[k][i], c[k][i]]) + for i in range(len(c[k])) + ] + else: + c_in[k] = torch.cat([unconditional_conditioning[k], c[k]]) + else: + c_in = torch.cat([unconditional_conditioning, c]) + e_t_uncond, e_t = self.model.apply_model(x_in, t_in, c_in).chunk(2) + e_t = e_t_uncond + unconditional_guidance_scale * (e_t - e_t_uncond) + + if score_corrector is not None: + assert self.model.parameterization == "eps" + e_t = score_corrector.modify_score(self.model, e_t, x, t, c, **corrector_kwargs) + + alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas + alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev + sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas + sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas + # select parameters corresponding to the currently considered timestep + a_t = torch.full((b, 1, 1, 1), alphas[index], device=device) + a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device) + sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device) + sqrt_one_minus_at = torch.full((b, 1, 1, 1), sqrt_one_minus_alphas[index],device=device) + + # current prediction for x_0 + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + if quantize_denoised: + pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) + # direction pointing to x_t + dir_xt = (1. - a_prev - sigma_t**2).sqrt() * e_t + noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise + return x_prev, pred_x0 + + +# ================================================================================================= +# Monkey patch LatentInpaintDiffusion to load the checkpoint with a proper config. +# Adapted from: +# https://github.com/runwayml/stable-diffusion/blob/main/ldm/models/diffusion/ddpm.py +# ================================================================================================= + +@torch.no_grad() +def get_unconditional_conditioning(self, batch_size, null_label=None): + if null_label is not None: + xc = null_label + if isinstance(xc, ListConfig): + xc = list(xc) + if isinstance(xc, dict) or isinstance(xc, list): + c = self.get_learned_conditioning(xc) + else: + if hasattr(xc, "to"): + xc = xc.to(self.device) + c = self.get_learned_conditioning(xc) + else: + # todo: get null label from cond_stage_model + raise NotImplementedError() + c = repeat(c, "1 ... -> b ...", b=batch_size).to(self.device) + return c + +class LatentInpaintDiffusion(LatentDiffusion): + def __init__( + self, + concat_keys=("mask", "masked_image"), + masked_image_key="masked_image", + *args, + **kwargs, + ): + super().__init__(*args, **kwargs) + self.masked_image_key = masked_image_key + assert self.masked_image_key in concat_keys + self.concat_keys = concat_keys + +def should_hijack_inpainting(checkpoint_info): + return str(checkpoint_info.filename).endswith("inpainting.ckpt") and not checkpoint_info.config.endswith("inpainting.yaml") + +def do_inpainting_hijack(): + ldm.models.diffusion.ddpm.get_unconditional_conditioning = get_unconditional_conditioning + ldm.models.diffusion.ddpm.LatentInpaintDiffusion = LatentInpaintDiffusion + ldm.models.diffusion.ddim.DDIMSampler.p_sample_ddim = p_sample_ddim + ldm.models.diffusion.ddim.DDIMSampler.sample = sample \ No newline at end of file diff --git a/modules/sd_models.py b/modules/sd_models.py index eae22e87..47836d25 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -9,6 +9,7 @@ from ldm.util import instantiate_from_config from modules import shared, modelloader, devices from modules.paths import models_path +from modules.sd_hijack_inpainting import do_inpainting_hijack, should_hijack_inpainting model_dir = "Stable-diffusion" model_path = os.path.abspath(os.path.join(models_path, model_dir)) @@ -211,6 +212,19 @@ def load_model(): print(f"Loading config from: {checkpoint_info.config}") sd_config = OmegaConf.load(checkpoint_info.config) + + if should_hijack_inpainting(checkpoint_info): + do_inpainting_hijack() + + # Hardcoded config for now... + sd_config.model.target = "ldm.models.diffusion.ddpm.LatentInpaintDiffusion" + sd_config.model.params.use_ema = False + sd_config.model.params.conditioning_key = "hybrid" + sd_config.model.params.unet_config.params.in_channels = 9 + + # Create a "fake" config with a different name so that we know to unload it when switching models. + checkpoint_info = checkpoint_info._replace(config=checkpoint_info.config.replace(".yaml", "-inpainting.yaml")) + sd_model = instantiate_from_config(sd_config.model) load_model_weights(sd_model, checkpoint_info) @@ -234,7 +248,7 @@ def reload_model_weights(sd_model, info=None): if sd_model.sd_model_checkpoint == checkpoint_info.filename: return - if sd_model.sd_checkpoint_info.config != checkpoint_info.config: + if sd_model.sd_checkpoint_info.config != checkpoint_info.config or should_hijack_inpainting(checkpoint_info) != should_hijack_inpainting(sd_model.sd_checkpoint_info): checkpoints_loaded.clear() shared.sd_model = load_model() return shared.sd_model diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index b58e810b..9d3cf289 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -136,9 +136,15 @@ class VanillaStableDiffusionSampler: if self.stop_at is not None and self.step > self.stop_at: raise InterruptedException + # Have to unwrap the inpainting conditioning here to perform pre-preocessing + image_conditioning = None + if isinstance(cond, dict): + image_conditioning = cond["c_concat"][0] + cond = cond["c_crossattn"][0] + unconditional_conditioning = unconditional_conditioning["c_crossattn"][0] conds_list, tensor = prompt_parser.reconstruct_multicond_batch(cond, self.step) - unconditional_conditioning = prompt_parser.reconstruct_cond_batch(unconditional_conditioning, self.step) + unconditional_conditioning = prompt_parser.reconstruct_cond_batch(unconditional_conditioning, self.step) assert all([len(conds) == 1 for conds in conds_list]), 'composition via AND is not supported for DDIM/PLMS samplers' cond = tensor @@ -157,6 +163,10 @@ class VanillaStableDiffusionSampler: img_orig = self.sampler.model.q_sample(self.init_latent, ts) x_dec = img_orig * self.mask + self.nmask * x_dec + if image_conditioning is not None: + cond = {"c_concat": [image_conditioning], "c_crossattn": [cond]} + unconditional_conditioning = {"c_concat": [image_conditioning], "c_crossattn": [unconditional_conditioning]} + res = self.orig_p_sample_ddim(x_dec, cond, ts, unconditional_conditioning=unconditional_conditioning, *args, **kwargs) if self.mask is not None: @@ -182,7 +192,7 @@ class VanillaStableDiffusionSampler: self.mask = p.mask if hasattr(p, 'mask') else None self.nmask = p.nmask if hasattr(p, 'nmask') else None - def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None): + def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): steps, t_enc = setup_img2img_steps(p, steps) self.initialize(p) @@ -202,7 +212,7 @@ class VanillaStableDiffusionSampler: return samples - def sample(self, p, x, conditioning, unconditional_conditioning, steps=None): + def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): self.initialize(p) self.init_latent = None @@ -210,6 +220,11 @@ class VanillaStableDiffusionSampler: steps = steps or p.steps + # Wrap the conditioning models with additional image conditioning for inpainting model + if image_conditioning is not None: + conditioning = {"c_concat": [image_conditioning], "c_crossattn": [conditioning]} + unconditional_conditioning = {"c_concat": [image_conditioning], "c_crossattn": [unconditional_conditioning]} + # existing code fails with certain step counts, like 9 try: samples_ddim = self.launch_sampling(steps, lambda: self.sampler.sample(S=steps, conditioning=conditioning, batch_size=int(x.shape[0]), shape=x[0].shape, verbose=False, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning, x_T=x, eta=self.eta)[0]) @@ -228,7 +243,7 @@ class CFGDenoiser(torch.nn.Module): self.init_latent = None self.step = 0 - def forward(self, x, sigma, uncond, cond, cond_scale): + def forward(self, x, sigma, uncond, cond, cond_scale, image_cond): if state.interrupted or state.skipped: raise InterruptedException @@ -239,28 +254,29 @@ class CFGDenoiser(torch.nn.Module): repeats = [len(conds_list[i]) for i in range(batch_size)] x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x]) + image_cond_in = torch.cat([torch.stack([image_cond[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [image_cond]) sigma_in = torch.cat([torch.stack([sigma[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [sigma]) if tensor.shape[1] == uncond.shape[1]: cond_in = torch.cat([tensor, uncond]) if shared.batch_cond_uncond: - x_out = self.inner_model(x_in, sigma_in, cond=cond_in) + x_out = self.inner_model(x_in, sigma_in, cond={"c_crossattn": [cond_in], "c_concat": [image_cond_in]}) else: x_out = torch.zeros_like(x_in) for batch_offset in range(0, x_out.shape[0], batch_size): a = batch_offset b = a + batch_size - x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond=cond_in[a:b]) + x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond={"c_crossattn": [cond_in[a:b]], "c_concat": [image_cond_in[a:b]]}) else: x_out = torch.zeros_like(x_in) batch_size = batch_size*2 if shared.batch_cond_uncond else batch_size for batch_offset in range(0, tensor.shape[0], batch_size): a = batch_offset b = min(a + batch_size, tensor.shape[0]) - x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond=tensor[a:b]) + x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond={"c_crossattn": [tensor[a:b]], "c_concat": [image_cond_in[a:b]]}) - x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], cond=uncond) + x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], cond={"c_crossattn": [uncond], "c_concat": [image_cond_in[-uncond.shape[0]:]]}) denoised_uncond = x_out[-uncond.shape[0]:] denoised = torch.clone(denoised_uncond) @@ -361,7 +377,7 @@ class KDiffusionSampler: return extra_params_kwargs - def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None): + def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): steps, t_enc = setup_img2img_steps(p, steps) if p.sampler_noise_scheduler_override: @@ -389,11 +405,16 @@ class KDiffusionSampler: self.model_wrap_cfg.init_latent = x - samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, xi, extra_args={'cond': conditioning, 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale}, disable=False, callback=self.callback_state, **extra_params_kwargs)) + samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, xi, extra_args={ + 'cond': conditioning, + 'image_cond': image_conditioning, + 'uncond': unconditional_conditioning, + 'cond_scale': p.cfg_scale + }, disable=False, callback=self.callback_state, **extra_params_kwargs)) return samples - def sample(self, p, x, conditioning, unconditional_conditioning, steps=None): + def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, image_conditioning = None): steps = steps or p.steps if p.sampler_noise_scheduler_override: @@ -414,7 +435,12 @@ class KDiffusionSampler: else: extra_params_kwargs['sigmas'] = sigmas - samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args={'cond': conditioning, 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale}, disable=False, callback=self.callback_state, **extra_params_kwargs)) + samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args={ + 'cond': conditioning, + 'image_cond': image_conditioning, + 'uncond': unconditional_conditioning, + 'cond_scale': p.cfg_scale + }, disable=False, callback=self.callback_state, **extra_params_kwargs)) return samples From 0719c10bf1b817364a498ee11b90d30d3d527344 Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Wed, 19 Oct 2022 13:56:26 -0700 Subject: [PATCH 0051/1118] Fixed copying mistake --- modules/sd_hijack_inpainting.py | 79 +++++++++++---------------------- 1 file changed, 25 insertions(+), 54 deletions(-) diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 7e5670d6..d4d28d2e 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -19,63 +19,35 @@ from ldm.models.diffusion.ddim import DDIMSampler, noise_like # https://github.com/runwayml/stable-diffusion/blob/main/ldm/models/diffusion/ddim.py # ================================================================================================= @torch.no_grad() -def sample( - self, - S, - batch_size, - shape, - conditioning=None, - callback=None, - normals_sequence=None, - img_callback=None, - quantize_x0=False, - eta=0., - mask=None, - x0=None, - temperature=1., - noise_dropout=0., - score_corrector=None, - corrector_kwargs=None, - verbose=True, - x_T=None, - log_every_t=100, - unconditional_guidance_scale=1., - unconditional_conditioning=None, - # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... - **kwargs - ): +def sample(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): if conditioning is not None: if isinstance(conditioning, dict): ctmp = conditioning[list(conditioning.keys())[0]] while isinstance(ctmp, list): - ctmp = elf.inpainting_fill == 2: - self.init_latent = self.init_latent * self.mask + create_random_tensors(self.init_latent.shape[1:], all_seeds[0:self.init_latent.shape[0]]) * self.nmask - elif self.inpainting_fill == 3: - self.init_latent = self.init_latent * self.mask - - if self.image_mask is not None: - conditioning_mask = np.array(self.image_mask.convert("L")) - conditioning_mask = conditioning_mask.astype(np.float32) / 255.0 - conditioning_mask = torch.from_numpy(conditioning_mask[None, None]) - - # Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0 - conditioning_mask = torch.round(conditioning_mask) - else: - conditioning_mask = torch.ones(1, 1, *image.shape[-2:]) - - # Create another latent image, this time with a masked version of the original input. - conditioning_mask = conditioning_mask.to(image.device) - conditioning_image = image * (1.0 - conditioning_mask) - conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(conditioning_image)) - - # Create the concatenated conditioning tensor to be fed to `c_concat` - conditioning_mask = torch.nn.functional.interpolate(conditioning_mask, size=self.init_latent.shape[-2:]) - conditioning_mask = conditioning_mask.expand(conditioning_image.shape[0], -1, -1, -1) - self.image_conditioning = torch.cat([conditioning_mask, conditioning_image], dim=1) - self.image_conditioning = self.image_conditioning.to(shared.device).type(self.sd_model.dtype) - - def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): - x = create_random_tensors([opctmp[0] + ctmp = ctmp[0] cbs = ctmp.shape[0] if cbs != batch_size: print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") @@ -106,7 +78,6 @@ def sample( ) return samples, intermediates - @torch.no_grad() def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, From dde9f960727bfe151d418e43685a2881cf580a17 Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Wed, 19 Oct 2022 14:14:24 -0700 Subject: [PATCH 0052/1118] added support for ddim img2img --- modules/sd_samplers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 9d3cf289..d270e4df 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -208,6 +208,12 @@ class VanillaStableDiffusionSampler: self.init_latent = x self.step = 0 + # Wrap the conditioning models with additional image conditioning for inpainting model + if image_conditioning is not None: + conditioning = {"c_concat": [image_conditioning], "c_crossattn": [conditioning]} + unconditional_conditioning = {"c_concat": [image_conditioning], "c_crossattn": [unconditional_conditioning]} + + samples = self.launch_sampling(steps, lambda: self.sampler.decode(x1, conditioning, t_enc, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning)) return samples From c418467c03db916c3e5312e6ac4a67365e196dbd Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Wed, 19 Oct 2022 15:09:43 -0700 Subject: [PATCH 0053/1118] Don't compute latent mask if were not using it. Also added support for fixed highres_fix generation. --- modules/processing.py | 70 ++++++++++++++++++++++++++---------------- modules/sd_samplers.py | 4 +++ 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index a6c308f9..684e5833 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -541,12 +541,8 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.truncate_y = int(self.firstphase_height - firstphase_height_truncated) // opt_f - def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): - self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers, self.sampler_index, self.sd_model) - - if not self.enable_hr: - x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) - + def create_dummy_mask(self, x): + if self.sampler.conditioning_key in {'hybrid', 'concat'}: # The "masked-image" in this case will just be all zeros since the entire image is masked. image_conditioning = torch.zeros(x.shape[0], 3, self.height, self.width, device=x.device) image_conditioning = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(image_conditioning)) @@ -555,11 +551,23 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0) image_conditioning = image_conditioning.to(x.dtype) - samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=image_conditioning) + else: + # Dummy zero conditioning if we're not using inpainting model. + # Still takes up a bit of memory, but no encoder call. + image_conditioning = torch.zeros(x.shape[0], 5, x.shape[-2], x.shape[-1], dtype=x.dtype, device=x.device) + + return image_conditioning + + def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): + self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers, self.sampler_index, self.sd_model) + + if not self.enable_hr: + x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) + samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.create_dummy_mask(x)) return samples x = create_random_tensors([opt_C, self.firstphase_height // opt_f, self.firstphase_width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) - samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning) + samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.create_dummy_mask(x)) samples = samples[:, :, self.truncate_y//2:samples.shape[2]-self.truncate_y//2, self.truncate_x//2:samples.shape[3]-self.truncate_x//2] @@ -596,7 +604,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): x = None devices.torch_gc() - samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps) + samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps, image_conditioning=self.create_dummy_mask(samples)) return samples @@ -723,26 +731,36 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): elif self.inpainting_fill == 3: self.init_latent = self.init_latent * self.mask - if self.image_mask is not None: - conditioning_mask = np.array(self.image_mask.convert("L")) - conditioning_mask = conditioning_mask.astype(np.float32) / 255.0 - conditioning_mask = torch.from_numpy(conditioning_mask[None, None]) + conditioning_key = self.sampler.conditioning_key - # Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0 - conditioning_mask = torch.round(conditioning_mask) + if conditioning_key in {'hybrid', 'concat'}: + if self.image_mask is not None: + conditioning_mask = np.array(self.image_mask.convert("L")) + conditioning_mask = conditioning_mask.astype(np.float32) / 255.0 + conditioning_mask = torch.from_numpy(conditioning_mask[None, None]) + + # Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0 + conditioning_mask = torch.round(conditioning_mask) + else: + conditioning_mask = torch.ones(1, 1, *image.shape[-2:]) + + # Create another latent image, this time with a masked version of the original input. + conditioning_mask = conditioning_mask.to(image.device) + conditioning_image = image * (1.0 - conditioning_mask) + conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(conditioning_image)) + + # Create the concatenated conditioning tensor to be fed to `c_concat` + conditioning_mask = torch.nn.functional.interpolate(conditioning_mask, size=self.init_latent.shape[-2:]) + conditioning_mask = conditioning_mask.expand(conditioning_image.shape[0], -1, -1, -1) + self.image_conditioning = torch.cat([conditioning_mask, conditioning_image], dim=1) + self.image_conditioning = self.image_conditioning.to(shared.device).type(self.sd_model.dtype) else: - conditioning_mask = torch.ones(1, 1, *image.shape[-2:]) + self.image_conditioning = torch.zeros( + self.init_latent.shape[0], 5, self.init_latent.shape[-2], self.init_latent.shape[-1], + dtype=self.init_latent.dtype, + device=self.init_latent.device + ) - # Create another latent image, this time with a masked version of the original input. - conditioning_mask = conditioning_mask.to(image.device) - conditioning_image = image * (1.0 - conditioning_mask) - conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(conditioning_image)) - - # Create the concatenated conditioning tensor to be fed to `c_concat` - conditioning_mask = torch.nn.functional.interpolate(conditioning_mask, size=self.init_latent.shape[-2:]) - conditioning_mask = conditioning_mask.expand(conditioning_image.shape[0], -1, -1, -1) - self.image_conditioning = torch.cat([conditioning_mask, conditioning_image], dim=1) - self.image_conditioning = self.image_conditioning.to(shared.device).type(self.sd_model.dtype) def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength): x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index d270e4df..c21be26e 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -117,6 +117,8 @@ class VanillaStableDiffusionSampler: self.config = None self.last_latent = None + self.conditioning_key = sd_model.model.conditioning_key + def number_of_needed_noises(self, p): return 0 @@ -328,6 +330,8 @@ class KDiffusionSampler: self.config = None self.last_latent = None + self.conditioning_key = sd_model.model.conditioning_key + def callback_state(self, d): step = d['i'] latent = d["denoised"] From d6ea5841374a28f3f6deb73abc251c8f0bcb240f Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:07:57 +0100 Subject: [PATCH 0054/1118] change html output --- modules/hypernetworks/hypernetwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 7d519cd9..73c1cb80 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -380,7 +380,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log Loss: {mean_loss:.7f}
Step: {hypernetwork.step}
Last prompt: {html.escape(entries[0].cond_text)}
-Last saved embedding: {html.escape(last_saved_file)}
+Last saved hypernetwork: {html.escape(last_saved_file)}
Last saved image: {html.escape(last_saved_image)}

""" From 166be3919b817cee5e702fd01c34afe9081b952c Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:09:40 +0100 Subject: [PATCH 0055/1118] allow overwrite old hn --- modules/hypernetworks/ui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 08f75f15..f45345ea 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -10,9 +10,10 @@ from modules import sd_hijack, shared, devices from modules.hypernetworks import hypernetwork -def create_hypernetwork(name, enable_sizes, layer_structure=None, add_layer_norm=False): +def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, add_layer_norm=False): fn = os.path.join(shared.cmd_opts.hypernetwork_dir, f"{name}.pt") - assert not os.path.exists(fn), f"file {fn} already exists" + if not overwrite_old: + assert not os.path.exists(fn), f"file {fn} already exists" if type(layer_structure) == str: layer_structure = tuple(map(int, re.sub(r'\D', '', layer_structure))) From 0087079c2d487b67b06ffc30f36ce486a74e6318 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:10:59 +0100 Subject: [PATCH 0056/1118] allow overwrite old embedding --- modules/textual_inversion/textual_inversion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 3be69562..5776778b 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -153,7 +153,7 @@ class EmbeddingDatabase: return None, None -def create_embedding(name, num_vectors_per_token, init_text='*'): +def create_embedding(name, num_vectors_per_token, overwrite_old, init_text='*'): cond_model = shared.sd_model.cond_stage_model embedding_layer = cond_model.wrapped.transformer.text_model.embeddings @@ -165,7 +165,8 @@ def create_embedding(name, num_vectors_per_token, init_text='*'): vec[i] = embedded[i * int(embedded.shape[0]) // num_vectors_per_token] fn = os.path.join(shared.cmd_opts.embeddings_dir, f"{name}.pt") - assert not os.path.exists(fn), f"file {fn} already exists" + if not overwrite_old: + assert not os.path.exists(fn), f"file {fn} already exists" embedding = Embedding(vec, name) embedding.step = 0 From 632e8d660293081cadb145d8062e5aff0a4a8f0d Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:19:40 +0100 Subject: [PATCH 0057/1118] split learn rates --- modules/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index cdb9d335..d07184ee 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1342,7 +1342,7 @@ def create_ui(wrap_gradio_gpu_call): _js="start_training_textual_inversion", inputs=[ train_embedding_name, - learn_rate, + embedding_learn_rate, batch_size, dataset_directory, log_directory, @@ -1367,7 +1367,7 @@ def create_ui(wrap_gradio_gpu_call): _js="start_training_textual_inversion", inputs=[ train_hypernetwork_name, - learn_rate, + hypernetwork_learn_rate, batch_size, dataset_directory, log_directory, From c3835ec85cbb44fa3c46fa871c622b6fee235c89 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:24:24 +0100 Subject: [PATCH 0058/1118] pass overwrite old flag --- modules/textual_inversion/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/textual_inversion/ui.py b/modules/textual_inversion/ui.py index 36881e7a..e712284d 100644 --- a/modules/textual_inversion/ui.py +++ b/modules/textual_inversion/ui.py @@ -7,8 +7,8 @@ import modules.textual_inversion.preprocess from modules import sd_hijack, shared -def create_embedding(name, initialization_text, nvpt): - filename = modules.textual_inversion.textual_inversion.create_embedding(name, nvpt, init_text=initialization_text) +def create_embedding(name, initialization_text, nvpt, overwrite_old): + filename = modules.textual_inversion.textual_inversion.create_embedding(name, nvpt, overwrite_old, init_text=initialization_text) sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() From 4d6b9f76a55fd0ac0f72634071032dd9c6efb409 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:27:16 +0100 Subject: [PATCH 0059/1118] reorder create_hypernetwork params --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index d07184ee..322c082b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1307,9 +1307,9 @@ def create_ui(wrap_gradio_gpu_call): inputs=[ new_hypernetwork_name, new_hypernetwork_sizes, + overwrite_old_hypernetwork, new_hypernetwork_layer_structure, new_hypernetwork_add_layer_norm, - overwrite_old_hypernetwork, ], outputs=[ train_hypernetwork_name, From fbcce66601994f6ed370db36d9c238840fed6bd2 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:46:54 +0100 Subject: [PATCH 0060/1118] add existing caption file handling --- modules/textual_inversion/preprocess.py | 32 ++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 886cf0c3..5c43fe13 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -48,7 +48,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro shared.state.textinfo = "Preprocessing..." shared.state.job_count = len(files) - def save_pic_with_caption(image, index): + def save_pic_with_caption(image, index, existing_caption=None): caption = "" if process_caption: @@ -66,17 +66,26 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro basename = f"{index:05}-{subindex[0]}-{filename_part}" image.save(os.path.join(dst, f"{basename}.png")) + if preprocess_txt_action == 'prepend' and existing_caption: + caption = existing_caption + ' ' + caption + elif preprocess_txt_action == 'append' and existing_caption: + caption = caption + ' ' + existing_caption + elif preprocess_txt_action == 'copy' and existing_caption: + caption = existing_caption + + caption = caption.strip() + if len(caption) > 0: with open(os.path.join(dst, f"{basename}.txt"), "w", encoding="utf8") as file: file.write(caption) subindex[0] += 1 - def save_pic(image, index): + def save_pic(image, index, existing_caption=None): save_pic_with_caption(image, index) if process_flip: - save_pic_with_caption(ImageOps.mirror(image), index) + save_pic_with_caption(ImageOps.mirror(image), index, existing_caption=existing_caption) for index, imagefile in enumerate(tqdm.tqdm(files)): subindex = [0] @@ -86,6 +95,13 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro except Exception: continue + existing_caption = None + + try: + existing_caption = open(os.path.splitext(filename)[0] + '.txt', 'r').read() + except Exception as e: + print(e) + if shared.state.interrupted: break @@ -97,20 +113,20 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro img = img.resize((width, height * img.height // img.width)) top = img.crop((0, 0, width, height)) - save_pic(top, index) + save_pic(top, index, existing_caption=existing_caption) bot = img.crop((0, img.height - height, width, img.height)) - save_pic(bot, index) + save_pic(bot, index, existing_caption=existing_caption) elif process_split and is_wide: img = img.resize((width * img.width // img.height, height)) left = img.crop((0, 0, width, height)) - save_pic(left, index) + save_pic(left, index, existing_caption=existing_caption) right = img.crop((img.width - width, 0, img.width, height)) - save_pic(right, index) + save_pic(right, index, existing_caption=existing_caption) else: img = images.resize_image(1, img, width, height) - save_pic(img, index) + save_pic(img, index, existing_caption=existing_caption) shared.state.nextjob() From ab353b141df8eee042b0964bcb645015dabf3459 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:48:07 +0100 Subject: [PATCH 0061/1118] link existing txt option --- modules/ui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ui.py b/modules/ui.py index 322c082b..7f52ac0c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1234,6 +1234,7 @@ def create_ui(wrap_gradio_gpu_call): process_dst = gr.Textbox(label='Destination directory') process_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) process_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) + preprocess_txt_action = gr.Dropdown(label='Existing Caption txt Action', choices=['ignore', 'copy', 'prepend', 'append']) with gr.Row(): process_flip = gr.Checkbox(label='Create flipped copies') @@ -1326,6 +1327,7 @@ def create_ui(wrap_gradio_gpu_call): process_dst, process_width, process_height, + preprocess_txt_action, process_flip, process_split, process_caption, From 9b65c4ecf4f8eb6187ee721918adebe68e9bc631 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:49:23 +0100 Subject: [PATCH 0062/1118] pass preprocess_txt_action param --- modules/textual_inversion/preprocess.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 5c43fe13..3713bc89 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -11,7 +11,7 @@ if cmd_opts.deepdanbooru: import modules.deepbooru as deepbooru -def preprocess(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False): +def preprocess(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False): try: if process_caption: shared.interrogator.load() @@ -21,7 +21,7 @@ def preprocess(process_src, process_dst, process_width, process_height, process_ db_opts[deepbooru.OPT_INCLUDE_RANKS] = False deepbooru.create_deepbooru_process(opts.interrogate_deepbooru_score_threshold, db_opts) - preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru) + preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru) finally: @@ -33,7 +33,7 @@ def preprocess(process_src, process_dst, process_width, process_height, process_ -def preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False): +def preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False): width = process_width height = process_height src = os.path.abspath(process_src) From 55d8c6cce6d3aef848b9f194adad2ce53064d8b7 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:53:29 +0100 Subject: [PATCH 0063/1118] default to ignore existing captions --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 7f52ac0c..bd5f1b05 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1234,7 +1234,7 @@ def create_ui(wrap_gradio_gpu_call): process_dst = gr.Textbox(label='Destination directory') process_width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) process_height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) - preprocess_txt_action = gr.Dropdown(label='Existing Caption txt Action', choices=['ignore', 'copy', 'prepend', 'append']) + preprocess_txt_action = gr.Dropdown(label='Existing Caption txt Action', value="ignore", choices=["ignore", "copy", "prepend", "append"]) with gr.Row(): process_flip = gr.Checkbox(label='Create flipped copies') From 8b74b9aa9a20e4c5c1f72641f8b9617479eb276b Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Wed, 19 Oct 2022 19:06:14 -0500 Subject: [PATCH 0064/1118] add symbol for clear button and simplify roll_col css selector --- modules/ui.py | 2 ++ style.css | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index a2dbd41e..9f6edc5f 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -83,6 +83,7 @@ folder_symbol = '\U0001f4c2' # 📂 refresh_symbol = '\U0001f504' # 🔄 save_style_symbol = '\U0001f4be' # 💾 apply_style_symbol = '\U0001f4cb' # 📋 +trash_prompt_symbol = '\U0001F5D1' # 🗑🗑🗑 def plaintext_to_html(text): @@ -498,6 +499,7 @@ def create_toprow(is_img2img): paste = gr.Button(value=paste_symbol, elem_id="paste") save_style = gr.Button(value=save_style_symbol, elem_id="style_create") prompt_style_apply = gr.Button(value=apply_style_symbol, elem_id="style_apply") + trash_prompt = gr.Button(value=trash_prompt_symbol, elem_id="trash_prompt") token_counter = gr.HTML(value="", elem_id=f"{id_part}_token_counter") token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") diff --git a/style.css b/style.css index 26ae36a5..21a8911f 100644 --- a/style.css +++ b/style.css @@ -114,7 +114,7 @@ padding: 0.4em 0; } -#roll, #paste, #style_create, #style_apply{ +#roll_col > button { min-width: 2em; min-height: 2em; max-width: 2em; From 6f98e89486f55b0e4657e96ce640cf1c4675d187 Mon Sep 17 00:00:00 2001 From: discus0434 Date: Thu, 20 Oct 2022 00:10:45 +0000 Subject: [PATCH 0065/1118] update --- modules/hypernetworks/hypernetwork.py | 29 +++++++++++------ modules/hypernetworks/ui.py | 3 +- modules/ui.py | 45 ++++++++++++++------------- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 74300122..7d617680 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -22,16 +22,20 @@ from modules.textual_inversion.learn_schedule import LearnRateScheduler class HypernetworkModule(torch.nn.Module): multiplier = 1.0 - def __init__(self, dim, state_dict=None, layer_structure=None, add_layer_norm=False): + def __init__(self, dim, state_dict=None, layer_structure=None, add_layer_norm=False, activation_func=None): super().__init__() - assert layer_structure is not None, "layer_structure mut not be None" + assert layer_structure is not None, "layer_structure must not be None" assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!" assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!" linears = [] for i in range(len(layer_structure) - 1): linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) + if activation_func == "relu": + linears.append(torch.nn.ReLU()) + if activation_func == "leakyrelu": + linears.append(torch.nn.LeakyReLU()) if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) @@ -42,8 +46,9 @@ class HypernetworkModule(torch.nn.Module): self.load_state_dict(state_dict) else: for layer in self.linear: - layer.weight.data.normal_(mean=0.0, std=0.01) - layer.bias.data.zero_() + if not "ReLU" in layer.__str__(): + layer.weight.data.normal_(mean=0.0, std=0.01) + layer.bias.data.zero_() self.to(devices.device) @@ -69,7 +74,8 @@ class HypernetworkModule(torch.nn.Module): def trainables(self): layer_structure = [] for layer in self.linear: - layer_structure += [layer.weight, layer.bias] + if not "ReLU" in layer.__str__(): + layer_structure += [layer.weight, layer.bias] return layer_structure @@ -81,7 +87,7 @@ class Hypernetwork: filename = None name = None - def __init__(self, name=None, enable_sizes=None, layer_structure=None, add_layer_norm=False): + def __init__(self, name=None, enable_sizes=None, layer_structure=None, add_layer_norm=False, activation_func=None): self.filename = None self.name = name self.layers = {} @@ -90,11 +96,12 @@ class Hypernetwork: self.sd_checkpoint_name = None self.layer_structure = layer_structure self.add_layer_norm = add_layer_norm + self.activation_func = activation_func for size in enable_sizes or []: self.layers[size] = ( - HypernetworkModule(size, None, self.layer_structure, self.add_layer_norm), - HypernetworkModule(size, None, self.layer_structure, self.add_layer_norm), + HypernetworkModule(size, None, self.layer_structure, self.add_layer_norm, self.activation_func), + HypernetworkModule(size, None, self.layer_structure, self.add_layer_norm, self.activation_func), ) def weights(self): @@ -117,6 +124,7 @@ class Hypernetwork: state_dict['name'] = self.name state_dict['layer_structure'] = self.layer_structure state_dict['is_layer_norm'] = self.add_layer_norm + state_dict['activation_func'] = self.activation_func state_dict['sd_checkpoint'] = self.sd_checkpoint state_dict['sd_checkpoint_name'] = self.sd_checkpoint_name @@ -131,12 +139,13 @@ class Hypernetwork: self.layer_structure = state_dict.get('layer_structure', [1, 2, 1]) self.add_layer_norm = state_dict.get('is_layer_norm', False) + self.activation_func = state_dict.get('activation_func', None) for size, sd in state_dict.items(): if type(size) == int: self.layers[size] = ( - HypernetworkModule(size, sd[0], self.layer_structure, self.add_layer_norm), - HypernetworkModule(size, sd[1], self.layer_structure, self.add_layer_norm), + HypernetworkModule(size, sd[0], self.layer_structure, self.add_layer_norm, self.activation_func), + HypernetworkModule(size, sd[1], self.layer_structure, self.add_layer_norm, self.activation_func), ) self.name = state_dict.get('name', self.name) diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 08f75f15..83f9547b 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -10,7 +10,7 @@ from modules import sd_hijack, shared, devices from modules.hypernetworks import hypernetwork -def create_hypernetwork(name, enable_sizes, layer_structure=None, add_layer_norm=False): +def create_hypernetwork(name, enable_sizes, layer_structure=None, add_layer_norm=False, activation_func=None): fn = os.path.join(shared.cmd_opts.hypernetwork_dir, f"{name}.pt") assert not os.path.exists(fn), f"file {fn} already exists" @@ -22,6 +22,7 @@ def create_hypernetwork(name, enable_sizes, layer_structure=None, add_layer_norm enable_sizes=[int(x) for x in enable_sizes], layer_structure=layer_structure, add_layer_norm=add_layer_norm, + activation_func=activation_func, ) hypernet.save(fn) diff --git a/modules/ui.py b/modules/ui.py index d2e24880..8751fa9c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -5,43 +5,44 @@ import json import math import mimetypes import os +import platform import random +import subprocess as sp import sys import tempfile import time import traceback -import platform -import subprocess as sp from functools import partial, reduce +import gradio as gr +import gradio.routes +import gradio.utils import numpy as np +import piexif import torch from PIL import Image, PngImagePlugin -import piexif -import gradio as gr -import gradio.utils -import gradio.routes - -from modules import sd_hijack, sd_models, localization +from modules import localization, sd_hijack, sd_models from modules.paths import script_path -from modules.shared import opts, cmd_opts, restricted_opts +from modules.shared import cmd_opts, opts, restricted_opts + if cmd_opts.deepdanbooru: from modules.deepbooru import get_deepbooru_tags -import modules.shared as shared -from modules.sd_samplers import samplers, samplers_for_img2img -from modules.sd_hijack import model_hijack -import modules.ldsr_model -import modules.scripts -import modules.gfpgan_model + import modules.codeformer_model -import modules.styles import modules.generation_parameters_copypaste -from modules import prompt_parser -from modules.images import save_image -import modules.textual_inversion.ui +import modules.gfpgan_model import modules.hypernetworks.ui import modules.images_history as img_his +import modules.ldsr_model +import modules.scripts +import modules.shared as shared +import modules.styles +import modules.textual_inversion.ui +from modules import prompt_parser +from modules.images import save_image +from modules.sd_hijack import model_hijack +from modules.sd_samplers import samplers, samplers_for_img2img # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() @@ -268,8 +269,8 @@ def calc_time_left(progress, threshold, label, force_display): time_since_start = time.time() - shared.state.time_start eta = (time_since_start/progress) eta_relative = eta-time_since_start - if (eta_relative > threshold and progress > 0.02) or force_display: - return label + time.strftime('%H:%M:%S', time.gmtime(eta_relative)) + if (eta_relative > threshold and progress > 0.02) or force_display: + return label + time.strftime('%H:%M:%S', time.gmtime(eta_relative)) else: return "" @@ -1219,6 +1220,7 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "320", "640", "1280"]) new_hypernetwork_layer_structure = gr.Textbox("1, 2, 1", label="Enter hypernetwork layer structure", placeholder="1st and last digit must be 1. ex:'1, 2, 1'") new_hypernetwork_add_layer_norm = gr.Checkbox(label="Add layer normalization") + new_hypernetwork_activation_func = gr.Dropdown(value="relu", label="Select activation function of hypernetwork", choices=["relu", "leakyrelu"]) with gr.Row(): with gr.Column(scale=3): @@ -1303,6 +1305,7 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_sizes, new_hypernetwork_layer_structure, new_hypernetwork_add_layer_norm, + new_hypernetwork_activation_func, ], outputs=[ train_hypernetwork_name, From ba469343e6a1c6e23e82acf5feb65c6101dacbb2 Mon Sep 17 00:00:00 2001 From: discus0434 Date: Thu, 20 Oct 2022 00:17:04 +0000 Subject: [PATCH 0066/1118] align ui.py imports with upstream --- modules/ui.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 987b1d7d..913b23b4 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -5,44 +5,43 @@ import json import math import mimetypes import os -import platform import random -import subprocess as sp import sys import tempfile import time import traceback +import platform +import subprocess as sp from functools import partial, reduce -import gradio as gr -import gradio.routes -import gradio.utils import numpy as np -import piexif import torch from PIL import Image, PngImagePlugin +import piexif -from modules import localization, sd_hijack, sd_models +import gradio as gr +import gradio.utils +import gradio.routes + +from modules import sd_hijack, sd_models, localization from modules.paths import script_path -from modules.shared import cmd_opts, opts, restricted_opts - +from modules.shared import opts, cmd_opts, restricted_opts if cmd_opts.deepdanbooru: from modules.deepbooru import get_deepbooru_tags - -import modules.codeformer_model -import modules.generation_parameters_copypaste -import modules.gfpgan_model -import modules.hypernetworks.ui -import modules.images_history as img_his +import modules.shared as shared +from modules.sd_samplers import samplers, samplers_for_img2img +from modules.sd_hijack import model_hijack import modules.ldsr_model import modules.scripts -import modules.shared as shared +import modules.gfpgan_model +import modules.codeformer_model import modules.styles -import modules.textual_inversion.ui +import modules.generation_parameters_copypaste from modules import prompt_parser from modules.images import save_image -from modules.sd_hijack import model_hijack -from modules.sd_samplers import samplers, samplers_for_img2img +import modules.textual_inversion.ui +import modules.hypernetworks.ui +import modules.images_history as img_his # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() From 59ed74438318af893d2cba552b0e28dbc2a9266c Mon Sep 17 00:00:00 2001 From: captin411 Date: Wed, 19 Oct 2022 17:19:02 -0700 Subject: [PATCH 0067/1118] face detection algo, configurability, reusability Try to move the crop in the direction of a face if it is present More internal configuration options for choosing weights of each of the algorithm's findings Move logic into its module --- modules/textual_inversion/autocrop.py | 216 ++++++++++++++++++++++++ modules/textual_inversion/preprocess.py | 150 ++-------------- 2 files changed, 230 insertions(+), 136 deletions(-) create mode 100644 modules/textual_inversion/autocrop.py diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py new file mode 100644 index 00000000..f858a958 --- /dev/null +++ b/modules/textual_inversion/autocrop.py @@ -0,0 +1,216 @@ +import cv2 +from collections import defaultdict +from math import log, sqrt +import numpy as np +from PIL import Image, ImageDraw + +GREEN = "#0F0" +BLUE = "#00F" +RED = "#F00" + +def crop_image(im, settings): + """ Intelligently crop an image to the subject matter """ + if im.height > im.width: + im = im.resize((settings.crop_width, settings.crop_height * im.height // im.width)) + else: + im = im.resize((settings.crop_width * im.width // im.height, settings.crop_height)) + + focus = focal_point(im, settings) + + # take the focal point and turn it into crop coordinates that try to center over the focal + # point but then get adjusted back into the frame + y_half = int(settings.crop_height / 2) + x_half = int(settings.crop_width / 2) + + x1 = focus.x - x_half + if x1 < 0: + x1 = 0 + elif x1 + settings.crop_width > im.width: + x1 = im.width - settings.crop_width + + y1 = focus.y - y_half + if y1 < 0: + y1 = 0 + elif y1 + settings.crop_height > im.height: + y1 = im.height - settings.crop_height + + x2 = x1 + settings.crop_width + y2 = y1 + settings.crop_height + + crop = [x1, y1, x2, y2] + + if settings.annotate_image: + d = ImageDraw.Draw(im) + rect = list(crop) + rect[2] -= 1 + rect[3] -= 1 + d.rectangle(rect, outline=GREEN) + if settings.destop_view_image: + im.show() + + return im.crop(tuple(crop)) + +def focal_point(im, settings): + corner_points = image_corner_points(im, settings) + entropy_points = image_entropy_points(im, settings) + face_points = image_face_points(im, settings) + + total_points = len(corner_points) + len(entropy_points) + len(face_points) + + corner_weight = settings.corner_points_weight + entropy_weight = settings.entropy_points_weight + face_weight = settings.face_points_weight + + weight_pref_total = corner_weight + entropy_weight + face_weight + + # weight things + pois = [] + if weight_pref_total == 0 or total_points == 0: + return pois + + pois.extend( + [ PointOfInterest( p.x, p.y, weight=p.weight * ( (corner_weight/weight_pref_total) / (len(corner_points)/total_points) )) for p in corner_points ] + ) + pois.extend( + [ PointOfInterest( p.x, p.y, weight=p.weight * ( (entropy_weight/weight_pref_total) / (len(entropy_points)/total_points) )) for p in entropy_points ] + ) + pois.extend( + [ PointOfInterest( p.x, p.y, weight=p.weight * ( (face_weight/weight_pref_total) / (len(face_points)/total_points) )) for p in face_points ] + ) + + if settings.annotate_image: + d = ImageDraw.Draw(im) + + average_point = poi_average(pois, settings, im=im) + + if settings.annotate_image: + d.ellipse([average_point.x - 25, average_point.y - 25, average_point.x + 25, average_point.y + 25], outline=GREEN) + + return average_point + + +def image_face_points(im, settings): + np_im = np.array(im) + gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY) + classifier = cv2.CascadeClassifier(f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml') + + minsize = int(min(im.width, im.height) * 0.15) # at least N percent of the smallest side + faces = classifier.detectMultiScale(gray, scaleFactor=1.05, + minNeighbors=5, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) + + if len(faces) == 0: + return [] + + rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces] + if settings.annotate_image: + for f in rects: + d = ImageDraw.Draw(im) + d.rectangle(f, outline=RED) + + return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2) for r in rects] + + +def image_corner_points(im, settings): + grayscale = im.convert("L") + + # naive attempt at preventing focal points from collecting at watermarks near the bottom + gd = ImageDraw.Draw(grayscale) + gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999") + + np_im = np.array(grayscale) + + points = cv2.goodFeaturesToTrack( + np_im, + maxCorners=100, + qualityLevel=0.04, + minDistance=min(grayscale.width, grayscale.height)*0.07, + useHarrisDetector=False, + ) + + if points is None: + return [] + + focal_points = [] + for point in points: + x, y = point.ravel() + focal_points.append(PointOfInterest(x, y)) + + return focal_points + + +def image_entropy_points(im, settings): + landscape = im.height < im.width + portrait = im.height > im.width + if landscape: + move_idx = [0, 2] + move_max = im.size[0] + elif portrait: + move_idx = [1, 3] + move_max = im.size[1] + else: + return [] + + e_max = 0 + crop_current = [0, 0, settings.crop_width, settings.crop_height] + crop_best = crop_current + while crop_current[move_idx[1]] < move_max: + crop = im.crop(tuple(crop_current)) + e = image_entropy(crop) + + if (e > e_max): + e_max = e + crop_best = list(crop_current) + + crop_current[move_idx[0]] += 4 + crop_current[move_idx[1]] += 4 + + x_mid = int(crop_best[0] + settings.crop_width/2) + y_mid = int(crop_best[1] + settings.crop_height/2) + + return [PointOfInterest(x_mid, y_mid)] + + +def image_entropy(im): + # greyscale image entropy + band = np.asarray(im.convert("1")) + hist, _ = np.histogram(band, bins=range(0, 256)) + hist = hist[hist > 0] + return -np.log2(hist / hist.sum()).sum() + + +def poi_average(pois, settings, im=None): + weight = 0.0 + x = 0.0 + y = 0.0 + for pois in pois: + if settings.annotate_image and im is not None: + w = 4 * 0.5 * sqrt(pois.weight) + d = ImageDraw.Draw(im) + d.ellipse([ + pois.x - w, pois.y - w, + pois.x + w, pois.y + w ], fill=BLUE) + weight += pois.weight + x += pois.x * pois.weight + y += pois.y * pois.weight + avg_x = round(x / weight) + avg_y = round(y / weight) + + return PointOfInterest(avg_x, avg_y) + + +class PointOfInterest: + def __init__(self, x, y, weight=1.0): + self.x = x + self.y = y + self.weight = weight + + +class Settings: + def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False): + self.crop_width = crop_width + self.crop_height = crop_height + self.corner_points_weight = corner_points_weight + self.entropy_points_weight = entropy_points_weight + self.face_points_weight = entropy_points_weight + self.annotate_image = annotate_image + self.destop_view_image = False \ No newline at end of file diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 7c1a594e..0c79f012 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -1,7 +1,5 @@ import os -import cv2 -import numpy as np -from PIL import Image, ImageOps, ImageDraw +from PIL import Image, ImageOps import platform import sys import tqdm @@ -9,6 +7,7 @@ import time from modules import shared, images from modules.shared import opts, cmd_opts +from modules.textual_inversion import autocrop if cmd_opts.deepdanbooru: import modules.deepbooru as deepbooru @@ -80,6 +79,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro if process_flip: save_pic_with_caption(ImageOps.mirror(image), index) + for index, imagefile in enumerate(tqdm.tqdm(files)): subindex = [0] filename = os.path.join(src, imagefile) @@ -118,37 +118,16 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro processing_option_ran = True - if process_entropy_focus and (is_tall or is_wide): - if is_tall: - img = img.resize((width, height * img.height // img.width)) - else: - img = img.resize((width * img.width // img.height, height)) - - x_focal_center, y_focal_center = image_central_focal_point(img, width, height) - - # take the focal point and turn it into crop coordinates that try to center over the focal - # point but then get adjusted back into the frame - y_half = int(height / 2) - x_half = int(width / 2) - - x1 = x_focal_center - x_half - if x1 < 0: - x1 = 0 - elif x1 + width > img.width: - x1 = img.width - width - - y1 = y_focal_center - y_half - if y1 < 0: - y1 = 0 - elif y1 + height > img.height: - y1 = img.height - height - - x2 = x1 + width - y2 = y1 + height - - crop = [x1, y1, x2, y2] - - focal = img.crop(tuple(crop)) + if process_entropy_focus and img.height != img.width: + autocrop_settings = autocrop.Settings( + crop_width = width, + crop_height = height, + face_points_weight = 0.9, + entropy_points_weight = 0.7, + corner_points_weight = 0.5, + annotate_image = False + ) + focal = autocrop.crop_image(img, autocrop_settings) save_pic(focal, index) processing_option_ran = True @@ -157,105 +136,4 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro img = images.resize_image(1, img, width, height) save_pic(img, index) - shared.state.nextjob() - - -def image_central_focal_point(im, target_width, target_height): - focal_points = [] - - focal_points.extend( - image_focal_points(im) - ) - - fp_entropy = image_entropy_point(im, target_width, target_height) - fp_entropy['weight'] = len(focal_points) + 1 # about half of the weight to entropy - - focal_points.append(fp_entropy) - - weight = 0.0 - x = 0.0 - y = 0.0 - for focal_point in focal_points: - weight += focal_point['weight'] - x += focal_point['x'] * focal_point['weight'] - y += focal_point['y'] * focal_point['weight'] - avg_x = round(x // weight) - avg_y = round(y // weight) - - return avg_x, avg_y - - -def image_focal_points(im): - grayscale = im.convert("L") - - # naive attempt at preventing focal points from collecting at watermarks near the bottom - gd = ImageDraw.Draw(grayscale) - gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999") - - np_im = np.array(grayscale) - - points = cv2.goodFeaturesToTrack( - np_im, - maxCorners=100, - qualityLevel=0.04, - minDistance=min(grayscale.width, grayscale.height)*0.07, - useHarrisDetector=False, - ) - - if points is None: - return [] - - focal_points = [] - for point in points: - x, y = point.ravel() - focal_points.append({ - 'x': x, - 'y': y, - 'weight': 1.0 - }) - - return focal_points - - -def image_entropy_point(im, crop_width, crop_height): - landscape = im.height < im.width - portrait = im.height > im.width - if landscape: - move_idx = [0, 2] - move_max = im.size[0] - elif portrait: - move_idx = [1, 3] - move_max = im.size[1] - - e_max = 0 - crop_current = [0, 0, crop_width, crop_height] - crop_best = crop_current - while crop_current[move_idx[1]] < move_max: - crop = im.crop(tuple(crop_current)) - e = image_entropy(crop) - - if (e > e_max): - e_max = e - crop_best = list(crop_current) - - crop_current[move_idx[0]] += 4 - crop_current[move_idx[1]] += 4 - - x_mid = int(crop_best[0] + crop_width/2) - y_mid = int(crop_best[1] + crop_height/2) - - - return { - 'x': x_mid, - 'y': y_mid, - 'weight': 1.0 - } - - -def image_entropy(im): - # greyscale image entropy - band = np.asarray(im.convert("1")) - hist, _ = np.histogram(band, bins=range(0, 256)) - hist = hist[hist > 0] - return -np.log2(hist / hist.sum()).sum() - + shared.state.nextjob() \ No newline at end of file From 858462f719c22ca9f24b94a41699653c34b5f4fb Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Thu, 20 Oct 2022 02:57:18 +0100 Subject: [PATCH 0068/1118] do caption copy for both flips --- modules/textual_inversion/preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 3713bc89..6bba3852 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -82,7 +82,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre subindex[0] += 1 def save_pic(image, index, existing_caption=None): - save_pic_with_caption(image, index) + save_pic_with_caption(image, index, existing_caption=existing_caption) if process_flip: save_pic_with_caption(ImageOps.mirror(image), index, existing_caption=existing_caption) From c6345bd445463b7aa41723d6637e80dfa293a890 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Wed, 19 Oct 2022 21:23:57 -0500 Subject: [PATCH 0069/1118] nerf line length --- modules/ui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 9f6edc5f..cb9a6c6e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -83,7 +83,7 @@ folder_symbol = '\U0001f4c2' # 📂 refresh_symbol = '\U0001f504' # 🔄 save_style_symbol = '\U0001f4be' # 💾 apply_style_symbol = '\U0001f4cb' # 📋 -trash_prompt_symbol = '\U0001F5D1' # 🗑🗑🗑 +trash_prompt_symbol = '\U0001F5D1' # def plaintext_to_html(text): @@ -617,7 +617,10 @@ def create_ui(wrap_gradio_gpu_call): return refresh_button with gr.Blocks(analytics_enabled=False) as txt2img_interface: - txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter, token_button = create_toprow(is_img2img=False) + txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, _,\ + txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter,\ + token_button = create_toprow(is_img2img=False) + dummy_component = gr.Label(visible=False) txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False) From aa7ff2a1972f3865883e10ba28c5414cdebe8e3b Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Wed, 19 Oct 2022 21:46:13 -0700 Subject: [PATCH 0070/1118] Fixed non-square highres fix generation --- modules/processing.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 684e5833..3caac25e 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -541,10 +541,13 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.truncate_y = int(self.firstphase_height - firstphase_height_truncated) // opt_f - def create_dummy_mask(self, x): + def create_dummy_mask(self, x, first_phase: bool = False): if self.sampler.conditioning_key in {'hybrid', 'concat'}: + height = self.firstphase_height if first_phase else self.height + width = self.firstphase_width if first_phase else self.width + # The "masked-image" in this case will just be all zeros since the entire image is masked. - image_conditioning = torch.zeros(x.shape[0], 3, self.height, self.width, device=x.device) + image_conditioning = torch.zeros(x.shape[0], 3, height, width, device=x.device) image_conditioning = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(image_conditioning)) # Add the fake full 1s mask to the first dimension. @@ -567,7 +570,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): return samples x = create_random_tensors([opt_C, self.firstphase_height // opt_f, self.firstphase_width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) - samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.create_dummy_mask(x)) + samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.create_dummy_mask(x, first_phase=True)) samples = samples[:, :, self.truncate_y//2:samples.shape[2]-self.truncate_y//2, self.truncate_x//2:samples.shape[3]-self.truncate_x//2] From 158d678f596d7fc304a6ce2f0dc31f8abfe62250 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Thu, 20 Oct 2022 01:08:24 -0500 Subject: [PATCH 0071/1118] clear prompt button now works on both relevant tabs. Device detection stuff will be added later. --- javascript/ui.js | 28 ++++++++++++++++++++++++++++ modules/ui.py | 21 ++++++++++++++++++--- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index cfd0dcd3..165383da 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -151,6 +151,34 @@ function ask_for_style_name(_, prompt_text, negative_prompt_text) { return [name_, prompt_text, negative_prompt_text] } +// returns css id for currently selected tab in ui +function selected_tab_id() { + tabs = gradioApp().querySelectorAll('#tabs div.tabitem') + + for(var tab = 0; tab < tabs.length; tab++) { + if (tabs[tab].style.display != "none") return tabs[tab].id + + } + +} + +function trash_prompt(_,_, is_img2img) { + + if(selected_tab_id() == "tab_txt2img") { + pos_prompt = txt2img_textarea = gradioApp().querySelector("#txt2img_prompt > label > textarea"); + neg_prompt = txt2img_textarea = gradioApp().querySelector("#txt2img_neg_prompt > label > textarea"); + + pos_prompt.value = "" + neg_prompt.value = "" + } else { + pos_prompt = txt2img_textarea = gradioApp().querySelector("#img2img_prompt > label > textarea"); + neg_prompt = txt2img_textarea = gradioApp().querySelector("#img2img_neg_prompt > label > textarea"); + + pos_prompt.value = "" + neg_prompt.value = "" + } +} + opts = {} diff --git a/modules/ui.py b/modules/ui.py index cb9a6c6e..bde546cc 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -424,6 +424,16 @@ def create_seed_inputs(): return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox +# setup button for clearing prompt input boxes on client side of webui +def connect_trash_prompt(dummy_component, button, is_img2img): + + button.click( + fn=lambda: print("Clearing prompt"), + _js="trash_prompt", + inputs=[], + outputs=[], + ) + def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed): """ Connects a 'reuse (sub)seed' button's click event so that it copies last used (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength @@ -540,7 +550,7 @@ def create_toprow(is_img2img): prompt_style2 = gr.Dropdown(label="Style 2", elem_id=f"{id_part}_style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys()))) prompt_style2.save_to_config = True - return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button + return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button, trash_prompt def setup_progressbar(progressbar, preview, id_part, textinfo=None): @@ -619,10 +629,11 @@ def create_ui(wrap_gradio_gpu_call): with gr.Blocks(analytics_enabled=False) as txt2img_interface: txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, _,\ txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter,\ - token_button = create_toprow(is_img2img=False) + token_button, trash_prompt_button = create_toprow(is_img2img=False) dummy_component = gr.Label(visible=False) txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False) + connect_trash_prompt(dummy_component, trash_prompt_button, False) with gr.Row(elem_id='txt2img_progress_row'): with gr.Column(scale=1): @@ -807,7 +818,11 @@ def create_ui(wrap_gradio_gpu_call): token_button.click(fn=update_token_counter, inputs=[txt2img_prompt, steps], outputs=[token_counter]) with gr.Blocks(analytics_enabled=False) as img2img_interface: - img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste, token_counter, token_button = create_toprow(is_img2img=True) + img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit,\ + img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,\ + token_counter, token_button, trash_prompt_button = create_toprow(is_img2img=True) + + connect_trash_prompt(dummy_component,trash_prompt_button, True) with gr.Row(elem_id='img2img_progress_row'): img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False) From 0ddaf8d2028a7251e8c4ad93551a43b5d4700841 Mon Sep 17 00:00:00 2001 From: captin411 Date: Thu, 20 Oct 2022 00:34:55 -0700 Subject: [PATCH 0072/1118] improve face detection a lot --- modules/textual_inversion/autocrop.py | 95 +++++++++++++++++---------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index f858a958..5a551c25 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -8,12 +8,18 @@ GREEN = "#0F0" BLUE = "#00F" RED = "#F00" + def crop_image(im, settings): """ Intelligently crop an image to the subject matter """ if im.height > im.width: im = im.resize((settings.crop_width, settings.crop_height * im.height // im.width)) - else: + elif im.width > im.height: im = im.resize((settings.crop_width * im.width // im.height, settings.crop_height)) + else: + im = im.resize((settings.crop_width, settings.crop_height)) + + if im.height == im.width: + return im focus = focal_point(im, settings) @@ -78,13 +84,18 @@ def focal_point(im, settings): [ PointOfInterest( p.x, p.y, weight=p.weight * ( (face_weight/weight_pref_total) / (len(face_points)/total_points) )) for p in face_points ] ) + average_point = poi_average(pois, settings) + if settings.annotate_image: d = ImageDraw.Draw(im) - - average_point = poi_average(pois, settings, im=im) - - if settings.annotate_image: - d.ellipse([average_point.x - 25, average_point.y - 25, average_point.x + 25, average_point.y + 25], outline=GREEN) + for f in face_points: + d.rectangle(f.bounding(f.size), outline=RED) + for f in entropy_points: + d.rectangle(f.bounding(30), outline=BLUE) + for poi in pois: + w = max(4, 4 * 0.5 * sqrt(poi.weight)) + d.ellipse(poi.bounding(w), fill=BLUE) + d.ellipse(average_point.bounding(25), outline=GREEN) return average_point @@ -92,22 +103,32 @@ def focal_point(im, settings): def image_face_points(im, settings): np_im = np.array(im) gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY) - classifier = cv2.CascadeClassifier(f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml') - minsize = int(min(im.width, im.height) * 0.15) # at least N percent of the smallest side - faces = classifier.detectMultiScale(gray, scaleFactor=1.05, - minNeighbors=5, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) + tries = [ + [ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ] + ] - if len(faces) == 0: - return [] + for t in tries: + # print(t[0]) + classifier = cv2.CascadeClassifier(t[0]) + minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side + try: + faces = classifier.detectMultiScale(gray, scaleFactor=1.1, + minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) + except: + continue - rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces] - if settings.annotate_image: - for f in rects: - d = ImageDraw.Draw(im) - d.rectangle(f, outline=RED) - - return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2) for r in rects] + if len(faces) > 0: + rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces] + return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2])) for r in rects] + return [] def image_corner_points(im, settings): @@ -132,8 +153,8 @@ def image_corner_points(im, settings): focal_points = [] for point in points: - x, y = point.ravel() - focal_points.append(PointOfInterest(x, y)) + x, y = point.ravel() + focal_points.append(PointOfInterest(x, y, size=4)) return focal_points @@ -167,31 +188,26 @@ def image_entropy_points(im, settings): x_mid = int(crop_best[0] + settings.crop_width/2) y_mid = int(crop_best[1] + settings.crop_height/2) - return [PointOfInterest(x_mid, y_mid)] + return [PointOfInterest(x_mid, y_mid, size=25)] def image_entropy(im): # greyscale image entropy - band = np.asarray(im.convert("1")) + # band = np.asarray(im.convert("L")) + band = np.asarray(im.convert("1"), dtype=np.uint8) hist, _ = np.histogram(band, bins=range(0, 256)) hist = hist[hist > 0] return -np.log2(hist / hist.sum()).sum() -def poi_average(pois, settings, im=None): +def poi_average(pois, settings): weight = 0.0 x = 0.0 y = 0.0 - for pois in pois: - if settings.annotate_image and im is not None: - w = 4 * 0.5 * sqrt(pois.weight) - d = ImageDraw.Draw(im) - d.ellipse([ - pois.x - w, pois.y - w, - pois.x + w, pois.y + w ], fill=BLUE) - weight += pois.weight - x += pois.x * pois.weight - y += pois.y * pois.weight + for poi in pois: + weight += poi.weight + x += poi.x * poi.weight + y += poi.y * poi.weight avg_x = round(x / weight) avg_y = round(y / weight) @@ -199,10 +215,19 @@ def poi_average(pois, settings, im=None): class PointOfInterest: - def __init__(self, x, y, weight=1.0): + def __init__(self, x, y, weight=1.0, size=10): self.x = x self.y = y self.weight = weight + self.size = size + + def bounding(self, size): + return [ + self.x - size//2, + self.y - size//2, + self.x + size//2, + self.y + size//2 + ] class Settings: From 21364c5c39b269497944b56dd6664792d779333b Mon Sep 17 00:00:00 2001 From: Dynamic Date: Thu, 20 Oct 2022 19:20:39 +0900 Subject: [PATCH 0073/1118] Updated file with basic template and added new translations Translation done in txt2img-img2img windows and following scripts --- localizations/ko-KR.json | 498 +++++++++++++++++++++++++++++++-------- 1 file changed, 400 insertions(+), 98 deletions(-) diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json index b263b13c..7cc431c6 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko-KR.json @@ -1,120 +1,422 @@ { - "⤡": "⤡", - "⊞": "⊞", "×": "×", + "•": "•", + "⊞": "⊞", "❮": "❮", "❯": "❯", - "Loading...": "", - "view": "api 보이기", - "hide": "api 숨기기", - "api": "", - "•": "•", - "txt2img": "텍스트→이미지", - "img2img": "이미지→이미지", - "Extras": "부가기능", - "PNG Info": "PNG 정보", - "History": "기록", - "Checkpoint Merger": "체크포인트 병합", - "Train": "훈련", - "Settings": "설정", - "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", - "Hypernetwork": "하이퍼네트워크", - "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", - "Generate": "생성", - "Style 1": "스타일 1", - "Style 2": "스타일 2", + "⤡": "⤡", + "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", + "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", + "A merger of the two checkpoints will be generated in your": "A merger of the two checkpoints will be generated in your", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", - "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", - "Save style": "스타일 저장", + "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", + "Add difference": "Add difference", + "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", + "Add layer normalization": "Add layer normalization", + "Add model hash to generation information": "Add model hash to generation information", + "Add model name to generation information": "Add model name to generation information", + "Always print all generation info to standard output": "Always print all generation info to standard output", + "Always save all generated image grids": "Always save all generated image grids", + "Always save all generated images": "생성된 이미지 항상 저장하기", + "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", - "Do not do anything special": "아무것도 하지 않기", - "Generate forever": "반복 생성", - "Cancel generate forever": "반복 생성 취소", - "Interrupt": "중단", - "Skip": "건너뛰기", - "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", - "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", - "Prompt": "프롬프트", - "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Negative prompt": "네거티브 프롬프트", - "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Sampling Steps": "샘플링 스텝 수", - "Sampling method": "샘플링 방법", - "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", - "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", - "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", - "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", - "Width": "가로", - "Height": "세로", - "Restore faces": "얼굴 보정", - "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", - "Tiling": "타일링", - "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", - "Highres. fix": "고해상도 보정", - "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", - "Firstpass width": "초기 가로길이", - "Firstpass height": "초기 세로길이", - "Denoising strength": "디노이즈 강도", - "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Apply settings": "설정 적용하기", + "BSRGAN 4x": "BSRGAN 4x", + "Batch Process": "Batch Process", "Batch count": "배치 수", + "Batch from Directory": "Batch from Directory", + "Batch img2img": "이미지→이미지 배치", "Batch size": "배치 크기", + "CFG Scale": "CFG 스케일", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "Cancel generate forever": "반복 생성 취소", + "Check progress (first)": "Check progress (first)", + "Check progress": "Check progress", + "Checkpoint Merger": "체크포인트 병합", + "Checkpoint name": "체크포인트 이름", + "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Clip skip": "클립 건너뛰기", + "CodeFormer visibility": "CodeFormer visibility", + "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", + "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", + "Color variation": "색깔 다양성", + "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", + "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", + "Create embedding": "Create embedding", + "Create flipped copies": "Create flipped copies", + "Create hypernetwork": "Create hypernetwork", + "Crop and resize": "잘라낸 후 리사이징", + "Crop to fit": "Crop to fit", + "Custom Name (Optional)": "Custom Name (Optional)", + "DDIM": "DDIM", + "DPM adaptive": "DPM adaptive", + "DPM fast": "DPM fast", + "DPM2 Karras": "DPM2 Karras", + "DPM2 a Karras": "DPM2 a Karras", + "DPM2 a": "DPM2 a", + "DPM2": "DPM2", + "Dataset directory": "Dataset directory", + "Decode CFG scale": "디코딩 CFG 스케일", + "Decode steps": "디코딩 스텝 수", + "Delete": "Delete", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Denoising strength change factor": "디노이즈 강도 변경 배수", + "Denoising strength": "디노이즈 강도", + "Denoising": "디노이징", + "Destination directory": "Destination directory", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Directory for saving images using the Save button": "Directory for saving images using the Save button", + "Directory name pattern": "Directory name pattern", + "Do not add watermark to images": "Do not add watermark to images", + "Do not do anything special": "아무것도 하지 않기", + "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", + "Do not show any images in results for web": "Do not show any images in results for web", + "Download localization template": "Download localization template", + "Draw legend": "범례 그리기", + "Draw mask": "마스크 직접 그리기", + "Drop File Here": "Drop File Here", + "Drop Image Here": "Drop Image Here", + "ESRGAN_4x": "ESRGAN_4x", + "Embedding": "Embedding", + "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", + "Enable full page image viewer": "Enable full page image viewer", + "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", + "End Page": "End Page", + "Enter hypernetwork layer structure": "Enter hypernetwork layer structure", + "Eta noise seed delta": "Eta noise seed delta", + "Eta": "Eta", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Euler a": "Euler a", + "Euler": "Euler", + "Extra": "고급", + "Extras": "부가기능", + "Face restoration": "Face restoration", + "Fall-off exponent (lower=higher detail)": "감쇠 지수 (낮을수록 디테일이 올라감)", + "File Name": "File Name", + "File format for grids": "File format for grids", + "File format for images": "File format for images", + "File with inputs": "설정값 파일", + "File": "File", + "Filename join string": "Filename join string", + "Filename word regex": "Filename word regex", + "Filter NSFW content": "Filter NSFW content", + "First Page": "First Page", + "Firstpass height": "초기 세로길이", + "Firstpass width": "초기 가로길이", + "Font for image grids that have text": "Font for image grids that have text", + "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "SD 업스케일링에서 타일 간 몇 픽셀을 겹치게 할지 결정하는 설정값입니다. 타일들이 다시 한 이미지로 합쳐질 때, 눈에 띄는 이음매가 없도록 서로 겹치게 됩니다.", + "GFPGAN visibility": "GFPGAN visibility", + "Generate Info": "Generate Info", + "Generate forever": "반복 생성", + "Generate": "생성", + "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", + "Height": "세로", + "Heun": "Heun", + "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", + "Highres. fix": "고해상도 보정", + "History": "기록", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", - "CFG Scale": "CFG 스케일", - "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", - "Seed": "시드", - "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", - "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", - "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", - "Extra": "고급", - "Variation seed": "바리에이션 시드", - "Variation strength": "바리에이션 강도", - "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", + "How many times to repeat processing an image and using it as input for the next iteration": "이미지를 생성 후 원본으로 몇 번 반복해서 사용할지 결정하는 값", + "How much to blur the mask before processing, in pixels.": "이미지 생성 전 마스크를 얼마나 블러처리할지 결정하는 값. 픽셀 단위", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", + "Hypernet str.": "하이퍼네트워크 강도", + "Hypernetwork strength": "Hypernetwork strength", + "Hypernetwork": "하이퍼네트워크", + "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", + "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "Image for img2img": "Image for img2img", + "Image for inpainting with mask": "Image for inpainting with mask", + "Image": "Image", + "Images filename pattern": "Images filename pattern", + "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "루프백 모드에서는 매 루프마다 디노이즈 강도에 이 값이 곱해집니다. 1보다 작을 경우 다양성이 낮아져 결과 이미지들이 고정된 형태로 모일 겁니다. 1보다 클 경우 다양성이 높아져 결과 이미지들이 갈수록 혼란스러워지겠죠.", + "Include Separate Images": "분리된 이미지 포함하기", + "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", + "Initialization text": "Initialization text", + "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", + "Inpaint at full resolution": "전체 해상도로 인페인트하기", + "Inpaint masked": "마스크만 처리", + "Inpaint not masked": "마스크 이외만 처리", + "Inpaint": "인페인트", + "Input directory": "인풋 이미지 경로", + "Interpolation Method": "Interpolation Method", + "Interrogate\nCLIP": "CLIP\n분석", + "Interrogate\nDeepBooru": "DeepBooru\n분석", + "Interrogate Options": "Interrogate Options", + "Interrogate: deepbooru score threshold": "Interrogate: deepbooru score threshold", + "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooru sort alphabetically", + "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).", + "Interrogate: keep models in VRAM": "Interrogate: keep models in VRAM", + "Interrogate: maximum description length": "Interrogate: maximum description length", + "Interrogate: minimum description length (excluding artists, etc..)": "Interrogate: minimum description length (excluding artists, etc..)", + "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", + "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", + "Interrupt": "중단", + "Just resize": "리사이징", + "Keep -1 for seeds": "시드값 -1로 유지", + "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", + "LDSR": "LDSR", + "LMS Karras": "LMS Karras", + "LMS": "LMS", + "Label": "Label", + "Lanczos": "Lanczos", + "Learning rate": "Learning rate", + "Leave blank to save images to the default path.": "Leave blank to save images to the default path.", + "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", + "Loading...": "로딩 중...", + "Localization (requires restart)": "Localization (requires restart)", + "Log directory": "Log directory", + "Loopback": "루프백", + "Loops": "루프 수", + "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", + "Make Zip when Save?": "저장 시 Zip 생성하기", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Mask blur": "마스크 블러", + "Mask mode": "Mask mode", + "Mask": "마스크", + "Masked content": "마스크된 부분", + "Masking mode": "Masking mode", + "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", + "Max steps": "Max steps", + "Modules": "Modules", + "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", + "Multiplier (M) - set to 0 to get model A": "Multiplier (M) - set to 0 to get model A", + "Name": "Name", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Negative prompt": "네거티브 프롬프트", + "Next Page": "Next Page", + "None": "None", + "Nothing": "없음", + "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", + "Number of vectors per token": "Number of vectors per token", + "Open images output directory": "이미지 저장 경로 열기", + "Open output directory": "Open output directory", + "Original negative prompt": "기존 네거티브 프롬프트", + "Original prompt": "기존 프롬프트", + "Outpainting direction": "아웃페인팅 방향", + "Outpainting mk2": "아웃페인팅 마크 2", + "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", + "Output directory for images from extras tab": "Output directory for images from extras tab", + "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", + "Output directory for img2img grids": "Output directory for img2img grids", + "Output directory for img2img images": "Output directory for img2img images", + "Output directory for txt2img grids": "Output directory for txt2img grids", + "Output directory for txt2img images": "Output directory for txt2img images", + "Output directory": "이미지 저장 경로", + "Override `Denoising strength` to 1?": "디노이즈 강도를 1로 적용할까요?", + "Override `Sampling Steps` to the same value as `Decode steps`?": "샘플링 스텝 수를 디코딩 스텝 수와 동일하게 적용할까요?", + "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", + "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "프롬프트 값을 기존 프롬프트와 동일하게 적용할까요?(네거티브 프롬프트 포함)", + "PLMS": "PLMS", + "PNG Info": "PNG 정보", + "Page Index": "Page Index", + "Path to directory where to write outputs": "Path to directory where to write outputs", + "Path to directory with input images": "Path to directory with input images", + "Paths for saving": "Paths for saving", + "Pixels to expand": "확장할 픽셀 수", + "Poor man's outpainting": "가난뱅이의 아웃페인팅", + "Preprocess images": "Preprocess images", + "Preprocess": "Preprocess", + "Prev Page": "Prev Page", + "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", + "Primary model (A)": "Primary model (A)", + "Process an image, use it as an input, repeat.": "이미지를 생성하고, 생성한 이미지를 다시 원본으로 사용하는 과정을 반복합니다.", + "Process images in a directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리의 이미지들을 처리합니다.", + "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Prompt S/R": "프롬프트 스타일 변경", + "Prompt matrix": "프롬프트 매트릭스", + "Prompt order": "프롬프트 순서", + "Prompt template file": "Prompt template file", + "Prompt": "프롬프트", + "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", + "Prompts": "프롬프트", + "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", + "Quality for saved jpeg images": "Quality for saved jpeg images", + "Quicksettings list": "Quicksettings list", + "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", + "Randomness": "랜덤성", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", + "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", + "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "추천 설정값 - 샘플링 스텝 수 : 80-100 , 샘플러 : Euler a, 디노이즈 강도 : 0.8", + "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", + "Renew Page": "Renew Page", + "Request browser notifications": "Request browser notifications", + "Resize and fill": "리사이징 후 채우기", + "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "설정된 해상도로 이미지 리사이징을 진행합니다. 원본과 가로/세로 길이가 일치하지 않을 경우, 부정확한 화면비의 이미지를 얻게 됩니다.", + "Resize mode": "Resize mode", "Resize seed from height": "시드 리사이징 가로길이", "Resize seed from width": "시드 리사이징 세로길이", - "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", - "Script": "스크립트", + "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "이미지 전체가 설정된 해상도 내부에 들어가게 리사이징을 진행합니다. 빈 공간은 이미지의 색상으로 채웁니다.", + "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "설정된 해상도 전체가 이미지로 가득차게 리사이징을 진행합니다. 튀어나오는 부분은 잘라냅니다.", + "Resize": "Resize", + "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", + "Restore faces": "얼굴 보정", + "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", + "Result = A * (1 - M) + B * M": "Result = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "Result = A + (B - C) * M", + "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", + "Run": "Run", + "SD upscale": "SD 업스케일링", + "Sampler parameters": "Sampler parameters", + "Sampler": "샘플러", + "Sampling Steps": "샘플링 스텝 수", + "Sampling method": "샘플링 방법", + "Save a copy of embedding to log directory every N steps, 0 to disable": "Save a copy of embedding to log directory every N steps, 0 to disable", + "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", + "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", + "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", + "Save an image to log directory every N steps, 0 to disable": "Save an image to log directory every N steps, 0 to disable", + "Save as float16": "Save as float16", + "Save grids to a subdirectory": "Save grids to a subdirectory", + "Save images to a subdirectory": "Save images to a subdirectory", + "Save images with embedding in PNG chunks": "Save images with embedding in PNG chunks", + "Save style": "스타일 저장", + "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", "Save": "저장", - "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "Saving images/grids": "Saving images/grids", + "Saving to a directory": "Saving to a directory", + "Scale by": "Scale by", + "Scale to": "Scale to", + "Script": "스크립트", + "ScuNET GAN": "ScuNET GAN", + "ScuNET PSNR": "ScuNET PSNR", + "Secondary model (B)": "Secondary model (B)", + "See": "See", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "Seed": "시드", + "Send to extras": "부가기능으로 전송", "Send to img2img": "이미지→이미지로 전송", "Send to inpaint": "인페인트로 전송", - "Send to extras": "부가기능으로 전송", - "Open images output directory": "이미지 저장 경로 열기", - "Make Zip when Save?": "저장 시 Zip 생성하기", - "Prompt matrix": "프롬프트 매트릭스", + "Send to txt2img": "텍스트→이미지로 전송", "Separate prompts into parts using vertical pipe character (|) and the script will create a picture for every combination of them (except for the first part, which will be present in all combinations)": "(|)를 이용해 프롬프트를 분리할 시 첫 프롬프트를 제외하고 모든 프롬프트의 조합마다 이미지를 생성합니다. 첫 프롬프트는 모든 조합에 포함되게 됩니다.", - "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", - "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", - "Show Textbox": "텍스트박스 보이기", - "File with inputs": "설정값 파일", - "Prompts": "프롬프트", - "X/Y plot": "X/Y 플롯", - "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", - "X type": "X축", - "Y type": "Y축", - "X values": "X 설정값", - "Y values": "Y 설정값", "Separate values for X axis using commas.": "쉼표로 X축에 적용할 값 분리", "Separate values for Y axis using commas.": "쉼표로 Y축에 적용할 값 분리", - "Draw legend": "범례 그리기", - "Include Separate Images": "분리된 이미지 포함하기", - "Keep -1 for seeds": "시드값 -1로 유지", + "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "Settings": "설정", + "Show Textbox": "텍스트박스 보이기", + "Show generation progress in window title.": "Show generation progress in window title.", + "Show grid in results for web": "Show grid in results for web", + "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", + "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", + "Show progressbar": "Show progressbar", + "Show result images": "Show result images", + "Sigma Churn": "시그마 섞기", + "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", + "Sigma max": "시그마 최댓값", + "Sigma min": "시그마 최솟값", + "Sigma noise": "시그마 노이즈", + "Single Image": "Single Image", + "Skip": "건너뛰기", + "Source directory": "Source directory", + "Source": "Source", + "Split oversized images into two": "Split oversized images into two", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Stable Diffusion": "Stable Diffusion", + "Steps": "스텝 수", + "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", + "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", + "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", + "Style 1": "스타일 1", + "Style 2": "스타일 2", + "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", + "SwinIR 4x": "SwinIR 4x", + "System": "System", + "Tertiary model (C)": "Tertiary model (C)", + "Textbox": "Textbox", + "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", + "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", + "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", + "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", + "Tile overlap": "타일 겹침", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", + "Tile size for all SwinIR.": "Tile size for all SwinIR.", + "Tiling": "타일링", + "Train Embedding": "Train Embedding", + "Train Hypernetwork": "Train Hypernetwork", + "Train an embedding; must specify a directory with a set of 1:1 ratio images": "Train an embedding; must specify a directory with a set of 1:1 ratio images", + "Train": "훈련", + "Training": "Training", + "Unload VAE and CLIP from VRAM when training": "Unload VAE and CLIP from VRAM when training", + "Upload mask": "마스크 업로드하기", + "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", + "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "마스크된 부분을 설정된 해상도로 업스케일하고, 인페인팅을 진행한 뒤, 다시 다운스케일 후 원본 이미지에 붙여넣습니다.", + "Upscaler 2 visibility": "Upscaler 2 visibility", + "Upscaler for img2img": "Upscaler for img2img", + "Upscaler": "업스케일러", + "Upscaling": "Upscaling", + "Use BLIP for caption": "Use BLIP for caption", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Use an empty output directory to save pictures normally instead of writing to the output directory.": "저장 경로를 비워두면 기본 저장 폴더에 이미지들이 저장됩니다.", + "Use deepbooru for caption": "Use deepbooru for caption", + "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", + "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", + "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", + "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", + "User interface": "User interface", + "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", "Var. seed": "바리에이션 시드", "Var. strength": "바리에이션 강도", - "Steps": "스텝 수", - "Prompt S/R": "프롬프트 스타일 변경", - "Prompt order": "프롬프트 순서", - "Sampler": "샘플러", - "Checkpoint name": "체크포인트 이름", - "Hypernet str.": "하이퍼네트워크 강도", - "Sigma Churn": "시그마 섞기", - "Sigma min": "시그마 최솟값", - "Sigma max": "시그마 최댓값", - "Sigma noise": "시그마 노이즈", - "Clip skip": "클립 건너뛰기", - "Denoising": "디노이징", - "Nothing": "없음", - "Apply settings": "설정 적용하기", - "Always save all generated images": "생성된 이미지 항상 저장하기" + "Variation seed": "바리에이션 시드", + "Variation strength": "바리에이션 강도", + "Weighted sum": "Weighted sum", + "What to put inside the masked area before processing it with Stable Diffusion.": "Stable Diffusion으로 이미지를 생성하기 전 마스크된 부분에 무엇을 채울지 결정하는 설정값", + "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", + "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", + "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", + "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", + "Width": "가로", + "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "이미지를 설정된 사이즈의 2배로 업스케일합니다. 상단의 가로와 세로 슬라이더를 이용해 타일 사이즈를 지정하세요.", + "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "X type": "X축", + "X values": "X 설정값", + "X/Y plot": "X/Y 플롯", + "Y type": "Y축", + "Y values": "Y 설정값", + "api": "", + "built with gradio": "gradio로 제작되었습니다", + "checkpoint": "checkpoint", + "directory.": "directory.", + "down": "아래쪽", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", + "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", + "extras history": "extras history", + "fill it with colors of the image": "이미지의 색상으로 채우기", + "fill it with latent space noise": "잠재 공간 노이즈로 채우기", + "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", + "fill": "채우기", + "for detailed explanation.": "for detailed explanation.", + "hide": "api 숨기기", + "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", + "img2img DDIM discretize": "img2img DDIM discretize", + "img2img alternative test": "이미지→이미지 대체버전 테스트", + "img2img history": "img2img history", + "img2img": "이미지→이미지", + "keep whatever was there originally": "이미지 원본 유지", + "latent noise": "잠재 노이즈", + "latent nothing": "잠재 공백", + "left": "왼쪽", + "number of images to delete consecutively next": "number of images to delete consecutively next", + "or": "or", + "original": "원본 유지", + "quad": "quad", + "right": "오른쪽", + "set_index": "set_index", + "should be 2 or lower.": "이 2 이하여야 합니다.", + "sigma churn": "sigma churn", + "sigma noise": "sigma noise", + "sigma tmin": "sigma tmin", + "txt2img history": "txt2img history", + "txt2img": "텍스트→이미지", + "uniform": "uniform", + "up": "위쪽", + "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", + "view": "api 보이기", + "wiki": "wiki" } \ No newline at end of file From f8733ad08be08bafb40f4299785590e11f049e96 Mon Sep 17 00:00:00 2001 From: discus0434 Date: Thu, 20 Oct 2022 11:07:37 +0000 Subject: [PATCH 0074/1118] add linear as a act func (option for doin nothing) --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 913b23b4..716f14b8 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1224,7 +1224,7 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "320", "640", "1280"]) new_hypernetwork_layer_structure = gr.Textbox("1, 2, 1", label="Enter hypernetwork layer structure", placeholder="1st and last digit must be 1. ex:'1, 2, 1'") new_hypernetwork_add_layer_norm = gr.Checkbox(label="Add layer normalization") - new_hypernetwork_activation_func = gr.Dropdown(value="relu", label="Select activation function of hypernetwork", choices=["relu", "leakyrelu"]) + new_hypernetwork_activation_func = gr.Dropdown(value="relu", label="Select activation function of hypernetwork", choices=["linear", "relu", "leakyrelu"]) with gr.Row(): with gr.Column(scale=3): From 4281f255d5e7c67515d619f53654be59a6fc1e13 Mon Sep 17 00:00:00 2001 From: wywywywy Date: Thu, 20 Oct 2022 15:31:09 +0100 Subject: [PATCH 0075/1118] Implemented batch count logic to Outpainting mk2 --- scripts/outpainting_mk_2.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index a6468e09..02e655e9 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -242,21 +242,37 @@ class Script(scripts.Script): out = out.crop((0, 0, res_w, res_h)) return out - img = init_image + batch_count = p.n_iter + p.n_iter = 1 + state.job_count = batch_count + all_images = [] - if left > 0: - img = expand(img, left, is_left=True) - if right > 0: - img = expand(img, right, is_right=True) - if up > 0: - img = expand(img, up, is_top=True) - if down > 0: - img = expand(img, down, is_bottom=True) + for i in range(batch_count): + img = init_image + state.job = f"Batch {i + 1} out of {state.job_count}" - res = Processed(p, [img], initial_seed_and_info[0], initial_seed_and_info[1]) + if left > 0: + img = expand(img, left, is_left=True) + if right > 0: + img = expand(img, right, is_right=True) + if up > 0: + img = expand(img, up, is_top=True) + if down > 0: + img = expand(img, down, is_bottom=True) + + all_images.append(img) + + combined_grid_image = images.image_grid(all_images) + if opts.return_grid: + all_images = [combined_grid_image] + all_images + + res = Processed(p, all_images, initial_seed_and_info[0], initial_seed_and_info[1]) if opts.samples_save: images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.grid_format, info=res.info, p=p) + if opts.grid_save: + images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.grid_format, info=res.info, short_filename=not opts.grid_extended_filename, grid=True, p=p) + return res From 9681419e422515e42444e0174355b760645a846f Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 20 Oct 2022 16:53:46 +0900 Subject: [PATCH 0076/1118] train: fixed preprocess image ratio --- modules/textual_inversion/preprocess.py | 54 ++++++++++++++++--------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 886cf0c3..2743bdeb 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -1,5 +1,6 @@ import os from PIL import Image, ImageOps +import math import platform import sys import tqdm @@ -38,6 +39,8 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro height = process_height src = os.path.abspath(process_src) dst = os.path.abspath(process_dst) + split_threshold = 0.5 + overlap_ratio = 0.2 assert src != dst, 'same directory specified as source and destination' @@ -78,6 +81,29 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro if process_flip: save_pic_with_caption(ImageOps.mirror(image), index) + def split_pic(image, inverse_xy): + if inverse_xy: + from_w, from_h = image.height, image.width + to_w, to_h = height, width + else: + from_w, from_h = image.width, image.height + to_w, to_h = width, height + h = from_h * to_w // from_w + if inverse_xy: + image = image.resize((h, to_w)) + else: + image = image.resize((to_w, h)) + + split_count = math.ceil((h - to_h * overlap_ratio) / (to_h * (1.0 - overlap_ratio))) + y_step = (h - to_h) / (split_count - 1) + for i in range(split_count): + y = int(y_step * i) + if inverse_xy: + splitted = image.crop((y, 0, y + to_h, to_w)) + else: + splitted = image.crop((0, y, to_w, y + to_h)) + yield splitted + for index, imagefile in enumerate(tqdm.tqdm(files)): subindex = [0] filename = os.path.join(src, imagefile) @@ -89,26 +115,16 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pro if shared.state.interrupted: break - ratio = img.height / img.width - is_tall = ratio > 1.35 - is_wide = ratio < 1 / 1.35 + if img.height > img.width: + ratio = (img.width * height) / (img.height * width) + inverse_xy = False + else: + ratio = (img.height * width) / (img.width * height) + inverse_xy = True - if process_split and is_tall: - img = img.resize((width, height * img.height // img.width)) - - top = img.crop((0, 0, width, height)) - save_pic(top, index) - - bot = img.crop((0, img.height - height, width, img.height)) - save_pic(bot, index) - elif process_split and is_wide: - img = img.resize((width * img.width // img.height, height)) - - left = img.crop((0, 0, width, height)) - save_pic(left, index) - - right = img.crop((img.width - width, 0, img.width, height)) - save_pic(right, index) + if process_split and ratio < 1.0 and ratio <= split_threshold: + for splitted in split_pic(img, inverse_xy): + save_pic(splitted, index) else: img = images.resize_image(1, img, width, height) save_pic(img, index) From 85dd62c4c7635b8e21a75f140d093036069e97a1 Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 20 Oct 2022 22:56:45 +0900 Subject: [PATCH 0077/1118] train: ui: added `Split image threshold` and `Split image overlap ratio` to preprocess --- modules/textual_inversion/preprocess.py | 10 +++++----- modules/ui.py | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 2743bdeb..c8df8aa0 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -12,7 +12,7 @@ if cmd_opts.deepdanbooru: import modules.deepbooru as deepbooru -def preprocess(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False): +def preprocess(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2): try: if process_caption: shared.interrogator.load() @@ -22,7 +22,7 @@ def preprocess(process_src, process_dst, process_width, process_height, process_ db_opts[deepbooru.OPT_INCLUDE_RANKS] = False deepbooru.create_deepbooru_process(opts.interrogate_deepbooru_score_threshold, db_opts) - preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru) + preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru, split_threshold, overlap_ratio) finally: @@ -34,13 +34,13 @@ def preprocess(process_src, process_dst, process_width, process_height, process_ -def preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False): +def preprocess_work(process_src, process_dst, process_width, process_height, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2): width = process_width height = process_height src = os.path.abspath(process_src) dst = os.path.abspath(process_dst) - split_threshold = 0.5 - overlap_ratio = 0.2 + split_threshold = max(0.0, min(1.0, split_threshold)) + overlap_ratio = max(0.0, min(0.9, overlap_ratio)) assert src != dst, 'same directory specified as source and destination' diff --git a/modules/ui.py b/modules/ui.py index a2dbd41e..bc7f3330 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1240,10 +1240,14 @@ def create_ui(wrap_gradio_gpu_call): with gr.Row(): process_flip = gr.Checkbox(label='Create flipped copies') - process_split = gr.Checkbox(label='Split oversized images into two') + process_split = gr.Checkbox(label='Split oversized images') process_caption = gr.Checkbox(label='Use BLIP for caption') process_caption_deepbooru = gr.Checkbox(label='Use deepbooru for caption', visible=True if cmd_opts.deepdanbooru else False) + with gr.Row(visible=False) as process_split_extra_row: + process_split_threshold = gr.Slider(label='Split image threshold', value=0.5, minimum=0.0, maximum=1.0, step=0.05) + process_overlap_ratio = gr.Slider(label='Split image overlap ratio', value=0.2, minimum=0.0, maximum=0.9, step=0.05) + with gr.Row(): with gr.Column(scale=3): gr.HTML(value="") @@ -1251,6 +1255,12 @@ def create_ui(wrap_gradio_gpu_call): with gr.Column(): run_preprocess = gr.Button(value="Preprocess", variant='primary') + process_split.change( + fn=lambda show: gr_show(show), + inputs=[process_split], + outputs=[process_split_extra_row], + ) + with gr.Tab(label="Train"): gr.HTML(value="

Train an embedding; must specify a directory with a set of 1:1 ratio images

") with gr.Row(): @@ -1327,7 +1337,9 @@ def create_ui(wrap_gradio_gpu_call): process_flip, process_split, process_caption, - process_caption_deepbooru + process_caption_deepbooru, + process_split_threshold, + process_overlap_ratio, ], outputs=[ ti_output, From d8acd34f66ab35a91f10d66330bcc95a83bfcac6 Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Thu, 20 Oct 2022 23:43:03 +0900 Subject: [PATCH 0078/1118] generalized some functions and option for ignoring first layer --- modules/hypernetworks/hypernetwork.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 7d617680..3a44b377 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -21,21 +21,27 @@ from modules.textual_inversion.learn_schedule import LearnRateScheduler class HypernetworkModule(torch.nn.Module): multiplier = 1.0 - + activation_dict = {"relu": torch.nn.ReLU, "leakyrelu": torch.nn.LeakyReLU, "elu": torch.nn.ELU, + "swish": torch.nn.Hardswish} + def __init__(self, dim, state_dict=None, layer_structure=None, add_layer_norm=False, activation_func=None): super().__init__() assert layer_structure is not None, "layer_structure must not be None" assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!" assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!" - + linears = [] for i in range(len(layer_structure) - 1): linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) - if activation_func == "relu": - linears.append(torch.nn.ReLU()) - if activation_func == "leakyrelu": - linears.append(torch.nn.LeakyReLU()) + # if skip_first_layer because first parameters potentially contain negative values + if i < 1: continue + if activation_func in HypernetworkModule.activation_dict: + linears.append(HypernetworkModule.activation_dict[activation_func]()) + else: + print("Invalid key {} encountered as activation function!".format(activation_func)) + # if use_dropout: + linears.append(torch.nn.Dropout(p=0.3)) if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) @@ -46,7 +52,7 @@ class HypernetworkModule(torch.nn.Module): self.load_state_dict(state_dict) else: for layer in self.linear: - if not "ReLU" in layer.__str__(): + if isinstance(layer, torch.nn.Linear): layer.weight.data.normal_(mean=0.0, std=0.01) layer.bias.data.zero_() @@ -298,7 +304,8 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log return hypernetwork, filename scheduler = LearnRateScheduler(learn_rate, steps, ititial_step) - optimizer = torch.optim.AdamW(weights, lr=scheduler.learn_rate) + # if optimizer == "Adam": or else Adam / AdamW / etc... + optimizer = torch.optim.Adam(weights, lr=scheduler.learn_rate) pbar = tqdm.tqdm(enumerate(ds), total=steps - ititial_step) for i, entries in pbar: From a71e0212363979c7cbbb797c9fbd5f8cd03b29d3 Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Thu, 20 Oct 2022 23:48:52 +0900 Subject: [PATCH 0079/1118] only linear --- modules/hypernetworks/hypernetwork.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 3a44b377..905cbeef 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -35,13 +35,13 @@ class HypernetworkModule(torch.nn.Module): for i in range(len(layer_structure) - 1): linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) # if skip_first_layer because first parameters potentially contain negative values - if i < 1: continue + # if i < 1: continue if activation_func in HypernetworkModule.activation_dict: linears.append(HypernetworkModule.activation_dict[activation_func]()) else: print("Invalid key {} encountered as activation function!".format(activation_func)) # if use_dropout: - linears.append(torch.nn.Dropout(p=0.3)) + # linears.append(torch.nn.Dropout(p=0.3)) if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) @@ -80,7 +80,7 @@ class HypernetworkModule(torch.nn.Module): def trainables(self): layer_structure = [] for layer in self.linear: - if not "ReLU" in layer.__str__(): + if isinstance(layer, torch.nn.Linear): layer_structure += [layer.weight, layer.bias] return layer_structure @@ -304,8 +304,8 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log return hypernetwork, filename scheduler = LearnRateScheduler(learn_rate, steps, ititial_step) - # if optimizer == "Adam": or else Adam / AdamW / etc... - optimizer = torch.optim.Adam(weights, lr=scheduler.learn_rate) + # if optimizer == "AdamW": or else Adam / AdamW / SGD, etc... + optimizer = torch.optim.AdamW(weights, lr=scheduler.learn_rate) pbar = tqdm.tqdm(enumerate(ds), total=steps - ititial_step) for i, entries in pbar: From 91efe138b35dda65e83070c14e9eb94f481fe476 Mon Sep 17 00:00:00 2001 From: wywywywy Date: Thu, 20 Oct 2022 16:02:32 +0100 Subject: [PATCH 0080/1118] Implemented batch_size logic in outpainting_mk2 --- scripts/outpainting_mk_2.py | 106 +++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 02e655e9..0377ab32 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -176,50 +176,53 @@ class Script(scripts.Script): state.job_count = (1 if left > 0 else 0) + (1 if right > 0 else 0) + (1 if up > 0 else 0) + (1 if down > 0 else 0) - def expand(init, expand_pixels, is_left=False, is_right=False, is_top=False, is_bottom=False): + def expand(init, count, expand_pixels, is_left=False, is_right=False, is_top=False, is_bottom=False): is_horiz = is_left or is_right is_vert = is_top or is_bottom pixels_horiz = expand_pixels if is_horiz else 0 pixels_vert = expand_pixels if is_vert else 0 - res_w = init.width + pixels_horiz - res_h = init.height + pixels_vert - process_res_w = math.ceil(res_w / 64) * 64 - process_res_h = math.ceil(res_h / 64) * 64 + images_to_process = [] + for n in range(count): + res_w = init[n].width + pixels_horiz + res_h = init[n].height + pixels_vert + process_res_w = math.ceil(res_w / 64) * 64 + process_res_h = math.ceil(res_h / 64) * 64 - img = Image.new("RGB", (process_res_w, process_res_h)) - img.paste(init, (pixels_horiz if is_left else 0, pixels_vert if is_top else 0)) - mask = Image.new("RGB", (process_res_w, process_res_h), "white") - draw = ImageDraw.Draw(mask) - draw.rectangle(( - expand_pixels + mask_blur if is_left else 0, - expand_pixels + mask_blur if is_top else 0, - mask.width - expand_pixels - mask_blur if is_right else res_w, - mask.height - expand_pixels - mask_blur if is_bottom else res_h, - ), fill="black") + img = Image.new("RGB", (process_res_w, process_res_h)) + img.paste(init[n], (pixels_horiz if is_left else 0, pixels_vert if is_top else 0)) + mask = Image.new("RGB", (process_res_w, process_res_h), "white") + draw = ImageDraw.Draw(mask) + draw.rectangle(( + expand_pixels + mask_blur if is_left else 0, + expand_pixels + mask_blur if is_top else 0, + mask.width - expand_pixels - mask_blur if is_right else res_w, + mask.height - expand_pixels - mask_blur if is_bottom else res_h, + ), fill="black") - np_image = (np.asarray(img) / 255.0).astype(np.float64) - np_mask = (np.asarray(mask) / 255.0).astype(np.float64) - noised = get_matched_noise(np_image, np_mask, noise_q, color_variation) - out = Image.fromarray(np.clip(noised * 255., 0., 255.).astype(np.uint8), mode="RGB") + np_image = (np.asarray(img) / 255.0).astype(np.float64) + np_mask = (np.asarray(mask) / 255.0).astype(np.float64) + noised = get_matched_noise(np_image, np_mask, noise_q, color_variation) + out = Image.fromarray(np.clip(noised * 255., 0., 255.).astype(np.uint8), mode="RGB") - target_width = min(process_width, init.width + pixels_horiz) if is_horiz else img.width - target_height = min(process_height, init.height + pixels_vert) if is_vert else img.height + target_width = min(process_width, init[n].width + pixels_horiz) if is_horiz else img.width + target_height = min(process_height, init[n].height + pixels_vert) if is_vert else img.height + p.width = target_width if is_horiz else img.width + p.height = target_height if is_vert else img.height - crop_region = ( - 0 if is_left else out.width - target_width, - 0 if is_top else out.height - target_height, - target_width if is_left else out.width, - target_height if is_top else out.height, - ) + crop_region = ( + 0 if is_left else out.width - target_width, + 0 if is_top else out.height - target_height, + target_width if is_left else out.width, + target_height if is_top else out.height, + ) + mask = mask.crop(crop_region) + p.image_mask = mask - image_to_process = out.crop(crop_region) - mask = mask.crop(crop_region) + image_to_process = out.crop(crop_region) + images_to_process.append(image_to_process) - p.width = target_width if is_horiz else img.width - p.height = target_height if is_vert else img.height - p.init_images = [image_to_process] - p.image_mask = mask + p.init_images = images_to_process latent_mask = Image.new("RGB", (p.width, p.height), "white") draw = ImageDraw.Draw(latent_mask) @@ -232,44 +235,49 @@ class Script(scripts.Script): p.latent_mask = latent_mask proc = process_images(p) - proc_img = proc.images[0] if initial_seed_and_info[0] is None: initial_seed_and_info[0] = proc.seed initial_seed_and_info[1] = proc.info - out.paste(proc_img, (0 if is_left else out.width - proc_img.width, 0 if is_top else out.height - proc_img.height)) - out = out.crop((0, 0, res_w, res_h)) - return out + for proc_img in proc.images: + out.paste(proc_img, (0 if is_left else out.width - proc_img.width, 0 if is_top else out.height - proc_img.height)) + out = out.crop((0, 0, res_w, res_h)) + + return proc.images batch_count = p.n_iter + batch_size = p.batch_size p.n_iter = 1 state.job_count = batch_count - all_images = [] + all_processed_images = [] for i in range(batch_count): - img = init_image - state.job = f"Batch {i + 1} out of {state.job_count}" + imgs = [init_img] * batch_size + state.job = f"Batch {i + 1} out of {batch_count}" if left > 0: - img = expand(img, left, is_left=True) + imgs = expand(imgs, batch_size, left, is_left=True) if right > 0: - img = expand(img, right, is_right=True) + imgs = expand(imgs, batch_size, right, is_right=True) if up > 0: - img = expand(img, up, is_top=True) + imgs = expand(imgs, batch_size, up, is_top=True) if down > 0: - img = expand(img, down, is_bottom=True) + imgs = expand(imgs, batch_size, down, is_bottom=True) - all_images.append(img) + all_processed_images += imgs + + combined_grid_image = images.image_grid(all_processed_images) + all_images = all_processed_images - combined_grid_image = images.image_grid(all_images) if opts.return_grid: - all_images = [combined_grid_image] + all_images - + all_images = [combined_grid_image] + all_processed_images + res = Processed(p, all_images, initial_seed_and_info[0], initial_seed_and_info[1]) if opts.samples_save: - images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.grid_format, info=res.info, p=p) + for img in all_processed_images: + images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.grid_format, info=res.info, p=p) if opts.grid_save: images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.grid_format, info=res.info, short_filename=not opts.grid_extended_filename, grid=True, p=p) From 18df060c3e9252f1cf79b494e7173aff4181049a Mon Sep 17 00:00:00 2001 From: wywywywy Date: Thu, 20 Oct 2022 16:16:09 +0100 Subject: [PATCH 0081/1118] Fixed outpainting_mk2 output cropping --- scripts/outpainting_mk_2.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 0377ab32..726417e7 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -183,6 +183,7 @@ class Script(scripts.Script): pixels_vert = expand_pixels if is_vert else 0 images_to_process = [] + output_images = [] for n in range(count): res_w = init[n].width + pixels_horiz res_h = init[n].height + pixels_vert @@ -203,7 +204,7 @@ class Script(scripts.Script): np_image = (np.asarray(img) / 255.0).astype(np.float64) np_mask = (np.asarray(mask) / 255.0).astype(np.float64) noised = get_matched_noise(np_image, np_mask, noise_q, color_variation) - out = Image.fromarray(np.clip(noised * 255., 0., 255.).astype(np.uint8), mode="RGB") + output_images.append(Image.fromarray(np.clip(noised * 255., 0., 255.).astype(np.uint8), mode="RGB")) target_width = min(process_width, init[n].width + pixels_horiz) if is_horiz else img.width target_height = min(process_height, init[n].height + pixels_vert) if is_vert else img.height @@ -211,15 +212,15 @@ class Script(scripts.Script): p.height = target_height if is_vert else img.height crop_region = ( - 0 if is_left else out.width - target_width, - 0 if is_top else out.height - target_height, - target_width if is_left else out.width, - target_height if is_top else out.height, + 0 if is_left else output_images[n].width - target_width, + 0 if is_top else output_images[n].height - target_height, + target_width if is_left else output_images[n].width, + target_height if is_top else output_images[n].height, ) mask = mask.crop(crop_region) p.image_mask = mask - image_to_process = out.crop(crop_region) + image_to_process = output_images[n].crop(crop_region) images_to_process.append(image_to_process) p.init_images = images_to_process @@ -240,11 +241,11 @@ class Script(scripts.Script): initial_seed_and_info[0] = proc.seed initial_seed_and_info[1] = proc.info - for proc_img in proc.images: - out.paste(proc_img, (0 if is_left else out.width - proc_img.width, 0 if is_top else out.height - proc_img.height)) - out = out.crop((0, 0, res_w, res_h)) + for n in range(count): + output_images[n].paste(proc.images[n], (0 if is_left else output_images[n].width - proc.images[n].width, 0 if is_top else output_images[n].height - proc.images[n].height)) + output_images[n] = output_images[n].crop((0, 0, res_w, res_h)) - return proc.images + return output_images batch_count = p.n_iter batch_size = p.batch_size From d07cb46f34b3d9fe7a78b102f899ebef352ea56b Mon Sep 17 00:00:00 2001 From: yfszzx Date: Thu, 20 Oct 2022 23:58:52 +0800 Subject: [PATCH 0082/1118] inspiration pull request --- .gitignore | 3 +- javascript/imageviewer.js | 1 - javascript/inspiration.js | 42 +++++++++ modules/inspiration.py | 122 +++++++++++++++++++++++++++ modules/shared.py | 1 + modules/ui.py | 13 +-- scripts/create_inspiration_images.py | 45 ++++++++++ webui.py | 5 ++ 8 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 javascript/inspiration.js create mode 100644 modules/inspiration.py create mode 100644 scripts/create_inspiration_images.py diff --git a/.gitignore b/.gitignore index f9c3357c..434d50b7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ __pycache__ notification.mp3 /SwinIR /textual_inversion -.vscode \ No newline at end of file +.vscode +/inspiration \ No newline at end of file diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index 9e380c65..d4ab6984 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -116,7 +116,6 @@ function showGalleryImage() { e.dataset.modded = true; if(e && e.parentElement.tagName == 'DIV'){ e.style.cursor='pointer' - e.style.userSelect='none' e.addEventListener('click', function (evt) { if(!opts.js_modal_lightbox) return; modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed) diff --git a/javascript/inspiration.js b/javascript/inspiration.js new file mode 100644 index 00000000..e1c0e114 --- /dev/null +++ b/javascript/inspiration.js @@ -0,0 +1,42 @@ +function public_image_index_in_gallery(item, gallery){ + var index; + var i = 0; + gallery.querySelectorAll("img").forEach(function(e){ + if (e == item) + index = i; + i += 1; + }); + return index; +} + +function inspiration_selected(name, types, name_list){ + var btn = gradioApp().getElementById("inspiration_select_button") + return [gradioApp().getElementById("inspiration_select_button").getAttribute("img-index"), types]; +} +var inspiration_image_click = function(){ + var index = public_image_index_in_gallery(this, gradioApp().getElementById("inspiration_gallery")); + var btn = gradioApp().getElementById("inspiration_select_button") + btn.setAttribute("img-index", index) + setTimeout(function(btn){btn.click();}, 10, btn) +} + +document.addEventListener("DOMContentLoaded", function() { + var mutationObserver = new MutationObserver(function(m){ + var gallery = gradioApp().getElementById("inspiration_gallery") + if (gallery) { + var node = gallery.querySelector(".absolute.backdrop-blur.h-full") + if (node) { + node.style.display = "None"; //parentNode.removeChild(node) + } + + gallery.querySelectorAll('img').forEach(function(e){ + e.onclick = inspiration_image_click + }) + + } + + + }); + mutationObserver.observe( gradioApp(), { childList:true, subtree:true }); + +}); diff --git a/modules/inspiration.py b/modules/inspiration.py new file mode 100644 index 00000000..456bfcb5 --- /dev/null +++ b/modules/inspiration.py @@ -0,0 +1,122 @@ +import os +import random +import gradio +inspiration_path = "inspiration" +inspiration_system_path = os.path.join(inspiration_path, "system") +def read_name_list(file): + if not os.path.exists(file): + return [] + f = open(file, "r") + ret = [] + line = f.readline() + while len(line) > 0: + line = line.rstrip("\n") + ret.append(line) + print(ret) + return ret + +def save_name_list(file, name): + print(file) + f = open(file, "a") + f.write(name + "\n") + +def get_inspiration_images(source, types): + path = os.path.join(inspiration_path , types) + if source == "Favorites": + names = read_name_list(os.path.join(inspiration_system_path, types + "_faverites.txt")) + names = random.sample(names, 25) + elif source == "Abandoned": + names = read_name_list(os.path.join(inspiration_system_path, types + "_abondened.txt")) + names = random.sample(names, 25) + elif source == "Exclude abandoned": + abondened = read_name_list(os.path.join(inspiration_system_path, types + "_abondened.txt")) + all_names = os.listdir(path) + names = [] + while len(names) < 25: + name = random.choice(all_names) + if name not in abondened: + names.append(name) + else: + names = random.sample(os.listdir(path), 25) + names = random.sample(names, 25) + image_list = [] + for a in names: + image_path = os.path.join(path, a) + images = os.listdir(image_path) + image_list.append(os.path.join(image_path, random.choice(images))) + return image_list, names + +def select_click(index, types, name_list): + name = name_list[int(index)] + path = os.path.join(inspiration_path, types, name) + images = os.listdir(path) + return name, [os.path.join(path, x) for x in images] + +def give_up_click(name, types): + file = os.path.join(inspiration_system_path, types + "_abandoned.txt") + name_list = read_name_list(file) + if name not in name_list: + save_name_list(file, name) + +def collect_click(name, types): + file = os.path.join(inspiration_system_path, types + "_faverites.txt") + print(file) + name_list = read_name_list(file) + print(name_list) + if name not in name_list: + save_name_list(file, name) + +def moveout_click(name, types): + file = os.path.join(inspiration_system_path, types + "_faverites.txt") + name_list = read_name_list(file) + if name not in name_list: + save_name_list(file, name) + +def source_change(source): + if source == "Abandoned" or source == "Favorites": + return gradio.Button.update(visible=True, value=f"Move out {source}") + else: + return gradio.Button.update(visible=False) + +def ui(gr, opts): + with gr.Blocks(analytics_enabled=False) as inspiration: + flag = os.path.exists(inspiration_path) + if flag: + types = os.listdir(inspiration_path) + types = [x for x in types if x != "system"] + flag = len(types) > 0 + if not flag: + os.mkdir(inspiration_path) + gr.HTML(""" +
" + """) + return inspiration + if not os.path.exists(inspiration_system_path): + os.mkdir(inspiration_system_path) + gallery, names = get_inspiration_images("Exclude abandoned", types[0]) + with gr.Row(): + with gr.Column(scale=2): + inspiration_gallery = gr.Gallery(gallery, show_label=False, elem_id="inspiration_gallery").style(grid=5, height='auto') + with gr.Column(scale=1): + types = gr.Dropdown(choices=types, value=types[0], label="Type", visible=len(types) > 1) + with gr.Row(): + source = gr.Dropdown(choices=["All", "Favorites", "Exclude abandoned", "Abandoned"], value="Exclude abandoned", label="Source") + get_inspiration = gr.Button("Get inspiration") + name = gr.Textbox(show_label=False, interactive=False) + with gr.Row(): + send_to_txt2img = gr.Button('to txt2img') + send_to_img2img = gr.Button('to img2img') + style_gallery = gr.Gallery(show_label=False, elem_id="inspiration_style_gallery").style(grid=2, height='auto') + + collect = gr.Button('Collect') + give_up = gr.Button("Don't show any more") + moveout = gr.Button("Move out", visible=False) + with gr.Row(): + select_button = gr.Button('set button', elem_id="inspiration_select_button") + name_list = gr.State(names) + source.change(source_change, inputs=[source], outputs=[moveout]) + get_inspiration.click(get_inspiration_images, inputs=[source, types], outputs=[inspiration_gallery, name_list]) + select_button.click(select_click, _js="inspiration_selected", inputs=[name, types, name_list], outputs=[name, style_gallery]) + give_up.click(give_up_click, inputs=[name, types], outputs=None) + collect.click(collect_click, inputs=[name, types], outputs=None) + return inspiration diff --git a/modules/shared.py b/modules/shared.py index faede821..ae033710 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -78,6 +78,7 @@ parser.add_argument('--vae-path', type=str, help='Path to Variational Autoencode parser.add_argument("--disable-safe-unpickle", action='store_true', help="disable checking pytorch models for malicious code", default=False) parser.add_argument("--api", action='store_true', help="use api=True to launch the api with the webui") parser.add_argument("--nowebui", action='store_true', help="use api=True to launch the api instead of the webui") +parser.add_argument("--ui-debug-mode", action='store_true', help="Don't load model to quickly launch UI") cmd_opts = parser.parse_args() restricted_opts = [ diff --git a/modules/ui.py b/modules/ui.py index a2dbd41e..6a0a3c3b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -41,7 +41,8 @@ from modules import prompt_parser from modules.images import save_image import modules.textual_inversion.ui import modules.hypernetworks.ui -import modules.images_history as img_his +import modules.images_history as images_history +import modules.inspiration as inspiration # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() @@ -1082,9 +1083,9 @@ def create_ui(wrap_gradio_gpu_call): upscaling_resize_w = gr.Number(label="Width", value=512, precision=0) upscaling_resize_h = gr.Number(label="Height", value=512, precision=0) upscaling_crop = gr.Checkbox(label='Crop to fit', value=True) - + with gr.Group(): - extras_upscaler_1 = gr.Radio(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index") + extras_upscaler_1 = gr.Radio(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers] , value=shared.sd_upscalers[0].name, type="index") with gr.Group(): extras_upscaler_2 = gr.Radio(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index") @@ -1178,7 +1179,8 @@ def create_ui(wrap_gradio_gpu_call): "i2i":img2img_paste_fields } - images_history = img_his.create_history_tabs(gr, opts, wrap_gradio_call(modules.extras.run_pnginfo), images_history_switch_dict) + browser_interface = images_history.create_history_tabs(gr, opts, wrap_gradio_call(modules.extras.run_pnginfo), images_history_switch_dict) + inspiration_interface = inspiration.ui(gr, opts) with gr.Blocks() as modelmerger_interface: with gr.Row().style(equal_height=False): @@ -1595,7 +1597,8 @@ Requested path was: {f} (img2img_interface, "img2img", "img2img"), (extras_interface, "Extras", "extras"), (pnginfo_interface, "PNG Info", "pnginfo"), - (images_history, "History", "images_history"), + (browser_interface, "History", "images_history"), + (inspiration_interface, "Inspiration", "inspiration"), (modelmerger_interface, "Checkpoint Merger", "modelmerger"), (train_interface, "Train", "ti"), (settings_interface, "Settings", "settings"), diff --git a/scripts/create_inspiration_images.py b/scripts/create_inspiration_images.py new file mode 100644 index 00000000..6a20def8 --- /dev/null +++ b/scripts/create_inspiration_images.py @@ -0,0 +1,45 @@ +import csv, os, shutil +import modules.scripts as scripts +from modules import processing, shared, sd_samplers, images +from modules.processing import Processed + + +class Script(scripts.Script): + def title(self): + return "Create artists style image" + + def show(self, is_img2img): + return not is_img2img + + def ui(self, is_img2img): + return [] + def show(self, is_img2img): + return not is_img2img + + def run(self, p): #, max_snapshoots_num): + path = os.path.join("style_snapshoot", "artist") + if not os.path.exists(path): + os.makedirs(path) + p.do_not_save_samples = True + p.do_not_save_grid = True + p.negative_prompt = "portrait photo" + f = open('artists.csv') + f_csv = csv.reader(f) + for row in f_csv: + name = row[0] + artist_path = os.path.join(path, name) + if not os.path.exists(artist_path): + os.mkdir(artist_path) + if len(os.listdir(artist_path)) > 0: + continue + print(name) + p.prompt = name + processed = processing.process_images(p) + for img in processed.images: + i = 0 + filename = os.path.join(artist_path, format(0, "03d") + ".jpg") + while os.path.exists(filename): + i += 1 + filename = os.path.join(artist_path, format(i, "03d") + ".jpg") + img.save(filename, quality=70) + return processed diff --git a/webui.py b/webui.py index 177bef74..5923905f 100644 --- a/webui.py +++ b/webui.py @@ -72,6 +72,11 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs) def initialize(): + if cmd_opts.ui_debug_mode: + class enmpty(): + name = None + shared.sd_upscalers = [enmpty()] + return modelloader.cleanup_models() modules.sd_models.setup_model() codeformer.setup_model(cmd_opts.codeformer_models_path) From 108be15500aac590b4e00420635d7b61fccfa530 Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Fri, 21 Oct 2022 01:00:41 +0900 Subject: [PATCH 0083/1118] fix bugs and optimizations --- modules/hypernetworks/hypernetwork.py | 93 +++++++++++++++------------ 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 905cbeef..893ba110 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -36,14 +36,14 @@ class HypernetworkModule(torch.nn.Module): linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) # if skip_first_layer because first parameters potentially contain negative values # if i < 1: continue + if add_layer_norm: + linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) if activation_func in HypernetworkModule.activation_dict: linears.append(HypernetworkModule.activation_dict[activation_func]()) else: print("Invalid key {} encountered as activation function!".format(activation_func)) # if use_dropout: # linears.append(torch.nn.Dropout(p=0.3)) - if add_layer_norm: - linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) self.linear = torch.nn.Sequential(*linears) @@ -115,11 +115,24 @@ class Hypernetwork: for k, layers in self.layers.items(): for layer in layers: - layer.train() res += layer.trainables() return res + def eval(self): + for k, layers in self.layers.items(): + for layer in layers: + layer.eval() + for items in self.weights(): + items.requires_grad = False + + def train(self): + for k, layers in self.layers.items(): + for layer in layers: + layer.train() + for items in self.weights(): + items.requires_grad = True + def save(self, filename): state_dict = {} @@ -290,10 +303,6 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log shared.sd_model.first_stage_model.to(devices.cpu) hypernetwork = shared.loaded_hypernetwork - weights = hypernetwork.weights() - for weight in weights: - weight.requires_grad = True - losses = torch.zeros((32,)) last_saved_file = "" @@ -304,10 +313,10 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log return hypernetwork, filename scheduler = LearnRateScheduler(learn_rate, steps, ititial_step) - # if optimizer == "AdamW": or else Adam / AdamW / SGD, etc... - optimizer = torch.optim.AdamW(weights, lr=scheduler.learn_rate) + optimizer = torch.optim.AdamW(hypernetwork.weights(), lr=scheduler.learn_rate) pbar = tqdm.tqdm(enumerate(ds), total=steps - ititial_step) + hypernetwork.train() for i, entries in pbar: hypernetwork.step = i + ititial_step @@ -328,8 +337,9 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log losses[hypernetwork.step % losses.shape[0]] = loss.item() - optimizer.zero_grad() + optimizer.zero_grad(set_to_none=True) loss.backward() + del loss optimizer.step() mean_loss = losses.mean() if torch.isnan(mean_loss): @@ -346,44 +356,47 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log }) if hypernetwork.step > 0 and images_dir is not None and hypernetwork.step % create_image_every == 0: + torch.cuda.empty_cache() last_saved_image = os.path.join(images_dir, f'{hypernetwork_name}-{hypernetwork.step}.png') + with torch.no_grad(): + hypernetwork.eval() + shared.sd_model.cond_stage_model.to(devices.device) + shared.sd_model.first_stage_model.to(devices.device) - optimizer.zero_grad() - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) + p = processing.StableDiffusionProcessingTxt2Img( + sd_model=shared.sd_model, + do_not_save_grid=True, + do_not_save_samples=True, + ) - p = processing.StableDiffusionProcessingTxt2Img( - sd_model=shared.sd_model, - do_not_save_grid=True, - do_not_save_samples=True, - ) + if preview_from_txt2img: + p.prompt = preview_prompt + p.negative_prompt = preview_negative_prompt + p.steps = preview_steps + p.sampler_index = preview_sampler_index + p.cfg_scale = preview_cfg_scale + p.seed = preview_seed + p.width = preview_width + p.height = preview_height + else: + p.prompt = entries[0].cond_text + p.steps = 20 - if preview_from_txt2img: - p.prompt = preview_prompt - p.negative_prompt = preview_negative_prompt - p.steps = preview_steps - p.sampler_index = preview_sampler_index - p.cfg_scale = preview_cfg_scale - p.seed = preview_seed - p.width = preview_width - p.height = preview_height - else: - p.prompt = entries[0].cond_text - p.steps = 20 + preview_text = p.prompt - preview_text = p.prompt + processed = processing.process_images(p) + image = processed.images[0] if len(processed.images)>0 else None - processed = processing.process_images(p) - image = processed.images[0] if len(processed.images)>0 else None + if unload: + shared.sd_model.cond_stage_model.to(devices.cpu) + shared.sd_model.first_stage_model.to(devices.cpu) - if unload: - shared.sd_model.cond_stage_model.to(devices.cpu) - shared.sd_model.first_stage_model.to(devices.cpu) + if image is not None: + shared.state.current_image = image + image.save(last_saved_image) + last_saved_image += f", prompt: {preview_text}" - if image is not None: - shared.state.current_image = image - image.save(last_saved_image) - last_saved_image += f", prompt: {preview_text}" + hypernetwork.train() shared.state.job_no = hypernetwork.step From f89829ec3a0baceb445451ad98d4fb4323e922aa Mon Sep 17 00:00:00 2001 From: aria1th <35677394+aria1th@users.noreply.github.com> Date: Fri, 21 Oct 2022 01:37:11 +0900 Subject: [PATCH 0084/1118] Revert "fix bugs and optimizations" This reverts commit 108be15500aac590b4e00420635d7b61fccfa530. --- modules/hypernetworks/hypernetwork.py | 93 ++++++++++++--------------- 1 file changed, 40 insertions(+), 53 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 893ba110..905cbeef 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -36,14 +36,14 @@ class HypernetworkModule(torch.nn.Module): linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) # if skip_first_layer because first parameters potentially contain negative values # if i < 1: continue - if add_layer_norm: - linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) if activation_func in HypernetworkModule.activation_dict: linears.append(HypernetworkModule.activation_dict[activation_func]()) else: print("Invalid key {} encountered as activation function!".format(activation_func)) # if use_dropout: # linears.append(torch.nn.Dropout(p=0.3)) + if add_layer_norm: + linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) self.linear = torch.nn.Sequential(*linears) @@ -115,24 +115,11 @@ class Hypernetwork: for k, layers in self.layers.items(): for layer in layers: + layer.train() res += layer.trainables() return res - def eval(self): - for k, layers in self.layers.items(): - for layer in layers: - layer.eval() - for items in self.weights(): - items.requires_grad = False - - def train(self): - for k, layers in self.layers.items(): - for layer in layers: - layer.train() - for items in self.weights(): - items.requires_grad = True - def save(self, filename): state_dict = {} @@ -303,6 +290,10 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log shared.sd_model.first_stage_model.to(devices.cpu) hypernetwork = shared.loaded_hypernetwork + weights = hypernetwork.weights() + for weight in weights: + weight.requires_grad = True + losses = torch.zeros((32,)) last_saved_file = "" @@ -313,10 +304,10 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log return hypernetwork, filename scheduler = LearnRateScheduler(learn_rate, steps, ititial_step) - optimizer = torch.optim.AdamW(hypernetwork.weights(), lr=scheduler.learn_rate) + # if optimizer == "AdamW": or else Adam / AdamW / SGD, etc... + optimizer = torch.optim.AdamW(weights, lr=scheduler.learn_rate) pbar = tqdm.tqdm(enumerate(ds), total=steps - ititial_step) - hypernetwork.train() for i, entries in pbar: hypernetwork.step = i + ititial_step @@ -337,9 +328,8 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log losses[hypernetwork.step % losses.shape[0]] = loss.item() - optimizer.zero_grad(set_to_none=True) + optimizer.zero_grad() loss.backward() - del loss optimizer.step() mean_loss = losses.mean() if torch.isnan(mean_loss): @@ -356,47 +346,44 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log }) if hypernetwork.step > 0 and images_dir is not None and hypernetwork.step % create_image_every == 0: - torch.cuda.empty_cache() last_saved_image = os.path.join(images_dir, f'{hypernetwork_name}-{hypernetwork.step}.png') - with torch.no_grad(): - hypernetwork.eval() - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) - p = processing.StableDiffusionProcessingTxt2Img( - sd_model=shared.sd_model, - do_not_save_grid=True, - do_not_save_samples=True, - ) + optimizer.zero_grad() + shared.sd_model.cond_stage_model.to(devices.device) + shared.sd_model.first_stage_model.to(devices.device) - if preview_from_txt2img: - p.prompt = preview_prompt - p.negative_prompt = preview_negative_prompt - p.steps = preview_steps - p.sampler_index = preview_sampler_index - p.cfg_scale = preview_cfg_scale - p.seed = preview_seed - p.width = preview_width - p.height = preview_height - else: - p.prompt = entries[0].cond_text - p.steps = 20 + p = processing.StableDiffusionProcessingTxt2Img( + sd_model=shared.sd_model, + do_not_save_grid=True, + do_not_save_samples=True, + ) - preview_text = p.prompt + if preview_from_txt2img: + p.prompt = preview_prompt + p.negative_prompt = preview_negative_prompt + p.steps = preview_steps + p.sampler_index = preview_sampler_index + p.cfg_scale = preview_cfg_scale + p.seed = preview_seed + p.width = preview_width + p.height = preview_height + else: + p.prompt = entries[0].cond_text + p.steps = 20 - processed = processing.process_images(p) - image = processed.images[0] if len(processed.images)>0 else None + preview_text = p.prompt - if unload: - shared.sd_model.cond_stage_model.to(devices.cpu) - shared.sd_model.first_stage_model.to(devices.cpu) + processed = processing.process_images(p) + image = processed.images[0] if len(processed.images)>0 else None - if image is not None: - shared.state.current_image = image - image.save(last_saved_image) - last_saved_image += f", prompt: {preview_text}" + if unload: + shared.sd_model.cond_stage_model.to(devices.cpu) + shared.sd_model.first_stage_model.to(devices.cpu) - hypernetwork.train() + if image is not None: + shared.state.current_image = image + image.save(last_saved_image) + last_saved_image += f", prompt: {preview_text}" shared.state.job_no = hypernetwork.step From 92a17a7a4a13fceb3c3e25a2e854b2a7dd6eb5df Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Thu, 20 Oct 2022 09:45:03 -0700 Subject: [PATCH 0085/1118] Made dummy latents smaller. Minor code cleanups --- modules/processing.py | 7 ++++--- modules/sd_samplers.py | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 3caac25e..539cde38 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -557,7 +557,8 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): else: # Dummy zero conditioning if we're not using inpainting model. # Still takes up a bit of memory, but no encoder call. - image_conditioning = torch.zeros(x.shape[0], 5, x.shape[-2], x.shape[-1], dtype=x.dtype, device=x.device) + # Pretty sure we can just make this a 1x1 image since its not going to be used besides its batch size. + image_conditioning = torch.zeros(x.shape[0], 5, 1, 1, dtype=x.dtype, device=x.device) return image_conditioning @@ -759,8 +760,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.image_conditioning = self.image_conditioning.to(shared.device).type(self.sd_model.dtype) else: self.image_conditioning = torch.zeros( - self.init_latent.shape[0], 5, self.init_latent.shape[-2], self.init_latent.shape[-1], - dtype=self.init_latent.dtype, + self.init_latent.shape[0], 5, 1, 1, + dtype=self.init_latent.dtype, device=self.init_latent.device ) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index c21be26e..cc682593 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -138,7 +138,7 @@ class VanillaStableDiffusionSampler: if self.stop_at is not None and self.step > self.stop_at: raise InterruptedException - # Have to unwrap the inpainting conditioning here to perform pre-preocessing + # Have to unwrap the inpainting conditioning here to perform pre-processing image_conditioning = None if isinstance(cond, dict): image_conditioning = cond["c_concat"][0] @@ -146,7 +146,7 @@ class VanillaStableDiffusionSampler: unconditional_conditioning = unconditional_conditioning["c_crossattn"][0] conds_list, tensor = prompt_parser.reconstruct_multicond_batch(cond, self.step) - unconditional_conditioning = prompt_parser.reconstruct_cond_batch(unconditional_conditioning, self.step) + unconditional_conditioning = prompt_parser.reconstruct_cond_batch(unconditional_conditioning, self.step) assert all([len(conds) == 1 for conds in conds_list]), 'composition via AND is not supported for DDIM/PLMS samplers' cond = tensor @@ -165,6 +165,8 @@ class VanillaStableDiffusionSampler: img_orig = self.sampler.model.q_sample(self.init_latent, ts) x_dec = img_orig * self.mask + self.nmask * x_dec + # Wrap the image conditioning back up since the DDIM code can accept the dict directly. + # Note that they need to be lists because it just concatenates them later. if image_conditioning is not None: cond = {"c_concat": [image_conditioning], "c_crossattn": [cond]} unconditional_conditioning = {"c_concat": [image_conditioning], "c_crossattn": [unconditional_conditioning]} From d1cb08bfb221cd1b0cfc6078162b4e206ea80a5c Mon Sep 17 00:00:00 2001 From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com> Date: Thu, 20 Oct 2022 22:49:06 +0300 Subject: [PATCH 0086/1118] fix skip and interrupt for highres. fix option --- modules/processing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index bcb0c32c..6324ca91 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -587,9 +587,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): x = None devices.torch_gc() - samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps) - - return samples + return self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps) or samples class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): From 708c3a7bd8ce68cbe1aa7c268e5a4b1980affc9f Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Thu, 20 Oct 2022 13:28:43 -0700 Subject: [PATCH 0087/1118] Added PLMS hijack and made sure to always replace methods --- modules/sd_hijack_inpainting.py | 163 ++++++++++++++++++++++++++++++-- modules/sd_models.py | 3 +- 2 files changed, 157 insertions(+), 9 deletions(-) diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index d4d28d2e..43938071 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -1,16 +1,14 @@ import torch -import numpy as np -from tqdm import tqdm -from einops import rearrange, repeat +from einops import repeat from omegaconf import ListConfig -from types import MethodType - import ldm.models.diffusion.ddpm import ldm.models.diffusion.ddim +import ldm.models.diffusion.plms from ldm.models.diffusion.ddpm import LatentDiffusion +from ldm.models.diffusion.plms import PLMSSampler from ldm.models.diffusion.ddim import DDIMSampler, noise_like # ================================================================================================= @@ -19,7 +17,7 @@ from ldm.models.diffusion.ddim import DDIMSampler, noise_like # https://github.com/runwayml/stable-diffusion/blob/main/ldm/models/diffusion/ddim.py # ================================================================================================= @torch.no_grad() -def sample(self, +def sample_ddim(self, S, batch_size, shape, @@ -132,6 +130,153 @@ def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=F return x_prev, pred_x0 +# ================================================================================================= +# Monkey patch PLMSSampler methods. +# This one was not actually patched correctly in the RunwayML repo, but we can replicate the changes. +# Adapted from: +# https://github.com/CompVis/stable-diffusion/blob/main/ldm/models/diffusion/plms.py +# ================================================================================================= +@torch.no_grad() +def sample_plms(self, + S, + batch_size, + shape, + conditioning=None, + callback=None, + normals_sequence=None, + img_callback=None, + quantize_x0=False, + eta=0., + mask=None, + x0=None, + temperature=1., + noise_dropout=0., + score_corrector=None, + corrector_kwargs=None, + verbose=True, + x_T=None, + log_every_t=100, + unconditional_guidance_scale=1., + unconditional_conditioning=None, + # this has to come in the same format as the conditioning, # e.g. as encoded tokens, ... + **kwargs + ): + if conditioning is not None: + if isinstance(conditioning, dict): + ctmp = conditioning[list(conditioning.keys())[0]] + while isinstance(ctmp, list): + ctmp = ctmp[0] + cbs = ctmp.shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + + self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose) + # sampling + C, H, W = shape + size = (batch_size, C, H, W) + print(f'Data shape for PLMS sampling is {size}') + + samples, intermediates = self.plms_sampling(conditioning, size, + callback=callback, + img_callback=img_callback, + quantize_denoised=quantize_x0, + mask=mask, x0=x0, + ddim_use_original_steps=False, + noise_dropout=noise_dropout, + temperature=temperature, + score_corrector=score_corrector, + corrector_kwargs=corrector_kwargs, + x_T=x_T, + log_every_t=log_every_t, + unconditional_guidance_scale=unconditional_guidance_scale, + unconditional_conditioning=unconditional_conditioning, + ) + return samples, intermediates + + +@torch.no_grad() +def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, + temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, + unconditional_guidance_scale=1., unconditional_conditioning=None, old_eps=None, t_next=None): + b, *_, device = *x.shape, x.device + + def get_model_output(x, t): + if unconditional_conditioning is None or unconditional_guidance_scale == 1.: + e_t = self.model.apply_model(x, t, c) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t] * 2) + + if isinstance(c, dict): + assert isinstance(unconditional_conditioning, dict) + c_in = dict() + for k in c: + if isinstance(c[k], list): + c_in[k] = [ + torch.cat([unconditional_conditioning[k][i], c[k][i]]) + for i in range(len(c[k])) + ] + else: + c_in[k] = torch.cat([unconditional_conditioning[k], c[k]]) + else: + c_in = torch.cat([unconditional_conditioning, c]) + + e_t_uncond, e_t = self.model.apply_model(x_in, t_in, c_in).chunk(2) + e_t = e_t_uncond + unconditional_guidance_scale * (e_t - e_t_uncond) + + if score_corrector is not None: + assert self.model.parameterization == "eps" + e_t = score_corrector.modify_score(self.model, e_t, x, t, c, **corrector_kwargs) + + return e_t + + alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas + alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev + sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas + sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas + + def get_x_prev_and_pred_x0(e_t, index): + # select parameters corresponding to the currently considered timestep + a_t = torch.full((b, 1, 1, 1), alphas[index], device=device) + a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device) + sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device) + sqrt_one_minus_at = torch.full((b, 1, 1, 1), sqrt_one_minus_alphas[index],device=device) + + # current prediction for x_0 + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + if quantize_denoised: + pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) + # direction pointing to x_t + dir_xt = (1. - a_prev - sigma_t**2).sqrt() * e_t + noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature + if noise_dropout > 0.: + noise = torch.nn.functional.dropout(noise, p=noise_dropout) + x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise + return x_prev, pred_x0 + + e_t = get_model_output(x, t) + if len(old_eps) == 0: + # Pseudo Improved Euler (2nd order) + x_prev, pred_x0 = get_x_prev_and_pred_x0(e_t, index) + e_t_next = get_model_output(x_prev, t_next) + e_t_prime = (e_t + e_t_next) / 2 + elif len(old_eps) == 1: + # 2nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (3 * e_t - old_eps[-1]) / 2 + elif len(old_eps) == 2: + # 3nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (23 * e_t - 16 * old_eps[-1] + 5 * old_eps[-2]) / 12 + elif len(old_eps) >= 3: + # 4nd order Pseudo Linear Multistep (Adams-Bashforth) + e_t_prime = (55 * e_t - 59 * old_eps[-1] + 37 * old_eps[-2] - 9 * old_eps[-3]) / 24 + + x_prev, pred_x0 = get_x_prev_and_pred_x0(e_t_prime, index) + + return x_prev, pred_x0, e_t + # ================================================================================================= # Monkey patch LatentInpaintDiffusion to load the checkpoint with a proper config. # Adapted from: @@ -175,5 +320,9 @@ def should_hijack_inpainting(checkpoint_info): def do_inpainting_hijack(): ldm.models.diffusion.ddpm.get_unconditional_conditioning = get_unconditional_conditioning ldm.models.diffusion.ddpm.LatentInpaintDiffusion = LatentInpaintDiffusion + ldm.models.diffusion.ddim.DDIMSampler.p_sample_ddim = p_sample_ddim - ldm.models.diffusion.ddim.DDIMSampler.sample = sample \ No newline at end of file + ldm.models.diffusion.ddim.DDIMSampler.sample = sample_ddim + + ldm.models.diffusion.plms.PLMSSampler.p_sample_plms = p_sample_plms + ldm.models.diffusion.plms.PLMSSampler.sample = sample_plms \ No newline at end of file diff --git a/modules/sd_models.py b/modules/sd_models.py index 47836d25..7072db08 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -214,8 +214,6 @@ def load_model(): sd_config = OmegaConf.load(checkpoint_info.config) if should_hijack_inpainting(checkpoint_info): - do_inpainting_hijack() - # Hardcoded config for now... sd_config.model.target = "ldm.models.diffusion.ddpm.LatentInpaintDiffusion" sd_config.model.params.use_ema = False @@ -225,6 +223,7 @@ def load_model(): # Create a "fake" config with a different name so that we know to unload it when switching models. checkpoint_info = checkpoint_info._replace(config=checkpoint_info.config.replace(".yaml", "-inpainting.yaml")) + do_inpainting_hijack() sd_model = instantiate_from_config(sd_config.model) load_model_weights(sd_model, checkpoint_info) From d23a46ceaa76af2847f11172f32c92665c268b1b Mon Sep 17 00:00:00 2001 From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com> Date: Thu, 20 Oct 2022 23:49:14 +0300 Subject: [PATCH 0088/1118] Different approach to skip/interrupt with highres fix --- modules/processing.py | 4 +++- modules/sd_samplers.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 6324ca91..bcb0c32c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -587,7 +587,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): x = None devices.torch_gc() - return self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps) or samples + samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps) + + return samples class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index b58e810b..7ff77c01 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -196,6 +196,7 @@ class VanillaStableDiffusionSampler: x1 = self.sampler.stochastic_encode(x, torch.tensor([t_enc] * int(x.shape[0])).to(shared.device), noise=noise) self.init_latent = x + self.last_latent = x self.step = 0 samples = self.launch_sampling(steps, lambda: self.sampler.decode(x1, conditioning, t_enc, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning)) @@ -206,6 +207,7 @@ class VanillaStableDiffusionSampler: self.initialize(p) self.init_latent = None + self.last_latent = x self.step = 0 steps = steps or p.steps @@ -388,6 +390,7 @@ class KDiffusionSampler: extra_params_kwargs['sigmas'] = sigma_sched self.model_wrap_cfg.init_latent = x + self.last_latent = x samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, xi, extra_args={'cond': conditioning, 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale}, disable=False, callback=self.callback_state, **extra_params_kwargs)) @@ -414,6 +417,7 @@ class KDiffusionSampler: else: extra_params_kwargs['sigmas'] = sigmas + self.last_latent = x samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args={'cond': conditioning, 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale}, disable=False, callback=self.callback_state, **extra_params_kwargs)) return samples From 9cc4974d2362a49a505e9408a4d992f26ffad02d Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Thu, 20 Oct 2022 17:03:25 -0500 Subject: [PATCH 0089/1118] add confirmation dialogue --- javascript/ui.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/javascript/ui.js b/javascript/ui.js index 165383da..037cffca 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -164,6 +164,8 @@ function selected_tab_id() { function trash_prompt(_,_, is_img2img) { +if(!confirm("Delete prompt?")) return false + if(selected_tab_id() == "tab_txt2img") { pos_prompt = txt2img_textarea = gradioApp().querySelector("#txt2img_prompt > label > textarea"); neg_prompt = txt2img_textarea = gradioApp().querySelector("#txt2img_neg_prompt > label > textarea"); @@ -177,6 +179,8 @@ function trash_prompt(_,_, is_img2img) { pos_prompt.value = "" neg_prompt.value = "" } + + return true } From a81651498018f6a0d5144f2ba957f685d7c28028 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Thu, 20 Oct 2022 17:33:33 -0500 Subject: [PATCH 0090/1118] remove unnecessary assignment --- javascript/ui.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 037cffca..39eae1f7 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -163,21 +163,20 @@ function selected_tab_id() { } function trash_prompt(_,_, is_img2img) { +//txt2img_token_button if(!confirm("Delete prompt?")) return false if(selected_tab_id() == "tab_txt2img") { - pos_prompt = txt2img_textarea = gradioApp().querySelector("#txt2img_prompt > label > textarea"); - neg_prompt = txt2img_textarea = gradioApp().querySelector("#txt2img_neg_prompt > label > textarea"); + gradioApp().querySelector("#txt2img_prompt > label > textarea").value = ""; + gradioApp().querySelector("#txt2img_neg_prompt > label > textarea").value = ""; - pos_prompt.value = "" - neg_prompt.value = "" + update_token_counter("img2img_token_button") } else { - pos_prompt = txt2img_textarea = gradioApp().querySelector("#img2img_prompt > label > textarea"); - neg_prompt = txt2img_textarea = gradioApp().querySelector("#img2img_neg_prompt > label > textarea"); + gradioApp().querySelector("#img2img_prompt > label > textarea").value = ""; + gradioApp().querySelector("#img2img_neg_prompt > label > textarea").value = ""; - pos_prompt.value = "" - neg_prompt.value = "" + update_token_counter("txt2img_token_button") } return true From 49533eed9e3aad19e9868ee140708baec4fd44be Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Thu, 20 Oct 2022 16:01:27 -0700 Subject: [PATCH 0091/1118] XY grid correctly re-assignes model when config changes --- modules/sd_models.py | 6 +++--- scripts/xy_grid.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 7072db08..fea84630 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -204,9 +204,9 @@ def load_model_weights(model, checkpoint_info): model.sd_checkpoint_info = checkpoint_info -def load_model(): +def load_model(checkpoint_info=None): from modules import lowvram, sd_hijack - checkpoint_info = select_checkpoint() + checkpoint_info = checkpoint_info or select_checkpoint() if checkpoint_info.config != shared.cmd_opts.config: print(f"Loading config from: {checkpoint_info.config}") @@ -249,7 +249,7 @@ def reload_model_weights(sd_model, info=None): if sd_model.sd_checkpoint_info.config != checkpoint_info.config or should_hijack_inpainting(checkpoint_info) != should_hijack_inpainting(sd_model.sd_checkpoint_info): checkpoints_loaded.clear() - shared.sd_model = load_model() + shared.sd_model = load_model(checkpoint_info) return shared.sd_model if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index 5cca168a..eff0c942 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -89,6 +89,7 @@ def apply_checkpoint(p, x, xs): if info is None: raise RuntimeError(f"Unknown checkpoint: {x}") modules.sd_models.reload_model_weights(shared.sd_model, info) + p.sd_model = shared.sd_model def confirm_checkpoints(p, xs): From a3b047b7c74dc6ca07f40aee778997fc1889d72f Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Thu, 20 Oct 2022 19:28:58 -0500 Subject: [PATCH 0092/1118] add settings option to toggle button visibility --- javascript/ui.js | 1 - modules/shared.py | 1 + modules/ui.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 39eae1f7..f19af550 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -163,7 +163,6 @@ function selected_tab_id() { } function trash_prompt(_,_, is_img2img) { -//txt2img_token_button if(!confirm("Delete prompt?")) return false diff --git a/modules/shared.py b/modules/shared.py index faede821..7e9c2696 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -300,6 +300,7 @@ options_templates.update(options_section(('ui', "User interface"), { "js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"), "js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"), "show_progress_in_title": OptionInfo(True, "Show generation progress in window title."), + "trash_prompt_visible": OptionInfo(True, "Show trash prompt button"), 'quicksettings': OptionInfo("sd_model_checkpoint", "Quicksettings list"), 'localization': OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), })) diff --git a/modules/ui.py b/modules/ui.py index bde546cc..13c0b4ca 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -509,7 +509,7 @@ def create_toprow(is_img2img): paste = gr.Button(value=paste_symbol, elem_id="paste") save_style = gr.Button(value=save_style_symbol, elem_id="style_create") prompt_style_apply = gr.Button(value=apply_style_symbol, elem_id="style_apply") - trash_prompt = gr.Button(value=trash_prompt_symbol, elem_id="trash_prompt") + trash_prompt = gr.Button(value=trash_prompt_symbol, elem_id="trash_prompt", visible=opts.trash_prompt_visible) token_counter = gr.HTML(value="", elem_id=f"{id_part}_token_counter") token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") From 1fc278bcc642f720484a77eb169271054d3153b1 Mon Sep 17 00:00:00 2001 From: wywywywy Date: Fri, 21 Oct 2022 02:38:24 +0100 Subject: [PATCH 0093/1118] Fixed job count & single-output grid --- scripts/outpainting_mk_2.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 726417e7..633dc119 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -172,10 +172,6 @@ class Script(scripts.Script): if down > 0: down = target_h - init_img.height - up - init_image = p.init_images[0] - - state.job_count = (1 if left > 0 else 0) + (1 if right > 0 else 0) + (1 if up > 0 else 0) + (1 if down > 0 else 0) - def expand(init, count, expand_pixels, is_left=False, is_right=False, is_top=False, is_bottom=False): is_horiz = is_left or is_right is_vert = is_top or is_bottom @@ -250,7 +246,7 @@ class Script(scripts.Script): batch_count = p.n_iter batch_size = p.batch_size p.n_iter = 1 - state.job_count = batch_count + state.job_count = batch_count * batch_size * ((1 if left > 0 else 0) + (1 if right > 0 else 0) + (1 if up > 0 else 0) + (1 if down > 0 else 0)) all_processed_images = [] for i in range(batch_count): @@ -268,10 +264,11 @@ class Script(scripts.Script): all_processed_images += imgs - combined_grid_image = images.image_grid(all_processed_images) all_images = all_processed_images - if opts.return_grid: + combined_grid_image = images.image_grid(all_processed_images) + unwanted_grid_because_of_img_count = len(all_processed_images) < 2 and opts.grid_only_if_multiple + if opts.return_grid and not unwanted_grid_because_of_img_count: all_images = [combined_grid_image] + all_processed_images res = Processed(p, all_images, initial_seed_and_info[0], initial_seed_and_info[1]) @@ -280,8 +277,7 @@ class Script(scripts.Script): for img in all_processed_images: images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.grid_format, info=res.info, p=p) - if opts.grid_save: + if opts.grid_save and not unwanted_grid_because_of_img_count: images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.grid_format, info=res.info, short_filename=not opts.grid_extended_filename, grid=True, p=p) return res - From 0110429dc4bc004ac56573fe1a6b05cb0123678e Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Thu, 20 Oct 2022 19:10:32 +0900 Subject: [PATCH 0094/1118] Fixed path issue while extras batch processing --- modules/extras.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/extras.py b/modules/extras.py index b853fa5b..f9796624 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -118,10 +118,14 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ while len(cached_images) > 2: del cached_images[next(iter(cached_images.keys()))] + + if opts.use_original_name_batch and image_name != None: + basename = os.path.splitext(os.path.basename(image_name))[0] + else: + basename = '' - images.save_image(image, path=outpath, basename="", seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, - no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, - forced_filename=image_name if opts.use_original_name_batch else None) + images.save_image(image, path=outpath, basename=basename, seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, + no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=None) if opts.enable_pnginfo: image.info = existing_pnginfo From aacc4c1ecbcba3cef421d8776dc5b4b239df9b42 Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Thu, 20 Oct 2022 21:31:29 +0900 Subject: [PATCH 0095/1118] Added try except to extras batch from directory --- modules/extras.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/extras.py b/modules/extras.py index f9796624..0d817cf9 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -41,7 +41,10 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ return outputs, "Please select an input directory.", '' image_list = [file for file in [os.path.join(input_dir, x) for x in os.listdir(input_dir)] if os.path.isfile(file)] for img in image_list: - image = Image.open(img) + try: + image = Image.open(img) + except Exception: + continue imageArr.append(image) imageNameArr.append(img) else: @@ -122,10 +125,10 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ if opts.use_original_name_batch and image_name != None: basename = os.path.splitext(os.path.basename(image_name))[0] else: - basename = '' + basename = None - images.save_image(image, path=outpath, basename=basename, seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, - no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=None) + images.save_image(image, path=outpath, basename='', seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, + no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=basename) if opts.enable_pnginfo: image.info = existing_pnginfo From bc16b135b527224545dca555a9d51edb0adcee2d Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Thu, 20 Oct 2022 21:43:27 +0900 Subject: [PATCH 0096/1118] Fixed path issue while extras batch processing --- modules/extras.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/extras.py b/modules/extras.py index 0d817cf9..ac85142c 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -125,10 +125,10 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ if opts.use_original_name_batch and image_name != None: basename = os.path.splitext(os.path.basename(image_name))[0] else: - basename = None + basename = '' - images.save_image(image, path=outpath, basename='', seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, - no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=basename) + images.save_image(image, path=outpath, basename=basename, seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, + no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=None) if opts.enable_pnginfo: image.info = existing_pnginfo From 991a595686b8d105025d68d0e833d1cbf44cb143 Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Fri, 21 Oct 2022 09:23:13 +0900 Subject: [PATCH 0097/1118] sort file list in alphabetical ordering in extras --- modules/extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extras.py b/modules/extras.py index ac85142c..22c5a1c1 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -39,7 +39,7 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ if input_dir == '': return outputs, "Please select an input directory.", '' - image_list = [file for file in [os.path.join(input_dir, x) for x in os.listdir(input_dir)] if os.path.isfile(file)] + image_list = [file for file in [os.path.join(input_dir, x) for x in sorted(os.listdir(input_dir))] if os.path.isfile(file)] for img in image_list: try: image = Image.open(img) From 45872181902ada06267e2de601586d512cf5df1a Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 09:00:39 +0300 Subject: [PATCH 0098/1118] updated readme and some small stylistic changes to code --- README.md | 1 + modules/processing.py | 14 ++++++-------- modules/sd_hijack_inpainting.py | 3 +++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 859a91b6..a98bb00b 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - No token limit for prompts (original stable diffusion lets you use up to 75 tokens) - DeepDanbooru integration, creates danbooru style tags for anime prompts (add --deepdanbooru to commandline args) - [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add --xformers to commandline args) +- Support for dedicated [inpainting model](https://github.com/runwayml/stable-diffusion#inpainting-with-stable-diffusion) by RunwayML. ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. diff --git a/modules/processing.py b/modules/processing.py index 539cde38..21786968 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -540,11 +540,10 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.truncate_x = int(self.firstphase_width - firstphase_width_truncated) // opt_f self.truncate_y = int(self.firstphase_height - firstphase_height_truncated) // opt_f - - def create_dummy_mask(self, x, first_phase: bool = False): + def create_dummy_mask(self, x, width=None, height=None): if self.sampler.conditioning_key in {'hybrid', 'concat'}: - height = self.firstphase_height if first_phase else self.height - width = self.firstphase_width if first_phase else self.width + height = height or self.height + width = width or self.width # The "masked-image" in this case will just be all zeros since the entire image is masked. image_conditioning = torch.zeros(x.shape[0], 3, height, width, device=x.device) @@ -571,7 +570,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): return samples x = create_random_tensors([opt_C, self.firstphase_height // opt_f, self.firstphase_width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) - samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.create_dummy_mask(x, first_phase=True)) + samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.create_dummy_mask(x, self.firstphase_width, self.firstphase_height)) samples = samples[:, :, self.truncate_y//2:samples.shape[2]-self.truncate_y//2, self.truncate_x//2:samples.shape[3]-self.truncate_x//2] @@ -634,6 +633,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.inpainting_mask_invert = inpainting_mask_invert self.mask = None self.nmask = None + self.image_conditioning = None def init(self, all_prompts, all_seeds, all_subseeds): self.sampler = sd_samplers.create_sampler_with_index(sd_samplers.samplers_for_img2img, self.sampler_index, self.sd_model) @@ -735,9 +735,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): elif self.inpainting_fill == 3: self.init_latent = self.init_latent * self.mask - conditioning_key = self.sampler.conditioning_key - - if conditioning_key in {'hybrid', 'concat'}: + if self.sampler.conditioning_key in {'hybrid', 'concat'}: if self.image_mask is not None: conditioning_mask = np.array(self.image_mask.convert("L")) conditioning_mask = conditioning_mask.astype(np.float32) / 255.0 diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 43938071..fd92a335 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -301,6 +301,7 @@ def get_unconditional_conditioning(self, batch_size, null_label=None): c = repeat(c, "1 ... -> b ...", b=batch_size).to(self.device) return c + class LatentInpaintDiffusion(LatentDiffusion): def __init__( self, @@ -314,9 +315,11 @@ class LatentInpaintDiffusion(LatentDiffusion): assert self.masked_image_key in concat_keys self.concat_keys = concat_keys + def should_hijack_inpainting(checkpoint_info): return str(checkpoint_info.filename).endswith("inpainting.ckpt") and not checkpoint_info.config.endswith("inpainting.yaml") + def do_inpainting_hijack(): ldm.models.diffusion.ddpm.get_unconditional_conditioning = get_unconditional_conditioning ldm.models.diffusion.ddpm.LatentInpaintDiffusion = LatentInpaintDiffusion From 74088c2a06a975092806362aede22f82716cb011 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 20 Oct 2022 08:18:02 +0300 Subject: [PATCH 0099/1118] allow float sizes for hypernet's layer_structure --- modules/hypernetworks/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 08f75f15..e0741d08 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -15,7 +15,7 @@ def create_hypernetwork(name, enable_sizes, layer_structure=None, add_layer_norm assert not os.path.exists(fn), f"file {fn} already exists" if type(layer_structure) == str: - layer_structure = tuple(map(int, re.sub(r'\D', '', layer_structure))) + layer_structure = [float(x.strip()) for x in layer_structure.split(",")] hypernet = modules.hypernetworks.hypernetwork.Hypernetwork( name=name, From 60872c5b404114336f9ca0c671ba88fa4a8201c9 Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Thu, 20 Oct 2022 19:10:32 +0900 Subject: [PATCH 0100/1118] Fixed path issue while extras batch processing --- modules/extras.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/extras.py b/modules/extras.py index b853fa5b..f9796624 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -118,10 +118,14 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ while len(cached_images) > 2: del cached_images[next(iter(cached_images.keys()))] + + if opts.use_original_name_batch and image_name != None: + basename = os.path.splitext(os.path.basename(image_name))[0] + else: + basename = '' - images.save_image(image, path=outpath, basename="", seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, - no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, - forced_filename=image_name if opts.use_original_name_batch else None) + images.save_image(image, path=outpath, basename=basename, seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, + no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=None) if opts.enable_pnginfo: image.info = existing_pnginfo From fb5a8cf0d9ed027ea3aa2e5422c946d8e6e72efe Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Thu, 20 Oct 2022 21:31:29 +0900 Subject: [PATCH 0101/1118] Added try except to extras batch from directory --- modules/extras.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/extras.py b/modules/extras.py index f9796624..0d817cf9 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -41,7 +41,10 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ return outputs, "Please select an input directory.", '' image_list = [file for file in [os.path.join(input_dir, x) for x in os.listdir(input_dir)] if os.path.isfile(file)] for img in image_list: - image = Image.open(img) + try: + image = Image.open(img) + except Exception: + continue imageArr.append(image) imageNameArr.append(img) else: @@ -122,10 +125,10 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ if opts.use_original_name_batch and image_name != None: basename = os.path.splitext(os.path.basename(image_name))[0] else: - basename = '' + basename = None - images.save_image(image, path=outpath, basename=basename, seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, - no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=None) + images.save_image(image, path=outpath, basename='', seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, + no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=basename) if opts.enable_pnginfo: image.info = existing_pnginfo From a13c3bed3cec27afe3c015d3d62db36e25b10d1f Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Thu, 20 Oct 2022 21:43:27 +0900 Subject: [PATCH 0102/1118] Fixed path issue while extras batch processing --- modules/extras.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/extras.py b/modules/extras.py index 0d817cf9..ac85142c 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -125,10 +125,10 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ if opts.use_original_name_batch and image_name != None: basename = os.path.splitext(os.path.basename(image_name))[0] else: - basename = None + basename = '' - images.save_image(image, path=outpath, basename='', seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, - no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=basename) + images.save_image(image, path=outpath, basename=basename, seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, + no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=None) if opts.enable_pnginfo: image.info = existing_pnginfo From 9d71eef02e7395e179b8d5e61e6d91ddd8928d2e Mon Sep 17 00:00:00 2001 From: winterspringsummer Date: Fri, 21 Oct 2022 09:23:13 +0900 Subject: [PATCH 0103/1118] sort file list in alphabetical ordering in extras --- modules/extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extras.py b/modules/extras.py index ac85142c..22c5a1c1 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -39,7 +39,7 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ if input_dir == '': return outputs, "Please select an input directory.", '' - image_list = [file for file in [os.path.join(input_dir, x) for x in os.listdir(input_dir)] if os.path.isfile(file)] + image_list = [file for file in [os.path.join(input_dir, x) for x in sorted(os.listdir(input_dir))] if os.path.isfile(file)] for img in image_list: try: image = Image.open(img) From c23f666dba2b484d521d2dc4be91cf9e09312647 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 09:47:43 +0300 Subject: [PATCH 0104/1118] a more strict check for activation type and a more reasonable check for type of layer in hypernets --- modules/hypernetworks/hypernetwork.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 7d617680..84e7e350 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -32,10 +32,16 @@ class HypernetworkModule(torch.nn.Module): linears = [] for i in range(len(layer_structure) - 1): linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) + if activation_func == "relu": linears.append(torch.nn.ReLU()) - if activation_func == "leakyrelu": + elif activation_func == "leakyrelu": linears.append(torch.nn.LeakyReLU()) + elif activation_func == 'linear' or activation_func is None: + pass + else: + raise RuntimeError(f'hypernetwork uses an unsupported activation function: {activation_func}') + if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) @@ -46,7 +52,7 @@ class HypernetworkModule(torch.nn.Module): self.load_state_dict(state_dict) else: for layer in self.linear: - if not "ReLU" in layer.__str__(): + if type(layer) == torch.nn.Linear: layer.weight.data.normal_(mean=0.0, std=0.01) layer.bias.data.zero_() @@ -74,7 +80,7 @@ class HypernetworkModule(torch.nn.Module): def trainables(self): layer_structure = [] for layer in self.linear: - if not "ReLU" in layer.__str__(): + if type(layer) == torch.nn.Linear: layer_structure += [layer.weight, layer.bias] return layer_structure From 5f4fec307c14dd7f817244ffa92e8a4a64abed0b Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 20 Oct 2022 11:32:17 -0400 Subject: [PATCH 0105/1118] [Bugfix][API] - Fix API arg in launch script --- webui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webui.py b/webui.py index 177bef74..87589064 100644 --- a/webui.py +++ b/webui.py @@ -118,7 +118,8 @@ def api_only(): api.launch(server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", port=cmd_opts.port if cmd_opts.port else 7861) -def webui(launch_api=False): +def webui(): + launch_api = cmd_opts.api initialize() while 1: @@ -158,4 +159,4 @@ if __name__ == "__main__": if cmd_opts.nowebui: api_only() else: - webui(cmd_opts.api) + webui() From 7157e5d064741fa57ca81a2c6432a651f21ee82f Mon Sep 17 00:00:00 2001 From: Patryk Wychowaniec Date: Thu, 20 Oct 2022 19:22:59 +0200 Subject: [PATCH 0106/1118] interrogate: Fix CLIP-interrogation on CPU Currently, trying to perform CLIP interrogation on a CPU fails, saying: ``` RuntimeError: "slow_conv2d_cpu" not implemented for 'Half' ``` This merge request fixes this issue by detecting whether the target device is CPU and, if so, force-enabling `--no-half` and passing `device="cpu"` to `clip.load()` (which then does some extra tricks to ensure it works correctly on CPU). --- modules/interrogate.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/interrogate.py b/modules/interrogate.py index 64b91eb4..65b05d34 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -28,9 +28,11 @@ class InterrogateModels: clip_preprocess = None categories = None dtype = None + running_on_cpu = None def __init__(self, content_dir): self.categories = [] + self.running_on_cpu = devices.device_interrogate == torch.device("cpu") if os.path.exists(content_dir): for filename in os.listdir(content_dir): @@ -53,7 +55,11 @@ class InterrogateModels: def load_clip_model(self): import clip - model, preprocess = clip.load(clip_model_name) + if self.running_on_cpu: + model, preprocess = clip.load(clip_model_name, device="cpu") + else: + model, preprocess = clip.load(clip_model_name) + model.eval() model = model.to(devices.device_interrogate) @@ -62,14 +68,14 @@ class InterrogateModels: def load(self): if self.blip_model is None: self.blip_model = self.load_blip_model() - if not shared.cmd_opts.no_half: + if not shared.cmd_opts.no_half and not self.running_on_cpu: self.blip_model = self.blip_model.half() self.blip_model = self.blip_model.to(devices.device_interrogate) if self.clip_model is None: self.clip_model, self.clip_preprocess = self.load_clip_model() - if not shared.cmd_opts.no_half: + if not shared.cmd_opts.no_half and not self.running_on_cpu: self.clip_model = self.clip_model.half() self.clip_model = self.clip_model.to(devices.device_interrogate) From b69c37d25e4ffc56e8f8c247fa2c38b4648cefb7 Mon Sep 17 00:00:00 2001 From: guaneec Date: Thu, 20 Oct 2022 22:21:12 +0800 Subject: [PATCH 0107/1118] Allow datasets with only 1 image in TI --- modules/textual_inversion/dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 23bb4b6a..5b1c5002 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -83,7 +83,7 @@ class PersonalizedBase(Dataset): self.dataset.append(entry) - assert len(self.dataset) > 1, "No images have been found in the dataset." + assert len(self.dataset) > 0, "No images have been found in the dataset." self.length = len(self.dataset) * repeats // batch_size self.initial_indexes = np.arange(len(self.dataset)) @@ -91,7 +91,7 @@ class PersonalizedBase(Dataset): self.shuffle() def shuffle(self): - self.indexes = self.initial_indexes[torch.randperm(self.initial_indexes.shape[0])] + self.indexes = self.initial_indexes[torch.randperm(self.initial_indexes.shape[0]).numpy()] def create_text(self, filename_text): text = random.choice(self.lines) From 5245c7a4935f67b677da0f5a1fc2b74c074aa0e2 Mon Sep 17 00:00:00 2001 From: timntorres Date: Wed, 19 Oct 2022 12:21:32 -0700 Subject: [PATCH 0108/1118] Issue #2921-Give PNG info to Hypernet previews. --- modules/hypernetworks/hypernetwork.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 84e7e350..68c8f26d 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -256,6 +256,9 @@ def stack_conds(conds): def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): + # images is required here to give training previews their infotext. Importing this at the very top causes a circular dependency. + from modules import images + assert hypernetwork_name, 'hypernetwork not selected' path = shared.hypernetworks.get(hypernetwork_name, None) @@ -298,6 +301,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log last_saved_file = "" last_saved_image = "" + forced_filename = "" ititial_step = hypernetwork.step or 0 if ititial_step > steps: @@ -345,7 +349,8 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log }) if hypernetwork.step > 0 and images_dir is not None and hypernetwork.step % create_image_every == 0: - last_saved_image = os.path.join(images_dir, f'{hypernetwork_name}-{hypernetwork.step}.png') + forced_filename = f'{hypernetwork_name}-{hypernetwork.step}' + last_saved_image = os.path.join(images_dir, forced_filename) optimizer.zero_grad() shared.sd_model.cond_stage_model.to(devices.device) @@ -381,7 +386,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log if image is not None: shared.state.current_image = image - image.save(last_saved_image) + last_saved_image, last_text_info = images.save_image(image, images_dir, "", p.seed, p.prompt, shared.opts.samples_format, processed.infotexts[0], p=p, forced_filename=forced_filename) last_saved_image += f", prompt: {preview_text}" shared.state.job_no = hypernetwork.step From 6014fb8afbe05c8d02fffe7a36a2e48128713bd2 Mon Sep 17 00:00:00 2001 From: timntorres Date: Wed, 19 Oct 2022 12:22:23 -0700 Subject: [PATCH 0109/1118] Do nothing if image file already exists. --- modules/images.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index b9589563..550e53ae 100644 --- a/modules/images.py +++ b/modules/images.py @@ -416,7 +416,11 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i dirname = apply_filename_pattern(opts.directories_filename_pattern or "[prompt_words]", p, seed, prompt).strip('\\ /') path = os.path.join(path, dirname) - os.makedirs(path, exist_ok=True) + try: + os.makedirs(path, exist_ok=True) + except FileExistsError: + # If the file already exists, continue and allow said file to be overwritten. + pass if forced_filename is None: basecount = get_next_sequence_number(path, basename) From 4ff274e1e35bb642687253ce744d2cfa738ab293 Mon Sep 17 00:00:00 2001 From: timntorres Date: Wed, 19 Oct 2022 12:32:22 -0700 Subject: [PATCH 0110/1118] Revise comments. --- modules/hypernetworks/hypernetwork.py | 2 +- modules/images.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 68c8f26d..3f96361c 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -256,7 +256,7 @@ def stack_conds(conds): def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): - # images is required here to give training previews their infotext. Importing this at the very top causes a circular dependency. + # images allows training previews to have infotext. Importing it at the top causes a circular import problem. from modules import images assert hypernetwork_name, 'hypernetwork not selected' diff --git a/modules/images.py b/modules/images.py index 550e53ae..b8834e3c 100644 --- a/modules/images.py +++ b/modules/images.py @@ -419,7 +419,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i try: os.makedirs(path, exist_ok=True) except FileExistsError: - # If the file already exists, continue and allow said file to be overwritten. + # If the file already exists, allow said file to be overwritten. pass if forced_filename is None: From 2273e752fb3e578f1047f6d38b96330b07bf61a9 Mon Sep 17 00:00:00 2001 From: timntorres Date: Wed, 19 Oct 2022 14:23:48 -0700 Subject: [PATCH 0111/1118] Remove redundant try/except. --- modules/images.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/images.py b/modules/images.py index b8834e3c..b9589563 100644 --- a/modules/images.py +++ b/modules/images.py @@ -416,11 +416,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i dirname = apply_filename_pattern(opts.directories_filename_pattern or "[prompt_words]", p, seed, prompt).strip('\\ /') path = os.path.join(path, dirname) - try: - os.makedirs(path, exist_ok=True) - except FileExistsError: - # If the file already exists, allow said file to be overwritten. - pass + os.makedirs(path, exist_ok=True) if forced_filename is None: basecount = get_next_sequence_number(path, basename) From 03a1e288c4973dd2dff57a97469b40f146b6fccf Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 10:13:24 +0300 Subject: [PATCH 0112/1118] turns out LayerNorm also has weight and bias and needs to be pre-multiplied and trained for hypernets --- modules/hypernetworks/hypernetwork.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 3274a802..b1a5d0c7 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -52,7 +52,7 @@ class HypernetworkModule(torch.nn.Module): self.load_state_dict(state_dict) else: for layer in self.linear: - if type(layer) == torch.nn.Linear: + if type(layer) == torch.nn.Linear or type(layer) == torch.nn.LayerNorm: layer.weight.data.normal_(mean=0.0, std=0.01) layer.bias.data.zero_() @@ -80,7 +80,7 @@ class HypernetworkModule(torch.nn.Module): def trainables(self): layer_structure = [] for layer in self.linear: - if type(layer) == torch.nn.Linear: + if type(layer) == torch.nn.Linear or type(layer) == torch.nn.LayerNorm: layer_structure += [layer.weight, layer.bias] return layer_structure From bf30673f5132c8f28357b31224c54331e788d3e7 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 10:19:25 +0300 Subject: [PATCH 0113/1118] Fix Hypernet infotext string split bug for PR #3283 --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 21786968..d1deffa9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -304,7 +304,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration "Size": f"{p.width}x{p.height}", "Model hash": getattr(p, 'sd_model_hash', None if not opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), "Model": (None if not opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', '')), - "Hypernet": (None if shared.loaded_hypernetwork is None else shared.loaded_hypernetwork.filename.split('\\')[-1].split('.')[0]), + "Hypernet": (None if shared.loaded_hypernetwork is None else os.path.splitext(os.path.basename(shared.loaded_hypernetwork.filename))[0]), "Batch size": (None if p.batch_size < 2 else p.batch_size), "Batch pos": (None if p.batch_size < 2 else position_in_batch), "Variation seed": (None if p.subseed_strength == 0 else all_subseeds[index]), From 1ed227b3b57f06f4152be3bfc9f83b0a839a2604 Mon Sep 17 00:00:00 2001 From: Leo Mozoloa Date: Fri, 21 Oct 2022 10:57:40 +0200 Subject: [PATCH 0114/1118] wtf is happening --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..f58c94a9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: WebUI Community Support + url: https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions + about: Please ask and answer questions here. From 003d2c7fe427edde299274c9e0d5fa59734e7f7e Mon Sep 17 00:00:00 2001 From: ClashSAN <98228077+ClashSAN@users.noreply.github.com> Date: Fri, 21 Oct 2022 11:40:37 +0000 Subject: [PATCH 0115/1118] Update README.md --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a89593bf..348aaf87 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - have as many embeddings as you want and use any names you like for them - use multiple embeddings with different numbers of vectors per token - works with half precision floating point numbers + - train embeddings on 8GB (also reports of 6GB working) - Extras tab with: - GFPGAN, neural network that fixes faces - CodeFormer, face restoration tool as an alternative to GFPGAN @@ -60,7 +61,7 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - CLIP interrogator, a button that tries to guess prompt from an image - Prompt Editing, a way to change prompt mid-generation, say to start making a watermelon and switch to anime girl midway - Batch Processing, process a group of files using img2img -- Img2img Alternative +- Img2img Alternative, reverse Euler method of cross attention control - Highres Fix, a convenience option to produce high resolution pictures in one click without usual distortions - Reloading checkpoints on the fly - Checkpoint Merger, a tab that allows you to merge up to 3 checkpoints into one @@ -73,15 +74,22 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add --xformers to commandline args) - History tab: view, direct and delete images conveniently within the UI - Generate forever option -- Training Tab -- Preprocessing Image Datasets: cropping, mirroring, autotagging using BLIP or deepdanbooru (for anime) +- Training tab + - hypernetworks and embeddings options + - Preprocessing images: cropping, mirroring, autotagging using BLIP or deepdanbooru (for anime) +- Clip skip +- Use Hypernetworks +- Use VAEs +- Estimated completion time in progress bar +- API +- Support for dedicated inpainting model by RunwayML. ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. -Alternatively, use online services(like Google Colab): +Alternatively, use online services (like Google Colab): - [List of Online Services](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Online-Services) From df5706409386cc2e88718bd9101045587c39f8bb Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 16:10:51 +0300 Subject: [PATCH 0116/1118] do not load aesthetic clip model until it's needed add refresh button for aesthetic embeddings add aesthetic params to images' infotext --- modules/aesthetic_clip.py | 40 ++++++++++++++--- modules/generation_parameters_copypaste.py | 18 +++++++- modules/img2img.py | 5 +-- modules/processing.py | 4 +- modules/sd_models.py | 3 -- modules/txt2img.py | 4 +- modules/ui.py | 52 +++++++++++++++------- style.css | 2 +- 8 files changed, 89 insertions(+), 39 deletions(-) diff --git a/modules/aesthetic_clip.py b/modules/aesthetic_clip.py index 34efa931..8c828541 100644 --- a/modules/aesthetic_clip.py +++ b/modules/aesthetic_clip.py @@ -40,6 +40,8 @@ def iter_to_batched(iterable, n=1): def create_ui(): + import modules.ui + with gr.Group(): with gr.Accordion("Open for Clip Aesthetic!", open=False): with gr.Row(): @@ -55,6 +57,8 @@ def create_ui(): label="Aesthetic imgs embedding", value="None") + modules.ui.create_refresh_button(aesthetic_imgs, shared.update_aesthetic_embeddings, lambda: {"choices": sorted(shared.aesthetic_embeddings.keys())}, "refresh_aesthetic_embeddings") + with gr.Row(): aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', placeholder="This text is used to rotate the feature space of the imgs embs", @@ -66,11 +70,21 @@ def create_ui(): return aesthetic_weight, aesthetic_steps, aesthetic_lr, aesthetic_slerp, aesthetic_imgs, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative +aesthetic_clip_model = None + + +def aesthetic_clip(): + global aesthetic_clip_model + + if aesthetic_clip_model is None or aesthetic_clip_model.name_or_path != shared.sd_model.cond_stage_model.wrapped.transformer.name_or_path: + aesthetic_clip_model = CLIPModel.from_pretrained(shared.sd_model.cond_stage_model.wrapped.transformer.name_or_path) + aesthetic_clip_model.cpu() + + return aesthetic_clip_model + + def generate_imgs_embd(name, folder, batch_size): - # clipModel = CLIPModel.from_pretrained( - # shared.sd_model.cond_stage_model.clipModel.name_or_path - # ) - model = shared.clip_model.to(device) + model = aesthetic_clip().to(device) processor = CLIPProcessor.from_pretrained(model.name_or_path) with torch.no_grad(): @@ -91,7 +105,7 @@ def generate_imgs_embd(name, folder, batch_size): path = str(Path(shared.cmd_opts.aesthetic_embeddings_dir) / f"{name}.pt") torch.save(embs, path) - model = model.cpu() + model.cpu() del processor del embs gc.collect() @@ -132,7 +146,7 @@ class AestheticCLIP: self.image_embs = None self.load_image_embs(None) - def set_aesthetic_params(self, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, image_embs_name=None, + def set_aesthetic_params(self, p, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, image_embs_name=None, aesthetic_slerp=True, aesthetic_imgs_text="", aesthetic_slerp_angle=0.15, aesthetic_text_negative=False): @@ -145,6 +159,18 @@ class AestheticCLIP: self.aesthetic_steps = aesthetic_steps self.load_image_embs(image_embs_name) + if self.image_embs_name is not None: + p.extra_generation_params.update({ + "Aesthetic LR": aesthetic_lr, + "Aesthetic weight": aesthetic_weight, + "Aesthetic steps": aesthetic_steps, + "Aesthetic embedding": self.image_embs_name, + "Aesthetic slerp": aesthetic_slerp, + "Aesthetic text": aesthetic_imgs_text, + "Aesthetic text negative": aesthetic_text_negative, + "Aesthetic slerp angle": aesthetic_slerp_angle, + }) + def set_skip(self, skip): self.skip = skip @@ -168,7 +194,7 @@ class AestheticCLIP: tokens = torch.asarray(remade_batch_tokens).to(device) - model = copy.deepcopy(shared.clip_model).to(device) + model = copy.deepcopy(aesthetic_clip()).to(device) model.requires_grad_(True) if self.aesthetic_imgs_text is not None and len(self.aesthetic_imgs_text) > 0: text_embs_2 = model.get_text_features( diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 0f041449..f73647da 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -4,13 +4,22 @@ import gradio as gr from modules.shared import script_path from modules import shared -re_param_code = r"\s*([\w ]+):\s*([^,]+)(?:,|$)" +re_param_code = r'\s*([\w ]+):\s*("(?:\\|\"|[^\"])+"|[^,]*)(?:,|$)' re_param = re.compile(re_param_code) re_params = re.compile(r"^(?:" + re_param_code + "){3,}$") re_imagesize = re.compile(r"^(\d+)x(\d+)$") type_of_gr_update = type(gr.update()) +def quote(text): + if ',' not in str(text): + return text + + text = str(text) + text = text.replace('\\', '\\\\') + text = text.replace('"', '\\"') + return f'"{text}"' + def parse_generation_parameters(x: str): """parses generation parameters string, the one you see in text field under the picture in UI: ``` @@ -83,7 +92,12 @@ def connect_paste(button, paste_fields, input_comp, js=None): else: try: valtype = type(output.value) - val = valtype(v) + + if valtype == bool and v == "False": + val = False + else: + val = valtype(v) + res.append(gr.update(value=val)) except Exception: res.append(gr.update()) diff --git a/modules/img2img.py b/modules/img2img.py index bc7c66bc..eea5199b 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -109,10 +109,7 @@ def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, pro inpainting_mask_invert=inpainting_mask_invert, ) - shared.aesthetic_clip.set_aesthetic_params(float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), - aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, - aesthetic_slerp_angle, - aesthetic_text_negative) + shared.aesthetic_clip.set_aesthetic_params(p, float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative) if shared.cmd_opts.enable_console_prompts: print(f"\nimg2img: {prompt}", file=shared.progress_print_out) diff --git a/modules/processing.py b/modules/processing.py index d1deffa9..f0852cd5 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -12,7 +12,7 @@ from skimage import exposure from typing import Any, Dict, List, Optional import modules.sd_hijack -from modules import devices, prompt_parser, masking, sd_samplers, lowvram +from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste from modules.sd_hijack import model_hijack from modules.shared import opts, cmd_opts, state import modules.shared as shared @@ -318,7 +318,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration generation_params.update(p.extra_generation_params) - generation_params_text = ", ".join([k if k == v else f'{k}: {v}' for k, v in generation_params.items() if v is not None]) + generation_params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in generation_params.items() if v is not None]) negative_prompt_text = "\nNegative prompt: " + p.negative_prompt if p.negative_prompt else "" diff --git a/modules/sd_models.py b/modules/sd_models.py index 05a1df28..b1c91b0d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -234,9 +234,6 @@ def load_model(checkpoint_info=None): sd_hijack.model_hijack.hijack(sd_model) - if shared.clip_model is None or shared.clip_model.transformer.name_or_path != sd_model.cond_stage_model.wrapped.transformer.name_or_path: - shared.clip_model = CLIPModel.from_pretrained(sd_model.cond_stage_model.wrapped.transformer.name_or_path) - sd_model.eval() print(f"Model loaded.") diff --git a/modules/txt2img.py b/modules/txt2img.py index 32ed1d8d..1761cfa2 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -36,9 +36,7 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: firstphase_height=firstphase_height if enable_hr else None, ) - shared.aesthetic_clip.set_aesthetic_params(float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), - aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, aesthetic_slerp_angle, - aesthetic_text_negative) + shared.aesthetic_clip.set_aesthetic_params(p, float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative) if cmd_opts.enable_console_prompts: print(f"\ntxt2img: {prompt}", file=shared.progress_print_out) diff --git a/modules/ui.py b/modules/ui.py index 381ca925..0d020de6 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -597,27 +597,29 @@ def apply_setting(key, value): return value +def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id): + def refresh(): + refresh_method() + args = refreshed_args() if callable(refreshed_args) else refreshed_args + + for k, v in args.items(): + setattr(refresh_component, k, v) + + return gr.update(**(args or {})) + + refresh_button = gr.Button(value=refresh_symbol, elem_id=elem_id) + refresh_button.click( + fn=refresh, + inputs=[], + outputs=[refresh_component] + ) + return refresh_button + + def create_ui(wrap_gradio_gpu_call): import modules.img2img import modules.txt2img - def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id): - def refresh(): - refresh_method() - args = refreshed_args() if callable(refreshed_args) else refreshed_args - - for k, v in args.items(): - setattr(refresh_component, k, v) - - return gr.update(**(args or {})) - - refresh_button = gr.Button(value=refresh_symbol, elem_id=elem_id) - refresh_button.click( - fn = refresh, - inputs = [], - outputs = [refresh_component] - ) - return refresh_button with gr.Blocks(analytics_enabled=False) as txt2img_interface: txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter, token_button = create_toprow(is_img2img=False) @@ -802,6 +804,14 @@ def create_ui(wrap_gradio_gpu_call): (hr_options, lambda d: gr.Row.update(visible="Denoising strength" in d)), (firstphase_width, "First pass size-1"), (firstphase_height, "First pass size-2"), + (aesthetic_lr, "Aesthetic LR"), + (aesthetic_weight, "Aesthetic weight"), + (aesthetic_steps, "Aesthetic steps"), + (aesthetic_imgs, "Aesthetic embedding"), + (aesthetic_slerp, "Aesthetic slerp"), + (aesthetic_imgs_text, "Aesthetic text"), + (aesthetic_text_negative, "Aesthetic text negative"), + (aesthetic_slerp_angle, "Aesthetic slerp angle"), ] txt2img_preview_params = [ @@ -1077,6 +1087,14 @@ def create_ui(wrap_gradio_gpu_call): (seed_resize_from_w, "Seed resize from-1"), (seed_resize_from_h, "Seed resize from-2"), (denoising_strength, "Denoising strength"), + (aesthetic_lr_im, "Aesthetic LR"), + (aesthetic_weight_im, "Aesthetic weight"), + (aesthetic_steps_im, "Aesthetic steps"), + (aesthetic_imgs_im, "Aesthetic embedding"), + (aesthetic_slerp_im, "Aesthetic slerp"), + (aesthetic_imgs_text_im, "Aesthetic text"), + (aesthetic_text_negative_im, "Aesthetic text negative"), + (aesthetic_slerp_angle_im, "Aesthetic slerp angle"), ] token_button.click(fn=update_token_counter, inputs=[img2img_prompt, steps], outputs=[token_counter]) diff --git a/style.css b/style.css index 26ae36a5..5d2bacc9 100644 --- a/style.css +++ b/style.css @@ -477,7 +477,7 @@ input[type="range"]{ padding: 0; } -#refresh_sd_model_checkpoint, #refresh_sd_hypernetwork, #refresh_train_hypernetwork_name, #refresh_train_embedding_name, #refresh_localization{ +#refresh_sd_model_checkpoint, #refresh_sd_hypernetwork, #refresh_train_hypernetwork_name, #refresh_train_embedding_name, #refresh_localization, #refresh_aesthetic_embeddings{ max-width: 2.5em; min-width: 2.5em; height: 2.4em; From 9286fe53de2eef91f13cc3ad5938ddf67ecc8413 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 16:38:06 +0300 Subject: [PATCH 0117/1118] make aestetic embedding ciompatible with prompts longer than 75 tokens --- modules/sd_hijack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 36198a3c..1f8587d1 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -332,8 +332,8 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): multipliers.append([1.0] * 75) z1 = self.process_tokens(tokens, multipliers) + z1 = shared.aesthetic_clip(z1, remade_batch_tokens) z = z1 if z is None else torch.cat((z, z1), axis=-2) - z = shared.aesthetic_clip(z, remade_batch_tokens) remade_batch_tokens = rem_tokens batch_multipliers = rem_multipliers From d0ea471b0cdaede163c6e7f6fae8535f5c3cd226 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Fri, 21 Oct 2022 14:04:41 +0100 Subject: [PATCH 0118/1118] Use opts in textual_inversion image_embedding.py for dynamic fonts --- modules/textual_inversion/image_embedding.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index 898ce3b3..c50b1e7b 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -5,6 +5,7 @@ import zlib from PIL import Image, PngImagePlugin, ImageDraw, ImageFont from fonts.ttf import Roboto import torch +from modules.shared import opts class EmbeddingEncoder(json.JSONEncoder): From 306e2ff6ab8f4c7e94ab55f4f08ab8f94d73d287 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Fri, 21 Oct 2022 14:47:21 +0100 Subject: [PATCH 0119/1118] Update image_embedding.py --- modules/textual_inversion/image_embedding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index c50b1e7b..ea653806 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -134,7 +134,7 @@ def caption_image_overlay(srcimage, title, footerLeft, footerMid, footerRight, t from math import cos image = srcimage.copy() - + fontsize = 32 if textfont is None: try: textfont = ImageFont.truetype(opts.font or Roboto, fontsize) @@ -151,7 +151,7 @@ def caption_image_overlay(srcimage, title, footerLeft, footerMid, footerRight, t image = Image.alpha_composite(image.convert('RGBA'), gradient.resize(image.size)) draw = ImageDraw.Draw(image) - fontsize = 32 + font = ImageFont.truetype(textfont, fontsize) padding = 10 From 85cb5918ee7c97cafafe4149880ed82f1933d919 Mon Sep 17 00:00:00 2001 From: parsec501 <105080989+parsec501@users.noreply.github.com> Date: Fri, 21 Oct 2022 14:43:23 +0200 Subject: [PATCH 0120/1118] Make commit hash mandatory field --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 35802a53..9c2ff313 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -45,6 +45,8 @@ body: attributes: label: Commit where the problem happens description: Which commit are you running ? (copy the **Commit hash** shown in the cmd/terminal when you launch the UI) + validations: + required: true - type: dropdown id: platforms attributes: From 51e3dc9ccad157d7161b697a246e26c868d46a7c Mon Sep 17 00:00:00 2001 From: timntorres Date: Fri, 21 Oct 2022 02:11:12 -0700 Subject: [PATCH 0121/1118] Sanitize hypernet name input. --- modules/hypernetworks/ui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 266f04f6..e6f50a1f 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -11,6 +11,9 @@ from modules.hypernetworks import hypernetwork def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, add_layer_norm=False, activation_func=None): + # Remove illegal characters from name. + name = "".join( x for x in name if (x.isalnum() or x in "._- ")) + fn = os.path.join(shared.cmd_opts.hypernetwork_dir, f"{name}.pt") if not overwrite_old: assert not os.path.exists(fn), f"file {fn} already exists" From 19818f023cfafc472c6c241cab0b72896a168481 Mon Sep 17 00:00:00 2001 From: timntorres Date: Fri, 21 Oct 2022 02:14:02 -0700 Subject: [PATCH 0122/1118] Match hypernet name with filename in all cases. --- modules/hypernetworks/hypernetwork.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index b1a5d0c7..6d392be4 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -340,7 +340,10 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log pbar.set_description(f"loss: {mean_loss:.7f}") if hypernetwork.step > 0 and hypernetwork_dir is not None and hypernetwork.step % save_hypernetwork_every == 0: - last_saved_file = os.path.join(hypernetwork_dir, f'{hypernetwork_name}-{hypernetwork.step}.pt') + temp = hypernetwork.name + # Before saving, change name to match current checkpoint. + hypernetwork.name = f'{hypernetwork_name}-{hypernetwork.step}' + last_saved_file = os.path.join(hypernetwork_dir, f'{hypernetwork.name}.pt') hypernetwork.save(last_saved_file) textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, len(ds), { @@ -405,6 +408,9 @@ Last saved image: {html.escape(last_saved_image)}
hypernetwork.sd_checkpoint = checkpoint.hash hypernetwork.sd_checkpoint_name = checkpoint.model_name + # Before saving for the last time, change name back to the base name (as opposed to the save_hypernetwork_every step-suffixed naming convention). + hypernetwork.name = hypernetwork_name + filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork.name}.pt') hypernetwork.save(filename) return hypernetwork, filename From fccad18a59e3c2c33fefbbb1763c6a87a3a68eba Mon Sep 17 00:00:00 2001 From: timntorres Date: Fri, 21 Oct 2022 02:17:26 -0700 Subject: [PATCH 0123/1118] Refer to Hypernet's name, sensibly, by its name variable. --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index f0852cd5..ff1ec4c9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -304,7 +304,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration "Size": f"{p.width}x{p.height}", "Model hash": getattr(p, 'sd_model_hash', None if not opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), "Model": (None if not opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', '')), - "Hypernet": (None if shared.loaded_hypernetwork is None else os.path.splitext(os.path.basename(shared.loaded_hypernetwork.filename))[0]), + "Hypernet": (None if shared.loaded_hypernetwork is None else shared.loaded_hypernetwork.name), "Batch size": (None if p.batch_size < 2 else p.batch_size), "Batch pos": (None if p.batch_size < 2 else position_in_batch), "Variation seed": (None if p.subseed_strength == 0 else all_subseeds[index]), From 272fa527bbe93143668ffc16838107b7dca35b40 Mon Sep 17 00:00:00 2001 From: timntorres Date: Fri, 21 Oct 2022 02:41:55 -0700 Subject: [PATCH 0124/1118] Remove unused variable. --- modules/hypernetworks/hypernetwork.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 6d392be4..47d91ea5 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -340,7 +340,6 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log pbar.set_description(f"loss: {mean_loss:.7f}") if hypernetwork.step > 0 and hypernetwork_dir is not None and hypernetwork.step % save_hypernetwork_every == 0: - temp = hypernetwork.name # Before saving, change name to match current checkpoint. hypernetwork.name = f'{hypernetwork_name}-{hypernetwork.step}' last_saved_file = os.path.join(hypernetwork_dir, f'{hypernetwork.name}.pt') From 02e4d4694dd9254a6ca9f05c2eb7b01ea508abc7 Mon Sep 17 00:00:00 2001 From: Rcmcpe Date: Fri, 21 Oct 2022 15:53:35 +0800 Subject: [PATCH 0125/1118] Change option description of unload_models_when_training --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 5c675b80..41d7f08e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -266,7 +266,7 @@ options_templates.update(options_section(('system', "System"), { })) options_templates.update(options_section(('training', "Training"), { - "unload_models_when_training": OptionInfo(False, "Unload VAE and CLIP from VRAM when training"), + "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM."), "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), From 704036ff07b71bf86cadcbbff2bcfeebdd1ed3a6 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 17:11:42 +0300 Subject: [PATCH 0126/1118] make aspect ratio overlay work regardless of selected localization --- javascript/aspectRatioOverlay.js | 36 +++++++++++++++----------------- javascript/dragdrop.js | 2 +- modules/ui.py | 4 ++-- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js index 96f1c00d..d3ca2781 100644 --- a/javascript/aspectRatioOverlay.js +++ b/javascript/aspectRatioOverlay.js @@ -3,12 +3,12 @@ let currentWidth = null; let currentHeight = null; let arFrameTimeout = setTimeout(function(){},0); -function dimensionChange(e,dimname){ +function dimensionChange(e, is_width, is_height){ - if(dimname == 'Width'){ + if(is_width){ currentWidth = e.target.value*1.0 } - if(dimname == 'Height'){ + if(is_height){ currentHeight = e.target.value*1.0 } @@ -98,22 +98,20 @@ onUiUpdate(function(){ var inImg2img = Boolean(gradioApp().querySelector("button.rounded-t-lg.border-gray-200")) if(inImg2img){ let inputs = gradioApp().querySelectorAll('input'); - inputs.forEach(function(e){ - let parentLabel = e.parentElement.querySelector('label') - if(parentLabel && parentLabel.innerText){ - if(!e.classList.contains('scrollwatch')){ - if(parentLabel.innerText == 'Width' || parentLabel.innerText == 'Height'){ - e.addEventListener('input', function(e){dimensionChange(e,parentLabel.innerText)} ) - e.classList.add('scrollwatch') - } - if(parentLabel.innerText == 'Width'){ - currentWidth = e.value*1.0 - } - if(parentLabel.innerText == 'Height'){ - currentHeight = e.value*1.0 - } - } - } + inputs.forEach(function(e){ + var is_width = e.parentElement.id == "img2img_width" + var is_height = e.parentElement.id == "img2img_height" + + if((is_width || is_height) && !e.classList.contains('scrollwatch')){ + e.addEventListener('input', function(e){dimensionChange(e, is_width, is_height)} ) + e.classList.add('scrollwatch') + } + if(is_width){ + currentWidth = e.value*1.0 + } + if(is_height){ + currentHeight = e.value*1.0 + } }) } }); diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index 070cf255..3ed1cb3c 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -43,7 +43,7 @@ function dropReplaceImage( imgWrap, files ) { window.document.addEventListener('dragover', e => { const target = e.composedPath()[0]; const imgWrap = target.closest('[data-testid="image"]'); - if ( !imgWrap && target.placeholder.indexOf("Prompt") == -1) { + if ( !imgWrap && target.placeholder && target.placeholder.indexOf("Prompt") == -1) { return; } e.stopPropagation(); diff --git a/modules/ui.py b/modules/ui.py index 0d020de6..85f95792 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -879,8 +879,8 @@ def create_ui(wrap_gradio_gpu_call): sampler_index = gr.Radio(label='Sampling method', choices=[x.name for x in samplers_for_img2img], value=samplers_for_img2img[0].name, type="index") with gr.Group(): - width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512) - height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512) + width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512, elem_id="img2img_width") + height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512, elem_id="img2img_height") with gr.Row(): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1) From 3d898044e5e55dca1698e9b5b7d3558b5b78675a Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 17:26:30 +0300 Subject: [PATCH 0127/1118] batch_size does not affect job count --- scripts/outpainting_mk_2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 633dc119..2afd4aa5 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -246,7 +246,7 @@ class Script(scripts.Script): batch_count = p.n_iter batch_size = p.batch_size p.n_iter = 1 - state.job_count = batch_count * batch_size * ((1 if left > 0 else 0) + (1 if right > 0 else 0) + (1 if up > 0 else 0) + (1 if down > 0 else 0)) + state.job_count = batch_count * ((1 if left > 0 else 0) + (1 if right > 0 else 0) + (1 if up > 0 else 0) + (1 if down > 0 else 0)) all_processed_images = [] for i in range(batch_count): From ac0aa2b18efeeb9220a5994c8dd54c7cdda7cc40 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 17:35:51 +0300 Subject: [PATCH 0128/1118] loading SD VAE, see PR #3303 --- modules/sd_models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index b1c91b0d..d99dbce8 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -155,6 +155,9 @@ def get_state_dict_from_checkpoint(pl_sd): return pl_sd +vae_ignore_keys = {"model_ema.decay", "model_ema.num_updates"} + + def load_model_weights(model, checkpoint_info): checkpoint_file = checkpoint_info.filename sd_model_hash = checkpoint_info.hash @@ -186,7 +189,7 @@ def load_model_weights(model, checkpoint_info): if os.path.exists(vae_file): print(f"Loading VAE weights from: {vae_file}") vae_ckpt = torch.load(vae_file, map_location=shared.weight_load_location) - vae_dict = {k: v for k, v in vae_ckpt["state_dict"].items() if k[0:4] != "loss"} + vae_dict = {k: v for k, v in vae_ckpt["state_dict"].items() if k[0:4] != "loss" and k not in vae_ignore_keys} model.first_stage_model.load_state_dict(vae_dict) model.first_stage_model.to(devices.dtype_vae) From 24ce67a13bd74202d298cd8e2a306d90214980d8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 17:41:47 +0300 Subject: [PATCH 0129/1118] make aspect ratio overlay work regardless of selected localization, pt2 --- javascript/aspectRatioOverlay.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js index d3ca2781..66f26a22 100644 --- a/javascript/aspectRatioOverlay.js +++ b/javascript/aspectRatioOverlay.js @@ -18,22 +18,13 @@ function dimensionChange(e, is_width, is_height){ return; } - var img2imgMode = gradioApp().querySelector('#mode_img2img.tabs > div > button.rounded-t-lg.border-gray-200') - if(img2imgMode){ - img2imgMode=img2imgMode.innerText - }else{ - return; - } - - var redrawImage = gradioApp().querySelector('div[data-testid=image] img'); - var inpaintImage = gradioApp().querySelector('#img2maskimg div[data-testid=image] img') - var targetElement = null; - if(img2imgMode=='img2img' && redrawImage){ - targetElement = redrawImage; - }else if(img2imgMode=='Inpaint' && inpaintImage){ - targetElement = inpaintImage; + var tabIndex = get_tab_index('mode_img2img') + if(tabIndex == 0){ + targetElement = gradioApp().querySelector('div[data-testid=image] img'); + } else if(tabIndex == 1){ + targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] img'); } if(targetElement){ From f49c08ea566385db339c6628f65c3a121033f67c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 21 Oct 2022 18:46:02 +0300 Subject: [PATCH 0130/1118] prevent error spam when processing images without txt files for captions --- modules/textual_inversion/preprocess.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 17e4ddc1..33eaddb6 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -122,11 +122,10 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre continue existing_caption = None - - try: - existing_caption = open(os.path.splitext(filename)[0] + '.txt', 'r').read() - except Exception as e: - print(e) + existing_caption_filename = os.path.splitext(filename)[0] + '.txt' + if os.path.exists(existing_caption_filename): + with open(existing_caption_filename, 'r', encoding="utf8") as file: + existing_caption = file.read() if shared.state.interrupted: break From bb0f1a2cdae3410a41d06ae878f56e29b8154c41 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sat, 22 Oct 2022 01:23:00 +0800 Subject: [PATCH 0131/1118] inspiration finished --- javascript/inspiration.js | 27 +++--- modules/inspiration.py | 192 +++++++++++++++++++++++++------------- modules/shared.py | 6 ++ modules/ui.py | 2 +- webui.py | 3 +- 5 files changed, 151 insertions(+), 79 deletions(-) diff --git a/javascript/inspiration.js b/javascript/inspiration.js index e1c0e114..791a80c9 100644 --- a/javascript/inspiration.js +++ b/javascript/inspiration.js @@ -1,25 +1,31 @@ function public_image_index_in_gallery(item, gallery){ + var imgs = gallery.querySelectorAll("img.h-full") var index; var i = 0; - gallery.querySelectorAll("img").forEach(function(e){ + imgs.forEach(function(e){ if (e == item) index = i; i += 1; }); + var num = imgs.length / 2 + index = (index < num) ? index : (index - num) return index; } -function inspiration_selected(name, types, name_list){ +function inspiration_selected(name, name_list){ var btn = gradioApp().getElementById("inspiration_select_button") - return [gradioApp().getElementById("inspiration_select_button").getAttribute("img-index"), types]; -} + return [gradioApp().getElementById("inspiration_select_button").getAttribute("img-index")]; +} +function inspiration_click_get_button(){ + gradioApp().getElementById("inspiration_get_button").click(); +} var inspiration_image_click = function(){ var index = public_image_index_in_gallery(this, gradioApp().getElementById("inspiration_gallery")); - var btn = gradioApp().getElementById("inspiration_select_button") - btn.setAttribute("img-index", index) - setTimeout(function(btn){btn.click();}, 10, btn) + var btn = gradioApp().getElementById("inspiration_select_button"); + btn.setAttribute("img-index", index); + setTimeout(function(btn){btn.click();}, 10, btn); } - + document.addEventListener("DOMContentLoaded", function() { var mutationObserver = new MutationObserver(function(m){ var gallery = gradioApp().getElementById("inspiration_gallery") @@ -27,11 +33,10 @@ document.addEventListener("DOMContentLoaded", function() { var node = gallery.querySelector(".absolute.backdrop-blur.h-full") if (node) { node.style.display = "None"; //parentNode.removeChild(node) - } - + } gallery.querySelectorAll('img').forEach(function(e){ e.onclick = inspiration_image_click - }) + }); } diff --git a/modules/inspiration.py b/modules/inspiration.py index 456bfcb5..f72ebf3a 100644 --- a/modules/inspiration.py +++ b/modules/inspiration.py @@ -1,122 +1,182 @@ import os import random -import gradio -inspiration_path = "inspiration" -inspiration_system_path = os.path.join(inspiration_path, "system") -def read_name_list(file): +import gradio +from modules.shared import opts +inspiration_system_path = os.path.join(opts.inspiration_dir, "system") +def read_name_list(file, types=None, keyword=None): if not os.path.exists(file): return [] - f = open(file, "r") ret = [] + f = open(file, "r") line = f.readline() while len(line) > 0: line = line.rstrip("\n") - ret.append(line) - print(ret) + if types is not None: + dirname = os.path.split(line) + if dirname[0] in types and keyword in dirname[1]: + ret.append(line) + else: + ret.append(line) + line = f.readline() return ret def save_name_list(file, name): - print(file) - f = open(file, "a") - f.write(name + "\n") + with open(file, "a") as f: + f.write(name + "\n") -def get_inspiration_images(source, types): - path = os.path.join(inspiration_path , types) +def get_types_list(): + files = os.listdir(opts.inspiration_dir) + types = [] + for x in files: + path = os.path.join(opts.inspiration_dir, x) + if x[0] == ".": + continue + if not os.path.isdir(path): + continue + if path == inspiration_system_path: + continue + types.append(x) + return types + +def get_inspiration_images(source, types, keyword): + get_num = int(opts.inspiration_rows_num * opts.inspiration_cols_num) if source == "Favorites": - names = read_name_list(os.path.join(inspiration_system_path, types + "_faverites.txt")) - names = random.sample(names, 25) + names = read_name_list(os.path.join(inspiration_system_path, "faverites.txt"), types, keyword) + names = random.sample(names, get_num) if len(names) > get_num else names elif source == "Abandoned": - names = read_name_list(os.path.join(inspiration_system_path, types + "_abondened.txt")) - names = random.sample(names, 25) - elif source == "Exclude abandoned": - abondened = read_name_list(os.path.join(inspiration_system_path, types + "_abondened.txt")) - all_names = os.listdir(path) - names = [] - while len(names) < 25: - name = random.choice(all_names) - if name not in abondened: - names.append(name) + names = read_name_list(os.path.join(inspiration_system_path, "abandoned.txt"), types, keyword) + print(names) + names = random.sample(names, get_num) if len(names) > get_num else names + elif source == "Exclude abandoned": + abandoned = read_name_list(os.path.join(inspiration_system_path, "abandoned.txt"), types, keyword) + all_names = [] + for tp in types: + name_list = os.listdir(os.path.join(opts.inspiration_dir, tp)) + all_names += [os.path.join(tp, x) for x in name_list if keyword in x] + + if len(all_names) > get_num: + names = [] + while len(names) < get_num: + name = random.choice(all_names) + if name not in abandoned: + names.append(name) + else: + names = all_names else: - names = random.sample(os.listdir(path), 25) - names = random.sample(names, 25) + all_names = [] + for tp in types: + name_list = os.listdir(os.path.join(opts.inspiration_dir, tp)) + all_names += [os.path.join(tp, x) for x in name_list if keyword in x] + names = random.sample(all_names, get_num) if len(all_names) > get_num else all_names image_list = [] for a in names: - image_path = os.path.join(path, a) + image_path = os.path.join(opts.inspiration_dir, a) images = os.listdir(image_path) - image_list.append(os.path.join(image_path, random.choice(images))) - return image_list, names + image_list.append((os.path.join(image_path, random.choice(images)), a)) + return image_list, names, "" -def select_click(index, types, name_list): +def select_click(index, name_list): name = name_list[int(index)] - path = os.path.join(inspiration_path, types, name) + path = os.path.join(opts.inspiration_dir, name) images = os.listdir(path) - return name, [os.path.join(path, x) for x in images] + return name, [os.path.join(path, x) for x in images], "" -def give_up_click(name, types): - file = os.path.join(inspiration_system_path, types + "_abandoned.txt") +def give_up_click(name): + file = os.path.join(inspiration_system_path, "abandoned.txt") name_list = read_name_list(file) if name not in name_list: save_name_list(file, name) + return "Added to abandoned list" -def collect_click(name, types): - file = os.path.join(inspiration_system_path, types + "_faverites.txt") - print(file) +def collect_click(name): + file = os.path.join(inspiration_system_path, "faverites.txt") name_list = read_name_list(file) - print(name_list) if name not in name_list: save_name_list(file, name) + return "Added to faverite list" -def moveout_click(name, types): - file = os.path.join(inspiration_system_path, types + "_faverites.txt") +def moveout_click(name, source): + if source == "Abandoned": + file = os.path.join(inspiration_system_path, "abandoned.txt") + if source == "Favorites": + file = os.path.join(inspiration_system_path, "faverites.txt") + else: + return None name_list = read_name_list(file) - if name not in name_list: - save_name_list(file, name) + os.remove(file) + with open(file, "a") as f: + for a in name_list: + if a != name: + f.write(a) + return "Moved out {name} from {source} list" def source_change(source): - if source == "Abandoned" or source == "Favorites": - return gradio.Button.update(visible=True, value=f"Move out {source}") + if source in ["Abandoned", "Favorites"]: + return gradio.update(visible=True), [] else: - return gradio.Button.update(visible=False) + return gradio.update(visible=False), [] +def add_to_prompt(name, prompt): + print(name, prompt) + name = os.path.basename(name) + return prompt + "," + name -def ui(gr, opts): +def ui(gr, opts, txt2img_prompt, img2img_prompt): with gr.Blocks(analytics_enabled=False) as inspiration: - flag = os.path.exists(inspiration_path) + flag = os.path.exists(opts.inspiration_dir) if flag: - types = os.listdir(inspiration_path) - types = [x for x in types if x != "system"] + types = get_types_list() flag = len(types) > 0 - if not flag: - os.mkdir(inspiration_path) + else: + os.makedirs(opts.inspiration_dir) + if not flag: gr.HTML(""" -
" +

To activate inspiration function, you need get "inspiration" images first.


+ You can create these images by run "Create inspiration images" script in txt2img page,
you can get the artists or art styles list from here
+ https://github.com/pharmapsychotic/clip-interrogator/tree/main/data
+ download these files, and select these files in the "Create inspiration images" script UI
+ There about 6000 artists and art styles in these files.
This takes server hours depending on your GPU type and how many pictures you generate for each artist/style +
I suggest at least four images for each


+

You can also download generated pictures from here:


+ https://huggingface.co/datasets/yfszzx/inspiration
+ unzip the file to the project directory of webui
+ and restart webui, and enjoy the joy of creation!
""") return inspiration if not os.path.exists(inspiration_system_path): os.mkdir(inspiration_system_path) - gallery, names = get_inspiration_images("Exclude abandoned", types[0]) with gr.Row(): with gr.Column(scale=2): - inspiration_gallery = gr.Gallery(gallery, show_label=False, elem_id="inspiration_gallery").style(grid=5, height='auto') + inspiration_gallery = gr.Gallery(show_label=False, elem_id="inspiration_gallery").style(grid=opts.inspiration_cols_num, height='auto') with gr.Column(scale=1): - types = gr.Dropdown(choices=types, value=types[0], label="Type", visible=len(types) > 1) + print(types) + types = gr.CheckboxGroup(choices=types, value=types) + keyword = gr.Textbox("", label="Key word") with gr.Row(): source = gr.Dropdown(choices=["All", "Favorites", "Exclude abandoned", "Abandoned"], value="Exclude abandoned", label="Source") - get_inspiration = gr.Button("Get inspiration") + get_inspiration = gr.Button("Get inspiration", elem_id="inspiration_get_button") name = gr.Textbox(show_label=False, interactive=False) with gr.Row(): send_to_txt2img = gr.Button('to txt2img') send_to_img2img = gr.Button('to img2img') - style_gallery = gr.Gallery(show_label=False, elem_id="inspiration_style_gallery").style(grid=2, height='auto') - + style_gallery = gr.Gallery(show_label=False).style(grid=2, height='auto') collect = gr.Button('Collect') - give_up = gr.Button("Don't show any more") + give_up = gr.Button("Don't show again") moveout = gr.Button("Move out", visible=False) - with gr.Row(): + warning = gr.HTML() + with gr.Row(visible=False): select_button = gr.Button('set button', elem_id="inspiration_select_button") - name_list = gr.State(names) - source.change(source_change, inputs=[source], outputs=[moveout]) - get_inspiration.click(get_inspiration_images, inputs=[source, types], outputs=[inspiration_gallery, name_list]) - select_button.click(select_click, _js="inspiration_selected", inputs=[name, types, name_list], outputs=[name, style_gallery]) - give_up.click(give_up_click, inputs=[name, types], outputs=None) - collect.click(collect_click, inputs=[name, types], outputs=None) + name_list = gr.State() + + get_inspiration.click(get_inspiration_images, inputs=[source, types, keyword], outputs=[inspiration_gallery, name_list, keyword]) + source.change(source_change, inputs=[source], outputs=[moveout, style_gallery]) + source.change(fn=None, _js="inspiration_click_get_button", inputs=None, outputs=None) + keyword.submit(fn=None, _js="inspiration_click_get_button", inputs=None, outputs=None) + select_button.click(select_click, _js="inspiration_selected", inputs=[name, name_list], outputs=[name, style_gallery, warning]) + give_up.click(give_up_click, inputs=[name], outputs=[warning]) + collect.click(collect_click, inputs=[name], outputs=[warning]) + moveout.click(moveout_click, inputs=[name, source], outputs=[warning]) + send_to_txt2img.click(add_to_prompt, inputs=[name, txt2img_prompt], outputs=[txt2img_prompt]) + send_to_img2img.click(add_to_prompt, inputs=[name, img2img_prompt], outputs=[img2img_prompt]) + send_to_txt2img.click(None, _js='switch_to_txt2img', inputs=None, outputs=None) + send_to_img2img.click(None, _js="switch_to_img2img_img2img", inputs=None, outputs=None) return inspiration diff --git a/modules/shared.py b/modules/shared.py index ae033710..564b1b8d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -316,6 +316,12 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}), })) +options_templates.update(options_section(('inspiration', "Inspiration"), { + "inspiration_dir": OptionInfo("inspiration", "Directory of inspiration", component_args=hide_dirs), + "inspiration_max_samples": OptionInfo(4, "Maximum number of samples, used to determine which folders to skip when continue running the create script", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1}), + "inspiration_rows_num": OptionInfo(4, "Rows of inspiration interface frame", gr.Slider, {"minimum": 4, "maximum": 16, "step": 1}), + "inspiration_cols_num": OptionInfo(8, "Columns of inspiration interface frame", gr.Slider, {"minimum": 4, "maximum": 16, "step": 1}), +})) class Options: data = None diff --git a/modules/ui.py b/modules/ui.py index 6a0a3c3b..b651eb9c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1180,7 +1180,7 @@ def create_ui(wrap_gradio_gpu_call): } browser_interface = images_history.create_history_tabs(gr, opts, wrap_gradio_call(modules.extras.run_pnginfo), images_history_switch_dict) - inspiration_interface = inspiration.ui(gr, opts) + inspiration_interface = inspiration.ui(gr, opts, txt2img_prompt, img2img_prompt) with gr.Blocks() as modelmerger_interface: with gr.Row().style(equal_height=False): diff --git a/webui.py b/webui.py index 5923905f..5ccae715 100644 --- a/webui.py +++ b/webui.py @@ -72,6 +72,7 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs) def initialize(): + modules.scripts.load_scripts(os.path.join(script_path, "scripts")) if cmd_opts.ui_debug_mode: class enmpty(): name = None @@ -84,7 +85,7 @@ def initialize(): shared.face_restorers.append(modules.face_restoration.FaceRestoration()) modelloader.load_upscalers() - modules.scripts.load_scripts(os.path.join(script_path, "scripts")) + shared.sd_model = modules.sd_models.load_model() shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(shared.sd_model))) From 2797b2cbf29a928ea84522d8d9478d47c7feede9 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sat, 22 Oct 2022 01:28:02 +0800 Subject: [PATCH 0132/1118] inspiration finished --- javascript/imageviewer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index d4ab6984..9e380c65 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -116,6 +116,7 @@ function showGalleryImage() { e.dataset.modded = true; if(e && e.parentElement.tagName == 'DIV'){ e.style.cursor='pointer' + e.style.userSelect='none' e.addEventListener('click', function (evt) { if(!opts.js_modal_lightbox) return; modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed) From 58ee008f0f559a947cc280a552d97050e638d611 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sat, 22 Oct 2022 01:30:12 +0800 Subject: [PATCH 0133/1118] inspiration finished --- scripts/create_inspiration_images.py | 76 ++++++++++++++++------------ 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/scripts/create_inspiration_images.py b/scripts/create_inspiration_images.py index 6a20def8..2fd30578 100644 --- a/scripts/create_inspiration_images.py +++ b/scripts/create_inspiration_images.py @@ -2,44 +2,56 @@ import csv, os, shutil import modules.scripts as scripts from modules import processing, shared, sd_samplers, images from modules.processing import Processed - - +from modules.shared import opts +import gradio class Script(scripts.Script): def title(self): - return "Create artists style image" + return "Create inspiration images" def show(self, is_img2img): - return not is_img2img + return True def ui(self, is_img2img): - return [] - def show(self, is_img2img): - return not is_img2img + file = gradio.Files(label="Artist or styles name list. '.txt' files with one name per line",) + with gradio.Row(): + prefix = gradio.Textbox("a painting in", label="Prompt words before artist or style name", file_count="multiple") + suffix= gradio.Textbox("style", label="Prompt words after artist or style name") + negative_prompt = gradio.Textbox("picture frame, portrait photo", label="Negative Prompt") + with gradio.Row(): + batch_size = gradio.Number(1, label="Batch size") + batch_count = gradio.Number(2, label="Batch count") + return [batch_size, batch_count, prefix, suffix, negative_prompt, file] - def run(self, p): #, max_snapshoots_num): - path = os.path.join("style_snapshoot", "artist") - if not os.path.exists(path): - os.makedirs(path) + def run(self, p, batch_size, batch_count, prefix, suffix, negative_prompt, files): + p.batch_size = int(batch_size) + p.n_iterint = int(batch_count) + p.negative_prompt = negative_prompt p.do_not_save_samples = True - p.do_not_save_grid = True - p.negative_prompt = "portrait photo" - f = open('artists.csv') - f_csv = csv.reader(f) - for row in f_csv: - name = row[0] - artist_path = os.path.join(path, name) - if not os.path.exists(artist_path): - os.mkdir(artist_path) - if len(os.listdir(artist_path)) > 0: - continue - print(name) - p.prompt = name - processed = processing.process_images(p) - for img in processed.images: - i = 0 - filename = os.path.join(artist_path, format(0, "03d") + ".jpg") - while os.path.exists(filename): - i += 1 - filename = os.path.join(artist_path, format(i, "03d") + ".jpg") - img.save(filename, quality=70) + p.do_not_save_grid = True + for file in files: + tp = file.orig_name.split(".")[0] + print(tp) + path = os.path.join(opts.inspiration_dir, tp) + if not os.path.exists(path): + os.makedirs(path) + f = open(file.name, "r") + line = f.readline() + while len(line) > 0: + name = line.rstrip("\n").split(",")[0] + line = f.readline() + artist_path = os.path.join(path, name) + if not os.path.exists(artist_path): + os.mkdir(artist_path) + if len(os.listdir(artist_path)) >= opts.inspiration_max_samples: + continue + p.prompt = f"{prefix} {name} {suffix}" + print(p.prompt) + processed = processing.process_images(p) + for img in processed.images: + i = 0 + filename = os.path.join(artist_path, format(0, "03d") + ".jpg") + while os.path.exists(filename): + i += 1 + filename = os.path.join(artist_path, format(i, "03d") + ".jpg") + img.save(filename, quality=80) return processed From 9ba372de90df81c4f1e992d8b33ae17c6630de95 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Fri, 21 Oct 2022 13:55:42 -0500 Subject: [PATCH 0134/1118] initial work on getting prompts cleared on the backend and synchronizing token counter --- javascript/ui.js | 10 +++++++--- modules/ui.py | 29 +++++++++++++++++++---------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index f19af550..a0f01d10 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -162,9 +162,13 @@ function selected_tab_id() { } -function trash_prompt(_,_, is_img2img) { +function trash_prompt(_, confirmed) { -if(!confirm("Delete prompt?")) return false +if(confirm("Delete prompt?")) { + confirmed = true +} else { +return [_, confirmed] +} if(selected_tab_id() == "tab_txt2img") { gradioApp().querySelector("#txt2img_prompt > label > textarea").value = ""; @@ -178,7 +182,7 @@ if(!confirm("Delete prompt?")) return false update_token_counter("txt2img_token_button") } - return true + return [_, confirmed] } diff --git a/modules/ui.py b/modules/ui.py index d2cb528e..2748a2e0 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -429,15 +429,16 @@ def create_seed_inputs(): return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox -# setup button for clearing prompt input boxes on client side of webui -def connect_trash_prompt(dummy_component, button, is_img2img): +def clear_prompt(prompt): + print(f"type: '{prompt}'") + print(prompt) + + # update_token_counter(prompt, steps) + return "" + +def connect_trash_prompt(prompt, confirmed): + if(confirmed): clear_prompt(prompt) - button.click( - fn=lambda: print("Clearing prompt"), - _js="trash_prompt", - inputs=[], - outputs=[], - ) def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed): """ Connects a 'reuse (sub)seed' button's click event so that it copies last used @@ -640,7 +641,16 @@ def create_ui(wrap_gradio_gpu_call): dummy_component = gr.Label(visible=False) txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False) - connect_trash_prompt(dummy_component, trash_prompt_button, False) + + + trash_prompt_button.click( + # fn=lambda: print("Clearing prompt"), + _js="trash_prompt", + fn=lambda: clear_prompt(), + inputs=[txt2img_prompt, dummy_component], + outputs=[txt2img_prompt, dummy_component], + ) + with gr.Row(elem_id='txt2img_progress_row'): with gr.Column(scale=1): @@ -848,7 +858,6 @@ def create_ui(wrap_gradio_gpu_call): img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,\ token_counter, token_button, trash_prompt_button = create_toprow(is_img2img=True) - connect_trash_prompt(dummy_component,trash_prompt_button, True) with gr.Row(elem_id='img2img_progress_row'): img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False) From ee0505dd0092ae7073b77aba93a858bda000dc60 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Fri, 21 Oct 2022 14:24:14 -0500 Subject: [PATCH 0135/1118] only delete prompt on back end and remove client-side deletion --- javascript/ui.js | 6 ------ modules/ui.py | 29 +++++++++++++++-------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index a0f01d10..29306abe 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -171,14 +171,8 @@ return [_, confirmed] } if(selected_tab_id() == "tab_txt2img") { - gradioApp().querySelector("#txt2img_prompt > label > textarea").value = ""; - gradioApp().querySelector("#txt2img_neg_prompt > label > textarea").value = ""; - update_token_counter("img2img_token_button") } else { - gradioApp().querySelector("#img2img_prompt > label > textarea").value = ""; - gradioApp().querySelector("#img2img_neg_prompt > label > textarea").value = ""; - update_token_counter("txt2img_token_button") } diff --git a/modules/ui.py b/modules/ui.py index 2748a2e0..90c338da 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -429,15 +429,21 @@ def create_seed_inputs(): return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox -def clear_prompt(prompt): - print(f"type: '{prompt}'") - print(prompt) - # update_token_counter(prompt, steps) - return "" +def connect_trash_prompt(_, confirmed): + if(confirmed): + # update_token_counter(prompt, steps) + return ["", confirmed] -def connect_trash_prompt(prompt, confirmed): - if(confirmed): clear_prompt(prompt) +def trash_prompt_click(button, prompt): + dummy_component = gradio.Label(visible=False) + + button.click( + _js="trash_prompt", + fn=connect_trash_prompt, + inputs=[prompt, dummy_component], + outputs=[prompt, dummy_component], + ) def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed): @@ -643,13 +649,7 @@ def create_ui(wrap_gradio_gpu_call): txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False) - trash_prompt_button.click( - # fn=lambda: print("Clearing prompt"), - _js="trash_prompt", - fn=lambda: clear_prompt(), - inputs=[txt2img_prompt, dummy_component], - outputs=[txt2img_prompt, dummy_component], - ) + trash_prompt_click(trash_prompt_button, txt2img_prompt) with gr.Row(elem_id='txt2img_progress_row'): @@ -858,6 +858,7 @@ def create_ui(wrap_gradio_gpu_call): img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,\ token_counter, token_button, trash_prompt_button = create_toprow(is_img2img=True) + trash_prompt_click(trash_prompt_button, img2img_prompt) with gr.Row(elem_id='img2img_progress_row'): img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False) From de70ddaf58fae98c561738a54f574abfa14cd8d1 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Fri, 21 Oct 2022 15:00:35 -0500 Subject: [PATCH 0136/1118] update token counter when clearing prompt --- javascript/ui.js | 4 ++-- modules/ui.py | 17 +++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 29306abe..acd57565 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -162,7 +162,7 @@ function selected_tab_id() { } -function trash_prompt(_, confirmed) { +function trash_prompt(_, confirmed,_steps) { if(confirm("Delete prompt?")) { confirmed = true @@ -176,7 +176,7 @@ return [_, confirmed] update_token_counter("txt2img_token_button") } - return [_, confirmed] + return [_, confirmed,_steps] } diff --git a/modules/ui.py b/modules/ui.py index 90c338da..d3a89bf7 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -430,19 +430,16 @@ def create_seed_inputs(): -def connect_trash_prompt(_, confirmed): +def connect_trash_prompt(_prompt, confirmed, _token_counter): if(confirmed): - # update_token_counter(prompt, steps) - return ["", confirmed] - -def trash_prompt_click(button, prompt): - dummy_component = gradio.Label(visible=False) + return ["", confirmed, update_token_counter("", 1)] +def trash_prompt_click(button, prompt, _dummy_confirmed, token_counter): button.click( _js="trash_prompt", fn=connect_trash_prompt, - inputs=[prompt, dummy_component], - outputs=[prompt, dummy_component], + inputs=[prompt, _dummy_confirmed, token_counter], + outputs=[prompt, _dummy_confirmed, token_counter], ) @@ -649,7 +646,6 @@ def create_ui(wrap_gradio_gpu_call): txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False) - trash_prompt_click(trash_prompt_button, txt2img_prompt) with gr.Row(elem_id='txt2img_progress_row'): @@ -720,6 +716,7 @@ def create_ui(wrap_gradio_gpu_call): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) + trash_prompt_click(trash_prompt_button, txt2img_prompt, dummy_component, token_counter) txt2img_args = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img), @@ -858,7 +855,6 @@ def create_ui(wrap_gradio_gpu_call): img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,\ token_counter, token_button, trash_prompt_button = create_toprow(is_img2img=True) - trash_prompt_click(trash_prompt_button, img2img_prompt) with gr.Row(elem_id='img2img_progress_row'): img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False) @@ -958,6 +954,7 @@ def create_ui(wrap_gradio_gpu_call): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) + trash_prompt_click(trash_prompt_button, img2img_prompt, dummy_component, token_counter) img2img_prompt_img.change( fn=modules.images.image_data, From 9e40520f00d836cfa93187f7f1e81e2a7bd100b9 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Fri, 21 Oct 2022 15:13:12 -0500 Subject: [PATCH 0137/1118] refactor internal terminology to use 'clear' instead of 'trash' like #2728 --- javascript/ui.js | 2 +- modules/shared.py | 2 +- modules/ui.py | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index acd57565..45d93a5c 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -162,7 +162,7 @@ function selected_tab_id() { } -function trash_prompt(_, confirmed,_steps) { +function clear_prompt(_, confirmed,_steps) { if(confirm("Delete prompt?")) { confirmed = true diff --git a/modules/shared.py b/modules/shared.py index 1585d532..ab5a0e9a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -317,7 +317,7 @@ options_templates.update(options_section(('ui', "User interface"), { "js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"), "js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"), "show_progress_in_title": OptionInfo(True, "Show generation progress in window title."), - "trash_prompt_visible": OptionInfo(True, "Show trash prompt button"), + "clear_prompt_visible": OptionInfo(True, "Show clear prompt button"), 'quicksettings': OptionInfo("sd_model_checkpoint", "Quicksettings list"), 'localization': OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), })) diff --git a/modules/ui.py b/modules/ui.py index d3a89bf7..31150800 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -88,7 +88,7 @@ folder_symbol = '\U0001f4c2' # 📂 refresh_symbol = '\U0001f504' # 🔄 save_style_symbol = '\U0001f4be' # 💾 apply_style_symbol = '\U0001f4cb' # 📋 -trash_prompt_symbol = '\U0001F5D1' # +clear_prompt_symbol = '\U0001F5D1' # 🗑️ def plaintext_to_html(text): @@ -430,14 +430,14 @@ def create_seed_inputs(): -def connect_trash_prompt(_prompt, confirmed, _token_counter): +def clear_prompt(_prompt, confirmed, _token_counter): if(confirmed): return ["", confirmed, update_token_counter("", 1)] -def trash_prompt_click(button, prompt, _dummy_confirmed, token_counter): +def connect_clear_prompt(button, prompt, _dummy_confirmed, token_counter): button.click( - _js="trash_prompt", - fn=connect_trash_prompt, + _js="clear_prompt", + fn=clear_prompt, inputs=[prompt, _dummy_confirmed, token_counter], outputs=[prompt, _dummy_confirmed, token_counter], ) @@ -518,7 +518,7 @@ def create_toprow(is_img2img): paste = gr.Button(value=paste_symbol, elem_id="paste") save_style = gr.Button(value=save_style_symbol, elem_id="style_create") prompt_style_apply = gr.Button(value=apply_style_symbol, elem_id="style_apply") - trash_prompt = gr.Button(value=trash_prompt_symbol, elem_id="trash_prompt", visible=opts.trash_prompt_visible) + clear_prompt_button = gr.Button(value=clear_prompt_symbol, elem_id="clear_prompt", visible=opts.clear_prompt_visible) token_counter = gr.HTML(value="", elem_id=f"{id_part}_token_counter") token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") @@ -559,7 +559,7 @@ def create_toprow(is_img2img): prompt_style2 = gr.Dropdown(label="Style 2", elem_id=f"{id_part}_style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys()))) prompt_style2.save_to_config = True - return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button, trash_prompt + return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button, clear_prompt_button def setup_progressbar(progressbar, preview, id_part, textinfo=None): @@ -640,7 +640,7 @@ def create_ui(wrap_gradio_gpu_call): with gr.Blocks(analytics_enabled=False) as txt2img_interface: txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, _,\ txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter,\ - token_button, trash_prompt_button = create_toprow(is_img2img=False) + token_button, clear_prompt_button = create_toprow(is_img2img=False) dummy_component = gr.Label(visible=False) txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False) @@ -716,7 +716,7 @@ def create_ui(wrap_gradio_gpu_call): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - trash_prompt_click(trash_prompt_button, txt2img_prompt, dummy_component, token_counter) + connect_clear_prompt(clear_prompt_button, txt2img_prompt, dummy_component, token_counter) txt2img_args = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img), @@ -853,7 +853,7 @@ def create_ui(wrap_gradio_gpu_call): with gr.Blocks(analytics_enabled=False) as img2img_interface: img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit,\ img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,\ - token_counter, token_button, trash_prompt_button = create_toprow(is_img2img=True) + token_counter, token_button, clear_prompt_button = create_toprow(is_img2img=True) with gr.Row(elem_id='img2img_progress_row'): @@ -954,7 +954,7 @@ def create_ui(wrap_gradio_gpu_call): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - trash_prompt_click(trash_prompt_button, img2img_prompt, dummy_component, token_counter) + connect_clear_prompt(clear_prompt_button, img2img_prompt, dummy_component, token_counter) img2img_prompt_img.change( fn=modules.images.image_data, From 0c7cf08b3d27a61bab4cd8b16f8be8ae74879423 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Fri, 21 Oct 2022 15:32:26 -0500 Subject: [PATCH 0138/1118] some doc and formatting --- modules/ui.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 31150800..b26cf10a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -88,7 +88,7 @@ folder_symbol = '\U0001f4c2' # 📂 refresh_symbol = '\U0001f504' # 🔄 save_style_symbol = '\U0001f4be' # 💾 apply_style_symbol = '\U0001f4cb' # 📋 -clear_prompt_symbol = '\U0001F5D1' # 🗑️ +clear_prompt_symbol = '\U0001F5D1' # 🗑️ def plaintext_to_html(text): @@ -429,12 +429,14 @@ def create_seed_inputs(): return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox - def clear_prompt(_prompt, confirmed, _token_counter): - if(confirmed): - return ["", confirmed, update_token_counter("", 1)] + """Given confirmation from a user on the client-side, go ahead with clearing prompt""" + if confirmed: + return ["", confirmed, update_token_counter("", 1)] + def connect_clear_prompt(button, prompt, _dummy_confirmed, token_counter): + """Given clear button, prompt, and token_counter objects, setup clear prompt button click event""" button.click( _js="clear_prompt", fn=clear_prompt, @@ -518,7 +520,12 @@ def create_toprow(is_img2img): paste = gr.Button(value=paste_symbol, elem_id="paste") save_style = gr.Button(value=save_style_symbol, elem_id="style_create") prompt_style_apply = gr.Button(value=apply_style_symbol, elem_id="style_apply") - clear_prompt_button = gr.Button(value=clear_prompt_symbol, elem_id="clear_prompt", visible=opts.clear_prompt_visible) + + clear_prompt_button = gr.Button( + value=clear_prompt_symbol, + elem_id="clear_prompt", + visible=opts.clear_prompt_visible + ) token_counter = gr.HTML(value="", elem_id=f"{id_part}_token_counter") token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") From 57eb54b838faa383c10079e1bb5471b7bee6a695 Mon Sep 17 00:00:00 2001 From: Extraltodeus Date: Sat, 22 Oct 2022 00:11:07 +0200 Subject: [PATCH 0139/1118] implement CUDA device selection by ID --- modules/devices.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index eb422583..8a159282 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -1,7 +1,6 @@ +import sys, os, shlex import contextlib - import torch - from modules import errors # has_mps is only available in nightly pytorch (for now), `getattr` for compatibility @@ -9,10 +8,26 @@ has_mps = getattr(torch, 'has_mps', False) cpu = torch.device("cpu") +def extract_device_id(args, name): + for x in range(len(args)): + if name in args[x]: return args[x+1] + return None def get_optimal_device(): if torch.cuda.is_available(): - return torch.device("cuda") + # CUDA device selection support: + if "shared" not in sys.modules: + commandline_args = os.environ.get('COMMANDLINE_ARGS', "") #re-parse the commandline arguments because using the shared.py module creates an import loop. + sys.argv += shlex.split(commandline_args) + device_id = extract_device_id(sys.argv, '--device-id') + else: + device_id = shared.cmd_opts.device_id + + if device_id is not None: + cuda_device = f"cuda:{device_id}" + return torch.device(cuda_device) + else: + return torch.device("cuda") if has_mps: return torch.device("mps") From 29bfacd63cb5c73b9643d94f255cca818fd49d9c Mon Sep 17 00:00:00 2001 From: Extraltodeus Date: Sat, 22 Oct 2022 00:12:46 +0200 Subject: [PATCH 0140/1118] implement CUDA device selection, --device-id arg --- modules/shared.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/shared.py b/modules/shared.py index 41d7f08e..03032a47 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -80,6 +80,7 @@ parser.add_argument('--vae-path', type=str, help='Path to Variational Autoencode parser.add_argument("--disable-safe-unpickle", action='store_true', help="disable checking pytorch models for malicious code", default=False) parser.add_argument("--api", action='store_true', help="use api=True to launch the api with the webui") parser.add_argument("--nowebui", action='store_true', help="use api=True to launch the api instead of the webui") +parser.add_argument("--device-id", type=str, help="Select the default CUDA device to use (export CUDA_VISIBLE_DEVICES=0,1,etc might be needed before)", default=None) cmd_opts = parser.parse_args() restricted_opts = [ From 700340448baa7412c7cc5ff3d1349ac79ee8ed0c Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Fri, 21 Oct 2022 17:24:04 -0500 Subject: [PATCH 0141/1118] forgot to clear neg prompt after moving to back. Add tooltip to hints --- javascript/hints.js | 1 + javascript/ui.js | 4 ++-- modules/ui.py | 14 +++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index a1fcc93b..54c8c238 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -17,6 +17,7 @@ titles = { "\u2199\ufe0f": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", "\u{1f4c2}": "Open images output directory", "\u{1f4be}": "Save style", + "\U0001F5D1": "Clear prompt" "\u{1f4cb}": "Apply selected styles to current prompt", "Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt", diff --git a/javascript/ui.js b/javascript/ui.js index 45d93a5c..6c99824b 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -162,7 +162,7 @@ function selected_tab_id() { } -function clear_prompt(_, confirmed,_steps) { +function clear_prompt(_, _prompt_neg, confirmed,_steps) { if(confirm("Delete prompt?")) { confirmed = true @@ -176,7 +176,7 @@ return [_, confirmed] update_token_counter("txt2img_token_button") } - return [_, confirmed,_steps] + return [_, _prompt_neg, confirmed,_steps] } diff --git a/modules/ui.py b/modules/ui.py index b26cf10a..25aeba3b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -429,19 +429,19 @@ def create_seed_inputs(): return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox -def clear_prompt(_prompt, confirmed, _token_counter): +def clear_prompt(_prompt, _prompt_neg, confirmed, _token_counter): """Given confirmation from a user on the client-side, go ahead with clearing prompt""" if confirmed: - return ["", confirmed, update_token_counter("", 1)] + return ["", "", confirmed, update_token_counter("", 1)] -def connect_clear_prompt(button, prompt, _dummy_confirmed, token_counter): +def connect_clear_prompt(button, prompt, prompt_neg, _dummy_confirmed, token_counter): """Given clear button, prompt, and token_counter objects, setup clear prompt button click event""" button.click( _js="clear_prompt", fn=clear_prompt, - inputs=[prompt, _dummy_confirmed, token_counter], - outputs=[prompt, _dummy_confirmed, token_counter], + inputs=[prompt, prompt_neg, _dummy_confirmed, token_counter], + outputs=[prompt, prompt_neg, _dummy_confirmed, token_counter], ) @@ -723,7 +723,7 @@ def create_ui(wrap_gradio_gpu_call): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - connect_clear_prompt(clear_prompt_button, txt2img_prompt, dummy_component, token_counter) + connect_clear_prompt(clear_prompt_button, txt2img_prompt, txt2img_negative_prompt, dummy_component, token_counter) txt2img_args = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img), @@ -961,7 +961,7 @@ def create_ui(wrap_gradio_gpu_call): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - connect_clear_prompt(clear_prompt_button, img2img_prompt, dummy_component, token_counter) + connect_clear_prompt(clear_prompt_button, img2img_prompt, img2img_negative_prompt, dummy_component, token_counter) img2img_prompt_img.change( fn=modules.images.image_data, From 40ddb6df61564684263c7442bacf61efe3882b87 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sat, 22 Oct 2022 10:16:22 +0800 Subject: [PATCH 0142/1118] inspiration perfected --- javascript/inspiration.js | 19 ++++++----- modules/inspiration.py | 71 +++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/javascript/inspiration.js b/javascript/inspiration.js index 791a80c9..39844544 100644 --- a/javascript/inspiration.js +++ b/javascript/inspiration.js @@ -1,5 +1,5 @@ function public_image_index_in_gallery(item, gallery){ - var imgs = gallery.querySelectorAll("img.h-full") + var imgs = gallery.querySelectorAll("img.h-full") var index; var i = 0; imgs.forEach(function(e){ @@ -7,18 +7,23 @@ function public_image_index_in_gallery(item, gallery){ index = i; i += 1; }); - var num = imgs.length / 2 - index = (index < num) ? index : (index - num) + var all_imgs = gallery.querySelectorAll("img") + if (all_imgs.length > imgs.length){ + var num = imgs.length / 2 + index = (index < num) ? index : (index - num) + } return index; } function inspiration_selected(name, name_list){ var btn = gradioApp().getElementById("inspiration_select_button") return [gradioApp().getElementById("inspiration_select_button").getAttribute("img-index")]; -} +} + function inspiration_click_get_button(){ gradioApp().getElementById("inspiration_get_button").click(); } + var inspiration_image_click = function(){ var index = public_image_index_in_gallery(this, gradioApp().getElementById("inspiration_gallery")); var btn = gradioApp().getElementById("inspiration_select_button"); @@ -32,16 +37,12 @@ document.addEventListener("DOMContentLoaded", function() { if (gallery) { var node = gallery.querySelector(".absolute.backdrop-blur.h-full") if (node) { - node.style.display = "None"; //parentNode.removeChild(node) + node.style.display = "None"; } gallery.querySelectorAll('img').forEach(function(e){ e.onclick = inspiration_image_click }); - } - - }); mutationObserver.observe( gradioApp(), { childList:true, subtree:true }); - }); diff --git a/modules/inspiration.py b/modules/inspiration.py index f72ebf3a..319183ab 100644 --- a/modules/inspiration.py +++ b/modules/inspiration.py @@ -13,7 +13,7 @@ def read_name_list(file, types=None, keyword=None): line = line.rstrip("\n") if types is not None: dirname = os.path.split(line) - if dirname[0] in types and keyword in dirname[1]: + if dirname[0] in types and keyword in dirname[1].lower(): ret.append(line) else: ret.append(line) @@ -21,8 +21,10 @@ def read_name_list(file, types=None, keyword=None): return ret def save_name_list(file, name): - with open(file, "a") as f: - f.write(name + "\n") + name_list = read_name_list(file) + if name not in name_list: + with open(file, "a") as f: + f.write(name + "\n") def get_types_list(): files = os.listdir(opts.inspiration_dir) @@ -39,20 +41,20 @@ def get_types_list(): return types def get_inspiration_images(source, types, keyword): + keyword = keyword.strip(" ").lower() get_num = int(opts.inspiration_rows_num * opts.inspiration_cols_num) if source == "Favorites": names = read_name_list(os.path.join(inspiration_system_path, "faverites.txt"), types, keyword) names = random.sample(names, get_num) if len(names) > get_num else names elif source == "Abandoned": names = read_name_list(os.path.join(inspiration_system_path, "abandoned.txt"), types, keyword) - print(names) names = random.sample(names, get_num) if len(names) > get_num else names elif source == "Exclude abandoned": abandoned = read_name_list(os.path.join(inspiration_system_path, "abandoned.txt"), types, keyword) all_names = [] for tp in types: name_list = os.listdir(os.path.join(opts.inspiration_dir, tp)) - all_names += [os.path.join(tp, x) for x in name_list if keyword in x] + all_names += [os.path.join(tp, x) for x in name_list if keyword in x.lower()] if len(all_names) > get_num: names = [] @@ -66,14 +68,14 @@ def get_inspiration_images(source, types, keyword): all_names = [] for tp in types: name_list = os.listdir(os.path.join(opts.inspiration_dir, tp)) - all_names += [os.path.join(tp, x) for x in name_list if keyword in x] + all_names += [os.path.join(tp, x) for x in name_list if keyword in x.lower()] names = random.sample(all_names, get_num) if len(all_names) > get_num else all_names image_list = [] for a in names: image_path = os.path.join(opts.inspiration_dir, a) images = os.listdir(image_path) image_list.append((os.path.join(image_path, random.choice(images)), a)) - return image_list, names, "" + return image_list, names def select_click(index, name_list): name = name_list[int(index)] @@ -83,22 +85,18 @@ def select_click(index, name_list): def give_up_click(name): file = os.path.join(inspiration_system_path, "abandoned.txt") - name_list = read_name_list(file) - if name not in name_list: - save_name_list(file, name) + save_name_list(file, name) return "Added to abandoned list" def collect_click(name): file = os.path.join(inspiration_system_path, "faverites.txt") - name_list = read_name_list(file) - if name not in name_list: - save_name_list(file, name) + save_name_list(file, name) return "Added to faverite list" def moveout_click(name, source): if source == "Abandoned": file = os.path.join(inspiration_system_path, "abandoned.txt") - if source == "Favorites": + elif source == "Favorites": file = os.path.join(inspiration_system_path, "faverites.txt") else: return None @@ -107,8 +105,8 @@ def moveout_click(name, source): with open(file, "a") as f: for a in name_list: if a != name: - f.write(a) - return "Moved out {name} from {source} list" + f.write(a + "\n") + return f"Moved out {name} from {source} list" def source_change(source): if source in ["Abandoned", "Favorites"]: @@ -116,10 +114,12 @@ def source_change(source): else: return gradio.update(visible=False), [] def add_to_prompt(name, prompt): - print(name, prompt) name = os.path.basename(name) return prompt + "," + name +def clear_keyword(): + return "" + def ui(gr, opts, txt2img_prompt, img2img_prompt): with gr.Blocks(analytics_enabled=False) as inspiration: flag = os.path.exists(opts.inspiration_dir) @@ -132,15 +132,15 @@ def ui(gr, opts, txt2img_prompt, img2img_prompt): gr.HTML("""

To activate inspiration function, you need get "inspiration" images first.


You can create these images by run "Create inspiration images" script in txt2img page,
you can get the artists or art styles list from here
- https://github.com/pharmapsychotic/clip-interrogator/tree/main/data
+ https://github.com/pharmapsychotic/clip-interrogator/tree/main/data
download these files, and select these files in the "Create inspiration images" script UI
There about 6000 artists and art styles in these files.
This takes server hours depending on your GPU type and how many pictures you generate for each artist/style
I suggest at least four images for each


You can also download generated pictures from here:


- https://huggingface.co/datasets/yfszzx/inspiration
+ https://huggingface.co/datasets/yfszzx/inspiration
unzip the file to the project directory of webui
and restart webui, and enjoy the joy of creation!
- """) + """) return inspiration if not os.path.exists(inspiration_system_path): os.mkdir(inspiration_system_path) @@ -148,35 +148,42 @@ def ui(gr, opts, txt2img_prompt, img2img_prompt): with gr.Column(scale=2): inspiration_gallery = gr.Gallery(show_label=False, elem_id="inspiration_gallery").style(grid=opts.inspiration_cols_num, height='auto') with gr.Column(scale=1): - print(types) types = gr.CheckboxGroup(choices=types, value=types) - keyword = gr.Textbox("", label="Key word") - with gr.Row(): - source = gr.Dropdown(choices=["All", "Favorites", "Exclude abandoned", "Abandoned"], value="Exclude abandoned", label="Source") - get_inspiration = gr.Button("Get inspiration", elem_id="inspiration_get_button") - name = gr.Textbox(show_label=False, interactive=False) with gr.Row(): + source = gr.Dropdown(choices=["All", "Favorites", "Exclude abandoned", "Abandoned"], value="Exclude abandoned", label="Source") + keyword = gr.Textbox("", label="Key word") + get_inspiration = gr.Button("Get inspiration", elem_id="inspiration_get_button") + name = gr.Textbox(show_label=False, interactive=False) + with gr.Row(): send_to_txt2img = gr.Button('to txt2img') send_to_img2img = gr.Button('to img2img') style_gallery = gr.Gallery(show_label=False).style(grid=2, height='auto') - collect = gr.Button('Collect') - give_up = gr.Button("Don't show again") - moveout = gr.Button("Move out", visible=False) warning = gr.HTML() + with gr.Row(): + collect = gr.Button('Collect') + give_up = gr.Button("Don't show again") + moveout = gr.Button("Move out", visible=False) + with gr.Row(visible=False): select_button = gr.Button('set button', elem_id="inspiration_select_button") name_list = gr.State() - get_inspiration.click(get_inspiration_images, inputs=[source, types, keyword], outputs=[inspiration_gallery, name_list, keyword]) - source.change(source_change, inputs=[source], outputs=[moveout, style_gallery]) - source.change(fn=None, _js="inspiration_click_get_button", inputs=None, outputs=None) + get_inspiration.click(get_inspiration_images, inputs=[source, types, keyword], outputs=[inspiration_gallery, name_list]) keyword.submit(fn=None, _js="inspiration_click_get_button", inputs=None, outputs=None) + source.change(source_change, inputs=[source], outputs=[moveout, style_gallery]) + source.change(fn=clear_keyword, _js="inspiration_click_get_button", inputs=None, outputs=[keyword]) + types.change(fn=clear_keyword, _js="inspiration_click_get_button", inputs=None, outputs=[keyword]) + select_button.click(select_click, _js="inspiration_selected", inputs=[name, name_list], outputs=[name, style_gallery, warning]) give_up.click(give_up_click, inputs=[name], outputs=[warning]) collect.click(collect_click, inputs=[name], outputs=[warning]) moveout.click(moveout_click, inputs=[name, source], outputs=[warning]) + moveout.click(fn=None, _js="inspiration_click_get_button", inputs=None, outputs=None) + send_to_txt2img.click(add_to_prompt, inputs=[name, txt2img_prompt], outputs=[txt2img_prompt]) send_to_img2img.click(add_to_prompt, inputs=[name, img2img_prompt], outputs=[img2img_prompt]) + send_to_txt2img.click(collect_click, inputs=[name], outputs=[warning]) + send_to_img2img.click(collect_click, inputs=[name], outputs=[warning]) send_to_txt2img.click(None, _js='switch_to_txt2img', inputs=None, outputs=None) send_to_img2img.click(None, _js="switch_to_img2img_img2img", inputs=None, outputs=None) return inspiration From d93ea5cdeb2fd3607b7265271ccab2c9bf4c1156 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sat, 22 Oct 2022 10:21:21 +0800 Subject: [PATCH 0143/1118] inspiration perfected --- modules/inspiration.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/inspiration.py b/modules/inspiration.py index 319183ab..94ff139a 100644 --- a/modules/inspiration.py +++ b/modules/inspiration.py @@ -73,8 +73,11 @@ def get_inspiration_images(source, types, keyword): image_list = [] for a in names: image_path = os.path.join(opts.inspiration_dir, a) - images = os.listdir(image_path) - image_list.append((os.path.join(image_path, random.choice(images)), a)) + images = os.listdir(image_path) + if len(images) > 0: + image_list.append((os.path.join(image_path, random.choice(images)), a)) + else: + print(image_path) return image_list, names def select_click(index, name_list): From 67b78f0ea6f196bfdca49932da062631bb40d0b1 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Sat, 22 Oct 2022 10:29:23 +0800 Subject: [PATCH 0144/1118] inspiration perfected --- modules/inspiration.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/inspiration.py b/modules/inspiration.py index 94ff139a..29cf8297 100644 --- a/modules/inspiration.py +++ b/modules/inspiration.py @@ -160,12 +160,13 @@ def ui(gr, opts, txt2img_prompt, img2img_prompt): with gr.Row(): send_to_txt2img = gr.Button('to txt2img') send_to_img2img = gr.Button('to img2img') - style_gallery = gr.Gallery(show_label=False).style(grid=2, height='auto') - warning = gr.HTML() - with gr.Row(): collect = gr.Button('Collect') give_up = gr.Button("Don't show again") - moveout = gr.Button("Move out", visible=False) + moveout = gr.Button("Move out", visible=False) + warning = gr.HTML() + style_gallery = gr.Gallery(show_label=False).style(grid=2, height='auto') + + with gr.Row(visible=False): select_button = gr.Button('set button', elem_id="inspiration_select_button") From 2b91251637078e04472c91a06a8d9c4db9c1dcf0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 12:23:45 +0300 Subject: [PATCH 0145/1118] removed aesthetic gradients as built-in added support for extensions --- .gitignore | 2 +- extensions/put extension here.txt | 0 modules/aesthetic_clip.py | 241 ------------------------------ modules/images_history.py | 2 +- modules/img2img.py | 5 +- modules/processing.py | 35 +++-- modules/script_callbacks.py | 42 ++++++ modules/scripts.py | 210 +++++++++++++++++++------- modules/sd_hijack.py | 1 - modules/sd_models.py | 7 +- modules/shared.py | 19 --- modules/txt2img.py | 5 +- modules/ui.py | 83 ++-------- webui.py | 7 +- 14 files changed, 249 insertions(+), 410 deletions(-) create mode 100644 extensions/put extension here.txt delete mode 100644 modules/aesthetic_clip.py create mode 100644 modules/script_callbacks.py diff --git a/.gitignore b/.gitignore index f9c3357c..2f1e08ed 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,4 @@ __pycache__ notification.mp3 /SwinIR /textual_inversion -.vscode \ No newline at end of file +.vscode diff --git a/extensions/put extension here.txt b/extensions/put extension here.txt new file mode 100644 index 00000000..e69de29b diff --git a/modules/aesthetic_clip.py b/modules/aesthetic_clip.py deleted file mode 100644 index 8c828541..00000000 --- a/modules/aesthetic_clip.py +++ /dev/null @@ -1,241 +0,0 @@ -import copy -import itertools -import os -from pathlib import Path -import html -import gc - -import gradio as gr -import torch -from PIL import Image -from torch import optim - -from modules import shared -from transformers import CLIPModel, CLIPProcessor, CLIPTokenizer -from tqdm.auto import tqdm, trange -from modules.shared import opts, device - - -def get_all_images_in_folder(folder): - return [os.path.join(folder, f) for f in os.listdir(folder) if - os.path.isfile(os.path.join(folder, f)) and check_is_valid_image_file(f)] - - -def check_is_valid_image_file(filename): - return filename.lower().endswith(('.png', '.jpg', '.jpeg', ".gif", ".tiff", ".webp")) - - -def batched(dataset, total, n=1): - for ndx in range(0, total, n): - yield [dataset.__getitem__(i) for i in range(ndx, min(ndx + n, total))] - - -def iter_to_batched(iterable, n=1): - it = iter(iterable) - while True: - chunk = tuple(itertools.islice(it, n)) - if not chunk: - return - yield chunk - - -def create_ui(): - import modules.ui - - with gr.Group(): - with gr.Accordion("Open for Clip Aesthetic!", open=False): - with gr.Row(): - aesthetic_weight = gr.Slider(minimum=0, maximum=1, step=0.01, label="Aesthetic weight", - value=0.9) - aesthetic_steps = gr.Slider(minimum=0, maximum=50, step=1, label="Aesthetic steps", value=5) - - with gr.Row(): - aesthetic_lr = gr.Textbox(label='Aesthetic learning rate', - placeholder="Aesthetic learning rate", value="0.0001") - aesthetic_slerp = gr.Checkbox(label="Slerp interpolation", value=False) - aesthetic_imgs = gr.Dropdown(sorted(shared.aesthetic_embeddings.keys()), - label="Aesthetic imgs embedding", - value="None") - - modules.ui.create_refresh_button(aesthetic_imgs, shared.update_aesthetic_embeddings, lambda: {"choices": sorted(shared.aesthetic_embeddings.keys())}, "refresh_aesthetic_embeddings") - - with gr.Row(): - aesthetic_imgs_text = gr.Textbox(label='Aesthetic text for imgs', - placeholder="This text is used to rotate the feature space of the imgs embs", - value="") - aesthetic_slerp_angle = gr.Slider(label='Slerp angle', minimum=0, maximum=1, step=0.01, - value=0.1) - aesthetic_text_negative = gr.Checkbox(label="Is negative text", value=False) - - return aesthetic_weight, aesthetic_steps, aesthetic_lr, aesthetic_slerp, aesthetic_imgs, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative - - -aesthetic_clip_model = None - - -def aesthetic_clip(): - global aesthetic_clip_model - - if aesthetic_clip_model is None or aesthetic_clip_model.name_or_path != shared.sd_model.cond_stage_model.wrapped.transformer.name_or_path: - aesthetic_clip_model = CLIPModel.from_pretrained(shared.sd_model.cond_stage_model.wrapped.transformer.name_or_path) - aesthetic_clip_model.cpu() - - return aesthetic_clip_model - - -def generate_imgs_embd(name, folder, batch_size): - model = aesthetic_clip().to(device) - processor = CLIPProcessor.from_pretrained(model.name_or_path) - - with torch.no_grad(): - embs = [] - for paths in tqdm(iter_to_batched(get_all_images_in_folder(folder), batch_size), - desc=f"Generating embeddings for {name}"): - if shared.state.interrupted: - break - inputs = processor(images=[Image.open(path) for path in paths], return_tensors="pt").to(device) - outputs = model.get_image_features(**inputs).cpu() - embs.append(torch.clone(outputs)) - inputs.to("cpu") - del inputs, outputs - - embs = torch.cat(embs, dim=0).mean(dim=0, keepdim=True) - - # The generated embedding will be located here - path = str(Path(shared.cmd_opts.aesthetic_embeddings_dir) / f"{name}.pt") - torch.save(embs, path) - - model.cpu() - del processor - del embs - gc.collect() - torch.cuda.empty_cache() - res = f""" - Done generating embedding for {name}! - Aesthetic embedding saved to {html.escape(path)} - """ - shared.update_aesthetic_embeddings() - return gr.Dropdown.update(choices=sorted(shared.aesthetic_embeddings.keys()), label="Imgs embedding", - value="None"), \ - gr.Dropdown.update(choices=sorted(shared.aesthetic_embeddings.keys()), - label="Imgs embedding", - value="None"), res, "" - - -def slerp(low, high, val): - low_norm = low / torch.norm(low, dim=1, keepdim=True) - high_norm = high / torch.norm(high, dim=1, keepdim=True) - omega = torch.acos((low_norm * high_norm).sum(1)) - so = torch.sin(omega) - res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze(1) * high - return res - - -class AestheticCLIP: - def __init__(self): - self.skip = False - self.aesthetic_steps = 0 - self.aesthetic_weight = 0 - self.aesthetic_lr = 0 - self.slerp = False - self.aesthetic_text_negative = "" - self.aesthetic_slerp_angle = 0 - self.aesthetic_imgs_text = "" - - self.image_embs_name = None - self.image_embs = None - self.load_image_embs(None) - - def set_aesthetic_params(self, p, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, image_embs_name=None, - aesthetic_slerp=True, aesthetic_imgs_text="", - aesthetic_slerp_angle=0.15, - aesthetic_text_negative=False): - self.aesthetic_imgs_text = aesthetic_imgs_text - self.aesthetic_slerp_angle = aesthetic_slerp_angle - self.aesthetic_text_negative = aesthetic_text_negative - self.slerp = aesthetic_slerp - self.aesthetic_lr = aesthetic_lr - self.aesthetic_weight = aesthetic_weight - self.aesthetic_steps = aesthetic_steps - self.load_image_embs(image_embs_name) - - if self.image_embs_name is not None: - p.extra_generation_params.update({ - "Aesthetic LR": aesthetic_lr, - "Aesthetic weight": aesthetic_weight, - "Aesthetic steps": aesthetic_steps, - "Aesthetic embedding": self.image_embs_name, - "Aesthetic slerp": aesthetic_slerp, - "Aesthetic text": aesthetic_imgs_text, - "Aesthetic text negative": aesthetic_text_negative, - "Aesthetic slerp angle": aesthetic_slerp_angle, - }) - - def set_skip(self, skip): - self.skip = skip - - def load_image_embs(self, image_embs_name): - if image_embs_name is None or len(image_embs_name) == 0 or image_embs_name == "None": - image_embs_name = None - self.image_embs_name = None - if image_embs_name is not None and self.image_embs_name != image_embs_name: - self.image_embs_name = image_embs_name - self.image_embs = torch.load(shared.aesthetic_embeddings[self.image_embs_name], map_location=device) - self.image_embs /= self.image_embs.norm(dim=-1, keepdim=True) - self.image_embs.requires_grad_(False) - - def __call__(self, z, remade_batch_tokens): - if not self.skip and self.aesthetic_steps != 0 and self.aesthetic_lr != 0 and self.aesthetic_weight != 0 and self.image_embs_name is not None: - tokenizer = shared.sd_model.cond_stage_model.tokenizer - if not opts.use_old_emphasis_implementation: - remade_batch_tokens = [ - [tokenizer.bos_token_id] + x[:75] + [tokenizer.eos_token_id] for x in - remade_batch_tokens] - - tokens = torch.asarray(remade_batch_tokens).to(device) - - model = copy.deepcopy(aesthetic_clip()).to(device) - model.requires_grad_(True) - if self.aesthetic_imgs_text is not None and len(self.aesthetic_imgs_text) > 0: - text_embs_2 = model.get_text_features( - **tokenizer([self.aesthetic_imgs_text], padding=True, return_tensors="pt").to(device)) - if self.aesthetic_text_negative: - text_embs_2 = self.image_embs - text_embs_2 - text_embs_2 /= text_embs_2.norm(dim=-1, keepdim=True) - img_embs = slerp(self.image_embs, text_embs_2, self.aesthetic_slerp_angle) - else: - img_embs = self.image_embs - - with torch.enable_grad(): - - # We optimize the model to maximize the similarity - optimizer = optim.Adam( - model.text_model.parameters(), lr=self.aesthetic_lr - ) - - for _ in trange(self.aesthetic_steps, desc="Aesthetic optimization"): - text_embs = model.get_text_features(input_ids=tokens) - text_embs = text_embs / text_embs.norm(dim=-1, keepdim=True) - sim = text_embs @ img_embs.T - loss = -sim - optimizer.zero_grad() - loss.mean().backward() - optimizer.step() - - zn = model.text_model(input_ids=tokens, output_hidden_states=-opts.CLIP_stop_at_last_layers) - if opts.CLIP_stop_at_last_layers > 1: - zn = zn.hidden_states[-opts.CLIP_stop_at_last_layers] - zn = model.text_model.final_layer_norm(zn) - else: - zn = zn.last_hidden_state - model.cpu() - del model - gc.collect() - torch.cuda.empty_cache() - zn = torch.concat([zn[77 * i:77 * (i + 1)] for i in range(max(z.shape[1] // 77, 1))], 1) - if self.slerp: - z = slerp(z, zn, self.aesthetic_weight) - else: - z = z * (1 - self.aesthetic_weight) + zn * self.aesthetic_weight - - return z diff --git a/modules/images_history.py b/modules/images_history.py index 78fd0543..bc5cf11f 100644 --- a/modules/images_history.py +++ b/modules/images_history.py @@ -310,7 +310,7 @@ def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): forward = gr.Button('Prev batch') backward = gr.Button('Next batch') with gr.Column(scale=3): - load_info = gr.HTML(visible=not custom_dir) + load_info = gr.HTML(visible=not custom_dir) with gr.Row(visible=False) as warning: warning_box = gr.Textbox("Message", interactive=False) diff --git a/modules/img2img.py b/modules/img2img.py index eea5199b..8d9f7cf9 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -56,7 +56,7 @@ def process_batch(p, input_dir, output_dir, args): processed_image.save(os.path.join(output_dir, filename)) -def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_img_inpaint, init_mask_inpaint, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, aesthetic_imgs=None, aesthetic_slerp=False, aesthetic_imgs_text="", aesthetic_slerp_angle=0.15, aesthetic_text_negative=False, *args): +def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_img_inpaint, init_mask_inpaint, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, *args): is_inpaint = mode == 1 is_batch = mode == 2 @@ -109,7 +109,8 @@ def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, pro inpainting_mask_invert=inpainting_mask_invert, ) - shared.aesthetic_clip.set_aesthetic_params(p, float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative) + p.scripts = modules.scripts.scripts_txt2img + p.script_args = args if shared.cmd_opts.enable_console_prompts: print(f"\nimg2img: {prompt}", file=shared.progress_print_out) diff --git a/modules/processing.py b/modules/processing.py index ff1ec4c9..372489f7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -104,6 +104,12 @@ class StableDiffusionProcessing(): self.seed_resize_from_h = 0 self.seed_resize_from_w = 0 + self.scripts = None + self.script_args = None + self.all_prompts = None + self.all_seeds = None + self.all_subseeds = None + def init(self, all_prompts, all_seeds, all_subseeds): pass @@ -350,32 +356,35 @@ def process_images(p: StableDiffusionProcessing) -> Processed: shared.prompt_styles.apply_styles(p) if type(p.prompt) == list: - all_prompts = p.prompt + p.all_prompts = p.prompt else: - all_prompts = p.batch_size * p.n_iter * [p.prompt] + p.all_prompts = p.batch_size * p.n_iter * [p.prompt] if type(seed) == list: - all_seeds = seed + p.all_seeds = seed else: - all_seeds = [int(seed) + (x if p.subseed_strength == 0 else 0) for x in range(len(all_prompts))] + p.all_seeds = [int(seed) + (x if p.subseed_strength == 0 else 0) for x in range(len(p.all_prompts))] if type(subseed) == list: - all_subseeds = subseed + p.all_subseeds = subseed else: - all_subseeds = [int(subseed) + x for x in range(len(all_prompts))] + p.all_subseeds = [int(subseed) + x for x in range(len(p.all_prompts))] def infotext(iteration=0, position_in_batch=0): - return create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration, position_in_batch) + return create_infotext(p, p.all_prompts, p.all_seeds, p.all_subseeds, comments, iteration, position_in_batch) if os.path.exists(cmd_opts.embeddings_dir) and not p.do_not_reload_embeddings: model_hijack.embedding_db.load_textual_inversion_embeddings() + if p.scripts is not None: + p.scripts.run_alwayson_scripts(p) + infotexts = [] output_images = [] with torch.no_grad(), p.sd_model.ema_scope(): with devices.autocast(): - p.init(all_prompts, all_seeds, all_subseeds) + p.init(p.all_prompts, p.all_seeds, p.all_subseeds) if state.job_count == -1: state.job_count = p.n_iter @@ -387,9 +396,9 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if state.interrupted: break - prompts = all_prompts[n * p.batch_size:(n + 1) * p.batch_size] - seeds = all_seeds[n * p.batch_size:(n + 1) * p.batch_size] - subseeds = all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] + prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size] + seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] + subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] if (len(prompts) == 0): break @@ -490,10 +499,10 @@ def process_images(p: StableDiffusionProcessing) -> Processed: index_of_first_image = 1 if opts.grid_save: - images.save_image(grid, p.outpath_grids, "grid", all_seeds[0], all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) + images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) devices.torch_gc() - return Processed(p, output_images, all_seeds[0], infotext() + "".join(["\n\n" + x for x in comments]), subseed=all_subseeds[0], all_prompts=all_prompts, all_seeds=all_seeds, all_subseeds=all_subseeds, index_of_first_image=index_of_first_image, infotexts=infotexts) + return Processed(p, output_images, p.all_seeds[0], infotext() + "".join(["\n\n" + x for x in comments]), subseed=p.all_subseeds[0], all_prompts=p.all_prompts, all_seeds=p.all_seeds, all_subseeds=p.all_subseeds, index_of_first_image=index_of_first_image, infotexts=infotexts) class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py new file mode 100644 index 00000000..866b7acd --- /dev/null +++ b/modules/script_callbacks.py @@ -0,0 +1,42 @@ + +callbacks_model_loaded = [] +callbacks_ui_tabs = [] + + +def clear_callbacks(): + callbacks_model_loaded.clear() + callbacks_ui_tabs.clear() + + +def model_loaded_callback(sd_model): + for callback in callbacks_model_loaded: + callback(sd_model) + + +def ui_tabs_callback(): + res = [] + + for callback in callbacks_ui_tabs: + res += callback() or [] + + return res + + +def on_model_loaded(callback): + """register a function to be called when the stable diffusion model is created; the model is + passed as an argument""" + callbacks_model_loaded.append(callback) + + +def on_ui_tabs(callback): + """register a function to be called when the UI is creating new tabs. + The function must either return a None, which means no new tabs to be added, or a list, where + each element is a tuple: + (gradio_component, title, elem_id) + + gradio_component is a gradio component to be used for contents of the tab (usually gr.Blocks) + title is tab text displayed to user in the UI + elem_id is HTML id for the tab + """ + callbacks_ui_tabs.append(callback) + diff --git a/modules/scripts.py b/modules/scripts.py index 1039fa9c..65f25f49 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -1,86 +1,153 @@ import os import sys import traceback +from collections import namedtuple import modules.ui as ui import gradio as gr from modules.processing import StableDiffusionProcessing -from modules import shared +from modules import shared, paths, script_callbacks + +AlwaysVisible = object() + class Script: filename = None args_from = None args_to = None + alwayson = False + + infotext_fields = None + """if set in ui(), this is a list of pairs of gradio component + text; the text will be used when + parsing infotext to set the value for the component; see ui.py's txt2img_paste_fields for an example + """ - # The title of the script. This is what will be displayed in the dropdown menu. def title(self): + """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" + raise NotImplementedError() - # How the script is displayed in the UI. See https://gradio.app/docs/#components - # for the different UI components you can use and how to create them. - # Most UI components can return a value, such as a boolean for a checkbox. - # The returned values are passed to the run method as parameters. def ui(self, is_img2img): + """this function should create gradio UI elements. See https://gradio.app/docs/#components + The return value should be an array of all components that are used in processing. + Values of those returned componenbts will be passed to run() and process() functions. + """ + pass - # Determines when the script should be shown in the dropdown menu via the - # returned value. As an example: - # is_img2img is True if the current tab is img2img, and False if it is txt2img. - # Thus, return is_img2img to only show the script on the img2img tab. def show(self, is_img2img): + """ + is_img2img is True if this function is called for the img2img interface, and Fasle otherwise + + This function should return: + - False if the script should not be shown in UI at all + - True if the script should be shown in UI if it's scelected in the scripts drowpdown + - script.AlwaysVisible if the script should be shown in UI at all times + """ + return True - # This is where the additional processing is implemented. The parameters include - # self, the model object "p" (a StableDiffusionProcessing class, see - # processing.py), and the parameters returned by the ui method. - # Custom functions can be defined here, and additional libraries can be imported - # to be used in processing. The return value should be a Processed object, which is - # what is returned by the process_images method. - def run(self, *args): + def run(self, p, *args): + """ + This function is called if the script has been selected in the script dropdown. + It must do all processing and return the Processed object with results, same as + one returned by processing.process_images. + + Usually the processing is done by calling the processing.process_images function. + + args contains all values returned by components from ui() + """ + raise NotImplementedError() - # The description method is currently unused. - # To add a description that appears when hovering over the title, amend the "titles" - # dict in script.js to include the script title (returned by title) as a key, and - # your description as the value. + def process(self, p, *args): + """ + This function is called before processing begins for AlwaysVisible scripts. + scripts. You can modify the processing object (p) here, inject hooks, etc. + """ + + pass + def describe(self): + """unused""" return "" +current_basedir = paths.script_path + + +def basedir(): + """returns the base directory for the current script. For scripts in the main scripts directory, + this is the main directory (where webui.py resides), and for scripts in extensions directory + (ie extensions/aesthetic/script/aesthetic.py), this is extension's directory (extensions/aesthetic) + """ + return current_basedir + + scripts_data = [] +ScriptFile = namedtuple("ScriptFile", ["basedir", "filename", "path"]) +ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir"]) -def load_scripts(basedir): - if not os.path.exists(basedir): - return +def list_scripts(scriptdirname, extension): + scripts_list = [] - for filename in sorted(os.listdir(basedir)): - path = os.path.join(basedir, filename) + basedir = os.path.join(paths.script_path, scriptdirname) + if os.path.exists(basedir): + for filename in sorted(os.listdir(basedir)): + scripts_list.append(ScriptFile(paths.script_path, filename, os.path.join(basedir, filename))) - if os.path.splitext(path)[1].lower() != '.py': - continue + extdir = os.path.join(paths.script_path, "extensions") + if os.path.exists(extdir): + for dirname in sorted(os.listdir(extdir)): + dirpath = os.path.join(extdir, dirname) + if not os.path.isdir(dirpath): + continue - if not os.path.isfile(path): - continue + for filename in sorted(os.listdir(os.path.join(dirpath, scriptdirname))): + scripts_list.append(ScriptFile(dirpath, filename, os.path.join(dirpath, scriptdirname, filename))) + scripts_list = [x for x in scripts_list if os.path.splitext(x.path)[1].lower() == extension and os.path.isfile(x.path)] + + return scripts_list + + +def load_scripts(): + global current_basedir + scripts_data.clear() + script_callbacks.clear_callbacks() + + scripts_list = list_scripts("scripts", ".py") + + syspath = sys.path + + for scriptfile in sorted(scripts_list): try: - with open(path, "r", encoding="utf8") as file: + if scriptfile.basedir != paths.script_path: + sys.path = [scriptfile.basedir] + sys.path + current_basedir = scriptfile.basedir + + with open(scriptfile.path, "r", encoding="utf8") as file: text = file.read() from types import ModuleType - compiled = compile(text, path, 'exec') - module = ModuleType(filename) + compiled = compile(text, scriptfile.path, 'exec') + module = ModuleType(scriptfile.filename) exec(compiled, module.__dict__) for key, script_class in module.__dict__.items(): if type(script_class) == type and issubclass(script_class, Script): - scripts_data.append((script_class, path)) + scripts_data.append(ScriptClassData(script_class, scriptfile.path, scriptfile.basedir)) except Exception: - print(f"Error loading script: {filename}", file=sys.stderr) + print(f"Error loading script: {scriptfile.filename}", file=sys.stderr) print(traceback.format_exc(), file=sys.stderr) + finally: + sys.path = syspath + current_basedir = paths.script_path + def wrap_call(func, filename, funcname, *args, default=None, **kwargs): try: @@ -96,56 +163,80 @@ def wrap_call(func, filename, funcname, *args, default=None, **kwargs): class ScriptRunner: def __init__(self): self.scripts = [] + self.selectable_scripts = [] + self.alwayson_scripts = [] self.titles = [] + self.infotext_fields = [] def setup_ui(self, is_img2img): - for script_class, path in scripts_data: + for script_class, path, basedir in scripts_data: script = script_class() script.filename = path - if not script.show(is_img2img): - continue + visibility = script.show(is_img2img) - self.scripts.append(script) + if visibility == AlwaysVisible: + self.scripts.append(script) + self.alwayson_scripts.append(script) + script.alwayson = True - self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.scripts] + elif visibility: + self.scripts.append(script) + self.selectable_scripts.append(script) - dropdown = gr.Dropdown(label="Script", choices=["None"] + self.titles, value="None", type="index") - dropdown.save_to_config = True - inputs = [dropdown] + self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts] - for script in self.scripts: + inputs = [None] + inputs_alwayson = [True] + + def create_script_ui(script, inputs, inputs_alwayson): script.args_from = len(inputs) script.args_to = len(inputs) controls = wrap_call(script.ui, script.filename, "ui", is_img2img) if controls is None: - continue + return for control in controls: control.custom_script_source = os.path.basename(script.filename) - control.visible = False + if not script.alwayson: + control.visible = False + + if script.infotext_fields is not None: + self.infotext_fields += script.infotext_fields inputs += controls + inputs_alwayson += [script.alwayson for _ in controls] script.args_to = len(inputs) + for script in self.alwayson_scripts: + with gr.Group(): + create_script_ui(script, inputs, inputs_alwayson) + + dropdown = gr.Dropdown(label="Script", choices=["None"] + self.titles, value="None", type="index") + dropdown.save_to_config = True + inputs[0] = dropdown + + for script in self.selectable_scripts: + create_script_ui(script, inputs, inputs_alwayson) + def select_script(script_index): - if 0 < script_index <= len(self.scripts): - script = self.scripts[script_index-1] + if 0 < script_index <= len(self.selectable_scripts): + script = self.selectable_scripts[script_index-1] args_from = script.args_from args_to = script.args_to else: args_from = 0 args_to = 0 - return [ui.gr_show(True if i == 0 else args_from <= i < args_to) for i in range(len(inputs))] + return [ui.gr_show(True if i == 0 else args_from <= i < args_to or is_alwayson) for i, is_alwayson in enumerate(inputs_alwayson)] def init_field(title): if title == 'None': return script_index = self.titles.index(title) - script = self.scripts[script_index] + script = self.selectable_scripts[script_index] for i in range(script.args_from, script.args_to): inputs[i].visible = True @@ -164,7 +255,7 @@ class ScriptRunner: if script_index == 0: return None - script = self.scripts[script_index-1] + script = self.selectable_scripts[script_index-1] if script is None: return None @@ -176,6 +267,15 @@ class ScriptRunner: return processed + def run_alwayson_scripts(self, p): + for script in self.alwayson_scripts: + try: + script_args = p.script_args[script.args_from:script.args_to] + script.process(p, *script_args) + except Exception: + print(f"Error running alwayson script: {script.filename}", file=sys.stderr) + print(traceback.format_exc(), file=sys.stderr) + def reload_sources(self): for si, script in list(enumerate(self.scripts)): with open(script.filename, "r", encoding="utf8") as file: @@ -197,19 +297,21 @@ class ScriptRunner: self.scripts[si].args_from = args_from self.scripts[si].args_to = args_to + scripts_txt2img = ScriptRunner() scripts_img2img = ScriptRunner() + def reload_script_body_only(): scripts_txt2img.reload_sources() scripts_img2img.reload_sources() -def reload_scripts(basedir): +def reload_scripts(): global scripts_txt2img, scripts_img2img - scripts_data.clear() - load_scripts(basedir) + load_scripts() scripts_txt2img = ScriptRunner() scripts_img2img = ScriptRunner() + diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 1f8587d1..0f10828e 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -332,7 +332,6 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module): multipliers.append([1.0] * 75) z1 = self.process_tokens(tokens, multipliers) - z1 = shared.aesthetic_clip(z1, remade_batch_tokens) z = z1 if z is None else torch.cat((z, z1), axis=-2) remade_batch_tokens = rem_tokens diff --git a/modules/sd_models.py b/modules/sd_models.py index d99dbce8..f9b3063d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -7,7 +7,7 @@ from omegaconf import OmegaConf from ldm.util import instantiate_from_config -from modules import shared, modelloader, devices +from modules import shared, modelloader, devices, script_callbacks from modules.paths import models_path from modules.sd_hijack_inpainting import do_inpainting_hijack, should_hijack_inpainting @@ -238,6 +238,9 @@ def load_model(checkpoint_info=None): sd_hijack.model_hijack.hijack(sd_model) sd_model.eval() + shared.sd_model = sd_model + + script_callbacks.model_loaded_callback(sd_model) print(f"Model loaded.") return sd_model @@ -252,7 +255,7 @@ def reload_model_weights(sd_model, info=None): if sd_model.sd_checkpoint_info.config != checkpoint_info.config or should_hijack_inpainting(checkpoint_info) != should_hijack_inpainting(sd_model.sd_checkpoint_info): checkpoints_loaded.clear() - shared.sd_model = load_model(checkpoint_info) + load_model(checkpoint_info) return shared.sd_model if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: diff --git a/modules/shared.py b/modules/shared.py index 0dbe360d..7d786f07 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -31,7 +31,6 @@ parser.add_argument("--no-half-vae", action='store_true', help="do not switch th 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 acceleration in browser)") parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI") parser.add_argument("--embeddings-dir", type=str, default=os.path.join(script_path, 'embeddings'), help="embeddings directory for textual inversion (default: embeddings)") -parser.add_argument("--aesthetic_embeddings-dir", type=str, default=os.path.join(models_path, 'aesthetic_embeddings'), help="aesthetic_embeddings directory(default: aesthetic_embeddings)") parser.add_argument("--hypernetwork-dir", type=str, default=os.path.join(models_path, 'hypernetworks'), help="hypernetwork directory") parser.add_argument("--localizations-dir", type=str, default=os.path.join(script_path, 'localizations'), help="localizations directory") parser.add_argument("--allow-code", action='store_true', help="allow custom script execution from webui") @@ -109,21 +108,6 @@ os.makedirs(cmd_opts.hypernetwork_dir, exist_ok=True) hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir) loaded_hypernetwork = None - -os.makedirs(cmd_opts.aesthetic_embeddings_dir, exist_ok=True) -aesthetic_embeddings = {} - - -def update_aesthetic_embeddings(): - global aesthetic_embeddings - aesthetic_embeddings = {f.replace(".pt", ""): os.path.join(cmd_opts.aesthetic_embeddings_dir, f) for f in - os.listdir(cmd_opts.aesthetic_embeddings_dir) if f.endswith(".pt")} - aesthetic_embeddings = OrderedDict(**{"None": None}, **aesthetic_embeddings) - - -update_aesthetic_embeddings() - - def reload_hypernetworks(): global hypernetworks @@ -415,9 +399,6 @@ sd_model = None clip_model = None -from modules.aesthetic_clip import AestheticCLIP -aesthetic_clip = AestheticCLIP() - progress_print_out = sys.stdout diff --git a/modules/txt2img.py b/modules/txt2img.py index 1761cfa2..c9d5a090 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -7,7 +7,7 @@ import modules.processing as processing from modules.ui import plaintext_to_html -def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, firstphase_width: int, firstphase_height: int, aesthetic_lr=0, aesthetic_weight=0, aesthetic_steps=0, aesthetic_imgs=None, aesthetic_slerp=False, aesthetic_imgs_text="", aesthetic_slerp_angle=0.15, aesthetic_text_negative=False, *args): +def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, firstphase_width: int, firstphase_height: int, *args): p = StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, @@ -36,7 +36,8 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: firstphase_height=firstphase_height if enable_hr else None, ) - shared.aesthetic_clip.set_aesthetic_params(p, float(aesthetic_lr), float(aesthetic_weight), int(aesthetic_steps), aesthetic_imgs, aesthetic_slerp, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative) + p.scripts = modules.scripts.scripts_txt2img + p.script_args = args if cmd_opts.enable_console_prompts: print(f"\ntxt2img: {prompt}", file=shared.progress_print_out) diff --git a/modules/ui.py b/modules/ui.py index 70a9cf10..c977482c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -23,10 +23,10 @@ import gradio as gr import gradio.utils import gradio.routes -from modules import sd_hijack, sd_models, localization +from modules import sd_hijack, sd_models, localization, script_callbacks from modules.paths import script_path -from modules.shared import opts, cmd_opts, restricted_opts, aesthetic_embeddings +from modules.shared import opts, cmd_opts, restricted_opts if cmd_opts.deepdanbooru: from modules.deepbooru import get_deepbooru_tags @@ -44,7 +44,6 @@ from modules.images import save_image import modules.textual_inversion.ui import modules.hypernetworks.ui -import modules.aesthetic_clip as aesthetic_clip import modules.images_history as img_his @@ -662,8 +661,6 @@ def create_ui(wrap_gradio_gpu_call): seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs() - aesthetic_weight, aesthetic_steps, aesthetic_lr, aesthetic_slerp, aesthetic_imgs, aesthetic_imgs_text, aesthetic_slerp_angle, aesthetic_text_negative = aesthetic_clip.create_ui() - with gr.Group(): custom_inputs = modules.scripts.scripts_txt2img.setup_ui(is_img2img=False) @@ -718,14 +715,6 @@ def create_ui(wrap_gradio_gpu_call): denoising_strength, firstphase_width, firstphase_height, - aesthetic_lr, - aesthetic_weight, - aesthetic_steps, - aesthetic_imgs, - aesthetic_slerp, - aesthetic_imgs_text, - aesthetic_slerp_angle, - aesthetic_text_negative ] + custom_inputs, outputs=[ @@ -804,14 +793,7 @@ def create_ui(wrap_gradio_gpu_call): (hr_options, lambda d: gr.Row.update(visible="Denoising strength" in d)), (firstphase_width, "First pass size-1"), (firstphase_height, "First pass size-2"), - (aesthetic_lr, "Aesthetic LR"), - (aesthetic_weight, "Aesthetic weight"), - (aesthetic_steps, "Aesthetic steps"), - (aesthetic_imgs, "Aesthetic embedding"), - (aesthetic_slerp, "Aesthetic slerp"), - (aesthetic_imgs_text, "Aesthetic text"), - (aesthetic_text_negative, "Aesthetic text negative"), - (aesthetic_slerp_angle, "Aesthetic slerp angle"), + *modules.scripts.scripts_txt2img.infotext_fields ] txt2img_preview_params = [ @@ -896,8 +878,6 @@ def create_ui(wrap_gradio_gpu_call): seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs() - aesthetic_weight_im, aesthetic_steps_im, aesthetic_lr_im, aesthetic_slerp_im, aesthetic_imgs_im, aesthetic_imgs_text_im, aesthetic_slerp_angle_im, aesthetic_text_negative_im = aesthetic_clip.create_ui() - with gr.Group(): custom_inputs = modules.scripts.scripts_img2img.setup_ui(is_img2img=True) @@ -988,14 +968,6 @@ def create_ui(wrap_gradio_gpu_call): inpainting_mask_invert, img2img_batch_input_dir, img2img_batch_output_dir, - aesthetic_lr_im, - aesthetic_weight_im, - aesthetic_steps_im, - aesthetic_imgs_im, - aesthetic_slerp_im, - aesthetic_imgs_text_im, - aesthetic_slerp_angle_im, - aesthetic_text_negative_im, ] + custom_inputs, outputs=[ img2img_gallery, @@ -1087,14 +1059,7 @@ def create_ui(wrap_gradio_gpu_call): (seed_resize_from_w, "Seed resize from-1"), (seed_resize_from_h, "Seed resize from-2"), (denoising_strength, "Denoising strength"), - (aesthetic_lr_im, "Aesthetic LR"), - (aesthetic_weight_im, "Aesthetic weight"), - (aesthetic_steps_im, "Aesthetic steps"), - (aesthetic_imgs_im, "Aesthetic embedding"), - (aesthetic_slerp_im, "Aesthetic slerp"), - (aesthetic_imgs_text_im, "Aesthetic text"), - (aesthetic_text_negative_im, "Aesthetic text negative"), - (aesthetic_slerp_angle_im, "Aesthetic slerp angle"), + *modules.scripts.scripts_img2img.infotext_fields ] token_button.click(fn=update_token_counter, inputs=[img2img_prompt, steps], outputs=[token_counter]) @@ -1217,9 +1182,9 @@ def create_ui(wrap_gradio_gpu_call): ) #images history images_history_switch_dict = { - "fn":modules.generation_parameters_copypaste.connect_paste, - "t2i":txt2img_paste_fields, - "i2i":img2img_paste_fields + "fn": modules.generation_parameters_copypaste.connect_paste, + "t2i": txt2img_paste_fields, + "i2i": img2img_paste_fields } images_history = img_his.create_history_tabs(gr, opts, cmd_opts, wrap_gradio_call(modules.extras.run_pnginfo), images_history_switch_dict) @@ -1264,18 +1229,6 @@ def create_ui(wrap_gradio_gpu_call): with gr.Column(): create_embedding = gr.Button(value="Create embedding", variant='primary') - with gr.Tab(label="Create aesthetic images embedding"): - - new_embedding_name_ae = gr.Textbox(label="Name") - process_src_ae = gr.Textbox(label='Source directory') - batch_ae = gr.Slider(minimum=1, maximum=1024, step=1, label="Batch size", value=256) - with gr.Row(): - with gr.Column(scale=3): - gr.HTML(value="") - - with gr.Column(): - create_embedding_ae = gr.Button(value="Create images embedding", variant='primary') - with gr.Tab(label="Create hypernetwork"): new_hypernetwork_name = gr.Textbox(label="Name") new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "320", "640", "1280"]) @@ -1375,21 +1328,6 @@ def create_ui(wrap_gradio_gpu_call): ] ) - create_embedding_ae.click( - fn=aesthetic_clip.generate_imgs_embd, - inputs=[ - new_embedding_name_ae, - process_src_ae, - batch_ae - ], - outputs=[ - aesthetic_imgs, - aesthetic_imgs_im, - ti_output, - ti_outcome, - ] - ) - create_hypernetwork.click( fn=modules.hypernetworks.ui.create_hypernetwork, inputs=[ @@ -1580,10 +1518,10 @@ Requested path was: {f} if not opts.same_type(value, opts.data_labels[key].default): return gr.update(visible=True), opts.dumpjson() + oldval = opts.data.get(key, None) if cmd_opts.hide_ui_dir_config and key in restricted_opts: return gr.update(value=oldval), opts.dumpjson() - oldval = opts.data.get(key, None) opts.data[key] = value if oldval != value: @@ -1692,9 +1630,12 @@ Requested path was: {f} (images_history, "Image Browser", "images_history"), (modelmerger_interface, "Checkpoint Merger", "modelmerger"), (train_interface, "Train", "ti"), - (settings_interface, "Settings", "settings"), ] + interfaces += script_callbacks.ui_tabs_callback() + + interfaces += [(settings_interface, "Settings", "settings")] + with open(os.path.join(script_path, "style.css"), "r", encoding="utf8") as file: css = file.read() diff --git a/webui.py b/webui.py index 87589064..b1deca1b 100644 --- a/webui.py +++ b/webui.py @@ -71,6 +71,7 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs) + def initialize(): modelloader.cleanup_models() modules.sd_models.setup_model() @@ -79,9 +80,9 @@ def initialize(): shared.face_restorers.append(modules.face_restoration.FaceRestoration()) modelloader.load_upscalers() - modules.scripts.load_scripts(os.path.join(script_path, "scripts")) + modules.scripts.load_scripts() - shared.sd_model = modules.sd_models.load_model() + modules.sd_models.load_model() shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(shared.sd_model))) shared.opts.onchange("sd_hypernetwork", wrap_queued_call(lambda: modules.hypernetworks.hypernetwork.load_hypernetwork(shared.opts.sd_hypernetwork))) shared.opts.onchange("sd_hypernetwork_strength", modules.hypernetworks.hypernetwork.apply_strength) @@ -145,7 +146,7 @@ def webui(): sd_samplers.set_samplers() print('Reloading Custom Scripts') - modules.scripts.reload_scripts(os.path.join(script_path, "scripts")) + modules.scripts.reload_scripts() print('Reloading modules: modules.ui') importlib.reload(modules.ui) print('Refreshing Model List') From 6398dc9b1049f242576ca309f95a3fb1e654951c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 13:34:49 +0300 Subject: [PATCH 0146/1118] further support for extensions --- .gitignore | 1 + README.md | 3 +-- modules/scripts.py | 44 +++++++++++++++++++++++++++++++++++--------- modules/ui.py | 19 ++++++++++--------- style.css | 2 +- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 2f1e08ed..8fa05852 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ notification.mp3 /SwinIR /textual_inversion .vscode +/extensions diff --git a/README.md b/README.md index 5b5dc8ba..6853aea0 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,7 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - Estimated completion time in progress bar - API - Support for dedicated [inpainting model](https://github.com/runwayml/stable-diffusion#inpainting-with-stable-diffusion) by RunwayML. -- Aesthetic Gradients, a way to generate images with a specific aesthetic by using clip images embds (implementation of [https://github.com/vicgalle/stable-diffusion-aesthetic-gradients](https://github.com/vicgalle/stable-diffusion-aesthetic-gradients)) - +- via extension: [Aesthetic Gradients](https://github.com/AUTOMATIC1111/stable-diffusion-webui-aesthetic-gradients), a way to generate images with a specific aesthetic by using clip images embds (implementation of [https://github.com/vicgalle/stable-diffusion-aesthetic-gradients](https://github.com/vicgalle/stable-diffusion-aesthetic-gradients)) ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. diff --git a/modules/scripts.py b/modules/scripts.py index 65f25f49..9323af3e 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -102,17 +102,39 @@ def list_scripts(scriptdirname, extension): if os.path.exists(extdir): for dirname in sorted(os.listdir(extdir)): dirpath = os.path.join(extdir, dirname) - if not os.path.isdir(dirpath): + scriptdirpath = os.path.join(dirpath, scriptdirname) + + if not os.path.isdir(scriptdirpath): continue - for filename in sorted(os.listdir(os.path.join(dirpath, scriptdirname))): - scripts_list.append(ScriptFile(dirpath, filename, os.path.join(dirpath, scriptdirname, filename))) + for filename in sorted(os.listdir(scriptdirpath)): + scripts_list.append(ScriptFile(dirpath, filename, os.path.join(scriptdirpath, filename))) scripts_list = [x for x in scripts_list if os.path.splitext(x.path)[1].lower() == extension and os.path.isfile(x.path)] return scripts_list +def list_files_with_name(filename): + res = [] + + dirs = [paths.script_path] + + extdir = os.path.join(paths.script_path, "extensions") + if os.path.exists(extdir): + dirs += [os.path.join(extdir, d) for d in sorted(os.listdir(extdir))] + + for dirpath in dirs: + if not os.path.isdir(dirpath): + continue + + path = os.path.join(dirpath, filename) + if os.path.isfile(filename): + res.append(path) + + return res + + def load_scripts(): global current_basedir scripts_data.clear() @@ -276,7 +298,7 @@ class ScriptRunner: print(f"Error running alwayson script: {script.filename}", file=sys.stderr) print(traceback.format_exc(), file=sys.stderr) - def reload_sources(self): + def reload_sources(self, cache): for si, script in list(enumerate(self.scripts)): with open(script.filename, "r", encoding="utf8") as file: args_from = script.args_from @@ -286,9 +308,12 @@ class ScriptRunner: from types import ModuleType - compiled = compile(text, filename, 'exec') - module = ModuleType(script.filename) - exec(compiled, module.__dict__) + module = cache.get(filename, None) + if module is None: + compiled = compile(text, filename, 'exec') + module = ModuleType(script.filename) + exec(compiled, module.__dict__) + cache[filename] = module for key, script_class in module.__dict__.items(): if type(script_class) == type and issubclass(script_class, Script): @@ -303,8 +328,9 @@ scripts_img2img = ScriptRunner() def reload_script_body_only(): - scripts_txt2img.reload_sources() - scripts_img2img.reload_sources() + cache = {} + scripts_txt2img.reload_sources(cache) + scripts_img2img.reload_sources(cache) def reload_scripts(): diff --git a/modules/ui.py b/modules/ui.py index c977482c..29986124 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1636,13 +1636,15 @@ Requested path was: {f} interfaces += [(settings_interface, "Settings", "settings")] - with open(os.path.join(script_path, "style.css"), "r", encoding="utf8") as file: - css = file.read() + css = "" + + for cssfile in modules.scripts.list_files_with_name("style.css"): + with open(cssfile, "r", encoding="utf8") as file: + css += file.read() + "\n" if os.path.exists(os.path.join(script_path, "user.css")): with open(os.path.join(script_path, "user.css"), "r", encoding="utf8") as file: - usercss = file.read() - css += usercss + css += file.read() + "\n" if not cmd_opts.no_progressbar_hiding: css += css_hide_progressbar @@ -1865,9 +1867,9 @@ def load_javascript(raw_response): with open(os.path.join(script_path, "script.js"), "r", encoding="utf8") as jsfile: javascript = f'' - jsdir = os.path.join(script_path, "javascript") - for filename in sorted(os.listdir(jsdir)): - with open(os.path.join(jsdir, filename), "r", encoding="utf8") as jsfile: + scripts_list = modules.scripts.list_scripts("javascript", ".js") + for basedir, filename, path in scripts_list: + with open(path, "r", encoding="utf8") as jsfile: javascript += f"\n" if cmd_opts.theme is not None: @@ -1885,6 +1887,5 @@ def load_javascript(raw_response): gradio.routes.templates.TemplateResponse = template_response -reload_javascript = partial(load_javascript, - gradio.routes.templates.TemplateResponse) +reload_javascript = partial(load_javascript, gradio.routes.templates.TemplateResponse) reload_javascript() diff --git a/style.css b/style.css index 5d2bacc9..26ae36a5 100644 --- a/style.css +++ b/style.css @@ -477,7 +477,7 @@ input[type="range"]{ padding: 0; } -#refresh_sd_model_checkpoint, #refresh_sd_hypernetwork, #refresh_train_hypernetwork_name, #refresh_train_embedding_name, #refresh_localization, #refresh_aesthetic_embeddings{ +#refresh_sd_model_checkpoint, #refresh_sd_hypernetwork, #refresh_train_hypernetwork_name, #refresh_train_embedding_name, #refresh_localization{ max-width: 2.5em; min-width: 2.5em; height: 2.4em; From 5aa9525046b7520d39fe8fc8c5c6cc10ab4d5fdb Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 13:40:07 +0300 Subject: [PATCH 0147/1118] updated readme with info about Aesthetic Gradients --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 6853aea0..1a0e4f6a 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,16 @@ Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-web - Support for dedicated [inpainting model](https://github.com/runwayml/stable-diffusion#inpainting-with-stable-diffusion) by RunwayML. - via extension: [Aesthetic Gradients](https://github.com/AUTOMATIC1111/stable-diffusion-webui-aesthetic-gradients), a way to generate images with a specific aesthetic by using clip images embds (implementation of [https://github.com/vicgalle/stable-diffusion-aesthetic-gradients](https://github.com/vicgalle/stable-diffusion-aesthetic-gradients)) +## Where are Aesthetic Gradients?!?! +Aesthetic Gradients are now an extension. You can install it using git: + +```commandline +git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui-aesthetic-gradients extensions/aesthetic-gradients +``` + +After running this command, make sure that you have `aesthetic-gradients` dir in webui's `extensions` directory and restart +the UI. The interface for Aesthetic Gradients should appear exactly the same as it was. + ## Installation and Running Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. From 50b5504401e50b6c94eba41b37fe212b2f27b792 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 14:04:14 +0300 Subject: [PATCH 0148/1118] remove parsing command line from devices.py --- modules/devices.py | 14 +++++--------- modules/lowvram.py | 9 ++++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 8a159282..dc1f3cdd 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -15,14 +15,10 @@ def extract_device_id(args, name): def get_optimal_device(): if torch.cuda.is_available(): - # CUDA device selection support: - if "shared" not in sys.modules: - commandline_args = os.environ.get('COMMANDLINE_ARGS', "") #re-parse the commandline arguments because using the shared.py module creates an import loop. - sys.argv += shlex.split(commandline_args) - device_id = extract_device_id(sys.argv, '--device-id') - else: - device_id = shared.cmd_opts.device_id - + from modules import shared + + device_id = shared.cmd_opts.device_id + if device_id is not None: cuda_device = f"cuda:{device_id}" return torch.device(cuda_device) @@ -49,7 +45,7 @@ def enable_tf32(): errors.run(enable_tf32, "Enabling TF32") -device = device_interrogate = device_gfpgan = device_bsrgan = device_esrgan = device_scunet = device_codeformer = get_optimal_device() +device = device_interrogate = device_gfpgan = device_bsrgan = device_esrgan = device_scunet = device_codeformer = None dtype = torch.float16 dtype_vae = torch.float16 diff --git a/modules/lowvram.py b/modules/lowvram.py index 7eba1349..f327c3df 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -1,9 +1,8 @@ import torch -from modules.devices import get_optimal_device +from modules import devices module_in_gpu = None cpu = torch.device("cpu") -device = gpu = get_optimal_device() def send_everything_to_cpu(): @@ -33,7 +32,7 @@ def setup_for_low_vram(sd_model, use_medvram): if module_in_gpu is not None: module_in_gpu.to(cpu) - module.to(gpu) + module.to(devices.device) module_in_gpu = module # see below for register_forward_pre_hook; @@ -51,7 +50,7 @@ def setup_for_low_vram(sd_model, use_medvram): # send the model to GPU. Then put modules back. the modules will be in CPU. stored = sd_model.cond_stage_model.transformer, sd_model.first_stage_model, sd_model.model sd_model.cond_stage_model.transformer, sd_model.first_stage_model, sd_model.model = None, None, None - sd_model.to(device) + sd_model.to(devices.device) sd_model.cond_stage_model.transformer, sd_model.first_stage_model, sd_model.model = stored # register hooks for those the first two models @@ -70,7 +69,7 @@ def setup_for_low_vram(sd_model, use_medvram): # so that only one of them is in GPU at a time stored = diff_model.input_blocks, diff_model.middle_block, diff_model.output_blocks, diff_model.time_embed diff_model.input_blocks, diff_model.middle_block, diff_model.output_blocks, diff_model.time_embed = None, None, None, None - sd_model.model.to(device) + sd_model.model.to(devices.device) diff_model.input_blocks, diff_model.middle_block, diff_model.output_blocks, diff_model.time_embed = stored # install hooks for bits of third model From 0e8ca8e7af05be22d7d2c07a47c3c7febe0f0ab6 Mon Sep 17 00:00:00 2001 From: discus0434 Date: Sat, 22 Oct 2022 11:07:00 +0000 Subject: [PATCH 0149/1118] add dropout --- modules/hypernetworks/hypernetwork.py | 70 ++++++++++++++++----------- modules/hypernetworks/ui.py | 10 ++-- modules/ui.py | 45 +++++++++-------- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 905cbeef..e493f366 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -1,47 +1,60 @@ +import csv import datetime import glob import html import os import sys import traceback -import tqdm -import csv -import torch - -from ldm.util import default -from modules import devices, shared, processing, sd_models -import torch -from torch import einsum -from einops import rearrange, repeat import modules.textual_inversion.dataset +import torch +import tqdm +from einops import rearrange, repeat +from ldm.util import default +from modules import devices, processing, sd_models, shared from modules.textual_inversion import textual_inversion from modules.textual_inversion.learn_schedule import LearnRateScheduler +from torch import einsum class HypernetworkModule(torch.nn.Module): multiplier = 1.0 - activation_dict = {"relu": torch.nn.ReLU, "leakyrelu": torch.nn.LeakyReLU, "elu": torch.nn.ELU, - "swish": torch.nn.Hardswish} - - def __init__(self, dim, state_dict=None, layer_structure=None, add_layer_norm=False, activation_func=None): + activation_dict = { + "relu": torch.nn.ReLU, + "leakyrelu": torch.nn.LeakyReLU, + "elu": torch.nn.ELU, + "swish": torch.nn.Hardswish, + } + + def __init__(self, dim, state_dict=None, layer_structure=None, activation_func=None, add_layer_norm=False, use_dropout=False): super().__init__() assert layer_structure is not None, "layer_structure must not be None" assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!" assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!" - + assert activation_func not in self.activation_dict.keys() + "linear", f"Valid activation funcs: 'linear', 'relu', 'leakyrelu', 'elu', 'swish'" + linears = [] for i in range(len(layer_structure) - 1): + + # Add a fully-connected layer linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) - # if skip_first_layer because first parameters potentially contain negative values - # if i < 1: continue - if activation_func in HypernetworkModule.activation_dict: - linears.append(HypernetworkModule.activation_dict[activation_func]()) + + # Add an activation func + if activation_func == "linear": + pass + elif activation_func in self.activation_dict: + linears.append(self.activation_dict[activation_func]()) else: - print("Invalid key {} encountered as activation function!".format(activation_func)) - # if use_dropout: - # linears.append(torch.nn.Dropout(p=0.3)) + raise NotImplementedError( + "Valid activation funcs: 'linear', 'relu', 'leakyrelu', 'elu', 'swish'" + ) + + # Add dropout + if use_dropout: + linears.append(torch.nn.Dropout(p=0.3)) + + # Add layer normalization if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) @@ -93,7 +106,7 @@ class Hypernetwork: filename = None name = None - def __init__(self, name=None, enable_sizes=None, layer_structure=None, add_layer_norm=False, activation_func=None): + def __init__(self, name=None, enable_sizes=None, layer_structure=None, activation_func=None, add_layer_norm=False, use_dropout=False): self.filename = None self.name = name self.layers = {} @@ -101,13 +114,14 @@ class Hypernetwork: self.sd_checkpoint = None self.sd_checkpoint_name = None self.layer_structure = layer_structure - self.add_layer_norm = add_layer_norm self.activation_func = activation_func + self.add_layer_norm = add_layer_norm + self.use_dropout = use_dropout for size in enable_sizes or []: self.layers[size] = ( - HypernetworkModule(size, None, self.layer_structure, self.add_layer_norm, self.activation_func), - HypernetworkModule(size, None, self.layer_structure, self.add_layer_norm, self.activation_func), + HypernetworkModule(size, None, self.layer_structure, self.activation_func, self.add_layer_norm, self.use_dropout), + HypernetworkModule(size, None, self.layer_structure, self.activation_func, self.add_layer_norm, self.use_dropout), ) def weights(self): @@ -129,8 +143,9 @@ class Hypernetwork: state_dict['step'] = self.step state_dict['name'] = self.name state_dict['layer_structure'] = self.layer_structure - state_dict['is_layer_norm'] = self.add_layer_norm state_dict['activation_func'] = self.activation_func + state_dict['is_layer_norm'] = self.add_layer_norm + state_dict['use_dropout'] = self.use_dropout state_dict['sd_checkpoint'] = self.sd_checkpoint state_dict['sd_checkpoint_name'] = self.sd_checkpoint_name @@ -144,8 +159,9 @@ class Hypernetwork: state_dict = torch.load(filename, map_location='cpu') self.layer_structure = state_dict.get('layer_structure', [1, 2, 1]) - self.add_layer_norm = state_dict.get('is_layer_norm', False) self.activation_func = state_dict.get('activation_func', None) + self.add_layer_norm = state_dict.get('is_layer_norm', False) + self.use_dropout = state_dict.get('use_dropout', False) for size, sd in state_dict.items(): if type(size) == int: diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 1a5a27d8..5f6f17b6 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -3,14 +3,13 @@ import os import re import gradio as gr - -import modules.textual_inversion.textual_inversion import modules.textual_inversion.preprocess -from modules import sd_hijack, shared, devices +import modules.textual_inversion.textual_inversion +from modules import devices, sd_hijack, shared from modules.hypernetworks import hypernetwork -def create_hypernetwork(name, enable_sizes, layer_structure=None, add_layer_norm=False, activation_func=None): +def create_hypernetwork(name, enable_sizes, layer_structure=None, activation_func=None, add_layer_norm=False, use_dropout=False): fn = os.path.join(shared.cmd_opts.hypernetwork_dir, f"{name}.pt") assert not os.path.exists(fn), f"file {fn} already exists" @@ -21,8 +20,9 @@ def create_hypernetwork(name, enable_sizes, layer_structure=None, add_layer_norm name=name, enable_sizes=[int(x) for x in enable_sizes], layer_structure=layer_structure, - add_layer_norm=add_layer_norm, activation_func=activation_func, + add_layer_norm=add_layer_norm, + use_dropout=use_dropout, ) hypernet.save(fn) diff --git a/modules/ui.py b/modules/ui.py index 716f14b8..d4b32c05 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -5,43 +5,44 @@ import json import math import mimetypes import os +import platform import random +import subprocess as sp import sys import tempfile import time import traceback -import platform -import subprocess as sp from functools import partial, reduce +import gradio as gr +import gradio.routes +import gradio.utils import numpy as np +import piexif import torch from PIL import Image, PngImagePlugin -import piexif -import gradio as gr -import gradio.utils -import gradio.routes - -from modules import sd_hijack, sd_models, localization +from modules import localization, sd_hijack, sd_models from modules.paths import script_path -from modules.shared import opts, cmd_opts, restricted_opts +from modules.shared import cmd_opts, opts, restricted_opts + if cmd_opts.deepdanbooru: from modules.deepbooru import get_deepbooru_tags -import modules.shared as shared -from modules.sd_samplers import samplers, samplers_for_img2img -from modules.sd_hijack import model_hijack -import modules.ldsr_model -import modules.scripts -import modules.gfpgan_model + import modules.codeformer_model -import modules.styles import modules.generation_parameters_copypaste -from modules import prompt_parser -from modules.images import save_image -import modules.textual_inversion.ui +import modules.gfpgan_model import modules.hypernetworks.ui import modules.images_history as img_his +import modules.ldsr_model +import modules.scripts +import modules.shared as shared +import modules.styles +import modules.textual_inversion.ui +from modules import prompt_parser +from modules.images import save_image +from modules.sd_hijack import model_hijack +from modules.sd_samplers import samplers, samplers_for_img2img # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() @@ -1223,8 +1224,9 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_name = gr.Textbox(label="Name") new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "320", "640", "1280"]) new_hypernetwork_layer_structure = gr.Textbox("1, 2, 1", label="Enter hypernetwork layer structure", placeholder="1st and last digit must be 1. ex:'1, 2, 1'") + new_hypernetwork_activation_func = gr.Dropdown(value="relu", label="Select activation function of hypernetwork", choices=["linear", "relu", "leakyrelu", "elu", "swish"]) new_hypernetwork_add_layer_norm = gr.Checkbox(label="Add layer normalization") - new_hypernetwork_activation_func = gr.Dropdown(value="relu", label="Select activation function of hypernetwork", choices=["linear", "relu", "leakyrelu"]) + new_hypernetwork_use_dropout = gr.Checkbox(label="Use dropout") with gr.Row(): with gr.Column(scale=3): @@ -1308,8 +1310,9 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_name, new_hypernetwork_sizes, new_hypernetwork_layer_structure, - new_hypernetwork_add_layer_norm, new_hypernetwork_activation_func, + new_hypernetwork_add_layer_norm, + new_hypernetwork_use_dropout ], outputs=[ train_hypernetwork_name, From 1cd3ed7def40198f46d30f74dd37d2906ebdbaa6 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 14:28:56 +0300 Subject: [PATCH 0150/1118] fix for extensions without style.css --- modules/ui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ui.py b/modules/ui.py index 29986124..d8d52db1 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1639,6 +1639,9 @@ Requested path was: {f} css = "" for cssfile in modules.scripts.list_files_with_name("style.css"): + if not os.path.isfile(cssfile): + continue + with open(cssfile, "r", encoding="utf8") as file: css += file.read() + "\n" From 7fd90128eb6d1820045bfe2c2c1269661023a712 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 14:48:43 +0300 Subject: [PATCH 0151/1118] added a guard for hypernet training that will stop early if weights are getting no gradients --- modules/hypernetworks/hypernetwork.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 47d91ea5..46039a49 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -310,6 +310,8 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log scheduler = LearnRateScheduler(learn_rate, steps, ititial_step) optimizer = torch.optim.AdamW(weights, lr=scheduler.learn_rate) + steps_without_grad = 0 + pbar = tqdm.tqdm(enumerate(ds), total=steps - ititial_step) for i, entries in pbar: hypernetwork.step = i + ititial_step @@ -332,8 +334,17 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log losses[hypernetwork.step % losses.shape[0]] = loss.item() optimizer.zero_grad() + weights[0].grad = None loss.backward() + + if weights[0].grad is None: + steps_without_grad += 1 + else: + steps_without_grad = 0 + assert steps_without_grad < 10, 'no gradient found for the trained weight after backward() for 10 steps in a row; this is a bug; training cannot continue' + optimizer.step() + mean_loss = losses.mean() if torch.isnan(mean_loss): raise RuntimeError("Loss diverged.") From fccba4729db341a299db3343e3264fecd9459a07 Mon Sep 17 00:00:00 2001 From: discus0434 Date: Sat, 22 Oct 2022 12:02:41 +0000 Subject: [PATCH 0152/1118] add an option to avoid dying relu --- modules/hypernetworks/hypernetwork.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index b7a04038..3132a56c 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -32,7 +32,6 @@ class HypernetworkModule(torch.nn.Module): assert layer_structure is not None, "layer_structure must not be None" assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!" assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!" - assert activation_func not in self.activation_dict.keys() + "linear", f"Valid activation funcs: 'linear', 'relu', 'leakyrelu', 'elu', 'swish'" linears = [] for i in range(len(layer_structure) - 1): @@ -43,12 +42,13 @@ class HypernetworkModule(torch.nn.Module): # Add an activation func if activation_func == "linear" or activation_func is None: pass + # If ReLU, Skip adding it to the first layer to avoid dying ReLU + elif activation_func == "relu" and i < 1: + pass elif activation_func in self.activation_dict: linears.append(self.activation_dict[activation_func]()) else: - raise RuntimeError( - "Valid activation funcs: 'linear', 'relu', 'leakyrelu', 'elu', 'swish'" - ) + raise RuntimeError(f'hypernetwork uses an unsupported activation function: {activation_func}') # Add dropout if use_dropout: @@ -166,8 +166,8 @@ class Hypernetwork: for size, sd in state_dict.items(): if type(size) == int: self.layers[size] = ( - HypernetworkModule(size, sd[0], self.layer_structure, self.add_layer_norm, self.activation_func), - HypernetworkModule(size, sd[1], self.layer_structure, self.add_layer_norm, self.activation_func), + HypernetworkModule(size, sd[0], self.layer_structure, self.activation_func, self.add_layer_norm, self.use_dropout), + HypernetworkModule(size, sd[1], self.layer_structure, self.activation_func, self.add_layer_norm, self.use_dropout), ) self.name = state_dict.get('name', self.name) From 7912acef725832debef58c4c7bf8ec22fb446c0b Mon Sep 17 00:00:00 2001 From: discus0434 Date: Sat, 22 Oct 2022 13:00:44 +0000 Subject: [PATCH 0153/1118] small fix --- modules/hypernetworks/hypernetwork.py | 12 +++++------- modules/ui.py | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 3132a56c..7d12e0ff 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -42,22 +42,20 @@ class HypernetworkModule(torch.nn.Module): # Add an activation func if activation_func == "linear" or activation_func is None: pass - # If ReLU, Skip adding it to the first layer to avoid dying ReLU - elif activation_func == "relu" and i < 1: - pass elif activation_func in self.activation_dict: linears.append(self.activation_dict[activation_func]()) else: raise RuntimeError(f'hypernetwork uses an unsupported activation function: {activation_func}') - # Add dropout - if use_dropout: - linears.append(torch.nn.Dropout(p=0.3)) - # Add layer normalization if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) + # Add dropout + if use_dropout: + p = 0.5 if 0 <= i <= len(layer_structure) - 3 else 0.2 + linears.append(torch.nn.Dropout(p=p)) + self.linear = torch.nn.Sequential(*linears) if state_dict is not None: diff --git a/modules/ui.py b/modules/ui.py index cd118552..eca887ca 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1244,7 +1244,6 @@ def create_ui(wrap_gradio_gpu_call): new_hypernetwork_add_layer_norm = gr.Checkbox(label="Add layer normalization") new_hypernetwork_use_dropout = gr.Checkbox(label="Use dropout") overwrite_old_hypernetwork = gr.Checkbox(value=False, label="Overwrite Old Hypernetwork") - new_hypernetwork_activation_func = gr.Dropdown(value="relu", label="Select activation function of hypernetwork", choices=["linear", "relu", "leakyrelu"]) with gr.Row(): with gr.Column(scale=3): From 6a4fa73a38935a18779ce1809892730fd1572bee Mon Sep 17 00:00:00 2001 From: discus0434 Date: Sat, 22 Oct 2022 13:44:39 +0000 Subject: [PATCH 0154/1118] small fix --- modules/hypernetworks/hypernetwork.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 3372aae2..3bc71ee5 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -51,10 +51,9 @@ class HypernetworkModule(torch.nn.Module): if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) - # Add dropout - if use_dropout: - p = 0.5 if 0 <= i <= len(layer_structure) - 3 else 0.2 - linears.append(torch.nn.Dropout(p=p)) + # Add dropout expect last layer + if use_dropout and i < len(layer_structure) - 3: + linears.append(torch.nn.Dropout(p=0.3)) self.linear = torch.nn.Sequential(*linears) From d37cfffd537cd29309afbcb192c4f979995c6a34 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 19:18:56 +0300 Subject: [PATCH 0155/1118] added callback for creating new settings in extensions --- modules/script_callbacks.py | 11 +++++++++++ modules/shared.py | 19 +++++++++++++++++-- modules/ui.py | 6 +++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 866b7acd..1270e50f 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -1,6 +1,7 @@ callbacks_model_loaded = [] callbacks_ui_tabs = [] +callbacks_ui_settings = [] def clear_callbacks(): @@ -22,6 +23,11 @@ def ui_tabs_callback(): return res +def ui_settings_callback(): + for callback in callbacks_ui_settings: + callback() + + def on_model_loaded(callback): """register a function to be called when the stable diffusion model is created; the model is passed as an argument""" @@ -40,3 +46,8 @@ def on_ui_tabs(callback): """ callbacks_ui_tabs.append(callback) + +def on_ui_settings(callback): + """register a function to be called before UI settingsare populated; add your settings + by using shared.opts.add_option(shared.OptionInfo(...)) """ + callbacks_ui_settings.append(callback) diff --git a/modules/shared.py b/modules/shared.py index 5d83971e..d9cb65ef 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -165,13 +165,13 @@ def realesrgan_models_names(): class OptionInfo: - def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, show_on_main_page=False, refresh=None): + def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None): self.default = default self.label = label self.component = component self.component_args = component_args self.onchange = onchange - self.section = None + self.section = section self.refresh = refresh @@ -327,6 +327,7 @@ options_templates.update(options_section(('images-history', "Images Browser"), { })) + class Options: data = None data_labels = options_templates @@ -389,6 +390,20 @@ class Options: d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} return json.dumps(d) + def add_option(self, key, info): + self.data_labels[key] = info + + def reorder(self): + """reorder settings so that all items related to section always go together""" + + section_ids = {} + settings_items = self.data_labels.items() + for k, item in settings_items: + if item.section not in section_ids: + section_ids[item.section] = len(section_ids) + + self.data_labels = {k: v for k, v in sorted(settings_items, key=lambda x: section_ids[x[1].section])} + opts = Options() if os.path.exists(config_filename): diff --git a/modules/ui.py b/modules/ui.py index d8d52db1..2849b111 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1461,6 +1461,9 @@ def create_ui(wrap_gradio_gpu_call): components = [] component_dict = {} + script_callbacks.ui_settings_callback() + opts.reorder() + def open_folder(f): if not os.path.exists(f): print(f'Folder "{f}" does not exist. After you create an image, the folder will be created.') @@ -1564,7 +1567,8 @@ Requested path was: {f} previous_section = item.section - gr.HTML(elem_id="settings_header_text_{}".format(item.section[0]), value='

{}

'.format(item.section[1])) + elem_id, text = item.section + gr.HTML(elem_id="settings_header_text_{}".format(elem_id), value='

{}

'.format(text)) if k in quicksettings_names: quicksettings_list.append((i, k, item)) From dbc8ab65f6d496459a76547776b656c96ad1350d Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 19:19:17 +0300 Subject: [PATCH 0156/1118] typo --- modules/script_callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 1270e50f..5bcccd67 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -48,6 +48,6 @@ def on_ui_tabs(callback): def on_ui_settings(callback): - """register a function to be called before UI settingsare populated; add your settings + """register a function to be called before UI settings are populated; add your settings by using shared.opts.add_option(shared.OptionInfo(...)) """ callbacks_ui_settings.append(callback) From 72383abacdc6a101704a6f73758ce4d0bb68c9d1 Mon Sep 17 00:00:00 2001 From: Greendayle Date: Sat, 22 Oct 2022 16:50:07 +0200 Subject: [PATCH 0157/1118] Deepdanbooru linux fix --- modules/deepbooru.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/deepbooru.py b/modules/deepbooru.py index 8914662d..3c34ab7c 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -50,7 +50,8 @@ def create_deepbooru_process(threshold, deepbooru_opts): the tags. """ from modules import shared # prevents circular reference - shared.deepbooru_process_manager = multiprocessing.Manager() + context = multiprocessing.get_context("spawn") + shared.deepbooru_process_manager = context.Manager() shared.deepbooru_process_queue = shared.deepbooru_process_manager.Queue() shared.deepbooru_process_return = shared.deepbooru_process_manager.dict() shared.deepbooru_process_return["value"] = -1 From e38625011cd4955da4bc67fe95d1d0f4c0c53899 Mon Sep 17 00:00:00 2001 From: Greendayle Date: Sat, 22 Oct 2022 16:56:52 +0200 Subject: [PATCH 0158/1118] fix part2 --- modules/deepbooru.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/deepbooru.py b/modules/deepbooru.py index 3c34ab7c..8bbc90a4 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -55,7 +55,7 @@ def create_deepbooru_process(threshold, deepbooru_opts): shared.deepbooru_process_queue = shared.deepbooru_process_manager.Queue() shared.deepbooru_process_return = shared.deepbooru_process_manager.dict() shared.deepbooru_process_return["value"] = -1 - shared.deepbooru_process = multiprocessing.Process(target=deepbooru_process, args=(shared.deepbooru_process_queue, shared.deepbooru_process_return, threshold, deepbooru_opts)) + shared.deepbooru_process = context.Process(target=deepbooru_process, args=(shared.deepbooru_process_queue, shared.deepbooru_process_return, threshold, deepbooru_opts)) shared.deepbooru_process.start() From 7613ea12f267143ceb70a9aeb45eb20aca086e3e Mon Sep 17 00:00:00 2001 From: random_thoughtss Date: Fri, 21 Oct 2022 11:32:56 -0700 Subject: [PATCH 0159/1118] Fixed img2imgalt after inpainting update --- scripts/img2imgalt.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index d438175c..88abc093 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -34,6 +34,9 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps): sigma_in = torch.cat([sigmas[i] * s_in] * 2) cond_in = torch.cat([uncond, cond]) + image_conditioning = torch.cat([p.image_conditioning] * 2) + cond_in = {"c_concat": [image_conditioning], "c_crossattn": [cond_in]} + c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)] t = dnw.sigma_to_t(sigma_in) @@ -78,6 +81,9 @@ def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): sigma_in = torch.cat([sigmas[i - 1] * s_in] * 2) cond_in = torch.cat([uncond, cond]) + image_conditioning = torch.cat([p.image_conditioning] * 2) + cond_in = {"c_concat": [image_conditioning], "c_crossattn": [cond_in]} + c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)] if i == 1: @@ -194,7 +200,7 @@ class Script(scripts.Script): p.seed = p.seed + 1 - return sampler.sample_img2img(p, p.init_latent, noise_dt, conditioning, unconditional_conditioning) + return sampler.sample_img2img(p, p.init_latent, noise_dt, conditioning, unconditional_conditioning, image_conditioning=p.image_conditioning) p.sample = sample_extra From 96ee7d77077eb3c1eacff802e9ccf194adc04592 Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Tue, 18 Oct 2022 14:16:26 +0900 Subject: [PATCH 0160/1118] add ja localization --- localizations/ja_JP.json | 413 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 localizations/ja_JP.json diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json new file mode 100644 index 00000000..5da28cb6 --- /dev/null +++ b/localizations/ja_JP.json @@ -0,0 +1,413 @@ +{ + "⤡": "⤡", + "⊞": "⊞", + "×": "×", + "❮": "❮", + "❯": "❯", + "Loading...": "読み込み中...", + "view": "view", + "api": "api", + "•": "•", + "gradioで作ろう": "gradioで作ろう", + "Stable Diffusion checkpoint": "Stable Diffusion checkpoint", + "txt2img": "txt2img", + "img2img": "img2img", + "Extras": "その他", + "PNG Info": "PNG Info", + "History": "履歴", + "Checkpoint Merger": "Checkpoint Merger", + "Train": "学習", + "Settings": "設定", + "Prompt": "プロンプト", + "Negative prompt": "ネガティブ プロンプト", + "Run": "実行", + "Skip": "スキップ", + "Interrupt": "中断", + "Generate": "生成!", + "Style 1": "スタイル 1", + "Style 2": "スタイル 2", + "Label": "Label", + "File": "ファイル", + "ここにファイルをドロップ": "ここにファイルをドロップ", + "-": "-", + "または": "または", + "クリックしてアップロード": "クリックしてアップロード", + "Image": "Image", + "Check progress": "Check progress", + "Check progress (first)": "Check progress (first)", + "Sampling Steps": "Sampling Steps", + "Sampling method": "Sampling method", + "Euler a": "Euler a", + "Euler": "Euler", + "LMS": "LMS", + "Heun": "Heun", + "DPM2": "DPM2", + "DPM2 a": "DPM2 a", + "DPM fast": "DPM fast", + "DPM adaptive": "DPM adaptive", + "LMS Karras": "LMS Karras", + "DPM2 Karras": "DPM2 Karras", + "DPM2 a Karras": "DPM2 a Karras", + "DDIM": "DDIM", + "PLMS": "PLMS", + "Width": "幅", + "Height": "高さ", + "Restore faces": "顔修復", + "Tiling": "テクスチャ生成モード", + "Highres. fix": "*高解像度 fix", + "Firstpass width": "Firstpass width", + "Firstpass height": "Firstpass height", + "Denoising strength": "Denoising 強度", + "Batch count": "連続生成回数", + "Batch size": "同時生成枚数", + "CFG Scale": "CFG Scale", + "Seed": "Seed", + "Extra": "Extra", + "Variation seed": "Variation seed", + "Variation strength": "Variation 強度", + "Resize seed from width": "Resize seed from width", + "Resize seed from height": "Resize seed from height", + "Script": "Script", + "None": "None", + "Prompt matrix": "Prompt matrix", + "Prompts from file or textbox": "Prompts from file or textbox", + "X/Y plot": "X/Y plot", + "Put variable parts at start of prompt": "Put variable parts at start of prompt", + "Show Textbox": "Show Textbox", + "File with inputs": "File with inputs", + "Prompts": "Prompts", + "X type": "X type", + "Nothing": "Nothing", + "Var. seed": "Var. seed", + "Var. strength": "Var. 強度", + "Steps": "Steps", + "Prompt S/R": "Prompt S/R", + "Prompt order": "Prompt order", + "Sampler": "Sampler", + "Checkpoint name": "Checkpoint name", + "Hypernetwork": "Hypernetwork", + "Hypernet str.": "Hypernet 強度", + "Sigma Churn": "Sigma Churn", + "Sigma min": "Sigma min", + "Sigma max": "Sigma max", + "Sigma noise": "Sigma noise", + "Eta": "Eta", + "Clip skip": "Clip skip", + "Denoising": "Denoising", + "X values": "X values", + "Y type": "Y type", + "Y values": "Y values", + "Draw legend": "Draw legend", + "Include Separate Images": "Include Separate Images", + "Keep -1 for seeds": "Keep -1 for seeds", + "ここに画像をドロップ": "ここに画像をドロップ", + "Save": "保存", + "Send to img2img": "img2imgに送る", + "Send to inpaint": "inpaintに送る", + "Send to extras": "その他タブに送る", + "Make Zip when Save?": "保存するときZipも同時に作る", + "Textbox": "Textbox", + "Interrogate\nCLIP": "Interrogate\nCLIP", + "Interrogate\nDeepBooru": "Interrogate\nDeepBooru", + "Inpaint": "Inpaint", + "Batch img2img": "Batch img2img", + "Image for img2img": "Image for img2img", + "Image for inpainting with mask": "Image for inpainting with mask", + "Mask": "Mask", + "Mask blur": "Mask blur", + "Mask mode": "Mask mode", + "Draw mask": "Draw mask", + "Upload mask": "Upload mask", + "Masking mode": "Masking mode", + "Inpaint masked": "Inpaint masked", + "Inpaint not masked": "Inpaint not masked", + "Masked content": "Masked content", + "fill": "fill", + "original": "original", + "latent noise": "latent noise", + "latent nothing": "latent nothing", + "Inpaint at full resolution": "Inpaint at full resolution", + "Inpaint at full resolution padding, pixels": "Inpaint at full resolution padding, pixels", + "Process images in a directory on the same machine where the server is running.": "Process images in a directory on the same machine where the server is running.", + "Use an empty output directory to save pictures normally instead of writing to the output directory.": "Use an empty output directory to save pictures normally instead of writing to the output directory.", + "Input directory": "Input directory", + "Output directory": "Output directory", + "Resize mode": "Resize mode", + "Just resize": "Just resize", + "Crop and resize": "Crop and resize", + "Resize and fill": "Resize and fill", + "img2img alternative test": "img2img alternative test", + "Loopback": "Loopback", + "Outpainting mk2": "Outpainting mk2", + "Poor man's outpainting": "Poor man's outpainting", + "SD upscale": "SD upscale", + "should be 2 or lower.": "should be 2 or lower.", + "Override `Sampling method` to Euler?(this method is built for it)": "Override `Sampling method` to Euler?(this method is built for it)", + "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)", + "Original prompt": "Original prompt", + "Original negative prompt": "Original negative prompt", + "Override `Sampling Steps` to the same value as `Decode steps`?": "Override `Sampling Steps` to the same value as `Decode steps`?", + "Decode steps": "Decode steps", + "Override `Denoising strength` to 1?": "Override `Denoising strength` to 1?", + "Decode CFG scale": "Decode CFG scale", + "Randomness": "Randomness", + "Sigma adjustment for finding noise for image": "Sigma adjustment for finding noise for image", + "Loops": "Loops", + "Denoising strength change factor": "Denoising strength change factor", + "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8", + "Pixels to expand": "Pixels to expand", + "Outpainting direction": "Outpainting direction", + "left": "left", + "right": "right", + "up": "up", + "down": "down", + "Fall-off exponent (lower=higher detail)": "Fall-off exponent (lower=higher detail)", + "Color variation": "Color variation", + "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "Will upscale the image to twice the dimensions; use width and height sliders to set tile size", + "Tile overlap": "Tile overlap", + "Upscaler": "Upscaler", + "Lanczos": "Lanczos", + "LDSR": "LDSR", + "BSRGAN 4x": "BSRGAN 4x", + "ESRGAN_4x": "ESRGAN_4x", + "ScuNET GAN": "ScuNET GAN", + "ScuNET PSNR": "ScuNET PSNR", + "SwinIR 4x": "SwinIR 4x", + "Single Image": "Single Image", + "Batch Process": "Batch Process", + "Batch from Directory": "Batch from Directory", + "Source": "Source", + "Show result images": "Show result images", + "Scale by": "Scale by", + "Scale to": "Scale to", + "Resize": "Resize", + "Crop to fit": "Crop to fit", + "Upscaler 2": "Upscaler 2", + "Upscaler 2 visibility": "Upscaler 2 visibility", + "GFPGAN visibility": "GFPGAN visibility", + "CodeFormer visibility": "CodeFormer visibility", + "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", + "Open output directory": "出力フォルダを開く", + "Send to txt2img": "Send to txt2img", + "txt2img history": "txt2img history", + "img2img history": "img2img history", + "extras history": "extras history", + "Renew Page": "Renew Page", + "First Page": "First Page", + "Prev Page": "Prev Page", + "Page Index": "Page Index", + "Next Page": "Next Page", + "End Page": "End Page", + "number of images to delete consecutively next": "number of images to delete consecutively next", + "Delete": "Delete", + "Generate Info": "Generate Info", + "File Name": "File Name", + "set_index": "set_index", + "A merger of the two checkpoints will be generated in your": "A merger of the two checkpoints will be generated in your", + "checkpoint": "checkpoint", + "directory.": "directory.", + "Primary model (A)": "Primary model (A)", + "Secondary model (B)": "Secondary model (B)", + "Tertiary model (C)": "Tertiary model (C)", + "Custom Name (Optional)": "Custom Name (Optional)", + "Multiplier (M) - set to 0 to get model A": "Multiplier (M) - set to 0 to get model A", + "Interpolation Method": "Interpolation Method", + "Weighted sum": "Weighted sum", + "Add difference": "Add difference", + "Save as float16": "Save as float16", + "See": "See", + "wiki": "wiki", + "for detailed explanation.": "for detailed explanation.", + "Create embedding": "Create embedding", + "Create hypernetwork": "Create hypernetwork", + "Preprocess images": "Preprocess images", + "Name": "Name", + "Initialization text": "Initialization text", + "Number of vectors per token": "Number of vectors per token", + "Modules": "Modules", + "Source directory": "Source directory", + "Destination directory": "Destination directory", + "Create flipped copies": "Create flipped copies", + "Split oversized images into two": "Split oversized images into two", + "Use BLIP for caption": "Use BLIP for caption", + "Use deepbooru for caption": "Use deepbooru for caption", + "Preprocess": "Preprocess", + "Train an embedding; must specify a directory with a set of 1:1 ratio images": "Train an embedding; must specify a directory with a set of 1:1 ratio images", + "Embedding": "Embedding", + "Learning rate": "Learning rate", + "Dataset directory": "Dataset directory", + "Log directory": "Log directory", + "Prompt template file": "Prompt template file", + "Max steps": "Max steps", + "Save an image to log directory every N steps, 0 to disable": "Save an image to log directory every N steps, 0 to disable", + "Save a copy of embedding to log directory every N steps, 0 to disable": "Save a copy of embedding to log directory every N steps, 0 to disable", + "Save images with embedding in PNG chunks": "Save images with embedding in PNG chunks", + "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", + "Train Hypernetwork": "Train Hypernetwork", + "Train Embedding": "Train Embedding", + "Apply settings": "Apply settings", + "Saving images/grids": "Saving images/grids", + "Always save all generated images": "Always save all generated images", + "File format for images": "File format for images", + "Images filename pattern": "Images filename pattern", + "Always save all generated image grids": "Always save all generated image grids", + "File format for grids": "File format for grids", + "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", + "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", + "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", + "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", + "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", + "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", + "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", + "Quality for saved jpeg images": "Quality for saved jpeg images", + "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", + "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", + "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", + "Do not add watermark to images": "Do not add watermark to images", + "Paths for saving": "Paths for saving", + "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", + "Output directory for txt2img images": "Output directory for txt2img images", + "Output directory for img2img images": "Output directory for img2img images", + "Output directory for images from extras tab": "Output directory for images from extras tab", + "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", + "Output directory for txt2img grids": "Output directory for txt2img grids", + "Output directory for img2img grids": "Output directory for img2img grids", + "Directory for saving images using the Save button": "Directory for saving images using the Save button", + "Saving to a directory": "Saving to a directory", + "Save images to a subdirectory": "Save images to a subdirectory", + "Save grids to a subdirectory": "Save grids to a subdirectory", + "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", + "Directory name pattern": "Directory name pattern", + "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", + "Upscaling": "Upscaling", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", + "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", + "Tile size for all SwinIR.": "Tile size for all SwinIR.", + "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", + "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", + "Upscaler for img2img": "Upscaler for img2img", + "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", + "Face restoration": "Face restoration", + "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", + "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", + "System": "System", + "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", + "Always print all generation info to standard output": "Always print all generation info to standard output", + "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", + "Training": "Training", + "Unload VAE and CLIP from VRAM when training": "Unload VAE and CLIP from VRAM when training", + "Filename word regex": "Filename word regex", + "Filename join string": "Filename join string", + "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", + "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", + "Stable Diffusion": "Stable Diffusion", + "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", + "Hypernetwork strength": "Hypernetwork strength", + "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", + "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", + "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", + "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", + "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", + "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", + "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", + "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", + "Filter NSFW content": "Filter NSFW content", + "Stop At last layers of CLIP model": "Stop At last layers of CLIP model", + "Interrogate Options": "Interrogate Options", + "Interrogate: keep models in VRAM": "Interrogate: keep models in VRAM", + "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", + "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).", + "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", + "Interrogate: minimum description length (excluding artists, etc..)": "Interrogate: minimum description length (excluding artists, etc..)", + "Interrogate: maximum description length": "Interrogate: maximum description length", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "Interrogate: deepbooru score threshold": "Interrogate: deepbooru score threshold", + "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooru sort alphabetically", + "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "User interface": "User interface", + "Show progressbar": "Show progressbar", + "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", + "Show grid in results for web": "Show grid in results for web", + "Do not show any images in results for web": "Do not show any images in results for web", + "Add model hash to generation information": "Add model hash to generation information", + "Add model name to generation information": "Add model name to generation information", + "Font for image grids that have text": "Font for image grids that have text", + "Enable full page image viewer": "Enable full page image viewer", + "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", + "Show generation progress in window title.": "Show generation progress in window title.", + "Quicksettings list": "Quicksettings list", + "Localization (requires restart)": "Localization (requires restart)", + "ja_JP": "ja_JP", + "Sampler parameters": "Sampler parameters", + "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", + "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", + "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", + "img2img DDIM discretize": "img2img DDIM discretize", + "uniform": "uniform", + "quad": "quad", + "sigma churn": "sigma churn", + "sigma tmin": "sigma tmin", + "sigma noise": "sigma noise", + "Eta noise seed delta": "Eta noise seed delta", + "Request browser notifications": "Request browser notifications", + "Download localization template": "Download localization template", + "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", + "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "ネガティブ プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", + "Add a random artist to the prompt.": "Add a random artist to the prompt.", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", + "Save style": "Save style", + "Apply selected styles to current prompt": "Apply selected styles to current prompt", + "Stop processing current image and continue processing.": "Stop processing current image and continue processing.", + "Stop processing images and return any results accumulated so far.": "Stop processing images and return any results accumulated so far.", + "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", + "Do not do anything special": "Do not do anything special", + "Which algorithm to use to produce the image": "Which algorithm to use to produce the image", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - best at inpainting", + "Produce an image that can be tiled.": "Produce an image that can be tiled.", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", + "How many batches of images to create": "How many batches of images to create", + "How many image to create in a single batch": "How many image to create in a single batch", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result", + "Set seed to -1, which will cause a new random number to be used every time": "Set seed to -1, which will cause a new random number to be used every time", + "Reuse seed from last generation, mostly useful if it was randomed": "Reuse seed from last generation, mostly useful if it was randomed", + "Seed of a different picture to be mixed into the generation.": "Seed of a different picture to be mixed into the generation.", + "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution", + "Separate values for X axis using commas.": "Separate values for X axis using commas.", + "Separate values for Y axis using commas.": "Separate values for Y axis using commas.", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "Write image to a directory (default - log/images) and generation parameters into csv file.", + "Open images output directory": "Open images output directory", + "How much to blur the mask before processing, in pixels.": "How much to blur the mask before processing, in pixels.", + "What to put inside the masked area before processing it with Stable Diffusion.": "What to put inside the masked area before processing it with Stable Diffusion.", + "fill it with colors of the image": "fill it with colors of the image", + "keep whatever was there originally": "keep whatever was there originally", + "fill it with latent space noise": "fill it with latent space noise", + "fill it with latent space zeroes": "fill it with latent space zeroes", + "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image", + "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.", + "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.", + "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.", + "How many times to repeat processing an image and using it as input for the next iteration": "How many times to repeat processing an image and using it as input for the next iteration", + "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.", + "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.", + "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", + "Leave blank to save images to the default path.": "Leave blank to save images to the default path.", + "Result = A * (1 - M) + B * M": "Result = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "Result = A + (B - C) * M", + "Path to directory with input images": "Path to directory with input images", + "Path to directory where to write outputs": "Path to directory where to write outputs", + "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", + "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", + "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", + "Restore low quality faces using GFPGAN neural network": "Restore low quality faces using GFPGAN neural network", + "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", + "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", + "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing." +} \ No newline at end of file From eb2dae196e5d901162d477f24c1ebb2597b13dfb Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Tue, 18 Oct 2022 14:40:07 +0900 Subject: [PATCH 0161/1118] add ja translation --- localizations/ja_JP.json | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index 5da28cb6..27bd342a 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -13,9 +13,9 @@ "txt2img": "txt2img", "img2img": "img2img", "Extras": "その他", - "PNG Info": "PNG Info", + "PNG Info": "PNG内の情報を表示", "History": "履歴", - "Checkpoint Merger": "Checkpoint Merger", + "Checkpoint Merger": "Checkpointの統合", "Train": "学習", "Settings": "設定", "Prompt": "プロンプト", @@ -35,8 +35,8 @@ "Image": "Image", "Check progress": "Check progress", "Check progress (first)": "Check progress (first)", - "Sampling Steps": "Sampling Steps", - "Sampling method": "Sampling method", + "Sampling Steps": "サンプリング回数", + "Sampling method": "サンプリングアルゴリズム", "Euler a": "Euler a", "Euler": "Euler", "LMS": "LMS", @@ -57,18 +57,18 @@ "Highres. fix": "*高解像度 fix", "Firstpass width": "Firstpass width", "Firstpass height": "Firstpass height", - "Denoising strength": "Denoising 強度", + "Denoising strength": "ノイズ除去 強度", "Batch count": "連続生成回数", "Batch size": "同時生成枚数", "CFG Scale": "CFG Scale", - "Seed": "Seed", - "Extra": "Extra", - "Variation seed": "Variation seed", + "Seed": "シード値", + "Extra": "その他", + "Variation seed": "Variation シード値", "Variation strength": "Variation 強度", "Resize seed from width": "Resize seed from width", "Resize seed from height": "Resize seed from height", - "Script": "Script", - "None": "None", + "Script": "スクリプト", + "None": "なし", "Prompt matrix": "Prompt matrix", "Prompts from file or textbox": "Prompts from file or textbox", "X/Y plot": "X/Y plot", @@ -84,7 +84,7 @@ "Prompt S/R": "Prompt S/R", "Prompt order": "Prompt order", "Sampler": "Sampler", - "Checkpoint name": "Checkpoint name", + "Checkpoint name": "Checkpoint名", "Hypernetwork": "Hypernetwork", "Hypernet str.": "Hypernet 強度", "Sigma Churn": "Sigma Churn", @@ -103,13 +103,13 @@ "ここに画像をドロップ": "ここに画像をドロップ", "Save": "保存", "Send to img2img": "img2imgに送る", - "Send to inpaint": "inpaintに送る", + "Send to inpaint": "描き直しに送る", "Send to extras": "その他タブに送る", "Make Zip when Save?": "保存するときZipも同時に作る", "Textbox": "Textbox", "Interrogate\nCLIP": "Interrogate\nCLIP", "Interrogate\nDeepBooru": "Interrogate\nDeepBooru", - "Inpaint": "Inpaint", + "Inpaint": "描き直し(Inpaint)", "Batch img2img": "Batch img2img", "Image for img2img": "Image for img2img", "Image for inpainting with mask": "Image for inpainting with mask", @@ -132,16 +132,16 @@ "Use an empty output directory to save pictures normally instead of writing to the output directory.": "Use an empty output directory to save pictures normally instead of writing to the output directory.", "Input directory": "Input directory", "Output directory": "Output directory", - "Resize mode": "Resize mode", - "Just resize": "Just resize", - "Crop and resize": "Crop and resize", - "Resize and fill": "Resize and fill", + "Resize mode": "リサイズモード", + "Just resize": "リサイズのみ", + "Crop and resize": "切り取ってからリサイズ", + "Resize and fill": "リサイズして埋める", "img2img alternative test": "img2img alternative test", "Loopback": "Loopback", "Outpainting mk2": "Outpainting mk2", "Poor man's outpainting": "Poor man's outpainting", - "SD upscale": "SD upscale", - "should be 2 or lower.": "should be 2 or lower.", + "SD upscale": "SD アップスケール", + "should be 2 or lower.": "2以下にすること", "Override `Sampling method` to Euler?(this method is built for it)": "Override `Sampling method` to Euler?(this method is built for it)", "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)", "Original prompt": "Original prompt", @@ -370,19 +370,19 @@ "Produce an image that can be tiled.": "Produce an image that can be tiled.", "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition", "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", - "How many batches of images to create": "How many batches of images to create", - "How many image to create in a single batch": "How many image to create in a single batch", + "How many batches of images to create": "バッチ処理を何回行うか", + "How many image to create in a single batch": "1回のバッチ処理で何枚の画像を生成するか", "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results", "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result", - "Set seed to -1, which will cause a new random number to be used every time": "Set seed to -1, which will cause a new random number to be used every time", - "Reuse seed from last generation, mostly useful if it was randomed": "Reuse seed from last generation, mostly useful if it was randomed", + "Set seed to -1, which will cause a new random number to be used every time": "シード値を -1 に設定するとランダムに生成します。", + "Reuse seed from last generation, mostly useful if it was randomed": "前回生成時のシード値を読み出す。(ランダム生成時に便利)", "Seed of a different picture to be mixed into the generation.": "Seed of a different picture to be mixed into the generation.", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).", "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution", "Separate values for X axis using commas.": "Separate values for X axis using commas.", "Separate values for Y axis using commas.": "Separate values for Y axis using commas.", "Write image to a directory (default - log/images) and generation parameters into csv file.": "Write image to a directory (default - log/images) and generation parameters into csv file.", - "Open images output directory": "Open images output directory", + "Open images output directory": "画像の出力フォルダを開く", "How much to blur the mask before processing, in pixels.": "How much to blur the mask before processing, in pixels.", "What to put inside the masked area before processing it with Stable Diffusion.": "What to put inside the masked area before processing it with Stable Diffusion.", "fill it with colors of the image": "fill it with colors of the image", From 070fda592bf80fb348ffe8e17b7c71cc288db729 Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Tue, 18 Oct 2022 15:20:00 +0900 Subject: [PATCH 0162/1118] add ja translation --- localizations/ja_JP.json | 128 +++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index 27bd342a..11f747b4 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -26,13 +26,13 @@ "Generate": "生成!", "Style 1": "スタイル 1", "Style 2": "スタイル 2", - "Label": "Label", + "Label": "ラベル", "File": "ファイル", "ここにファイルをドロップ": "ここにファイルをドロップ", "-": "-", "または": "または", "クリックしてアップロード": "クリックしてアップロード", - "Image": "Image", + "Image": "画像", "Check progress": "Check progress", "Check progress (first)": "Check progress (first)", "Sampling Steps": "サンプリング回数", @@ -54,12 +54,12 @@ "Height": "高さ", "Restore faces": "顔修復", "Tiling": "テクスチャ生成モード", - "Highres. fix": "*高解像度 fix", + "Highres. fix": "(※)高解像度 fix", "Firstpass width": "Firstpass width", "Firstpass height": "Firstpass height", "Denoising strength": "ノイズ除去 強度", - "Batch count": "連続生成回数", - "Batch size": "同時生成枚数", + "Batch count": "バッチ生成回数", + "Batch size": "バッチあたり生成枚数", "CFG Scale": "CFG Scale", "Seed": "シード値", "Extra": "その他", @@ -77,16 +77,16 @@ "File with inputs": "File with inputs", "Prompts": "Prompts", "X type": "X type", - "Nothing": "Nothing", + "Nothing": "なし", "Var. seed": "Var. seed", "Var. strength": "Var. 強度", - "Steps": "Steps", + "Steps": "ステップ数", "Prompt S/R": "Prompt S/R", "Prompt order": "Prompt order", - "Sampler": "Sampler", + "Sampler": "サンプラー", "Checkpoint name": "Checkpoint名", "Hypernetwork": "Hypernetwork", - "Hypernet str.": "Hypernet 強度", + "Hypernet str.": "Hypernet強度", "Sigma Churn": "Sigma Churn", "Sigma min": "Sigma min", "Sigma max": "Sigma max", @@ -192,78 +192,78 @@ "txt2img history": "txt2img history", "img2img history": "img2img history", "extras history": "extras history", - "Renew Page": "Renew Page", - "First Page": "First Page", - "Prev Page": "Prev Page", - "Page Index": "Page Index", - "Next Page": "Next Page", - "End Page": "End Page", + "Renew Page": "更新", + "First Page": "最初のぺージへ", + "Prev Page": "前ページへ", + "Page Index": "ページ番号", + "Next Page": "次ページへ", + "End Page": "最後のページへ", "number of images to delete consecutively next": "number of images to delete consecutively next", - "Delete": "Delete", + "Delete": "削除", "Generate Info": "Generate Info", "File Name": "File Name", "set_index": "set_index", - "A merger of the two checkpoints will be generated in your": "A merger of the two checkpoints will be generated in your", + "A merger of the two checkpoints will be generated in your": "統合されたチェックポイントはあなたの", "checkpoint": "checkpoint", - "directory.": "directory.", - "Primary model (A)": "Primary model (A)", - "Secondary model (B)": "Secondary model (B)", - "Tertiary model (C)": "Tertiary model (C)", - "Custom Name (Optional)": "Custom Name (Optional)", - "Multiplier (M) - set to 0 to get model A": "Multiplier (M) - set to 0 to get model A", + "directory.": "フォルダに保存されます.", + "Primary model (A)": "1つめのmodel (A)", + "Secondary model (B)": "2つめのmodel (B)", + "Tertiary model (C)": "3つめのmodel (C)", + "Custom Name (Optional)": "Custom Name (任意)", + "Multiplier (M) - set to 0 to get model A": "Multiplier (M) 0にすると完全にmodel Aとなります", "Interpolation Method": "Interpolation Method", "Weighted sum": "Weighted sum", "Add difference": "Add difference", - "Save as float16": "Save as float16", - "See": "See", + "Save as float16": "float16で保存", + "See": "詳細な説明については", "wiki": "wiki", - "for detailed explanation.": "for detailed explanation.", - "Create embedding": "Create embedding", - "Create hypernetwork": "Create hypernetwork", - "Preprocess images": "Preprocess images", - "Name": "Name", + "for detailed explanation.": "を見てください。", + "Create embedding": "Embeddingを作る", + "Create hypernetwork": "Hypernetworkを作る", + "Preprocess images": "画像の前処理", + "Name": "ファイル名", "Initialization text": "Initialization text", "Number of vectors per token": "Number of vectors per token", "Modules": "Modules", - "Source directory": "Source directory", - "Destination directory": "Destination directory", - "Create flipped copies": "Create flipped copies", - "Split oversized images into two": "Split oversized images into two", - "Use BLIP for caption": "Use BLIP for caption", - "Use deepbooru for caption": "Use deepbooru for caption", - "Preprocess": "Preprocess", - "Train an embedding; must specify a directory with a set of 1:1 ratio images": "Train an embedding; must specify a directory with a set of 1:1 ratio images", + "Source directory": "入力フォルダ", + "Destination directory": "出力フォルダ", + "Create flipped copies": "反転画像を生成する", + "Split oversized images into two": "大きすぎる画像を2分割する", + "Use BLIP for caption": "BLIPで説明をつける", + "Use deepbooru for caption": "deepbooruで説明をつける", + "Preprocess": "前処理開始", + "Train an embedding; must specify a directory with a set of 1:1 ratio images": "embeddingの学習をします;データセット内の画像は正方形でなければなりません。", "Embedding": "Embedding", - "Learning rate": "Learning rate", - "Dataset directory": "Dataset directory", - "Log directory": "Log directory", + "Learning rate": "学習率", + "Dataset directory": "データセットフォルダ", + "Log directory": "ログフォルダ", "Prompt template file": "Prompt template file", - "Max steps": "Max steps", - "Save an image to log directory every N steps, 0 to disable": "Save an image to log directory every N steps, 0 to disable", - "Save a copy of embedding to log directory every N steps, 0 to disable": "Save a copy of embedding to log directory every N steps, 0 to disable", - "Save images with embedding in PNG chunks": "Save images with embedding in PNG chunks", + "Max steps": "最大ステップ数", + "Save an image to log directory every N steps, 0 to disable": "指定したステップ数ごとに画像を生成し、ログに保存する。0で無効化。", + "Save a copy of embedding to log directory every N steps, 0 to disable": "指定したステップ数ごとにEmbeddingのコピーをログに保存する。0で無効化。", + "Save images with embedding in PNG chunks": "保存する画像にembeddingを埋め込む", "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", - "Train Hypernetwork": "Train Hypernetwork", - "Train Embedding": "Train Embedding", + "Train Hypernetwork": "Hypernetworkの学習を開始", + "Train Embedding": "Embeddingの学習を開始", "Apply settings": "Apply settings", - "Saving images/grids": "Saving images/grids", - "Always save all generated images": "Always save all generated images", - "File format for images": "File format for images", - "Images filename pattern": "Images filename pattern", - "Always save all generated image grids": "Always save all generated image grids", - "File format for grids": "File format for grids", - "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", - "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", - "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", - "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", - "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", - "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", - "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", - "Quality for saved jpeg images": "Quality for saved jpeg images", - "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", + "Saving images/grids": "画像/グリッドの保存", + "Always save all generated images": "生成された画像をすべて保存する", + "File format for images": "画像ファイルの保存形式", + "Images filename pattern": "ファイル名のパターン", + "Always save all generated image grids": "グリッド画像を常に保存する", + "File format for grids": "グリッド画像の保存形式", + "Add extended info (seed, prompt) to filename when saving grid": "保存するグリッド画像のファイル名に追加情報(シード値、プロンプト)を加える", + "Do not save grids consisting of one picture": "1画像からなるグリッド画像は保存しない", + "Prevent empty spots in grid (when set to autodetect)": "(自動設定のとき)グリッドに空隙が生じるのを防ぐ", + "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "グリッドの列数; -1で自動設定、0でバッチ生成回数と同じにする", + "Save text information about generation parameters as chunks to png files": "生成に関するパラメーターをpng画像に含める", + "Create a text file next to every image with generation parameters.": "保存する画像とともに生成パラメータをテキストファイルで保存する", + "Save a copy of image before doing face restoration.": "顔修復を行う前にコピーを保存しておく。", + "Quality for saved jpeg images": "JPG保存時の画質", + "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "PNG画像が4MBを超えるか、どちらか1辺の長さが4000を超えたなら、ダウンスケールしてコピーを別にJPGで保存する", "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", - "Do not add watermark to images": "Do not add watermark to images", + "Do not add watermark to images": "電子透かしを画像に追加しない", "Paths for saving": "Paths for saving", "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", "Output directory for txt2img images": "Output directory for txt2img images", @@ -403,7 +403,7 @@ "Path to directory with input images": "Path to directory with input images", "Path to directory where to write outputs": "Path to directory where to write outputs", "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", - "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", + "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "このオプションを有効にすると、作成された画像にウォーターマークが追加されなくなります。警告:ウォーターマークを追加しない場合、非倫理的な行動とみなされる場合があります。", "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", "Restore low quality faces using GFPGAN neural network": "Restore low quality faces using GFPGAN neural network", "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", From 7043f4eff3913ac1ed0ae1621f622c90437c6843 Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Tue, 18 Oct 2022 15:57:39 +0900 Subject: [PATCH 0163/1118] improve ja translation --- localizations/ja_JP.json | 92 ++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index 11f747b4..87809d72 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -54,7 +54,7 @@ "Height": "高さ", "Restore faces": "顔修復", "Tiling": "テクスチャ生成モード", - "Highres. fix": "(※)高解像度 fix", + "Highres. fix": "高解像度 fix(マウスオーバーで詳細)", "Firstpass width": "Firstpass width", "Firstpass height": "Firstpass height", "Denoising strength": "ノイズ除去 強度", @@ -75,7 +75,7 @@ "Put variable parts at start of prompt": "Put variable parts at start of prompt", "Show Textbox": "Show Textbox", "File with inputs": "File with inputs", - "Prompts": "Prompts", + "Prompts": "プロンプト", "X type": "X type", "Nothing": "なし", "Var. seed": "Var. seed", @@ -123,26 +123,26 @@ "Inpaint not masked": "Inpaint not masked", "Masked content": "Masked content", "fill": "fill", - "original": "original", + "original": "オリジナル", "latent noise": "latent noise", "latent nothing": "latent nothing", "Inpaint at full resolution": "Inpaint at full resolution", "Inpaint at full resolution padding, pixels": "Inpaint at full resolution padding, pixels", "Process images in a directory on the same machine where the server is running.": "Process images in a directory on the same machine where the server is running.", "Use an empty output directory to save pictures normally instead of writing to the output directory.": "Use an empty output directory to save pictures normally instead of writing to the output directory.", - "Input directory": "Input directory", - "Output directory": "Output directory", + "Input directory": "入力フォルダ", + "Output directory": "出力フォルダ", "Resize mode": "リサイズモード", "Just resize": "リサイズのみ", "Crop and resize": "切り取ってからリサイズ", "Resize and fill": "リサイズして埋める", "img2img alternative test": "img2img alternative test", - "Loopback": "Loopback", + "Loopback": "ループバック", "Outpainting mk2": "Outpainting mk2", "Poor man's outpainting": "Poor man's outpainting", "SD upscale": "SD アップスケール", "should be 2 or lower.": "2以下にすること", - "Override `Sampling method` to Euler?(this method is built for it)": "Override `Sampling method` to Euler?(this method is built for it)", + "Override `Sampling method` to Euler?(this method is built for it)": "サンプリングアルゴリズムをEulerに上書きする(そうすることを前提に設計されています)", "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)", "Original prompt": "Original prompt", "Original negative prompt": "Original negative prompt", @@ -157,15 +157,15 @@ "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8", "Pixels to expand": "Pixels to expand", "Outpainting direction": "Outpainting direction", - "left": "left", - "right": "right", - "up": "up", - "down": "down", + "left": "左", + "right": "右", + "up": "上", + "down": "下", "Fall-off exponent (lower=higher detail)": "Fall-off exponent (lower=higher detail)", "Color variation": "Color variation", "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "Will upscale the image to twice the dimensions; use width and height sliders to set tile size", "Tile overlap": "Tile overlap", - "Upscaler": "Upscaler", + "Upscaler": "アップスケーラー", "Lanczos": "Lanczos", "LDSR": "LDSR", "BSRGAN 4x": "BSRGAN 4x", @@ -173,25 +173,25 @@ "ScuNET GAN": "ScuNET GAN", "ScuNET PSNR": "ScuNET PSNR", "SwinIR 4x": "SwinIR 4x", - "Single Image": "Single Image", - "Batch Process": "Batch Process", - "Batch from Directory": "Batch from Directory", - "Source": "Source", - "Show result images": "Show result images", - "Scale by": "Scale by", - "Scale to": "Scale to", - "Resize": "Resize", - "Crop to fit": "Crop to fit", - "Upscaler 2": "Upscaler 2", + "Single Image": "単一画像", + "Batch Process": "バッチ処理", + "Batch from Directory": "フォルダからバッチ処理", + "Source": "入力", + "Show result images": "出力画像を表示", + "Scale by": "倍率指定", + "Scale to": "解像度指定", + "Resize": "倍率", + "Crop to fit": "合うように切り抜き", + "Upscaler 2": "アップスケーラー 2", "Upscaler 2 visibility": "Upscaler 2 visibility", "GFPGAN visibility": "GFPGAN visibility", "CodeFormer visibility": "CodeFormer visibility", "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", "Open output directory": "出力フォルダを開く", - "Send to txt2img": "Send to txt2img", - "txt2img history": "txt2img history", - "img2img history": "img2img history", - "extras history": "extras history", + "Send to txt2img": "txt2imgに送る", + "txt2img history": "txt2imgの履歴", + "img2img history": "img2imgの履歴", + "extras history": "その他タブの履歴", "Renew Page": "更新", "First Page": "最初のぺージへ", "Prev Page": "前ページへ", @@ -200,8 +200,8 @@ "End Page": "最後のページへ", "number of images to delete consecutively next": "number of images to delete consecutively next", "Delete": "削除", - "Generate Info": "Generate Info", - "File Name": "File Name", + "Generate Info": "生成情報", + "File Name": "ファイル名", "set_index": "set_index", "A merger of the two checkpoints will be generated in your": "統合されたチェックポイントはあなたの", "checkpoint": "checkpoint", @@ -265,27 +265,27 @@ "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", "Do not add watermark to images": "電子透かしを画像に追加しない", "Paths for saving": "Paths for saving", - "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", - "Output directory for txt2img images": "Output directory for txt2img images", - "Output directory for img2img images": "Output directory for img2img images", - "Output directory for images from extras tab": "Output directory for images from extras tab", - "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", - "Output directory for txt2img grids": "Output directory for txt2img grids", - "Output directory for img2img grids": "Output directory for img2img grids", - "Directory for saving images using the Save button": "Directory for saving images using the Save button", - "Saving to a directory": "Saving to a directory", - "Save images to a subdirectory": "Save images to a subdirectory", - "Save grids to a subdirectory": "Save grids to a subdirectory", - "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", - "Directory name pattern": "Directory name pattern", + "Output directory for images; if empty, defaults to three directories below": "画像の保存先フォルダ(下項目のデフォルト値になります)", + "Output directory for txt2img images": "txt2imgで作った画像の保存先フォルダ", + "Output directory for img2img images": "img2imgで作った画像の保存先フォルダ", + "Output directory for images from extras tab": "その他タブで作った画像の保存先フォルダ", + "Output directory for grids; if empty, defaults to two directories below": "画像の保存先フォルダ(下項目のデフォルト値になります)", + "Output directory for txt2img grids": "txt2imgで作ったグリッドの保存先フォルダ", + "Output directory for img2img grids": "img2imgで作ったグリッドの保存先フォルダ", + "Directory for saving images using the Save button": "保存ボタンを押したときの画像の保存先フォルダ", + "Saving to a directory": "フォルダについて", + "Save images to a subdirectory": "画像をサブフォルダに保存する", + "Save grids to a subdirectory": "グリッドをサブフォルダに保存する", + "When using \"Save\" button, save images to a subdirectory": "保存ボタンを押した時、画像をサブフォルダに保存する", + "Directory name pattern": "フォルダ名のパターン", "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", - "Upscaling": "Upscaling", - "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", - "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", + "Upscaling": "アップスケール", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "ESRGANのタイルサイズ。0とするとタイルしない。", + "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "ESRGANのタイルの重複部分のピクセル数。少なくするとつなぎ目が見えやすくなる。", "Tile size for all SwinIR.": "Tile size for all SwinIR.", "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", - "Upscaler for img2img": "Upscaler for img2img", + "Upscaler for img2img": "img2imgで使うアップスケーラー", "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", "Face restoration": "Face restoration", "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", @@ -368,7 +368,7 @@ "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help", "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - best at inpainting", "Produce an image that can be tiled.": "Produce an image that can be tiled.", - "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "2ステップで、まず部分的に小さい解像度で画像を作成し、その後アップスケールすることで、構図を変えずにディテールが改善されます。", "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", "How many batches of images to create": "バッチ処理を何回行うか", "How many image to create in a single batch": "1回のバッチ処理で何枚の画像を生成するか", From 0262bf64ddbf85e05ddd120929138d3c5dac3bac Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Tue, 18 Oct 2022 16:38:31 +0900 Subject: [PATCH 0164/1118] improve ja translation --- localizations/ja_JP.json | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index 87809d72..fc958656 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -282,19 +282,19 @@ "Upscaling": "アップスケール", "Tile size for ESRGAN upscalers. 0 = no tiling.": "ESRGANのタイルサイズ。0とするとタイルしない。", "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "ESRGANのタイルの重複部分のピクセル数。少なくするとつなぎ目が見えやすくなる。", - "Tile size for all SwinIR.": "Tile size for all SwinIR.", - "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", + "Tile size for all SwinIR.": "SwinIRのタイルサイズ", + "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "SwinIRのタイルの重複部分のピクセル数。少なくするとつなぎ目が見えやすくなる。", "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", "Upscaler for img2img": "img2imgで使うアップスケーラー", - "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", - "Face restoration": "Face restoration", - "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", + "Upscale latent space image when doing hires. fix": "高解像度 fix時に潜在空間(latent space)の画像をアップスケールする", + "Face restoration": "顔修復", + "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormerの重みパラメーター;0が最大で1が最小", "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", - "System": "System", + "System": "システム設定", "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", - "Always print all generation info to standard output": "Always print all generation info to standard output", + "Always print all generation info to standard output": "常にすべての生成に関する情報を標準出力(stdout)に出力する", "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", - "Training": "Training", + "Training": "学習", "Unload VAE and CLIP from VRAM when training": "Unload VAE and CLIP from VRAM when training", "Filename word regex": "Filename word regex", "Filename join string": "Filename join string", @@ -307,15 +307,15 @@ "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", - "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", - "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", + "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "強調: (text)とするとモデルはtextをより強く扱い、[text]とするとモデルはtextをより弱く扱います。", + "Use old emphasis implementation. Can be useful to reproduce old seeds.": "古い強調の実装を使う。古い生成物を再現するのに使えます。", "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", "Filter NSFW content": "Filter NSFW content", - "Stop At last layers of CLIP model": "Stop At last layers of CLIP model", - "Interrogate Options": "Interrogate Options", - "Interrogate: keep models in VRAM": "Interrogate: keep models in VRAM", - "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", + "Stop At last layers of CLIP model": "最後から何層目でCLIPを止めるか(stop…layers of CLIP model)", + "Interrogate Options": "Interrogate 設定", + "Interrogate: keep models in VRAM": "Interrogate: モデルをVRAMに保持する", + "Interrogate: use artists from artists.csv": "Interrogate: artists.csvにある芸術家などの名称を利用する", "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).", "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", "Interrogate: minimum description length (excluding artists, etc..)": "Interrogate: minimum description length (excluding artists, etc..)", @@ -337,9 +337,9 @@ "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", "Show generation progress in window title.": "Show generation progress in window title.", "Quicksettings list": "Quicksettings list", - "Localization (requires restart)": "Localization (requires restart)", + "Localization (requires restart)": "言語 (プログラムの再起動が必要)", "ja_JP": "ja_JP", - "Sampler parameters": "Sampler parameters", + "Sampler parameters": "サンプラー parameters", "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", @@ -350,16 +350,16 @@ "sigma tmin": "sigma tmin", "sigma noise": "sigma noise", "Eta noise seed delta": "Eta noise seed delta", - "Request browser notifications": "Request browser notifications", - "Download localization template": "Download localization template", - "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", - "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", + "Request browser notifications": "ブラウザ通知の許可を要求する", + "Download localization template": "ローカライゼーション用のテンプレートをダウンロードする", + "Reload custom script bodies (No ui updates, No restart)": "カスタムスクリプトを再読み込み (UIは変更されず、再起動もしません。)", + "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Gradioを再起動してコンポーネントをリフレッシュする (Custom Scripts, ui.py, js, cssのみ影響を受ける)", "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "ネガティブ プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", "Add a random artist to the prompt.": "Add a random artist to the prompt.", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", - "Save style": "Save style", - "Apply selected styles to current prompt": "Apply selected styles to current prompt", + "Save style": "スタイルを保存する", + "Apply selected styles to current prompt": "現在のプロンプトに選択したスタイルを適用する", "Stop processing current image and continue processing.": "Stop processing current image and continue processing.", "Stop processing images and return any results accumulated so far.": "Stop processing images and return any results accumulated so far.", "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", @@ -379,8 +379,8 @@ "Seed of a different picture to be mixed into the generation.": "Seed of a different picture to be mixed into the generation.", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).", "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution", - "Separate values for X axis using commas.": "Separate values for X axis using commas.", - "Separate values for Y axis using commas.": "Separate values for Y axis using commas.", + "Separate values for X axis using commas.": "X軸に用いる値をカンマ(,)で区切って入力してください。", + "Separate values for Y axis using commas.": "Y軸に用いる値をカンマ(,)で区切って入力してください。", "Write image to a directory (default - log/images) and generation parameters into csv file.": "Write image to a directory (default - log/images) and generation parameters into csv file.", "Open images output directory": "画像の出力フォルダを開く", "How much to blur the mask before processing, in pixels.": "How much to blur the mask before processing, in pixels.", From f613c6b8c532dbcfb3570cdbb3ce56a0d2821d0b Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Tue, 18 Oct 2022 23:01:41 +0900 Subject: [PATCH 0165/1118] improve ja translation --- localizations/ja_JP.json | 66 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index fc958656..a9f6cb20 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -163,7 +163,7 @@ "down": "下", "Fall-off exponent (lower=higher detail)": "Fall-off exponent (lower=higher detail)", "Color variation": "Color variation", - "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "Will upscale the image to twice the dimensions; use width and height sliders to set tile size", + "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "画像を2倍の大きさにアップスケールします。幅と高さのスライダーでタイルの大きさを設定します。", "Tile overlap": "Tile overlap", "Upscaler": "アップスケーラー", "Lanczos": "Lanczos", @@ -210,10 +210,10 @@ "Secondary model (B)": "2つめのmodel (B)", "Tertiary model (C)": "3つめのmodel (C)", "Custom Name (Optional)": "Custom Name (任意)", - "Multiplier (M) - set to 0 to get model A": "Multiplier (M) 0にすると完全にmodel Aとなります", - "Interpolation Method": "Interpolation Method", - "Weighted sum": "Weighted sum", - "Add difference": "Add difference", + "Multiplier (M) - set to 0 to get model A": "Multiplier (M) 0にすると完全にmodel Aとなります (ツールチップ参照)", + "Interpolation Method": "混合(Interpolation)方式", + "Weighted sum": "加重平均", + "Add difference": "差を加える", "Save as float16": "float16で保存", "See": "詳細な説明については", "wiki": "wiki", @@ -289,7 +289,7 @@ "Upscale latent space image when doing hires. fix": "高解像度 fix時に潜在空間(latent space)の画像をアップスケールする", "Face restoration": "顔修復", "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormerの重みパラメーター;0が最大で1が最小", - "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", + "Move face restoration model from VRAM into RAM after processing": "処理終了後、顔修復モデルをVRAMからRAMへと移動する", "System": "システム設定", "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", "Always print all generation info to standard output": "常にすべての生成に関する情報を標準出力(stdout)に出力する", @@ -301,9 +301,9 @@ "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", "Stable Diffusion": "Stable Diffusion", - "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", + "Checkpoints to cache in RAM": "RAMにキャッシュするCheckpoint数", "Hypernetwork strength": "Hypernetwork strength", - "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", + "Apply color correction to img2img results to match original colors.": "元画像に合わせてimg2imgの結果を色補正する", "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", @@ -325,24 +325,24 @@ "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooru sort alphabetically", "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", - "User interface": "User interface", - "Show progressbar": "Show progressbar", - "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", - "Show grid in results for web": "Show grid in results for web", - "Do not show any images in results for web": "Do not show any images in results for web", - "Add model hash to generation information": "Add model hash to generation information", - "Add model name to generation information": "Add model name to generation information", - "Font for image grids that have text": "Font for image grids that have text", - "Enable full page image viewer": "Enable full page image viewer", - "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", - "Show generation progress in window title.": "Show generation progress in window title.", + "User interface": "UI設定", + "Show progressbar": "プログレスバーを表示", + "Show image creation progress every N sampling steps. Set 0 to disable.": "指定したステップ数ごとに画像の生成過程を表示する。0で無効化。", + "Show grid in results for web": "WebUI上でグリッド表示", + "Do not show any images in results for web": "WebUI上で一切画像を表示しない", + "Add model hash to generation information": "モデルのハッシュ値を生成情報に追加", + "Add model name to generation information": "モデルの名称を生成情報に追加", + "Font for image grids that have text": "画像グリッド内のテキストフォント", + "Enable full page image viewer": "フルページの画像ビューワーを有効化", + "Show images zoomed in by default in full page image viewer": "フルページ画像ビューアでデフォルトで画像を拡大して表示する", + "Show generation progress in window title.": "ウィンドウのタイトルで生成の進捗を表示", "Quicksettings list": "Quicksettings list", "Localization (requires restart)": "言語 (プログラムの再起動が必要)", "ja_JP": "ja_JP", "Sampler parameters": "サンプラー parameters", - "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", - "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", - "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", + "Hide samplers in user interface (requires restart)": "使わないサンプリングアルゴリズムを隠す (再起動が必要)", + "eta (noise multiplier) for DDIM": "DDIMで用いるeta (noise multiplier)", + "eta (noise multiplier) for ancestral samplers": "ancestral サンプラーで用いるeta (noise multiplier)", "img2img DDIM discretize": "img2img DDIM discretize", "uniform": "uniform", "quad": "quad", @@ -356,17 +356,17 @@ "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Gradioを再起動してコンポーネントをリフレッシュする (Custom Scripts, ui.py, js, cssのみ影響を受ける)", "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "ネガティブ プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", - "Add a random artist to the prompt.": "Add a random artist to the prompt.", + "Add a random artist to the prompt.": "芸術家などの名称をプロンプトに追加", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", "Save style": "スタイルを保存する", "Apply selected styles to current prompt": "現在のプロンプトに選択したスタイルを適用する", - "Stop processing current image and continue processing.": "Stop processing current image and continue processing.", - "Stop processing images and return any results accumulated so far.": "Stop processing images and return any results accumulated so far.", + "Stop processing current image and continue processing.": "現在の処理を中断し、その後の処理は続ける", + "Stop processing images and return any results accumulated so far.": "処理を中断し、それまでに出来た結果を表示する", "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", - "Do not do anything special": "Do not do anything special", - "Which algorithm to use to produce the image": "Which algorithm to use to produce the image", - "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help", - "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - best at inpainting", + "Do not do anything special": "特別なことをなにもしない", + "Which algorithm to use to produce the image": "どのアルゴリズムを使って生成するか", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 非常に独創的で、ステップ数によって全く異なる画像が得られる、ステップ数を30~40より高く設定しても効果がない。", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 描き直しには最適", "Produce an image that can be tiled.": "Produce an image that can be tiled.", "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "2ステップで、まず部分的に小さい解像度で画像を作成し、その後アップスケールすることで、構図を変えずにディテールが改善されます。", "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", @@ -397,15 +397,15 @@ "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.", "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.", "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", - "Leave blank to save images to the default path.": "Leave blank to save images to the default path.", - "Result = A * (1 - M) + B * M": "Result = A * (1 - M) + B * M", - "Result = A + (B - C) * M": "Result = A + (B - C) * M", + "Leave blank to save images to the default path.": "空欄でデフォルトの場所へ画像を保存", + "Result = A * (1 - M) + B * M": "結果モデル = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "結果モデル = A + (B - C) * M", "Path to directory with input images": "Path to directory with input images", "Path to directory where to write outputs": "Path to directory where to write outputs", "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "このオプションを有効にすると、作成された画像にウォーターマークが追加されなくなります。警告:ウォーターマークを追加しない場合、非倫理的な行動とみなされる場合があります。", "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", - "Restore low quality faces using GFPGAN neural network": "Restore low quality faces using GFPGAN neural network", + "Restore low quality faces using GFPGAN neural network": "GFPGANを用いて低クオリティーの画像を修復", "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", From 774be6d2f271415a82d9a83147e8ee8bbad018d0 Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Tue, 18 Oct 2022 23:29:34 +0900 Subject: [PATCH 0166/1118] improve ja translation --- localizations/ja_JP.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index a9f6cb20..514b579e 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -76,7 +76,7 @@ "Show Textbox": "Show Textbox", "File with inputs": "File with inputs", "Prompts": "プロンプト", - "X type": "X type", + "X type": "X軸の種類", "Nothing": "なし", "Var. seed": "Var. seed", "Var. strength": "Var. 強度", @@ -94,12 +94,12 @@ "Eta": "Eta", "Clip skip": "Clip skip", "Denoising": "Denoising", - "X values": "X values", - "Y type": "Y type", - "Y values": "Y values", - "Draw legend": "Draw legend", + "X values": "Xの値", + "Y type": "Y軸の種類", + "Y values": "Yの値", + "Draw legend": "凡例を描画", "Include Separate Images": "Include Separate Images", - "Keep -1 for seeds": "Keep -1 for seeds", + "Keep -1 for seeds": "シード値を-1で固定", "ここに画像をドロップ": "ここに画像をドロップ", "Save": "保存", "Send to img2img": "img2imgに送る", @@ -295,7 +295,7 @@ "Always print all generation info to standard output": "常にすべての生成に関する情報を標準出力(stdout)に出力する", "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", "Training": "学習", - "Unload VAE and CLIP from VRAM when training": "Unload VAE and CLIP from VRAM when training", + "Unload VAE and CLIP from VRAM when training": "学習を行う際、VAEとCLIPをVRAMから削除する", "Filename word regex": "Filename word regex", "Filename join string": "Filename join string", "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", @@ -304,14 +304,14 @@ "Checkpoints to cache in RAM": "RAMにキャッシュするCheckpoint数", "Hypernetwork strength": "Hypernetwork strength", "Apply color correction to img2img results to match original colors.": "元画像に合わせてimg2imgの結果を色補正する", - "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", - "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", + "Save a copy of image before applying color correction to img2img results": "色補正をする前の画像も保存する", + "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "img2imgでスライダーで指定されたステップ数を正確に実行する(通常は、ノイズ除去を少なくするためにより少ないステップ数で実行します)。", "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "強調: (text)とするとモデルはtextをより強く扱い、[text]とするとモデルはtextをより弱く扱います。", "Use old emphasis implementation. Can be useful to reproduce old seeds.": "古い強調の実装を使う。古い生成物を再現するのに使えます。", "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", - "Filter NSFW content": "Filter NSFW content", + "Filter NSFW content": "NSFW(≒R-18)なコンテンツを検閲する", "Stop At last layers of CLIP model": "最後から何層目でCLIPを止めるか(stop…layers of CLIP model)", "Interrogate Options": "Interrogate 設定", "Interrogate: keep models in VRAM": "Interrogate: モデルをVRAMに保持する", @@ -321,10 +321,10 @@ "Interrogate: minimum description length (excluding artists, etc..)": "Interrogate: minimum description length (excluding artists, etc..)", "Interrogate: maximum description length": "Interrogate: maximum description length", "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", - "Interrogate: deepbooru score threshold": "Interrogate: deepbooru score threshold", - "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooru sort alphabetically", - "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", - "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "Interrogate: deepbooru score threshold": "Interrogate: deepbooruで拾う単語のスコア閾値", + "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooruで単語をアルファベット順に並べる", + "use spaces for tags in deepbooru": "deepbooruのタグでスペースを使う", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "deepbooruで括弧をエスケープする(\\) (強調を示す()ではなく、文字通りの()であることをモデルに示すため)", "User interface": "UI設定", "Show progressbar": "プログレスバーを表示", "Show image creation progress every N sampling steps. Set 0 to disable.": "指定したステップ数ごとに画像の生成過程を表示する。0で無効化。", From 324c7c732dd9afc3d4c397c354797ae5d655b514 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 20:09:37 +0300 Subject: [PATCH 0167/1118] record First pass size as 0x0 for #3328 --- modules/processing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 372489f7..27c669b0 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -524,6 +524,8 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): else: state.job_count = state.job_count * 2 + self.extra_generation_params["First pass size"] = f"{self.firstphase_width}x{self.firstphase_height}" + if self.firstphase_width == 0 or self.firstphase_height == 0: desired_pixel_count = 512 * 512 actual_pixel_count = self.width * self.height @@ -545,7 +547,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): firstphase_width_truncated = self.firstphase_height * self.width / self.height firstphase_height_truncated = self.firstphase_height - self.extra_generation_params["First pass size"] = f"{self.firstphase_width}x{self.firstphase_height}" self.truncate_x = int(self.firstphase_width - firstphase_width_truncated) // opt_f self.truncate_y = int(self.firstphase_height - firstphase_height_truncated) // opt_f From 0df94d3fcf9d1fc47c4d39039352a3d5b3380c1f Mon Sep 17 00:00:00 2001 From: MrCheeze Date: Sat, 22 Oct 2022 12:59:21 -0400 Subject: [PATCH 0168/1118] fix aesthetic gradients doing nothing after loading a different model --- modules/sd_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index f9b3063d..49dc3238 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -236,12 +236,11 @@ def load_model(checkpoint_info=None): sd_model.to(shared.device) sd_hijack.model_hijack.hijack(sd_model) + script_callbacks.model_loaded_callback(sd_model) sd_model.eval() shared.sd_model = sd_model - script_callbacks.model_loaded_callback(sd_model) - print(f"Model loaded.") return sd_model @@ -268,6 +267,7 @@ def reload_model_weights(sd_model, info=None): load_model_weights(sd_model, checkpoint_info) sd_hijack.model_hijack.hijack(sd_model) + script_callbacks.model_loaded_callback(sd_model) if not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram: sd_model.to(devices.device) From 321bacc6a9eaf4a25f31279f288fa752be507a20 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 20:15:12 +0300 Subject: [PATCH 0169/1118] call model_loaded_callback after setting shared.sd_model in case scripts refer to it using that --- modules/sd_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 49dc3238..e697bb72 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -236,11 +236,12 @@ def load_model(checkpoint_info=None): sd_model.to(shared.device) sd_hijack.model_hijack.hijack(sd_model) - script_callbacks.model_loaded_callback(sd_model) sd_model.eval() shared.sd_model = sd_model + script_callbacks.model_loaded_callback(sd_model) + print(f"Model loaded.") return sd_model From 24694e5983d0944b901892cb101878e6dec89a20 Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Sun, 23 Oct 2022 01:57:58 +0900 Subject: [PATCH 0170/1118] Update hypernetwork.py --- modules/hypernetworks/hypernetwork.py | 55 +++++++++++++++++++++------ 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 3bc71ee5..81132be4 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -16,6 +16,7 @@ from modules.textual_inversion import textual_inversion from modules.textual_inversion.learn_schedule import LearnRateScheduler from torch import einsum +from statistics import stdev, mean class HypernetworkModule(torch.nn.Module): multiplier = 1.0 @@ -268,6 +269,32 @@ def stack_conds(conds): return torch.stack(conds) +def log_statistics(loss_info:dict, key, value): + if key not in loss_info: + loss_info[key] = [value] + else: + loss_info[key].append(value) + if len(loss_info) > 1024: + loss_info.pop(0) + + +def statistics(data): + total_information = f"loss:{mean(data):.3f}"+u"\u00B1"+f"({stdev(data)/ (len(data)**0.5):.3f})" + recent_data = data[-32:] + recent_information = f"recent 32 loss:{mean(recent_data):.3f}"+u"\u00B1"+f"({stdev(recent_data)/ (len(recent_data)**0.5):.3f})" + return total_information, recent_information + + +def report_statistics(loss_info:dict): + keys = sorted(loss_info.keys(), key=lambda x: sum(loss_info[x]) / len(loss_info[x])) + for key in keys: + info, recent = statistics(loss_info[key]) + print("Loss statistics for file " + key) + print(info) + print(recent) + + + def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # images allows training previews to have infotext. Importing it at the top causes a circular import problem. from modules import images @@ -310,7 +337,11 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log for weight in weights: weight.requires_grad = True - losses = torch.zeros((32,)) + size = len(ds.indexes) + loss_dict = {} + losses = torch.zeros((size,)) + previous_mean_loss = 0 + print("Mean loss of {} elements".format(size)) last_saved_file = "" last_saved_image = "" @@ -329,7 +360,9 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log pbar = tqdm.tqdm(enumerate(ds), total=steps - ititial_step) for i, entries in pbar: hypernetwork.step = i + ititial_step - + if loss_dict and i % size == 0: + previous_mean_loss = sum(i[-1] for i in loss_dict.values()) / len(loss_dict) + scheduler.apply(optimizer, hypernetwork.step) if scheduler.finished: break @@ -346,7 +379,9 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log del c losses[hypernetwork.step % losses.shape[0]] = loss.item() - + for entry in entries: + log_statistics(loss_dict, entry.filename, loss.item()) + optimizer.zero_grad() weights[0].grad = None loss.backward() @@ -359,10 +394,9 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log optimizer.step() - mean_loss = losses.mean() - if torch.isnan(mean_loss): + if torch.isnan(losses[hypernetwork.step % losses.shape[0]]): raise RuntimeError("Loss diverged.") - pbar.set_description(f"loss: {mean_loss:.7f}") + pbar.set_description(f"dataset loss: {previous_mean_loss:.7f}") if hypernetwork.step > 0 and hypernetwork_dir is not None and hypernetwork.step % save_hypernetwork_every == 0: # Before saving, change name to match current checkpoint. @@ -371,7 +405,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log hypernetwork.save(last_saved_file) textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, len(ds), { - "loss": f"{mean_loss:.7f}", + "loss": f"{previous_mean_loss:.7f}", "learn_rate": scheduler.learn_rate }) @@ -420,14 +454,15 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log shared.state.textinfo = f"""

-Loss: {mean_loss:.7f}
+Loss: {previous_mean_loss:.7f}
Step: {hypernetwork.step}
Last prompt: {html.escape(entries[0].cond_text)}
Last saved hypernetwork: {html.escape(last_saved_file)}
Last saved image: {html.escape(last_saved_image)}

""" - + + report_statistics(loss_dict) checkpoint = sd_models.select_checkpoint() hypernetwork.sd_checkpoint = checkpoint.hash @@ -438,5 +473,3 @@ Last saved image: {html.escape(last_saved_image)}
hypernetwork.save(filename) return hypernetwork, filename - - From 4fdb53c1e9962507fc8336dad9a0fabfe6c418c0 Mon Sep 17 00:00:00 2001 From: Unnoen Date: Wed, 19 Oct 2022 21:38:10 +1100 Subject: [PATCH 0171/1118] Generate grid preview for progress image --- modules/sd_samplers.py | 26 +++++++++++++++++++++++++- modules/shared.py | 1 + modules/ui.py | 5 ++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index f58a29b9..74a480e5 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -7,7 +7,7 @@ import inspect import k_diffusion.sampling import ldm.models.diffusion.ddim import ldm.models.diffusion.plms -from modules import prompt_parser, devices, processing +from modules import prompt_parser, devices, processing, images from modules.shared import opts, cmd_opts, state import modules.shared as shared @@ -89,6 +89,30 @@ def sample_to_image(samples): x_sample = x_sample.astype(np.uint8) return Image.fromarray(x_sample) +def samples_to_image_grid(samples): + progress_images = [] + for i in range(len(samples)): + # Decode the samples individually to reduce VRAM usage at the cost of a bit of speed. + x_sample = processing.decode_first_stage(shared.sd_model, samples[i:i+1])[0] + x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) + x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) + x_sample = x_sample.astype(np.uint8) + progress_images.append(Image.fromarray(x_sample)) + + return images.image_grid(progress_images) + +def samples_to_image_grid_combined(samples): + progress_images = [] + # Decode all samples at once to increase speed at the cost of VRAM usage. + x_samples = processing.decode_first_stage(shared.sd_model, samples) + x_samples = torch.clamp((x_samples + 1.0) / 2.0, min=0.0, max=1.0) + + for x_sample in x_samples: + x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) + x_sample = x_sample.astype(np.uint8) + progress_images.append(Image.fromarray(x_sample)) + + return images.image_grid(progress_images) def store_latent(decoded): state.current_latent = decoded diff --git a/modules/shared.py b/modules/shared.py index d9cb65ef..95d6e225 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -294,6 +294,7 @@ options_templates.update(options_section(('interrogate', "Interrogate Options"), options_templates.update(options_section(('ui', "User interface"), { "show_progressbar": OptionInfo(True, "Show progressbar"), "show_progress_every_n_steps": OptionInfo(0, "Show image creation progress every N sampling steps. Set 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 32, "step": 1}), + "progress_decode_combined": OptionInfo(False, "Decode all progress images at once. (Slighty speeds up progress generation but consumes significantly more VRAM with large batches.)"), "return_grid": OptionInfo(True, "Show grid in results for web"), "do_not_show_images": OptionInfo(False, "Do not show any images in results for web"), "add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"), diff --git a/modules/ui.py b/modules/ui.py index 56c233ab..de0abc7e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -318,7 +318,10 @@ def check_progress_call(id_part): if shared.parallel_processing_allowed: if shared.state.sampling_step - shared.state.current_image_sampling_step >= opts.show_progress_every_n_steps and shared.state.current_latent is not None: - shared.state.current_image = modules.sd_samplers.sample_to_image(shared.state.current_latent) + if opts.progress_decode_combined: + shared.state.current_image = modules.sd_samplers.samples_to_image_grid_combined(shared.state.current_latent) + else: + shared.state.current_image = modules.sd_samplers.samples_to_image_grid(shared.state.current_latent) shared.state.current_image_sampling_step = shared.state.sampling_step image = shared.state.current_image From d213d6ca6f90094cb45c11e2f3cb37d25a8d1f94 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 20:48:13 +0300 Subject: [PATCH 0172/1118] removed the option to use 2x more memory when generating previews added an option to always only show one image in previews removed duplicate code --- modules/sd_samplers.py | 33 +++++++++------------------------ modules/shared.py | 2 +- modules/ui.py | 6 +++--- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 74a480e5..0b408a70 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -71,6 +71,7 @@ sampler_extra_params = { 'sample_dpm_2': ['s_churn', 's_tmin', 's_tmax', 's_noise'], } + def setup_img2img_steps(p, steps=None): if opts.img2img_fix_steps or steps is not None: steps = int((steps or p.steps) / min(p.denoising_strength, 0.999)) if p.denoising_strength > 0 else 0 @@ -82,37 +83,21 @@ def setup_img2img_steps(p, steps=None): return steps, t_enc -def sample_to_image(samples): - x_sample = processing.decode_first_stage(shared.sd_model, samples[0:1])[0] +def single_sample_to_image(sample): + x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) x_sample = x_sample.astype(np.uint8) return Image.fromarray(x_sample) + +def sample_to_image(samples): + return single_sample_to_image(samples[0]) + + def samples_to_image_grid(samples): - progress_images = [] - for i in range(len(samples)): - # Decode the samples individually to reduce VRAM usage at the cost of a bit of speed. - x_sample = processing.decode_first_stage(shared.sd_model, samples[i:i+1])[0] - x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) - x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) - x_sample = x_sample.astype(np.uint8) - progress_images.append(Image.fromarray(x_sample)) + return images.image_grid([single_sample_to_image(sample) for sample in samples]) - return images.image_grid(progress_images) - -def samples_to_image_grid_combined(samples): - progress_images = [] - # Decode all samples at once to increase speed at the cost of VRAM usage. - x_samples = processing.decode_first_stage(shared.sd_model, samples) - x_samples = torch.clamp((x_samples + 1.0) / 2.0, min=0.0, max=1.0) - - for x_sample in x_samples: - x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) - x_sample = x_sample.astype(np.uint8) - progress_images.append(Image.fromarray(x_sample)) - - return images.image_grid(progress_images) def store_latent(decoded): state.current_latent = decoded diff --git a/modules/shared.py b/modules/shared.py index 95d6e225..25bfc895 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -294,7 +294,7 @@ options_templates.update(options_section(('interrogate', "Interrogate Options"), options_templates.update(options_section(('ui', "User interface"), { "show_progressbar": OptionInfo(True, "Show progressbar"), "show_progress_every_n_steps": OptionInfo(0, "Show image creation progress every N sampling steps. Set 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 32, "step": 1}), - "progress_decode_combined": OptionInfo(False, "Decode all progress images at once. (Slighty speeds up progress generation but consumes significantly more VRAM with large batches.)"), + "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "return_grid": OptionInfo(True, "Show grid in results for web"), "do_not_show_images": OptionInfo(False, "Do not show any images in results for web"), "add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"), diff --git a/modules/ui.py b/modules/ui.py index de0abc7e..ffa14cac 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -318,10 +318,10 @@ def check_progress_call(id_part): if shared.parallel_processing_allowed: if shared.state.sampling_step - shared.state.current_image_sampling_step >= opts.show_progress_every_n_steps and shared.state.current_latent is not None: - if opts.progress_decode_combined: - shared.state.current_image = modules.sd_samplers.samples_to_image_grid_combined(shared.state.current_latent) - else: + if opts.show_progress_grid: shared.state.current_image = modules.sd_samplers.samples_to_image_grid(shared.state.current_latent) + else: + shared.state.current_image = modules.sd_samplers.sample_to_image(shared.state.current_latent) shared.state.current_image_sampling_step = shared.state.sampling_step image = shared.state.current_image From be748e8b086bd9834d08bdd9160649a5e7700af7 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 22:05:22 +0300 Subject: [PATCH 0173/1118] add --freeze-settings commandline argument to disable changing settings --- modules/shared.py | 1 + modules/ui.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 25bfc895..b55371d3 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -64,6 +64,7 @@ parser.add_argument("--port", type=int, help="launch gradio with given server po parser.add_argument("--show-negative-prompt", action='store_true', help="does not do anything", default=False) parser.add_argument("--ui-config-file", type=str, help="filename to use for ui configuration", default=os.path.join(script_path, 'ui-config.json')) parser.add_argument("--hide-ui-dir-config", action='store_true', help="hide directory configuration from webui", default=False) +parser.add_argument("--freeze-settings", action='store_true', help="disable editing settings", default=False) parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui settings", default=os.path.join(script_path, 'config.json')) parser.add_argument("--gradio-debug", action='store_true', help="launch gradio with --debug option") parser.add_argument("--gradio-auth", type=str, help='set gradio authentication like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None) diff --git a/modules/ui.py b/modules/ui.py index ffa14cac..2311572c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -580,6 +580,9 @@ def apply_setting(key, value): if value is None: return gr.update() + if shared.cmd_opts.freeze_settings: + return gr.update() + # dont allow model to be swapped when model hash exists in prompt if key == "sd_model_checkpoint" and opts.disable_weights_auto_swap: return gr.update() @@ -1501,6 +1504,8 @@ Requested path was: {f} def run_settings(*args): changed = 0 + assert not shared.cmd_opts.freeze_settings, "changing settings is disabled" + for key, value, comp in zip(opts.data_labels.keys(), args, components): if comp != dummy_component and not opts.same_type(value, opts.data_labels[key].default): return f"Bad value for setting {key}: {value}; expecting {type(opts.data_labels[key].default).__name__}", opts.dumpjson() @@ -1530,6 +1535,8 @@ Requested path was: {f} return f'{changed} settings changed.', opts.dumpjson() def run_settings_single(value, key): + assert not shared.cmd_opts.freeze_settings, "changing settings is disabled" + if not opts.same_type(value, opts.data_labels[key].default): return gr.update(visible=True), opts.dumpjson() @@ -1582,7 +1589,7 @@ Requested path was: {f} elem_id, text = item.section gr.HTML(elem_id="settings_header_text_{}".format(elem_id), value='

{}

'.format(text)) - if k in quicksettings_names: + if k in quicksettings_names and not shared.cmd_opts.freeze_settings: quicksettings_list.append((i, k, item)) components.append(dummy_component) else: @@ -1615,7 +1622,7 @@ Requested path was: {f} def reload_scripts(): modules.scripts.reload_script_body_only() - reload_javascript() # need to refresh the html page + reload_javascript() # need to refresh the html page reload_script_bodies.click( fn=reload_scripts, From ca5a9e79dc28eeaa3a161427a82e34703bf15765 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 22 Oct 2022 22:06:54 +0300 Subject: [PATCH 0174/1118] fix for img2img color correction in a batch #3218 --- modules/processing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 27c669b0..b1877b80 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -403,8 +403,6 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if (len(prompts) == 0): break - #uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt]) - #c = p.sd_model.get_learned_conditioning(prompts) with devices.autocast(): uc = prompt_parser.get_learned_conditioning(shared.sd_model, len(prompts) * [p.negative_prompt], p.steps) c = prompt_parser.get_multicond_learned_conditioning(shared.sd_model, prompts, p.steps) @@ -716,6 +714,10 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): batch_images = np.expand_dims(imgs[0], axis=0).repeat(self.batch_size, axis=0) if self.overlay_images is not None: self.overlay_images = self.overlay_images * self.batch_size + + if self.color_corrections is not None and len(self.color_corrections) == 1: + self.color_corrections = self.color_corrections * self.batch_size + elif len(imgs) <= self.batch_size: self.batch_size = len(imgs) batch_images = np.array(imgs) From 48dbf99e84045ee7af55bc5b1b86492a240e631e Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Sun, 23 Oct 2022 04:17:16 +0900 Subject: [PATCH 0175/1118] Allow tracking real-time loss Someone had 6000 images in their dataset, and it was shown as 0, which was confusing. This will allow tracking real time dataset-average loss for registered objects. --- modules/hypernetworks/hypernetwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 81132be4..99fd0f8f 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -360,7 +360,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log pbar = tqdm.tqdm(enumerate(ds), total=steps - ititial_step) for i, entries in pbar: hypernetwork.step = i + ititial_step - if loss_dict and i % size == 0: + if len(loss_dict) > 0: previous_mean_loss = sum(i[-1] for i in loss_dict.values()) / len(loss_dict) scheduler.apply(optimizer, hypernetwork.step) From ce42879438bf2dbd76b5b346be656292e42ffb2b Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 22 Oct 2022 14:53:37 -0500 Subject: [PATCH 0176/1118] fix js func signature and not forget to initialize confirmation var to prevent exception upon cancelling confirmation --- javascript/ui.js | 7 ++++--- modules/ui.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 6c99824b..39011079 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -162,12 +162,13 @@ function selected_tab_id() { } -function clear_prompt(_, _prompt_neg, confirmed,_steps) { +function clear_prompt(_, _prompt_neg, confirmed, _token_counter) { +confirmed = false if(confirm("Delete prompt?")) { confirmed = true } else { -return [_, confirmed] +return [_, _prompt_neg, confirmed, _token_counter] } if(selected_tab_id() == "tab_txt2img") { @@ -176,7 +177,7 @@ return [_, confirmed] update_token_counter("txt2img_token_button") } - return [_, _prompt_neg, confirmed,_steps] + return [_, _prompt_neg, confirmed, _token_counter] } diff --git a/modules/ui.py b/modules/ui.py index 25aeba3b..e58f040e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -429,10 +429,12 @@ def create_seed_inputs(): return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox -def clear_prompt(_prompt, _prompt_neg, confirmed, _token_counter): +def clear_prompt(prompt, _prompt_neg, confirmed, _token_counter): """Given confirmation from a user on the client-side, go ahead with clearing prompt""" if confirmed: return ["", "", confirmed, update_token_counter("", 1)] + else: + return [prompt, _prompt_neg, confirmed, _token_counter] def connect_clear_prompt(button, prompt, prompt_neg, _dummy_confirmed, token_counter): From 1b4d04737ac513cbd55958bb60a4f85166f3484b Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sat, 22 Oct 2022 20:13:16 -0300 Subject: [PATCH 0177/1118] Remove unused imports --- modules/api/api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 5b0c934e..a5136b4b 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -1,11 +1,9 @@ from modules.api.processing import StableDiffusionProcessingAPI from modules.processing import StableDiffusionProcessingTxt2Img, process_images from modules.sd_samplers import all_samplers -from modules.extras import run_pnginfo import modules.shared as shared import uvicorn -from fastapi import Body, APIRouter, HTTPException -from fastapi.responses import JSONResponse +from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field, Json import json import io @@ -18,7 +16,6 @@ class TextToImageResponse(BaseModel): parameters: Json info: Json - class Api: def __init__(self, app, queue_lock): self.router = APIRouter() From b02926df1393df311db734af149fb9faf4389cbe Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sat, 22 Oct 2022 20:24:04 -0300 Subject: [PATCH 0178/1118] Moved moodels to their own file and extracted base64 conversion to its own function --- modules/api/api.py | 17 ++++++----------- modules/api/models.py | 8 ++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 modules/api/models.py diff --git a/modules/api/api.py b/modules/api/api.py index a5136b4b..c17d7580 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -4,17 +4,17 @@ from modules.sd_samplers import all_samplers import modules.shared as shared import uvicorn from fastapi import APIRouter, HTTPException -from pydantic import BaseModel, Field, Json import json import io import base64 +from modules.api.models import * sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None) -class TextToImageResponse(BaseModel): - images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") - parameters: Json - info: Json +def img_to_base64(img): + buffer = io.BytesIO() + img.save(buffer, format="png") + return base64.b64encode(buffer.getvalue()) class Api: def __init__(self, app, queue_lock): @@ -41,15 +41,10 @@ class Api: with self.queue_lock: processed = process_images(p) - b64images = [] - for i in processed.images: - buffer = io.BytesIO() - i.save(buffer, format="png") - b64images.append(base64.b64encode(buffer.getvalue())) + b64images = list(map(img_to_base64, processed.images)) return TextToImageResponse(images=b64images, parameters=json.dumps(vars(txt2imgreq)), info=json.dumps(processed.info)) - def img2imgapi(self): raise NotImplementedError diff --git a/modules/api/models.py b/modules/api/models.py new file mode 100644 index 00000000..a7d247d8 --- /dev/null +++ b/modules/api/models.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel, Field, Json + +class TextToImageResponse(BaseModel): + images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") + parameters: Json + info: Json + + \ No newline at end of file From 28e26c2bef217ae82eb9e980cceb3f67ef22e109 Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sat, 22 Oct 2022 23:13:32 -0300 Subject: [PATCH 0179/1118] Add "extra" single image operation - Separate extra modes into 3 endpoints so the user ddoesn't ahve to handle so many unused parameters. - Add response model for codumentation --- modules/api/api.py | 43 ++++++++++++++++++++++++++++++++++++++----- modules/api/models.py | 26 +++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index c17d7580..3b804373 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -8,20 +8,42 @@ import json import io import base64 from modules.api.models import * +from PIL import Image +from modules.extras import run_extras + +def upscaler_to_index(name: str): + try: + return [x.name.lower() for x in shared.sd_upscalers].index(name.lower()) + except: + raise HTTPException(status_code=400, detail="Upscaler not found") sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None) -def img_to_base64(img): +def img_to_base64(img: str): buffer = io.BytesIO() img.save(buffer, format="png") return base64.b64encode(buffer.getvalue()) +def base64_to_bytes(base64Img: str): + if "," in base64Img: + base64Img = base64Img.split(",")[1] + return io.BytesIO(base64.b64decode(base64Img)) + +def base64_to_images(base64Imgs: list[str]): + imgs = [] + for img in base64Imgs: + img = Image.open(base64_to_bytes(img)) + imgs.append(img) + return imgs + + class Api: def __init__(self, app, queue_lock): self.router = APIRouter() self.app = app self.queue_lock = queue_lock - self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"]) + self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=TextToImageResponse) + self.app.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse) def text2imgapi(self, txt2imgreq: StableDiffusionProcessingAPI ): sampler_index = sampler_to_index(txt2imgreq.sampler_index) @@ -45,12 +67,23 @@ class Api: return TextToImageResponse(images=b64images, parameters=json.dumps(vars(txt2imgreq)), info=json.dumps(processed.info)) - def img2imgapi(self): raise NotImplementedError - def extrasapi(self): - raise NotImplementedError + def extras_single_image_api(self, req: ExtrasSingleImageRequest): + upscaler1Index = upscaler_to_index(req.upscaler_1) + upscaler2Index = upscaler_to_index(req.upscaler_2) + + reqDict = vars(req) + reqDict.pop('upscaler_1') + reqDict.pop('upscaler_2') + + reqDict['image'] = base64_to_images([reqDict['image']])[0] + + with self.queue_lock: + result = run_extras(**reqDict, extras_upscaler_1=upscaler1Index, extras_upscaler_2=upscaler2Index, extras_mode=0, image_folder="", input_dir="", output_dir="") + + return ExtrasSingleImageResponse(image="data:image/png;base64,"+img_to_base64(result[0]), html_info_x=result[1], html_info=result[2]) def pnginfoapi(self): raise NotImplementedError diff --git a/modules/api/models.py b/modules/api/models.py index a7d247d8..dcf1ab54 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -1,8 +1,32 @@ from pydantic import BaseModel, Field, Json +from typing_extensions import Literal +from modules.shared import sd_upscalers class TextToImageResponse(BaseModel): images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") parameters: Json info: Json - \ No newline at end of file +class ExtrasBaseRequest(BaseModel): + resize_mode: Literal[0, 1] = Field(default=0, title="Resize Mode", description="Sets the resize mode: 0 to upscale by upscaling_resize amount, 1 to upscale up to upscaling_resize_h x upscaling_resize_w.") + show_extras_results: bool = Field(default=True, title="Show results", description="Should the backend return the generated image?") + gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.") + codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.") + codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.") + upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=4, description="By how much to upscale the image, only used when resize_mode=0.") + upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.") + upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.") + upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the choosen size?") + upscaler_1: str = Field(default="None", title="Main upscaler", description=f"The name of the main upscaler to use, it has to be one of this list: {' , '.join([x.name for x in sd_upscalers])}") + upscaler_2: str = Field(default="None", title="Secondary upscaler", description=f"The name of the secondary upscaler to use, it has to be one of this list: {' , '.join([x.name for x in sd_upscalers])}") + extras_upscaler_2_visibility: float = Field(default=0, title="Secondary upscaler visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of secondary upscaler, values should be between 0 and 1.") + +class ExtraBaseResponse(BaseModel): + html_info_x: str + html_info: str + +class ExtrasSingleImageRequest(ExtrasBaseRequest): + image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") + +class ExtrasSingleImageResponse(ExtraBaseResponse): + image: str = Field(default=None, title="Image", description="The generated image in base64 format.") \ No newline at end of file From 1fbfc052eb529d8cf8ce5baf578bcf93d0280c29 Mon Sep 17 00:00:00 2001 From: DepFA <35278260+dfaker@users.noreply.github.com> Date: Sun, 23 Oct 2022 05:43:34 +0100 Subject: [PATCH 0180/1118] Update hypernetwork.py --- modules/hypernetworks/hypernetwork.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 99fd0f8f..98a7b62e 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -288,10 +288,13 @@ def statistics(data): def report_statistics(loss_info:dict): keys = sorted(loss_info.keys(), key=lambda x: sum(loss_info[x]) / len(loss_info[x])) for key in keys: - info, recent = statistics(loss_info[key]) - print("Loss statistics for file " + key) - print(info) - print(recent) + try: + print("Loss statistics for file " + key) + info, recent = statistics(loss_info[key]) + print(info) + print(recent) + except Exception as e: + print(e) From a7c213d0f5ebb10722629b8490a5863f9ce6c4fa Mon Sep 17 00:00:00 2001 From: Stephen Date: Fri, 21 Oct 2022 19:27:40 -0400 Subject: [PATCH 0181/1118] [API][Feature] - Add img2img API endpoint --- modules/api/api.py | 58 +++++++++++++++++++++++++++++++++++---- modules/api/processing.py | 11 ++++++-- modules/processing.py | 2 +- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 5b0c934e..a04f2428 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -1,5 +1,5 @@ -from modules.api.processing import StableDiffusionProcessingAPI -from modules.processing import StableDiffusionProcessingTxt2Img, process_images +from modules.api.processing import StableDiffusionTxt2ImgProcessingAPI, StableDiffusionImg2ImgProcessingAPI +from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images from modules.sd_samplers import all_samplers from modules.extras import run_pnginfo import modules.shared as shared @@ -10,6 +10,7 @@ from pydantic import BaseModel, Field, Json import json import io import base64 +from PIL import Image sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None) @@ -18,6 +19,11 @@ class TextToImageResponse(BaseModel): parameters: Json info: Json +class ImageToImageResponse(BaseModel): + images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") + parameters: Json + info: Json + class Api: def __init__(self, app, queue_lock): @@ -25,8 +31,9 @@ class Api: self.app = app self.queue_lock = queue_lock self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"]) + self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"]) - def text2imgapi(self, txt2imgreq: StableDiffusionProcessingAPI ): + def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): sampler_index = sampler_to_index(txt2imgreq.sampler_index) if sampler_index is None: @@ -54,8 +61,49 @@ class Api: - def img2imgapi(self): - raise NotImplementedError + def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI): + sampler_index = sampler_to_index(img2imgreq.sampler_index) + + if sampler_index is None: + raise HTTPException(status_code=404, detail="Sampler not found") + + + init_images = img2imgreq.init_images + if init_images is None: + raise HTTPException(status_code=404, detail="Init image not found") + + + populate = img2imgreq.copy(update={ # Override __init__ params + "sd_model": shared.sd_model, + "sampler_index": sampler_index[0], + "do_not_save_samples": True, + "do_not_save_grid": True + } + ) + p = StableDiffusionProcessingImg2Img(**vars(populate)) + + imgs = [] + for img in init_images: + # if has a comma, deal with prefix + if "," in img: + img = img.split(",")[1] + # convert base64 to PIL image + img = base64.b64decode(img) + img = Image.open(io.BytesIO(img)) + imgs = [img] * p.batch_size + + p.init_images = imgs + # Override object param + with self.queue_lock: + processed = process_images(p) + + b64images = [] + for i in processed.images: + buffer = io.BytesIO() + i.save(buffer, format="png") + b64images.append(base64.b64encode(buffer.getvalue())) + + return ImageToImageResponse(images=b64images, parameters=json.dumps(vars(img2imgreq)), info=json.dumps(processed.info)) def extrasapi(self): raise NotImplementedError diff --git a/modules/api/processing.py b/modules/api/processing.py index 4c541241..9f1d65c0 100644 --- a/modules/api/processing.py +++ b/modules/api/processing.py @@ -1,7 +1,8 @@ +from array import array from inflection import underscore from typing import Any, Dict, Optional from pydantic import BaseModel, Field, create_model -from modules.processing import StableDiffusionProcessingTxt2Img +from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img import inspect @@ -92,8 +93,14 @@ class PydanticModelGenerator: DynamicModel.__config__.allow_mutation = True return DynamicModel -StableDiffusionProcessingAPI = PydanticModelGenerator( +StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingTxt2Img", StableDiffusionProcessingTxt2Img, [{"key": "sampler_index", "type": str, "default": "Euler"}] +).generate_model() + +StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( + "StableDiffusionProcessingImg2Img", + StableDiffusionProcessingImg2Img, + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}] ).generate_model() \ No newline at end of file diff --git a/modules/processing.py b/modules/processing.py index b1877b80..1557ed8c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -623,7 +623,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): sampler = None - def __init__(self, init_images=None, resize_mode=0, denoising_strength=0.75, mask=None, mask_blur=4, inpainting_fill=0, inpaint_full_res=True, inpaint_full_res_padding=0, inpainting_mask_invert=0, **kwargs): + def __init__(self, init_images: list=None, resize_mode: int=0, denoising_strength: float=0.75, mask: str=None, mask_blur: int=4, inpainting_fill: int=0, inpaint_full_res: bool=True, inpaint_full_res_padding: int=0, inpainting_mask_invert: int=0, **kwargs): super().__init__(**kwargs) self.init_images = init_images From 9e1a8b7734a2881451a2efbf80def011ea41ba49 Mon Sep 17 00:00:00 2001 From: Stephen Date: Sat, 22 Oct 2022 15:42:00 -0400 Subject: [PATCH 0182/1118] non-implemented mask with any type --- modules/api/api.py | 4 ++++ modules/api/processing.py | 2 +- modules/processing.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index a04f2428..3df6ff96 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -72,6 +72,10 @@ class Api: if init_images is None: raise HTTPException(status_code=404, detail="Init image not found") + mask = img2imgreq.mask + if mask: + raise HTTPException(status_code=400, detail="Mask not supported yet") + populate = img2imgreq.copy(update={ # Override __init__ params "sd_model": shared.sd_model, diff --git a/modules/api/processing.py b/modules/api/processing.py index 9f1d65c0..f551fa35 100644 --- a/modules/api/processing.py +++ b/modules/api/processing.py @@ -102,5 +102,5 @@ StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingImg2Img", StableDiffusionProcessingImg2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}] + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}] ).generate_model() \ No newline at end of file diff --git a/modules/processing.py b/modules/processing.py index 1557ed8c..ff83023c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -623,7 +623,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): sampler = None - def __init__(self, init_images: list=None, resize_mode: int=0, denoising_strength: float=0.75, mask: str=None, mask_blur: int=4, inpainting_fill: int=0, inpaint_full_res: bool=True, inpaint_full_res_padding: int=0, inpainting_mask_invert: int=0, **kwargs): + def __init__(self, init_images: list=None, resize_mode: int=0, denoising_strength: float=0.75, mask: Any=None, mask_blur: int=4, inpainting_fill: int=0, inpaint_full_res: bool=True, inpaint_full_res_padding: int=0, inpainting_mask_invert: int=0, **kwargs): super().__init__(**kwargs) self.init_images = init_images From 5dc0739ecdc1ade8fcf4eb77f2a503ef12489f32 Mon Sep 17 00:00:00 2001 From: Stephen Date: Sat, 22 Oct 2022 17:10:28 -0400 Subject: [PATCH 0183/1118] working mask --- modules/api/api.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 3df6ff96..3caa83a4 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -33,6 +33,14 @@ class Api: self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"]) self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"]) + def __base64_to_image(self, base64_string): + # if has a comma, deal with prefix + if "," in base64_string: + base64_string = base64_string.split(",")[1] + imgdata = base64.b64decode(base64_string) + # convert base64 to PIL image + return Image.open(io.BytesIO(imgdata)) + def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): sampler_index = sampler_to_index(txt2imgreq.sampler_index) @@ -74,26 +82,22 @@ class Api: mask = img2imgreq.mask if mask: - raise HTTPException(status_code=400, detail="Mask not supported yet") + mask = self.__base64_to_image(mask) populate = img2imgreq.copy(update={ # Override __init__ params "sd_model": shared.sd_model, "sampler_index": sampler_index[0], "do_not_save_samples": True, - "do_not_save_grid": True + "do_not_save_grid": True, + "mask": mask } ) p = StableDiffusionProcessingImg2Img(**vars(populate)) imgs = [] for img in init_images: - # if has a comma, deal with prefix - if "," in img: - img = img.split(",")[1] - # convert base64 to PIL image - img = base64.b64decode(img) - img = Image.open(io.BytesIO(img)) + img = self.__base64_to_image(img) imgs = [img] * p.batch_size p.init_images = imgs From 1ef32c8b8fa3e16a1e7b287eb19d4fc943d1f2a5 Mon Sep 17 00:00:00 2001 From: kabachuha Date: Sun, 23 Oct 2022 00:01:13 +0300 Subject: [PATCH 0184/1118] Add ru_RU localization --- localizations/ru_RU.json | 475 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100644 localizations/ru_RU.json diff --git a/localizations/ru_RU.json b/localizations/ru_RU.json new file mode 100644 index 00000000..664d36ea --- /dev/null +++ b/localizations/ru_RU.json @@ -0,0 +1,475 @@ +{ + "⤡": "⤡", + "⊞": "⊞", + "×": "×", + "❮": "❮", + "❯": "❯", + "Loading...": "Загрузка...", + "view": "просмотр", + "api": "api", + "•": "•", + "built with gradio": "На основе Gradio", + "Stable Diffusion checkpoint": "Веса Stable Diffusion", + "txt2img": "текст-в-рисунок", + "img2img": "рисунок-в-рисунок", + "Extras": "Дополнения", + "PNG Info": "Информация о PNG", + "Image Browser": "Просмотр изображений", + "History": "Журнал", + "Checkpoint Merger": "Слияние весов", + "Train": "Обучение", + "Create aesthetic embedding": "Создать эмбеддинг эстетики", + "Settings": "Настройки", + "Prompt": "Запрос", + "Negative prompt": "Исключающий запрос", + "Run": "Запустить", + "Skip": "Пропустить", + "Interrupt": "Прервать", + "Generate": "Создать", + "Style 1": "Стиль 1", + "Style 2": "Стиль 2", + "Label": "Метка", + "File": "Файл", + "Drop File Here": "Перетащите файл сюда", + "-": "-", + "or": "или", + "Click to Upload": "Нажмите, чтобы загрузить", + "Image": "Рисунок", + "Check progress": "Узнать состояние", + "Check progress (first)": "Узнать состояние первого", + "Sampling Steps": "Шагов семплера", + "Sampling method": "Метод семплирования", + "Euler a": "Euler a", + "Euler": "Euler", + "LMS": "LMS", + "Heun": "Heun", + "DPM2": "DPM2", + "DPM2 a": "DPM2 a", + "DPM fast": "DPM fast", + "DPM adaptive": "DPM adaptive", + "LMS Karras": "LMS Karras", + "DPM2 Karras": "DPM2 Karras", + "DPM2 a Karras": "DPM2 a Karras", + "DDIM": "DDIM", + "PLMS": "PLMS", + "Width": "Ширина", + "Height": "Высота", + "Restore faces": "Восстановить лица", + "Tiling": "Замощение", + "Highres. fix": "HD-режим", + "Firstpass width": "Ширина первого прохода", + "Firstpass height": "Высота первого прохода", + "Denoising strength": "Сила шумоподавления", + "Batch count": "Рисунков подряд", + "Batch size": "Рисунков параллельно", + "CFG Scale": "Близость к запросу", + "Seed": "Семя", + "Extra": "Дополнения", + "Variation seed": "Вариация семени", + "Variation strength": "Вариация шумоподавления", + "Resize seed from width": "Поправка в семя от ширины", + "Resize seed from height": "Поправка в семя от высоты", + "Open for Clip Aesthetic!": "Clip-эстетика!", + "▼": "▼", + "Aesthetic weight": "Вес эстетики", + "Aesthetic steps": "Шагов эстетики", + "Aesthetic learning rate": "Скорость обучения эстетики", + "Slerp interpolation": "Slerp-интерполяция", + "Aesthetic imgs embedding": "Рисунки - эмбеддинги эстетики", + "None": "Ничего", + "Aesthetic text for imgs": "Имя эстетики рисунков", + "Slerp angle": "Угол slerp", + "Is negative text": "Это текст для исключения", + "Script": "Скрипт", + "Prompt matrix": "Матрица запросов", + "Prompts from file or textbox": "Запросы из файла или текста", + "X/Y plot": "X/Y-график", + "Put variable parts at start of prompt": "Переменное начало запроса", + "Show Textbox": "Показать текстовый ввод", + "File with inputs": "Файл входа", + "Prompts": "Запросы", + "X type": "Ось X", + "Nothing": "Ничего", + "Var. seed": "Вариация семени", + "Var. strength": "Вариация силы", + "Steps": "Число шагов", + "Prompt S/R": "Вариация запроса", + "Prompt order": "Порядок запросов", + "Sampler": "Семплер", + "Checkpoint name": "Имя файла весов", + "Hypernetwork": "Гиперсеть", + "Hypernet str.": "Строка гиперсети", + "Sigma Churn": "Возмущение сигмы", + "Sigma min": "Мин. сигма", + "Sigma max": "Макс. сигма", + "Sigma noise": "Сигма-шум", + "Eta": "Расчётное время", + "Clip skip": "Пропустить Clip", + "Denoising": "Шумоподавление", + "X values": "Значения X", + "Y type": "Тип Y", + "Y values": "Значения Y", + "Draw legend": "Легенда графика", + "Include Separate Images": "Включить отдельные рисунки", + "Keep -1 for seeds": "-1 для семени", + "Drop Image Here": "Перетащите рисунок сюда", + "Save": "Сохранить", + "Send to img2img": "В рисунок-в-рисунок", + "Send to inpaint": "В режим врисовывания", + "Send to extras": "В дополнения", + "Make Zip when Save?": "Создать zip при сохранении?", + "Textbox": "Текст", + "Interrogate\nCLIP": "Распознавание\nCLIP", + "Interrogate\nDeepBooru": "Распознавание\nDeepBooru", + "Inpaint": "врисовать", + "Batch img2img": "рисунок-в-рисунок (набор)", + "Image for img2img": "рисунок-в-рисунок (вход)", + "Image for inpainting with mask": "врисовать (вход с трафаретом)", + "Mask": "Трафарет", + "Mask blur": "Размытие трафарета", + "Mask mode": "Режим трафарета", + "Draw mask": "Нарисовать трафарет", + "Upload mask": "Загрузить трафарет", + "Masking mode": "Режим трафарета", + "Inpaint masked": "Внутри трафарета", + "Inpaint not masked": "Вне трафарета", + "Masked content": "Под трафаретом", + "fill": "залить", + "original": "сохранить", + "latent noise": "латентный шум", + "latent nothing": "латентная пустота", + "Inpaint at full resolution": "Врисовать при полном разрешении", + "Inpaint at full resolution padding, pixels": "Врисовать с достройкой до полного разрешения, в пикселях", + "Process images in a directory on the same machine where the server is running.": "Обрабатывать рисунки на том же компьютере, где сервер", + "Use an empty output directory to save pictures normally instead of writing to the output directory.": "Использовать пустую папку вместо того, чтобы выводить в output", + "Disabled when launched with --hide-ui-dir-config.": "Выключено при запуске с --hide-ui-dir-config", + "Input directory": "Папка входа", + "Output directory": "Папка выхода", + "Resize mode": "Масштабирование", + "Just resize": "Только сжать", + "Crop and resize": "Сжать и обрезать", + "Resize and fill": "Сжать и залить", + "img2img alternative test": "рисунок-в-рисунок (альтернатива)", + "Loopback": "Прокручивание", + "Outpainting mk2": "Обрисовыватель mk2", + "Poor man's outpainting": "Хоть какой-то обрисовыватель", + "SD upscale": "SD-апскейл", + "should be 2 or lower.": "должно быть меньше равно 2", + "Override `Sampling method` to Euler?(this method is built for it)": "Сменить метод семплирования на метод Эйлера?(скрипт строился с его учётом)", + "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "Сменить `запрос` на `изначальный запрос`?(и `запрос-исключение`)", + "Original prompt": "Изначальный запрос", + "Original negative prompt": "Изначальный запрос-исключение", + "Override `Sampling Steps` to the same value as `Decode steps`?": "Сменить число шагов на число шагов декодирования?", + "Decode steps": "Шагов декодирования", + "Override `Denoising strength` to 1?": "Сменить силу шумоподавления на 1?", + "Decode CFG scale": "Близость к запросу декодирования", + "Randomness": "Случайность", + "Sigma adjustment for finding noise for image": "Поправка к сигме подбора шума для рисунка", + "Loops": "Циклов", + "Denoising strength change factor": "Множитель силы шумоподавления", + "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "Рекоммендуемые настройки: Число шагов:80-100,Метод:Euler a,Шумоподавление:0.8", + "Pixels to expand": "Пикселов расширить", + "Outpainting direction": "Направление обрисовывания", + "left": "влево", + "right": "вправо", + "up": "вверх", + "down": "вниз", + "Fall-off exponent (lower=higher detail)": "Степень затухания (меньше=больше деталей)", + "Color variation": "Вариация цвета", + "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "Расширит рисунок дважды; ползунки ширины и высоты устанавливают размеры плиток", + "Tile overlap": "Перекрытие плиток", + "Upscaler": "Апскейлер", + "Lanczos": "Lanczos", + "LDSR": "LDSR", + "BSRGAN 4x": "BSRGAN 4x", + "ESRGAN_4x": "ESRGAN_4x", + "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", + "ScuNET GAN": "ScuNET GAN", + "ScuNET PSNR": "ScuNET PSNR", + "SwinIR_4x": "SwinIR 4x", + "Single Image": "Один рисунок", + "Batch Process": "Набор рисунков", + "Batch from Directory": "Рисунки из папки", + "Source": "Вход", + "Show result images": "Показать результаты", + "Scale by": "Увеличить в", + "Scale to": "Увеличить до", + "Resize": "Масштабировать", + "Crop to fit": "Обрезать до рамки", + "Upscaler 2": "Апскейлер 2", + "Upscaler 2 visibility": "Видимость Апскейлера 2", + "GFPGAN visibility": "Видимость GFPGAN", + "CodeFormer visibility": "Видимость CodeFormer", + "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "Вес CodeFormer (0 = максимальное действие, 1 = минимальное)", + "Open output directory": "Открыть папку выхода", + "Send to txt2img": "В текст-в-рисунок", + "txt2img history": "журнал текста-в-рисунок", + "img2img history": "журнал рисунка-в-рисунок", + "extras history": "журнал дополнений", + "Renew Page": "Обновить страницу", + "extras": "дополнения", + "favorites": "избранное", + "Load": "Загрузить", + "Images directory": "Папка с рисунками", + "Prev batch": "Пред. набор", + "Next batch": "След. набор", + "First Page": "Первая страница", + "Prev Page": "Пред. страница", + "Page Index": "Список страниц", + "Next Page": "След. страница", + "End Page": "Конец страницы", + "number of images to delete consecutively next": "сколько рисунков удалить подряд", + "Delete": "Удалить", + "Generate Info": "Сведения о генерации", + "File Name": "Имя файла", + "Collect": "Накопить", + "Refresh page": "Обновить страницу", + "Date to": "Дата", + "Number": "Число", + "set_index": "индекс", + "Checkbox": "Галочка", + "A merger of the two checkpoints will be generated in your": "Слияние весов будет создано, где хранятся", + "checkpoint": "ckpt", + "directory.": "веса", + "Primary model (A)": "Первичная модель (A)", + "Secondary model (B)": "Вторичная модель (B)", + "Tertiary model (C)": "Третичная модель (C)", + "Custom Name (Optional)": "Произвольное имя (необязательно)", + "Multiplier (M) - set to 0 to get model A": "Множитель (M) - 0 даст модель A", + "Interpolation Method": "Метод интерполяции", + "Weighted sum": "Взвешенная сумма", + "Add difference": "Сумма разностей", + "Save as float16": "Сохранить как float16", + "See": "См.", + "wiki": "вики", + "for detailed explanation.": "для подробных объяснений.", + "Create embedding": "Создать эмбеддинг", + "Create aesthetic images embedding": "Создать эмбеддинг эстетики по рисункам", + "Create hypernetwork": "Создать гиперсеть", + "Preprocess images": "Предобработать рисунки", + "Name": "Имя", + "Initialization text": "Соответствующий текст", + "Number of vectors per token": "Векторов на токен", + "Overwrite Old Embedding": "Перезаписать эмбеддинг", + "Source directory": "Исходная папка", + "Modules": "Модули", + "Enter hypernetwork layer structure": "Структура слоёв гиперсети", + "Add layer normalization": "Добавить нормализацию слоёв", + "Overwrite Old Hypernetwork": "Перезаписать гиперсеть", + "Select activation function of hypernetwork": "Функция активации гиперсети", + "linear": "линейная", + "relu": "relu", + "leakyrelu": "leakyrelu", + "Destination directory": "Папка назначения", + "Existing Caption txt Action": "Что делать с предыдущим текстом", + "ignore": "игнорировать", + "copy": "копировать", + "prepend": "в начало", + "append": "в конец", + "Create flipped copies": "Создать отражённые копии", + "Split oversized images into two": "Поделить слишком большие рисунки пополам", + "Split oversized images": "Поделить слишком большие рисунки", + "Use BLIP for caption": "Использовать BLIP для названий", + "Use deepbooru for caption": "Использовать deepbooru для тегов", + "Split image threshold": "Порог разделения рисунков", + "Split image overlap ratio": "Пропорции разделения рисунков", + "Preprocess": "Предобработка", + "Train an embedding; must specify a directory with a set of 1:1 ratio images": "Обучить эмбеддинг; укажите папку рисунков с пропорциями 1:1", + "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "Обучить эмбеддинг или гиперсеть; укажите папку рисунков с пропорциями 1:1", + "[wiki]": "[вики]", + "Embedding": "Эмбеддинг", + "Embedding Learning rate": "Скорость обучения эмбеддинга", + "Hypernetwork Learning rate": "Скорость обучения гиперсети", + "Learning rate": "Скорость обучения", + "Dataset directory": "Папка датасета", + "Log directory": "Папка журнала", + "Prompt template file": "Файл шаблона запроса", + "Max steps": "Макс. шагов", + "Save an image to log directory every N steps, 0 to disable": "Сохранять рисунок каждые N шагов, 0 чтобы отключить", + "Save a copy of embedding to log directory every N steps, 0 to disable": "Сохранять эмбеддинг каждые N шагов, 0 чтобы отключить", + "Save images with embedding in PNG chunks": "Сохранить рисунок с эмбеддингом в виде PNG-фрагментов", + "Read parameters (prompt, etc...) from txt2img tab when making previews": "Считать параметры (запрос и т.д.) из вкладки текст-в-рисунок для предпросмотра", + "Train Hypernetwork": "Обучить гиперсеть", + "Train Embedding": "Обучить эмбеддинг", + "Create an aesthetic embedding out of any number of images": "Создать эмбеддинг эстетики по любому числу рисунков", + "Create images embedding": "Создать эмбеддинг рисунков", + "Apply settings": "Применить настройки", + "Saving images/grids": "Сохранение рисунков/таблиц", + "Always save all generated images": "Всегда сохранять созданные рисунки", + "File format for images": "Формат файла рисунков", + "Images filename pattern": "Формат имени файлов рисунков", + "Always save all generated image grids": "Всегда сохранять созданные таблицы", + "File format for grids": "Формат файла таблиц", + "Add extended info (seed, prompt) to filename when saving grid": "Вставлять доп. сведения (семя, запрос) в имя файла таблиц", + "Do not save grids consisting of one picture": "Не сохранять таблицы из одного рисунка", + "Prevent empty spots in grid (when set to autodetect)": "Не допускать пустоты в таблицах (автообнаружение)", + "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Число строк таблицы; -1, чтобы автоматически, 0 — размер набора", + "Save text information about generation parameters as chunks to png files": "Встроить сведения о генерации в файлы png", + "Create a text file next to every image with generation parameters.": "Создать текстовый файл для каждого рисунка с параметрами генерации", + "Save a copy of image before doing face restoration.": "Сохранить копию перед восстановлением лиц", + "Quality for saved jpeg images": "Качество jpeg-рисунков", + "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "Если размер PNG больше 4МБ или рисунок шире 4000 пикселей, пересжать в JPEG", + "Use original name for output filename during batch process in extras tab": "Использовать исходное имя выходного файла для обработки набора во вкладке дополнений", + "When using 'Save' button, only save a single selected image": "Сохранять только один рисунок при нажатии кнопки Сохранить", + "Do not add watermark to images": "Не добавлять водяной знак", + "Paths for saving": "Папки сохранений", + "Output directory for images; if empty, defaults to three directories below": "Папка выхода рисунков; если пусто, использует те, что ниже", + "Output directory for txt2img images": "Папка выхода текста-в-рисунок", + "Output directory for img2img images": "Папка выхода рисунка-в-рисунок", + "Output directory for images from extras tab": "Папка выхода для дополнений", + "Output directory for grids; if empty, defaults to two directories below": "Папка выхода таблиц; если пусто, использует папки выше", + "Output directory for txt2img grids": "Папка выхода текста-в-рисунок", + "Output directory for img2img grids": "Папка выхода рисунка-в-рисунок", + "Directory for saving images using the Save button": "Папка выхода для кнопки Сохранить", + "Saving to a directory": "Сохранить в папку", + "Save images to a subdirectory": "Сохранить рисунки в подпапку", + "Save grids to a subdirectory": "Сохранить таблицы в подпапку", + "When using \"Save\" button, save images to a subdirectory": "При нажатии кнопки Сохранить, сложить рисунки в подпапку", + "Directory name pattern": "Шаблон имени папки", + "Max prompt words for [prompt_words] pattern": "Макс. число слов для шаблона [prompt_words]", + "Upscaling": "Апскейл", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "Размер плитки для ESRGAN. 0 = нет замощения", + "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Наложение плиток ESRGAN, в пикселях. Меньше = выделеннее шов", + "Tile size for all SwinIR.": "Размер плиток SwinIR", + "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Наложение плиток SwinIR, в пикселях. Меньше = выделеннее шов", + "LDSR processing steps. Lower = faster": "Число шагов LDSR. Меньше = быстрее", + "Upscaler for img2img": "Апскейлер рисунка-в-рисунок", + "Upscale latent space image when doing hires. fix": "Апскейлить образ латентного пространства для HD-режима", + "Face restoration": "Восстановление лиц", + "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "Вес CodeFormer; 0 = максимальное действие; 1 = минимальное", + "Move face restoration model from VRAM into RAM after processing": "Переместить модель восстановления лиц из ВОЗУ в ОЗУ после обработки", + "System": "Система", + "VRAM usage polls per second during generation. Set to 0 to disable.": "Сколько раз в секунду следить за потреблением ВОЗУ. 0, чтобы отключить", + "Always print all generation info to standard output": "Выводить все сведения о генерации в стандартный вывод", + "Add a second progress bar to the console that shows progress for an entire job.": "Вторая шкала прогресса для всей задачи", + "Training": "Обучение", + "Unload VAE and CLIP from VRAM when training": "Убрать VAE и CLIP из ВОЗУ на время обучения", + "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "Переместить VAE и CLIP в ОЗУ на время обучения гиперсети. Сохраняет ВОЗУ", + "Filename word regex": "Regex имени файла", + "Filename join string": "Дополнить к имени файла", + "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Число повторов для каждого рисунка за эпоху; используется только, чтобы отобразить число эпохи", + "Save an csv containing the loss to log directory every N steps, 0 to disable": "Сохранять csv с параметром loss в папку журнала каждые N шагов, 0 - отключить", + "Stable Diffusion": "Stable Diffusion", + "Checkpoints to cache in RAM": "Удерживать веса в ОЗУ", + "Hypernetwork strength": "Сила гиперсети", + "Apply color correction to img2img results to match original colors.": "Цветокоррекция вывода рисунка-в-рисунок, сохраняющая исходные цвета", + "Save a copy of image before applying color correction to img2img results": "Сохранить копию рисунка перед цветокоррекцией", + "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "В режиме рисунок-в-рисунок сделать ровно указанное ползунком число шагов (обычно шумоподавление их уменьшает)", + "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Включить квантование К-семплерах для более резких и чистых результатов. Может потребовать поменять семя. Требует перезапуска.", + "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Скобки: (понятие) - больше внимания к тексту, [понятие] - меньше внимания к тексту", + "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Включить старую обработку скобок. Может потребоваться, чтобы воспроизвести старые семена.", + "Make K-diffusion samplers produce same images in a batch as when making a single image": "Заставить семплеры K-diffusion производить тот же самый рисунок в наборе, как и в единичной генерации", + "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Увеличить связность, достраивая запрос от последней запятой до n токенов, когда используется свыше 75 токенов", + "Filter NSFW content": "Фильтровать небезопасный контент", + "Stop At last layers of CLIP model": "Остановиться на последних слоях модели CLIP", + "Interrogate Options": "Опции распознавания", + "Interrogate: keep models in VRAM": "Распознавание: хранить модели в ВОЗУ", + "Interrogate: use artists from artists.csv": "Распознавание: использовать художников из artists.csv", + "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "Распознавание: включить ранжирование совпавших тегов в результате (не работает для распознавателей-создателей заголовков)", + "Interrogate: num_beams for BLIP": "Распознавание: num_beams для BLIP", + "Interrogate: minimum description length (excluding artists, etc..)": "Распознавание: минимальная длина описания (исключая художников и т.п.)", + "Interrogate: maximum description length": "Распознавание: максимальная длина описания", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: максимальное число строк в текстовом файле (0 = без ограничений)", + "Interrogate: deepbooru score threshold": "Распознавание: ограничение счёта deepbooru", + "Interrogate: deepbooru sort alphabetically": "Распознавание: сортировать deepbooru по алфавиту", + "use spaces for tags in deepbooru": "Пробелы для тегов deepbooru", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "Использовать скобки в deepbooru как обычные скобки, а не для усиления", + "User interface": "Пользовательский интерфейс", + "Show progressbar": "Шкала прогресса", + "Show image creation progress every N sampling steps. Set 0 to disable.": "Показывать процесс созданния рисунка каждые N шагов. 0 - отключить", + "Show grid in results for web": "Показать таблицу в выводе браузера", + "Do not show any images in results for web": "Не показывать выходные рисунки в браузере", + "Add model hash to generation information": "Добавить хеш весов к параметрам генерации", + "Add model name to generation information": "Добавить имя весов к параметрам генерации", + "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "При считывании параметров генерации из текста в интерфейс, не менять выбранную модель/веса.", + "Font for image grids that have text": "Шрифт для таблиц, содержащих текст", + "Enable full page image viewer": "Включить полноэкранный просмотр картинок", + "Show images zoomed in by default in full page image viewer": "По умолчанию увеличивать картинки в полноэкранном просмотре", + "Show generation progress in window title.": "Отображать прогресс в имени вкладки", + "Quicksettings list": "Список быстрых настроек", + "Localization (requires restart)": "Перевод (требует перезапуск)", + "Sampler parameters": "Параметры семплера", + "Hide samplers in user interface (requires restart)": "Убрать семплеры из интерфейса (требует перезапуск)", + "eta (noise multiplier) for DDIM": "eta (множитель шума) DDIM", + "eta (noise multiplier) for ancestral samplers": "eta (множитель шума) для ancestral-семплеров", + "img2img DDIM discretize": "дискретизация DDIM для рисунка-в-рисунок", + "uniform": "однородная", + "quad": "квадратичная", + "sigma churn": "сигма-вариация", + "sigma tmin": "сигма-tmin", + "sigma noise": "сигма-шум", + "Eta noise seed delta": "Eta (дельта шума семени)", + "Images Browser": "Просмотр изображений", + "Preload images at startup": "Предзагружать рисунки во время запуска", + "Number of pictures displayed on each page": "Число рисунков на каждой странице", + "Minimum number of pages per load": "Мин. число загружаемых страниц", + "Number of grids in each row": "Число таблиц в каждой строке", + "Request browser notifications": "Запросить уведомления браузера", + "Download localization template": "Загрузить щаблон перевода", + "Reload custom script bodies (No ui updates, No restart)": "Перезагрузить пользовательские скрипты (не требует обновления интерфейса и перезапуска)", + "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Перезагрузить Gradio и обновить компоненты (только пользовательские скрипты, ui.py, js и css)", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "Запрос (нажмите Ctrl+Enter или Alt+Enter для генерации)", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "Запрос-исключение (нажмите Ctrl+Enter или Alt+Enter для генерации)", + "Add a random artist to the prompt.": "Добавить случайного художника к запросу", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "Считать параметры генерации из запроса или из предыдущей генерации в пользовательский интерфейс, если пусто", + "Save style": "Сохранить стиль", + "Apply selected styles to current prompt": "Применить выбранные стили к текущему промпту", + "Stop processing current image and continue processing.": "Прекратить обрабатывать текущий рисунок, но продолжить работу", + "Stop processing images and return any results accumulated so far.": "Прекратить обрабатку рисунков и вернуть всё, что успели сделать.", + "Style to apply; styles have components for both positive and negative prompts and apply to both": "Стиль к применению; стили содержат как запрос, так и исключение, и применяют их оба", + "Do not do anything special": "Не делать ничего особенного", + "Which algorithm to use to produce the image": "Какой алгоритм использовать для того, чтобы произвести рисунок", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - очень творческий, в зависимости от числа шагов может привести совершенно к различным результатам, выше 30-40 лучше не ставить", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit модели - лучше всего для обрисовки", + "Produce an image that can be tiled.": "Сделать из рисунка непрерывную обёртку", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "Применить двушаговый процесс, чтобы создать рисунок на меньшем разрешении, апскейлнуть, а затем улучшить детали без смены композиции", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "Определяет, насколько сильно алгоритм будет опираться на содержание изображения. 0 - не меняет ничего, 1 - совсем не связанный выход. Меньше 1.0 процесс использует меньше шагов, чем указано их ползунком.", + "How many batches of images to create": "Сколько создать наборов из картинок", + "How many image to create in a single batch": "Сколько картинок создать в каждом наборе", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale: насколько сильно изображение должно соответсвтовать запросу — меньшие значения приведут к более свободным итогам", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "Значение, которое определяет выход генератора случайных чисел — если вы создадите рисунок с теми же параметрами и семенем, как у другого изображения, вы получите тот же результат", + "Set seed to -1, which will cause a new random number to be used every time": "Установить семя в -1, что вызовет каждый раз случайное число", + "Reuse seed from last generation, mostly useful if it was randomed": "Использовать семя предыдущей генерации, обычно полезно, если оно было случайным", + "Seed of a different picture to be mixed into the generation.": "Семя с другого рисунка, подмешенного в генерацию.", + "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "Насколько сильную вариацию произвести. При 0м значении действия не будет. Для 1 вы получите полноценный рисунок с семенем вариации (кроме ancestral-семплеров, где вы просто что-то получите).", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "Попытаться воспроизвести изображение, похожее на то, чтобы получилось с тем же семенем на выбранном разрешении", + "This text is used to rotate the feature space of the imgs embs": "Этот текст используется, чтобы произвести вращение пространства признаков из эмбеддинга рисунков", + "Separate values for X axis using commas.": "Отдельные значения оси X через запятую.", + "Separate values for Y axis using commas.": "Отдельные значения оси Y через запятую.", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "Записать изображение в папку (по-умолчанию - log/images), а параметры генерации - в csv файл", + "Open images output directory": "Открыть папку сохранения изображений", + "How much to blur the mask before processing, in pixels.": "Насколько пикселей размыть трафарет перед обработкой", + "What to put inside the masked area before processing it with Stable Diffusion.": "Что поместить в область под трафаретом перед обработкой Stable Diffusion", + "fill it with colors of the image": "залить цветами изображения", + "keep whatever was there originally": "сохранить то, что было до этого", + "fill it with latent space noise": "залить латентным шумом", + "fill it with latent space zeroes": "залить латентными нулями", + "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "апскейл до нужного разрешения, врисовка, сжатие до начального размера и вставка в исходный рисунок", + "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "Масшабировать изображение до нужного разрешения. Если только высота и ширина не совпадают, вы получите неверное соотношение сторон.", + "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "Масштабировать изображение так, чтобы им заполнялось всё выбранное выходное разрешение. Обрезать выступающие части", + "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "Масштабировать изображение так, всё изображение помещалось в выбранное выходное разрешение. Заполнить пустое место цветами изображения.", + "How many times to repeat processing an image and using it as input for the next iteration": "Сколько раз повторить обработку изображения и использовать её как вход для следующией итерации", + "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "В режиме прокрутки, для каждого цикла сила шумоподавления умножается на это значение. <1 уменьшает вариации так, чтобы последовательность сошлась на какой-то одной картинке. >1 увеличивает вариации, так что ваша последовательность станет всё более и более сумбурной.", + "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "Для SD-апскейла, как много перекрытия в пикселях должно быть между плитками. Плитки перекрываются таким образом, чтобы они могли сойтись обратно в единое изображение, без видимого шва.", + "A directory on the same machine where the server is running.": "Папка на той же машине, где запущен сервер", + "Leave blank to save images to the default path.": "Оставьте пустым, чтобы сохранить рисунки в папку по-умолчанию", + "Result = A * (1 - M) + B * M": "Выход = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "Выход = A + (B - C) * M", + "1st and last digit must be 1. ex:'1, 2, 1'": "1я и последняя цифры должны быть 1. напр.'1, 2, 1'", + "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "как быстро будет происходить обучение. Меньшие значения увеличат время обучения, но высокие могут нарушить сходимость модели (не будет создавать должные результаты) и/или сломать эмбеддинг. (Это случилось, если вы видете Loss: nan в текстовом окне вывода обучения. В этом случае вам придётся восстанавливать эмбеддинг вручную из старой, не повреждённой резервной копии).\n\nВы также можете указать единичное значение или последовательность из нескольких, используя следующий синтаксис:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nБудет обучаться со скоростью 0.005 первые 100 шагов, затем 1e-3 до 1000 шагов, после 1e-5 для всех оставшихся шагов.", + "Path to directory with input images": "Путь к папке со входными изображениями", + "Path to directory where to write outputs": "Путь к папке, в которую записывать результаты", + "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Используйте следующие теги, чтобы определить, как подбираются названия файлов для изображений: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; если пусто, используется значение по-умолчанию", + "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "Когда эта опция включена, на созданные изображения не будет добавляться водяной знак. Предупреждение: не добавляя водяной знак, вы, вероятно, ведёте себя аморально.", + "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Используйте следующие теги, чтобы определить, как подбираются названия подпапок для рисунков и табоиц: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; если пусто, используется значение по-умолчанию", + "Restore low quality faces using GFPGAN neural network": "Восстановить низкокачественные лица, используя нейросеть GFPGAN", + "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "Это регулярное выражение будет использовано, чтобы извлечь слова из имени файла, и они будут соединены с текстом в метке ниже как вход во время обучения. Оставьте пустым, чтобы сохранить имя файла как есть", + "This string will be used to join split words into a single line if the option above is enabled.": "Эта строка будет использована, чтобы объединить разделённые слова в одну строку, если включена опция выше.", + "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "Список имён настроек, разделённый запятыми, предназначенных для быстрого доступа через панель наверху, а не через привычную вкладку настроек. Для применения требует перезапуска.", + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "Если это значение не нулевое, оно будет добавлено к семени и использовано для инициалицации ГСЧ шума семплеров с параметром Eta. Вы можете использовать это, чтобы произвести ещё больше вариаций рисунков, либо же для того, чтобы подойти близко к результатам других программ, если знаете, что делаете.", + "Enable Autocomplete": "Включить автодополнение", + "Allowed categories for random artists selection when using the Roll button": "Разрешённые категории художников для случайного выбора при использовании кнопки + три", + "Roll three": "+ три", + "Generate forever": "Непрерывная генерация", + "Cancel generate forever": "Отключить непрерывную генерацию" +} From 696cb33e50faf3f37859ebfba70fff902f46b8fb Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 23 Oct 2022 16:46:54 +0900 Subject: [PATCH 0185/1118] after initial launch, disable --autolaunch for subsequent restarts --- webui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webui.py b/webui.py index b1deca1b..a742c17d 100644 --- a/webui.py +++ b/webui.py @@ -135,6 +135,8 @@ def webui(): inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True ) + # after initial launch, disable --autolaunch for subsequent restarts + cmd_opts.autolaunch = False app.add_middleware(GZipMiddleware, minimum_size=1000) From 1be5933ba21a3badec42b7b2753d626f849b609d Mon Sep 17 00:00:00 2001 From: captin411 Date: Sun, 23 Oct 2022 04:11:07 -0700 Subject: [PATCH 0186/1118] auto cropping now works with non square crops --- modules/textual_inversion/autocrop.py | 509 ++++++++++++++------------ 1 file changed, 269 insertions(+), 240 deletions(-) diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index 5a551c25..b2f9241c 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -1,241 +1,270 @@ -import cv2 -from collections import defaultdict -from math import log, sqrt -import numpy as np -from PIL import Image, ImageDraw - -GREEN = "#0F0" -BLUE = "#00F" -RED = "#F00" - - -def crop_image(im, settings): - """ Intelligently crop an image to the subject matter """ - if im.height > im.width: - im = im.resize((settings.crop_width, settings.crop_height * im.height // im.width)) - elif im.width > im.height: - im = im.resize((settings.crop_width * im.width // im.height, settings.crop_height)) - else: - im = im.resize((settings.crop_width, settings.crop_height)) - - if im.height == im.width: - return im - - focus = focal_point(im, settings) - - # take the focal point and turn it into crop coordinates that try to center over the focal - # point but then get adjusted back into the frame - y_half = int(settings.crop_height / 2) - x_half = int(settings.crop_width / 2) - - x1 = focus.x - x_half - if x1 < 0: - x1 = 0 - elif x1 + settings.crop_width > im.width: - x1 = im.width - settings.crop_width - - y1 = focus.y - y_half - if y1 < 0: - y1 = 0 - elif y1 + settings.crop_height > im.height: - y1 = im.height - settings.crop_height - - x2 = x1 + settings.crop_width - y2 = y1 + settings.crop_height - - crop = [x1, y1, x2, y2] - - if settings.annotate_image: - d = ImageDraw.Draw(im) - rect = list(crop) - rect[2] -= 1 - rect[3] -= 1 - d.rectangle(rect, outline=GREEN) - if settings.destop_view_image: - im.show() - - return im.crop(tuple(crop)) - -def focal_point(im, settings): - corner_points = image_corner_points(im, settings) - entropy_points = image_entropy_points(im, settings) - face_points = image_face_points(im, settings) - - total_points = len(corner_points) + len(entropy_points) + len(face_points) - - corner_weight = settings.corner_points_weight - entropy_weight = settings.entropy_points_weight - face_weight = settings.face_points_weight - - weight_pref_total = corner_weight + entropy_weight + face_weight - - # weight things - pois = [] - if weight_pref_total == 0 or total_points == 0: - return pois - - pois.extend( - [ PointOfInterest( p.x, p.y, weight=p.weight * ( (corner_weight/weight_pref_total) / (len(corner_points)/total_points) )) for p in corner_points ] - ) - pois.extend( - [ PointOfInterest( p.x, p.y, weight=p.weight * ( (entropy_weight/weight_pref_total) / (len(entropy_points)/total_points) )) for p in entropy_points ] - ) - pois.extend( - [ PointOfInterest( p.x, p.y, weight=p.weight * ( (face_weight/weight_pref_total) / (len(face_points)/total_points) )) for p in face_points ] - ) - - average_point = poi_average(pois, settings) - - if settings.annotate_image: - d = ImageDraw.Draw(im) - for f in face_points: - d.rectangle(f.bounding(f.size), outline=RED) - for f in entropy_points: - d.rectangle(f.bounding(30), outline=BLUE) - for poi in pois: - w = max(4, 4 * 0.5 * sqrt(poi.weight)) - d.ellipse(poi.bounding(w), fill=BLUE) - d.ellipse(average_point.bounding(25), outline=GREEN) - - return average_point - - -def image_face_points(im, settings): - np_im = np.array(im) - gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY) - - tries = [ - [ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ] - ] - - for t in tries: - # print(t[0]) - classifier = cv2.CascadeClassifier(t[0]) - minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side - try: - faces = classifier.detectMultiScale(gray, scaleFactor=1.1, - minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) - except: - continue - - if len(faces) > 0: - rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces] - return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2])) for r in rects] - return [] - - -def image_corner_points(im, settings): - grayscale = im.convert("L") - - # naive attempt at preventing focal points from collecting at watermarks near the bottom - gd = ImageDraw.Draw(grayscale) - gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999") - - np_im = np.array(grayscale) - - points = cv2.goodFeaturesToTrack( - np_im, - maxCorners=100, - qualityLevel=0.04, - minDistance=min(grayscale.width, grayscale.height)*0.07, - useHarrisDetector=False, - ) - - if points is None: - return [] - - focal_points = [] - for point in points: - x, y = point.ravel() - focal_points.append(PointOfInterest(x, y, size=4)) - - return focal_points - - -def image_entropy_points(im, settings): - landscape = im.height < im.width - portrait = im.height > im.width - if landscape: - move_idx = [0, 2] - move_max = im.size[0] - elif portrait: - move_idx = [1, 3] - move_max = im.size[1] - else: - return [] - - e_max = 0 - crop_current = [0, 0, settings.crop_width, settings.crop_height] - crop_best = crop_current - while crop_current[move_idx[1]] < move_max: - crop = im.crop(tuple(crop_current)) - e = image_entropy(crop) - - if (e > e_max): - e_max = e - crop_best = list(crop_current) - - crop_current[move_idx[0]] += 4 - crop_current[move_idx[1]] += 4 - - x_mid = int(crop_best[0] + settings.crop_width/2) - y_mid = int(crop_best[1] + settings.crop_height/2) - - return [PointOfInterest(x_mid, y_mid, size=25)] - - -def image_entropy(im): - # greyscale image entropy - # band = np.asarray(im.convert("L")) - band = np.asarray(im.convert("1"), dtype=np.uint8) - hist, _ = np.histogram(band, bins=range(0, 256)) - hist = hist[hist > 0] - return -np.log2(hist / hist.sum()).sum() - - -def poi_average(pois, settings): - weight = 0.0 - x = 0.0 - y = 0.0 - for poi in pois: - weight += poi.weight - x += poi.x * poi.weight - y += poi.y * poi.weight - avg_x = round(x / weight) - avg_y = round(y / weight) - - return PointOfInterest(avg_x, avg_y) - - -class PointOfInterest: - def __init__(self, x, y, weight=1.0, size=10): - self.x = x - self.y = y - self.weight = weight - self.size = size - - def bounding(self, size): - return [ - self.x - size//2, - self.y - size//2, - self.x + size//2, - self.y + size//2 - ] - - -class Settings: - def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False): - self.crop_width = crop_width - self.crop_height = crop_height - self.corner_points_weight = corner_points_weight - self.entropy_points_weight = entropy_points_weight - self.face_points_weight = entropy_points_weight - self.annotate_image = annotate_image +import cv2 +from collections import defaultdict +from math import log, sqrt +import numpy as np +from PIL import Image, ImageDraw + +GREEN = "#0F0" +BLUE = "#00F" +RED = "#F00" + + +def crop_image(im, settings): + """ Intelligently crop an image to the subject matter """ + + scale_by = 1 + if is_landscape(im.width, im.height): + scale_by = settings.crop_height / im.height + elif is_portrait(im.width, im.height): + scale_by = settings.crop_width / im.width + elif is_square(im.width, im.height): + if is_square(settings.crop_width, settings.crop_height): + scale_by = settings.crop_width / im.width + elif is_landscape(settings.crop_width, settings.crop_height): + scale_by = settings.crop_width / im.width + elif is_portrait(settings.crop_width, settings.crop_height): + scale_by = settings.crop_height / im.height + + im = im.resize((int(im.width * scale_by), int(im.height * scale_by))) + + if im.width == settings.crop_width and im.height == settings.crop_height: + if settings.annotate_image: + d = ImageDraw.Draw(im) + rect = [0, 0, im.width, im.height] + rect[2] -= 1 + rect[3] -= 1 + d.rectangle(rect, outline=GREEN) + if settings.destop_view_image: + im.show() + return im + + focus = focal_point(im, settings) + + # take the focal point and turn it into crop coordinates that try to center over the focal + # point but then get adjusted back into the frame + y_half = int(settings.crop_height / 2) + x_half = int(settings.crop_width / 2) + + x1 = focus.x - x_half + if x1 < 0: + x1 = 0 + elif x1 + settings.crop_width > im.width: + x1 = im.width - settings.crop_width + + y1 = focus.y - y_half + if y1 < 0: + y1 = 0 + elif y1 + settings.crop_height > im.height: + y1 = im.height - settings.crop_height + + x2 = x1 + settings.crop_width + y2 = y1 + settings.crop_height + + crop = [x1, y1, x2, y2] + + if settings.annotate_image: + d = ImageDraw.Draw(im) + rect = list(crop) + rect[2] -= 1 + rect[3] -= 1 + d.rectangle(rect, outline=GREEN) + if settings.destop_view_image: + im.show() + + return im.crop(tuple(crop)) + +def focal_point(im, settings): + corner_points = image_corner_points(im, settings) + entropy_points = image_entropy_points(im, settings) + face_points = image_face_points(im, settings) + + total_points = len(corner_points) + len(entropy_points) + len(face_points) + + corner_weight = settings.corner_points_weight + entropy_weight = settings.entropy_points_weight + face_weight = settings.face_points_weight + + weight_pref_total = corner_weight + entropy_weight + face_weight + + # weight things + pois = [] + if weight_pref_total == 0 or total_points == 0: + return pois + + pois.extend( + [ PointOfInterest( p.x, p.y, weight=p.weight * ( (corner_weight/weight_pref_total) / (len(corner_points)/total_points) )) for p in corner_points ] + ) + pois.extend( + [ PointOfInterest( p.x, p.y, weight=p.weight * ( (entropy_weight/weight_pref_total) / (len(entropy_points)/total_points) )) for p in entropy_points ] + ) + pois.extend( + [ PointOfInterest( p.x, p.y, weight=p.weight * ( (face_weight/weight_pref_total) / (len(face_points)/total_points) )) for p in face_points ] + ) + + average_point = poi_average(pois, settings) + + if settings.annotate_image: + d = ImageDraw.Draw(im) + for f in face_points: + d.rectangle(f.bounding(f.size), outline=RED) + for f in entropy_points: + d.rectangle(f.bounding(30), outline=BLUE) + for poi in pois: + w = max(4, 4 * 0.5 * sqrt(poi.weight)) + d.ellipse(poi.bounding(w), fill=BLUE) + d.ellipse(average_point.bounding(25), outline=GREEN) + + return average_point + + +def image_face_points(im, settings): + np_im = np.array(im) + gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY) + + tries = [ + [ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ], + [ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ] + ] + + for t in tries: + # print(t[0]) + classifier = cv2.CascadeClassifier(t[0]) + minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side + try: + faces = classifier.detectMultiScale(gray, scaleFactor=1.1, + minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) + except: + continue + + if len(faces) > 0: + rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces] + return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2])) for r in rects] + return [] + + +def image_corner_points(im, settings): + grayscale = im.convert("L") + + # naive attempt at preventing focal points from collecting at watermarks near the bottom + gd = ImageDraw.Draw(grayscale) + gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999") + + np_im = np.array(grayscale) + + points = cv2.goodFeaturesToTrack( + np_im, + maxCorners=100, + qualityLevel=0.04, + minDistance=min(grayscale.width, grayscale.height)*0.07, + useHarrisDetector=False, + ) + + if points is None: + return [] + + focal_points = [] + for point in points: + x, y = point.ravel() + focal_points.append(PointOfInterest(x, y, size=4)) + + return focal_points + + +def image_entropy_points(im, settings): + landscape = im.height < im.width + portrait = im.height > im.width + if landscape: + move_idx = [0, 2] + move_max = im.size[0] + elif portrait: + move_idx = [1, 3] + move_max = im.size[1] + else: + return [] + + e_max = 0 + crop_current = [0, 0, settings.crop_width, settings.crop_height] + crop_best = crop_current + while crop_current[move_idx[1]] < move_max: + crop = im.crop(tuple(crop_current)) + e = image_entropy(crop) + + if (e > e_max): + e_max = e + crop_best = list(crop_current) + + crop_current[move_idx[0]] += 4 + crop_current[move_idx[1]] += 4 + + x_mid = int(crop_best[0] + settings.crop_width/2) + y_mid = int(crop_best[1] + settings.crop_height/2) + + return [PointOfInterest(x_mid, y_mid, size=25)] + + +def image_entropy(im): + # greyscale image entropy + # band = np.asarray(im.convert("L")) + band = np.asarray(im.convert("1"), dtype=np.uint8) + hist, _ = np.histogram(band, bins=range(0, 256)) + hist = hist[hist > 0] + return -np.log2(hist / hist.sum()).sum() + + +def poi_average(pois, settings): + weight = 0.0 + x = 0.0 + y = 0.0 + for poi in pois: + weight += poi.weight + x += poi.x * poi.weight + y += poi.y * poi.weight + avg_x = round(x / weight) + avg_y = round(y / weight) + + return PointOfInterest(avg_x, avg_y) + + +def is_landscape(w, h): + return w > h + + +def is_portrait(w, h): + return h > w + + +def is_square(w, h): + return w == h + + +class PointOfInterest: + def __init__(self, x, y, weight=1.0, size=10): + self.x = x + self.y = y + self.weight = weight + self.size = size + + def bounding(self, size): + return [ + self.x - size//2, + self.y - size//2, + self.x + size//2, + self.y + size//2 + ] + + +class Settings: + def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False): + self.crop_width = crop_width + self.crop_height = crop_height + self.corner_points_weight = corner_points_weight + self.entropy_points_weight = entropy_points_weight + self.face_points_weight = entropy_points_weight + self.annotate_image = annotate_image self.destop_view_image = False \ No newline at end of file From 705bbf327f54e26facc833ddf620425453358dbc Mon Sep 17 00:00:00 2001 From: Dynamic Date: Sun, 23 Oct 2022 22:37:40 +0900 Subject: [PATCH 0187/1118] Rename ko-KR.json to ko_KR.json --- localizations/{ko-KR.json => ko_KR.json} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename localizations/{ko-KR.json => ko_KR.json} (98%) diff --git a/localizations/ko-KR.json b/localizations/ko_KR.json similarity index 98% rename from localizations/ko-KR.json rename to localizations/ko_KR.json index 7cc431c6..f665042e 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko_KR.json @@ -419,4 +419,4 @@ "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", "view": "api 보이기", "wiki": "wiki" -} \ No newline at end of file +} From c729cd41303ee258e1fbca9d0dcf9e54c7f6993f Mon Sep 17 00:00:00 2001 From: Dynamic Date: Sun, 23 Oct 2022 22:38:49 +0900 Subject: [PATCH 0188/1118] Update ko_KR.json Updated translation for everything except the Settings tab --- localizations/ko_KR.json | 381 ++++++++++++++++++++++----------------- 1 file changed, 219 insertions(+), 162 deletions(-) diff --git a/localizations/ko_KR.json b/localizations/ko_KR.json index f665042e..a48ece87 100644 --- a/localizations/ko_KR.json +++ b/localizations/ko_KR.json @@ -5,118 +5,158 @@ "❮": "❮", "❯": "❯", "⤡": "⤡", + " images in this directory. Loaded ": "개의 이미지가 이 경로에 존재합니다. ", + " images during ": "개의 이미지를 불러왔고, 생성 기간은 ", + ", divided into ": "입니다. ", + " pages": "페이지로 나뉘어 표시합니다.", "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", - "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", - "A merger of the two checkpoints will be generated in your": "A merger of the two checkpoints will be generated in your", + "[wiki]": " [위키] 참조", + "A directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리를 선택해 주세요.", + "A merger of the two checkpoints will be generated in your": "체크포인트들이 병합된 결과물이 당신의", "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", - "Add difference": "Add difference", + "Add difference": "차이점 추가", "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", - "Add layer normalization": "Add layer normalization", + "Add layer normalization": "레이어 정규화(normalization) 추가", "Add model hash to generation information": "Add model hash to generation information", "Add model name to generation information": "Add model name to generation information", + "Aesthetic imgs embedding": "스타일 이미지 임베딩", + "Aesthetic learning rate": "스타일 학습 수", + "Aesthetic steps": "스타일 스텝 수", + "Aesthetic text for imgs": "스타일 텍스트", + "Aesthetic weight": "스타일 가중치", "Always print all generation info to standard output": "Always print all generation info to standard output", "Always save all generated image grids": "Always save all generated image grids", "Always save all generated images": "생성된 이미지 항상 저장하기", + "api": "", + "append": "뒤에 삽입", "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", "Apply settings": "설정 적용하기", - "BSRGAN 4x": "BSRGAN 4x", - "Batch Process": "Batch Process", "Batch count": "배치 수", - "Batch from Directory": "Batch from Directory", + "Batch from Directory": "저장 경로로부터 여러장 처리", "Batch img2img": "이미지→이미지 배치", + "Batch Process": "이미지 여러장 처리", "Batch size": "배치 크기", - "CFG Scale": "CFG 스케일", - "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "BSRGAN 4x": "BSRGAN 4x", + "built with gradio": "gradio로 제작되었습니다", "Cancel generate forever": "반복 생성 취소", - "Check progress (first)": "Check progress (first)", + "CFG Scale": "CFG 스케일", "Check progress": "Check progress", + "Check progress (first)": "Check progress (first)", + "checkpoint": " 체크포인트 ", "Checkpoint Merger": "체크포인트 병합", "Checkpoint name": "체크포인트 이름", "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Click to Upload": "Click to Upload", "Clip skip": "클립 건너뛰기", - "CodeFormer visibility": "CodeFormer visibility", - "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "CodeFormer visibility": "CodeFormer 가시성", + "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer 가중치 (0 = 최대 효과, 1 = 최소 효과)", "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", "Color variation": "색깔 다양성", + "Collect": "즐겨찾기", + "copy": "복사", "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", - "Create embedding": "Create embedding", - "Create flipped copies": "Create flipped copies", - "Create hypernetwork": "Create hypernetwork", + "Create aesthetic images embedding": "Create aesthetic images embedding", + "Create embedding": "임베딩 생성", + "Create flipped copies": "좌우로 뒤집은 복사본 생성", + "Create hypernetwork": "하이퍼네트워크 생성", + "Create images embedding": "Create images embedding", "Crop and resize": "잘라낸 후 리사이징", - "Crop to fit": "Crop to fit", - "Custom Name (Optional)": "Custom Name (Optional)", + "Crop to fit": "잘라내서 맞추기", + "Custom Name (Optional)": "병합 모델 이름 (선택사항)", + "Dataset directory": "데이터셋 경로", "DDIM": "DDIM", - "DPM adaptive": "DPM adaptive", - "DPM fast": "DPM fast", - "DPM2 Karras": "DPM2 Karras", - "DPM2 a Karras": "DPM2 a Karras", - "DPM2 a": "DPM2 a", - "DPM2": "DPM2", - "Dataset directory": "Dataset directory", "Decode CFG scale": "디코딩 CFG 스케일", "Decode steps": "디코딩 스텝 수", - "Delete": "Delete", - "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", - "Denoising strength change factor": "디노이즈 강도 변경 배수", - "Denoising strength": "디노이즈 강도", + "Delete": "삭제", "Denoising": "디노이징", - "Destination directory": "Destination directory", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Denoising strength": "디노이즈 강도", + "Denoising strength change factor": "디노이즈 강도 변경 배수", + "Destination directory": "결과물 저장 경로", "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", "Directory for saving images using the Save button": "Directory for saving images using the Save button", "Directory name pattern": "Directory name pattern", + "directory.": "저장 경로에 저장됩니다.", "Do not add watermark to images": "Do not add watermark to images", "Do not do anything special": "아무것도 하지 않기", "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", "Do not show any images in results for web": "Do not show any images in results for web", + "down": "아래쪽", "Download localization template": "Download localization template", + "Download": "다운로드", + "DPM adaptive": "DPM adaptive", + "DPM fast": "DPM fast", + "DPM2": "DPM2", + "DPM2 a": "DPM2 a", + "DPM2 a Karras": "DPM2 a Karras", + "DPM2 Karras": "DPM2 Karras", "Draw legend": "범례 그리기", "Draw mask": "마스크 직접 그리기", "Drop File Here": "Drop File Here", "Drop Image Here": "Drop Image Here", - "ESRGAN_4x": "ESRGAN_4x", - "Embedding": "Embedding", + "Embedding": "임베딩", + "Embedding Learning rate": "임베딩 학습률", "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", "Enable full page image viewer": "Enable full page image viewer", "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", - "End Page": "End Page", - "Enter hypernetwork layer structure": "Enter hypernetwork layer structure", - "Eta noise seed delta": "Eta noise seed delta", + "End Page": "마지막 페이지", + "Enter hypernetwork layer structure": "하이퍼네트워크 레이어 구조 입력", + "Error": "오류", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "ESRGAN_4x": "ESRGAN_4x", "Eta": "Eta", - "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", - "Euler a": "Euler a", + "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", + "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", + "Eta noise seed delta": "Eta noise seed delta", "Euler": "Euler", + "Euler a": "Euler a", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Existing Caption txt Action": "이미 존재하는 캡션 텍스트 처리", "Extra": "고급", "Extras": "부가기능", + "extras history": "extras history", "Face restoration": "Face restoration", "Fall-off exponent (lower=higher detail)": "감쇠 지수 (낮을수록 디테일이 올라감)", - "File Name": "File Name", + "favorites": "즐겨찾기", + "File": "File", "File format for grids": "File format for grids", "File format for images": "File format for images", + "File Name": "파일 이름", "File with inputs": "설정값 파일", - "File": "File", "Filename join string": "Filename join string", "Filename word regex": "Filename word regex", + "fill": "채우기", + "fill it with colors of the image": "이미지의 색상으로 채우기", + "fill it with latent space noise": "잠재 공간 노이즈로 채우기", + "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", "Filter NSFW content": "Filter NSFW content", - "First Page": "First Page", + "First Page": "처음 페이지", "Firstpass height": "초기 세로길이", "Firstpass width": "초기 가로길이", "Font for image grids that have text": "Font for image grids that have text", + "for detailed explanation.": "를 참조하십시오.", "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "SD 업스케일링에서 타일 간 몇 픽셀을 겹치게 할지 결정하는 설정값입니다. 타일들이 다시 한 이미지로 합쳐질 때, 눈에 띄는 이음매가 없도록 서로 겹치게 됩니다.", - "GFPGAN visibility": "GFPGAN visibility", - "Generate Info": "Generate Info", - "Generate forever": "반복 생성", "Generate": "생성", + "Generate forever": "반복 생성", + "Generate Info": "생성 정보", + "GFPGAN visibility": "GFPGAN 가시성", "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", "Height": "세로", "Heun": "Heun", + "hide": "api 숨기기", "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", "Highres. fix": "고해상도 보정", "History": "기록", + "Image Browser": "이미지 브라우저", + "Images directory": "이미지 경로", + "extras": "부가기능", + "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", @@ -124,26 +164,32 @@ "How much to blur the mask before processing, in pixels.": "이미지 생성 전 마스크를 얼마나 블러처리할지 결정하는 값. 픽셀 단위", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", "Hypernet str.": "하이퍼네트워크 강도", - "Hypernetwork strength": "Hypernetwork strength", "Hypernetwork": "하이퍼네트워크", + "Hypernetwork Learning rate": "하이퍼네트워크 학습률", + "Hypernetwork strength": "Hypernetwork strength", "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "ignore": "무시", + "Image": "Image", "Image for img2img": "Image for img2img", "Image for inpainting with mask": "Image for inpainting with mask", - "Image": "Image", "Images filename pattern": "Images filename pattern", + "img2img": "이미지→이미지", + "img2img alternative test": "이미지→이미지 대체버전 테스트", + "img2img DDIM discretize": "img2img DDIM discretize", + "img2img history": "img2img history", "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "루프백 모드에서는 매 루프마다 디노이즈 강도에 이 값이 곱해집니다. 1보다 작을 경우 다양성이 낮아져 결과 이미지들이 고정된 형태로 모일 겁니다. 1보다 클 경우 다양성이 높아져 결과 이미지들이 갈수록 혼란스러워지겠죠.", "Include Separate Images": "분리된 이미지 포함하기", "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", - "Initialization text": "Initialization text", - "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", + "Initialization text": "초기화 텍스트", + "Inpaint": "인페인트", "Inpaint at full resolution": "전체 해상도로 인페인트하기", + "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", "Inpaint masked": "마스크만 처리", "Inpaint not masked": "마스크 이외만 처리", - "Inpaint": "인페인트", "Input directory": "인풋 이미지 경로", - "Interpolation Method": "Interpolation Method", + "Interpolation Method": "보간 방법", "Interrogate\nCLIP": "CLIP\n분석", "Interrogate\nDeepBooru": "DeepBooru\n분석", "Interrogate Options": "Interrogate Options", @@ -156,49 +202,68 @@ "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", "Interrupt": "중단", + "Is negative text": "네거티브 텍스트일시 체크", "Just resize": "리사이징", "Keep -1 for seeds": "시드값 -1로 유지", - "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", - "LDSR": "LDSR", - "LMS Karras": "LMS Karras", - "LMS": "LMS", + "keep whatever was there originally": "이미지 원본 유지", "Label": "Label", "Lanczos": "Lanczos", - "Learning rate": "Learning rate", - "Leave blank to save images to the default path.": "Leave blank to save images to the default path.", + "Last prompt:": "Last prompt:", + "Last saved hypernetwork:": "Last saved hypernetwork:", + "Last saved image:": "Last saved image:", + "latent noise": "잠재 노이즈", + "latent nothing": "잠재 공백", + "LDSR": "LDSR", + "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", + "leakyrelu": "leakyrelu", + "Leave blank to save images to the default path.": "기존 저장 경로에 이미지들을 저장하려면 비워두세요.", + "left": "왼쪽", + "linear": "linear", "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", + "LMS": "LMS", + "LMS Karras": "LMS Karras", + "Load": "불러오기", "Loading...": "로딩 중...", "Localization (requires restart)": "Localization (requires restart)", - "Log directory": "Log directory", + "Log directory": "로그 경로", "Loopback": "루프백", "Loops": "루프 수", + "Loss:": "Loss:", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", "Make Zip when Save?": "저장 시 Zip 생성하기", - "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Mask": "마스크", "Mask blur": "마스크 블러", "Mask mode": "Mask mode", - "Mask": "마스크", "Masked content": "마스크된 부분", "Masking mode": "Masking mode", "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", - "Max steps": "Max steps", - "Modules": "Modules", + "Max steps": "최대 스텝 수", + "Modules": "모듈", "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", - "Multiplier (M) - set to 0 to get model A": "Multiplier (M) - set to 0 to get model A", - "Name": "Name", - "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.", + "Multiplier (M) - set to 0 to get model A": "배율 (M) - 0으로 적용하면 모델 A를 얻게 됩니다", + "Name": "이름", "Negative prompt": "네거티브 프롬프트", - "Next Page": "Next Page", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Next batch": "다음 묶음", + "Next Page": "다음 페이지", "None": "None", "Nothing": "없음", + "Nothing found in the image.": "Nothing found in the image.", + "number of images to delete consecutively next": "연속적으로 삭제할 이미지 수", "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", - "Number of vectors per token": "Number of vectors per token", + "Number of vectors per token": "토큰별 벡터 수", + "Open for Clip Aesthetic!": "클립 스타일 기능을 활성화하려면 클릭!", "Open images output directory": "이미지 저장 경로 열기", - "Open output directory": "Open output directory", + "Open output directory": "저장 경로 열기", + "or": "or", + "original": "원본 유지", "Original negative prompt": "기존 네거티브 프롬프트", "Original prompt": "기존 프롬프트", "Outpainting direction": "아웃페인팅 방향", "Outpainting mk2": "아웃페인팅 마크 2", + "Output directory": "이미지 저장 경로", "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", "Output directory for images from extras tab": "Output directory for images from extras tab", "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", @@ -206,46 +271,54 @@ "Output directory for img2img images": "Output directory for img2img images", "Output directory for txt2img grids": "Output directory for txt2img grids", "Output directory for txt2img images": "Output directory for txt2img images", - "Output directory": "이미지 저장 경로", "Override `Denoising strength` to 1?": "디노이즈 강도를 1로 적용할까요?", - "Override `Sampling Steps` to the same value as `Decode steps`?": "샘플링 스텝 수를 디코딩 스텝 수와 동일하게 적용할까요?", - "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "프롬프트 값을 기존 프롬프트와 동일하게 적용할까요?(네거티브 프롬프트 포함)", - "PLMS": "PLMS", - "PNG Info": "PNG 정보", - "Page Index": "Page Index", + "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", + "Override `Sampling Steps` to the same value as `Decode steps`?": "샘플링 스텝 수를 디코딩 스텝 수와 동일하게 적용할까요?", + "Overwrite Old Embedding": "기존 임베딩 덮어쓰기", + "Overwrite Old Hypernetwork": "기존 하이퍼네트워크 덮어쓰기", + "Page Index": "페이지 인덱스", + "parameters": "설정값", "Path to directory where to write outputs": "Path to directory where to write outputs", - "Path to directory with input images": "Path to directory with input images", + "Path to directory with input images": "인풋 이미지가 있는 경로", "Paths for saving": "Paths for saving", "Pixels to expand": "확장할 픽셀 수", + "PLMS": "PLMS", + "PNG Info": "PNG 정보", "Poor man's outpainting": "가난뱅이의 아웃페인팅", - "Preprocess images": "Preprocess images", - "Preprocess": "Preprocess", - "Prev Page": "Prev Page", + "Preparing dataset from": "Preparing dataset from", + "prepend": "앞에 삽입", + "Preprocess": "전처리", + "Preprocess images": "이미지 전처리", + "Prev batch": "이전 묶음", + "Prev Page": "이전 페이지", "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", - "Primary model (A)": "Primary model (A)", + "Primary model (A)": "주 모델 (A)", "Process an image, use it as an input, repeat.": "이미지를 생성하고, 생성한 이미지를 다시 원본으로 사용하는 과정을 반복합니다.", "Process images in a directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리의 이미지들을 처리합니다.", "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Prompt": "프롬프트", "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Prompt S/R": "프롬프트 스타일 변경", "Prompt matrix": "프롬프트 매트릭스", "Prompt order": "프롬프트 순서", - "Prompt template file": "Prompt template file", - "Prompt": "프롬프트", - "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", + "Prompt S/R": "프롬프트 스타일 변경", + "Prompt template file": "프롬프트 템플릿 파일 경로", "Prompts": "프롬프트", + "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", + "quad": "quad", "Quality for saved jpeg images": "Quality for saved jpeg images", "Quicksettings list": "Quicksettings list", "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", "Randomness": "랜덤성", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", - "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", + "Read parameters (prompt, etc...) from txt2img tab when making previews": "프리뷰 이미지 생성 시 텍스트→이미지 탭에서 설정값(프롬프트 등) 읽어오기", "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "추천 설정값 - 샘플링 스텝 수 : 80-100 , 샘플러 : Euler a, 디노이즈 강도 : 0.8", "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", + "relu": "relu", "Renew Page": "Renew Page", "Request browser notifications": "Request browser notifications", + "Resize": "리사이징 배수", "Resize and fill": "리사이징 후 채우기", "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "설정된 해상도로 이미지 리사이징을 진행합니다. 원본과 가로/세로 길이가 일치하지 않을 경우, 부정확한 화면비의 이미지를 얻게 됩니다.", "Resize mode": "Resize mode", @@ -253,42 +326,43 @@ "Resize seed from width": "시드 리사이징 세로길이", "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "이미지 전체가 설정된 해상도 내부에 들어가게 리사이징을 진행합니다. 빈 공간은 이미지의 색상으로 채웁니다.", "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "설정된 해상도 전체가 이미지로 가득차게 리사이징을 진행합니다. 튀어나오는 부분은 잘라냅니다.", - "Resize": "Resize", "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", "Restore faces": "얼굴 보정", "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", - "Result = A * (1 - M) + B * M": "Result = A * (1 - M) + B * M", - "Result = A + (B - C) * M": "Result = A + (B - C) * M", + "Result = A * (1 - M) + B * M": "결과물 = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "결과물 = A + (B - C) * M", "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", - "Run": "Run", - "SD upscale": "SD 업스케일링", - "Sampler parameters": "Sampler parameters", + "right": "오른쪽", + "Run": "가동", "Sampler": "샘플러", - "Sampling Steps": "샘플링 스텝 수", + "Sampler parameters": "Sampler parameters", "Sampling method": "샘플링 방법", - "Save a copy of embedding to log directory every N steps, 0 to disable": "Save a copy of embedding to log directory every N steps, 0 to disable", + "Sampling Steps": "샘플링 스텝 수", + "Save": "저장", + "Save a copy of embedding to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 임베딩을 저장합니다, 비활성화하려면 0으로 설정하십시오.", "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", - "Save an image to log directory every N steps, 0 to disable": "Save an image to log directory every N steps, 0 to disable", - "Save as float16": "Save as float16", + "Save an image to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 이미지를 저장합니다, 비활성화하려면 0으로 설정하십시오.", + "Save as float16": "float16으로 저장", "Save grids to a subdirectory": "Save grids to a subdirectory", "Save images to a subdirectory": "Save images to a subdirectory", - "Save images with embedding in PNG chunks": "Save images with embedding in PNG chunks", + "Save images with embedding in PNG chunks": "PNG 청크로 이미지에 임베딩을 포함시켜 저장", "Save style": "스타일 저장", "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", - "Save": "저장", "Saving images/grids": "Saving images/grids", "Saving to a directory": "Saving to a directory", - "Scale by": "Scale by", - "Scale to": "Scale to", + "Scale by": "스케일링 배수 지정", + "Scale to": "스케일링 사이즈 지정", "Script": "스크립트", "ScuNET GAN": "ScuNET GAN", "ScuNET PSNR": "ScuNET PSNR", - "Secondary model (B)": "Secondary model (B)", - "See": "See", - "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "SD upscale": "SD 업스케일링", + "Secondary model (B)": "2차 모델 (B)", + "See": "자세한 설명은", "Seed": "시드", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "Select activation function of hypernetwork": "하이퍼네트워크 활성화 함수 선택", "Send to extras": "부가기능으로 전송", "Send to img2img": "이미지→이미지로 전송", "Send to inpaint": "인페인트로 전송", @@ -297,26 +371,36 @@ "Separate values for X axis using commas.": "쉼표로 X축에 적용할 값 분리", "Separate values for Y axis using commas.": "쉼표로 Y축에 적용할 값 분리", "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "set_index": "set_index", "Settings": "설정", - "Show Textbox": "텍스트박스 보이기", + "should be 2 or lower.": "이 2 이하여야 합니다.", "Show generation progress in window title.": "Show generation progress in window title.", "Show grid in results for web": "Show grid in results for web", "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", "Show progressbar": "Show progressbar", - "Show result images": "Show result images", - "Sigma Churn": "시그마 섞기", + "Show result images": "이미지 결과 보이기", + "Show Textbox": "텍스트박스 보이기", "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", + "Sigma Churn": "시그마 섞기", + "sigma churn": "sigma churn", "Sigma max": "시그마 최댓값", "Sigma min": "시그마 최솟값", "Sigma noise": "시그마 노이즈", - "Single Image": "Single Image", + "sigma noise": "sigma noise", + "sigma tmin": "sigma tmin", + "Single Image": "단일 이미지", "Skip": "건너뛰기", - "Source directory": "Source directory", - "Source": "Source", - "Split oversized images into two": "Split oversized images into two", - "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Slerp angle": "구면 선형 보간 각도", + "Slerp interpolation": "구면 선형 보간", + "Source": "원본", + "Source directory": "원본 경로", + "Split image threshold": "Split image threshold", + "Split image overlap ratio": "Split image overlap ratio", + "Split oversized images": "사이즈가 큰 이미지 분할하기", "Stable Diffusion": "Stable Diffusion", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Step:": "Step:", "Steps": "스텝 수", "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", @@ -325,51 +409,65 @@ "Style 2": "스타일 2", "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", "SwinIR 4x": "SwinIR 4x", + "Sys VRAM:": "시스템 VRAM : ", "System": "System", - "Tertiary model (C)": "Tertiary model (C)", + "Tertiary model (C)": "3차 모델 (C)", "Textbox": "Textbox", "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", + "This text is used to rotate the feature space of the imgs embs": "이 텍스트는 이미지 임베딩의 특징 공간을 회전하는 데 사용됩니다.", + "Tile overlap": "타일 겹침", "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", - "Tile overlap": "타일 겹침", - "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", "Tile size for all SwinIR.": "Tile size for all SwinIR.", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", "Tiling": "타일링", - "Train Embedding": "Train Embedding", - "Train Hypernetwork": "Train Hypernetwork", - "Train an embedding; must specify a directory with a set of 1:1 ratio images": "Train an embedding; must specify a directory with a set of 1:1 ratio images", + "Time taken:": "소요 시간 : ", + "Torch active/reserved:": "활성화/예약된 Torch 양 : ", + "Torch active: Peak amount of VRAM used by Torch during generation, excluding cached data.\nTorch reserved: Peak amount of VRAM allocated by Torch, including all active and cached data.\nSys VRAM: Peak amount of VRAM allocation across all applications / total GPU VRAM (peak utilization%).": "활성화된 Torch : 생성 도중 캐시된 데이터를 포함해 사용된 VRAM의 최대량\n예약된 Torch : 활성화되고 캐시된 모든 데이터를 포함해 Torch에게 할당된 VRAM의 최대량\n시스템 VRAM : 모든 어플리케이션에 할당된 VRAM 최대량 / 총 GPU VRAM (최고 이용도%)", "Train": "훈련", + "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "임베딩이나 하이퍼네트워크를 훈련시킵니다. 1:1 비율의 이미지가 있는 경로를 지정해야 합니다.", + "Train Embedding": "임베딩 훈련", + "Train Hypernetwork": "하이퍼네트워크 훈련", "Training": "Training", - "Unload VAE and CLIP from VRAM when training": "Unload VAE and CLIP from VRAM when training", + "txt2img": "텍스트→이미지", + "txt2img history": "txt2img history", + "uniform": "uniform", + "up": "위쪽", "Upload mask": "마스크 업로드하기", "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "마스크된 부분을 설정된 해상도로 업스케일하고, 인페인팅을 진행한 뒤, 다시 다운스케일 후 원본 이미지에 붙여넣습니다.", - "Upscaler 2 visibility": "Upscaler 2 visibility", - "Upscaler for img2img": "Upscaler for img2img", "Upscaler": "업스케일러", + "Upscaler 1": "업스케일러 1", + "Upscaler 2": "업스케일러 2", + "Upscaler 2 visibility": "업스케일러 2 가시성", + "Upscaler for img2img": "Upscaler for img2img", "Upscaling": "Upscaling", - "Use BLIP for caption": "Use BLIP for caption", "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", "Use an empty output directory to save pictures normally instead of writing to the output directory.": "저장 경로를 비워두면 기본 저장 폴더에 이미지들이 저장됩니다.", - "Use deepbooru for caption": "Use deepbooru for caption", + "Use BLIP for caption": "캡션에 BLIP 사용", + "Use deepbooru for caption": "캡션에 deepbooru 사용", + "Use dropout": "드롭아웃 사용", "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", + "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", "User interface": "User interface", - "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", "Var. seed": "바리에이션 시드", "Var. strength": "바리에이션 강도", "Variation seed": "바리에이션 시드", "Variation strength": "바리에이션 강도", - "Weighted sum": "Weighted sum", + "view": "api 보이기", + "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", + "Weighted sum": "가중 합", "What to put inside the masked area before processing it with Stable Diffusion.": "Stable Diffusion으로 이미지를 생성하기 전 마스크된 부분에 무엇을 채울지 결정하는 설정값", "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", "Width": "가로", + "wiki": " 위키", "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "이미지를 설정된 사이즈의 2배로 업스케일합니다. 상단의 가로와 세로 슬라이더를 이용해 타일 사이즈를 지정하세요.", "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", @@ -377,46 +475,5 @@ "X values": "X 설정값", "X/Y plot": "X/Y 플롯", "Y type": "Y축", - "Y values": "Y 설정값", - "api": "", - "built with gradio": "gradio로 제작되었습니다", - "checkpoint": "checkpoint", - "directory.": "directory.", - "down": "아래쪽", - "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", - "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", - "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", - "extras history": "extras history", - "fill it with colors of the image": "이미지의 색상으로 채우기", - "fill it with latent space noise": "잠재 공간 노이즈로 채우기", - "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", - "fill": "채우기", - "for detailed explanation.": "for detailed explanation.", - "hide": "api 숨기기", - "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", - "img2img DDIM discretize": "img2img DDIM discretize", - "img2img alternative test": "이미지→이미지 대체버전 테스트", - "img2img history": "img2img history", - "img2img": "이미지→이미지", - "keep whatever was there originally": "이미지 원본 유지", - "latent noise": "잠재 노이즈", - "latent nothing": "잠재 공백", - "left": "왼쪽", - "number of images to delete consecutively next": "number of images to delete consecutively next", - "or": "or", - "original": "원본 유지", - "quad": "quad", - "right": "오른쪽", - "set_index": "set_index", - "should be 2 or lower.": "이 2 이하여야 합니다.", - "sigma churn": "sigma churn", - "sigma noise": "sigma noise", - "sigma tmin": "sigma tmin", - "txt2img history": "txt2img history", - "txt2img": "텍스트→이미지", - "uniform": "uniform", - "up": "위쪽", - "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", - "view": "api 보이기", - "wiki": "wiki" -} + "Y values": "Y 설정값" +} \ No newline at end of file From 0523704dade0508bf3ae0c8cb799b1ae332d449b Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sun, 23 Oct 2022 12:27:50 -0300 Subject: [PATCH 0189/1118] Update run_extras to use the temp filename In batch mode run_extras tries to preserve the original file name of the images. The problem is that this makes no sense since the user only gets a list of images in the UI, trying to manually save them shows that this images have random temp names. Also, trying to keep "orig_name" in the API is a hassle that adds complexity to the consuming UI since the client has to use (or emulate) an input (type=file) element in a form. Using the normal file name not only doesn't change the output and functionality in the original UI but also helps keep the API simple. --- modules/extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/extras.py b/modules/extras.py index 22c5a1c1..29ac312e 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -33,7 +33,7 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ for img in image_folder: image = Image.open(img) imageArr.append(image) - imageNameArr.append(os.path.splitext(img.orig_name)[0]) + imageNameArr.append(os.path.splitext(img.name)[0]) elif extras_mode == 2: assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled' From 4ff852ffb50859f2eae75375cab94dd790a46886 Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sun, 23 Oct 2022 13:07:59 -0300 Subject: [PATCH 0190/1118] Add batch processing "extras" endpoint --- modules/api/api.py | 25 +++++++++++++++++++++++-- modules/api/models.py | 15 ++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 3b804373..528134a8 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -10,6 +10,7 @@ import base64 from modules.api.models import * from PIL import Image from modules.extras import run_extras +from gradio import processing_utils def upscaler_to_index(name: str): try: @@ -44,6 +45,7 @@ class Api: self.queue_lock = queue_lock self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=TextToImageResponse) self.app.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse) + self.app.add_api_route("/sdapi/v1/extra-batch-image", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) def text2imgapi(self, txt2imgreq: StableDiffusionProcessingAPI ): sampler_index = sampler_to_index(txt2imgreq.sampler_index) @@ -78,12 +80,31 @@ class Api: reqDict.pop('upscaler_1') reqDict.pop('upscaler_2') - reqDict['image'] = base64_to_images([reqDict['image']])[0] + reqDict['image'] = processing_utils.decode_base64_to_file(reqDict['image']) with self.queue_lock: result = run_extras(**reqDict, extras_upscaler_1=upscaler1Index, extras_upscaler_2=upscaler2Index, extras_mode=0, image_folder="", input_dir="", output_dir="") - return ExtrasSingleImageResponse(image="data:image/png;base64,"+img_to_base64(result[0]), html_info_x=result[1], html_info=result[2]) + return ExtrasSingleImageResponse(image=processing_utils.encode_pil_to_base64(result[0]), html_info_x=result[1], html_info=result[2]) + + def extras_batch_images_api(self, req: ExtrasBatchImagesRequest): + upscaler1Index = upscaler_to_index(req.upscaler_1) + upscaler2Index = upscaler_to_index(req.upscaler_2) + + reqDict = vars(req) + reqDict.pop('upscaler_1') + reqDict.pop('upscaler_2') + + reqDict['image_folder'] = list(map(processing_utils.decode_base64_to_file, reqDict['imageList'])) + reqDict.pop('imageList') + + with self.queue_lock: + result = run_extras(**reqDict, extras_upscaler_1=upscaler1Index, extras_upscaler_2=upscaler2Index, extras_mode=1, image="", input_dir="", output_dir="") + + return ExtrasBatchImagesResponse(images=list(map(processing_utils.encode_pil_to_base64, result[0])), html_info_x=result[1], html_info=result[2]) + + def extras_folder_processing_api(self): + raise NotImplementedError def pnginfoapi(self): raise NotImplementedError diff --git a/modules/api/models.py b/modules/api/models.py index dcf1ab54..bbd0ef53 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -29,4 +29,17 @@ class ExtrasSingleImageRequest(ExtrasBaseRequest): image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") class ExtrasSingleImageResponse(ExtraBaseResponse): - image: str = Field(default=None, title="Image", description="The generated image in base64 format.") \ No newline at end of file + image: str = Field(default=None, title="Image", description="The generated image in base64 format.") + +class SerializableImage(BaseModel): + path: str = Field(title="Path", description="The image's path ()") + +class ImageItem(BaseModel): + data: str = Field(title="image data") + name: str = Field(title="filename") + +class ExtrasBatchImagesRequest(ExtrasBaseRequest): + imageList: list[str] = Field(title="Images", description="List of images to work on. Must be Base64 strings") + +class ExtrasBatchImagesResponse(ExtraBaseResponse): + images: list[str] = Field(title="Images", description="The generated images in base64 format.") \ No newline at end of file From e0ca4dfbc10e0af8dfc4185e5e758f33fd2f0d81 Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sun, 23 Oct 2022 15:13:37 -0300 Subject: [PATCH 0191/1118] Update endpoints to use gradio's own utils functions --- modules/api/api.py | 71 +++++++++++++++++++++---------------------- modules/api/models.py | 4 +-- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 3f490ce2..3acb1f36 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -20,27 +20,27 @@ def upscaler_to_index(name: str): sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None) -def img_to_base64(img: str): - buffer = io.BytesIO() - img.save(buffer, format="png") - return base64.b64encode(buffer.getvalue()) +# def img_to_base64(img: str): +# buffer = io.BytesIO() +# img.save(buffer, format="png") +# return base64.b64encode(buffer.getvalue()) -def base64_to_bytes(base64Img: str): - if "," in base64Img: - base64Img = base64Img.split(",")[1] - return io.BytesIO(base64.b64decode(base64Img)) +# def base64_to_bytes(base64Img: str): +# if "," in base64Img: +# base64Img = base64Img.split(",")[1] +# return io.BytesIO(base64.b64decode(base64Img)) -def base64_to_images(base64Imgs: list[str]): - imgs = [] - for img in base64Imgs: - img = Image.open(base64_to_bytes(img)) - imgs.append(img) - return imgs +# def base64_to_images(base64Imgs: list[str]): +# imgs = [] +# for img in base64Imgs: +# img = Image.open(base64_to_bytes(img)) +# imgs.append(img) +# return imgs class ImageToImageResponse(BaseModel): images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") - parameters: Json - info: Json + parameters: dict + info: str class Api: @@ -49,17 +49,17 @@ class Api: self.app = app self.queue_lock = queue_lock self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=TextToImageResponse) - self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"]) + self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=ImageToImageResponse) self.app.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse) self.app.add_api_route("/sdapi/v1/extra-batch-image", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) - def __base64_to_image(self, base64_string): - # if has a comma, deal with prefix - if "," in base64_string: - base64_string = base64_string.split(",")[1] - imgdata = base64.b64decode(base64_string) - # convert base64 to PIL image - return Image.open(io.BytesIO(imgdata)) + # def __base64_to_image(self, base64_string): + # # if has a comma, deal with prefix + # if "," in base64_string: + # base64_string = base64_string.split(",")[1] + # imgdata = base64.b64decode(base64_string) + # # convert base64 to PIL image + # return Image.open(io.BytesIO(imgdata)) def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): sampler_index = sampler_to_index(txt2imgreq.sampler_index) @@ -79,11 +79,9 @@ class Api: with self.queue_lock: processed = process_images(p) - b64images = list(map(img_to_base64, processed.images)) - - return TextToImageResponse(images=b64images, parameters=json.dumps(vars(txt2imgreq)), info=json.dumps(processed.info)) - + b64images = list(map(processing_utils.encode_pil_to_base64, processed.images)) + return TextToImageResponse(images=b64images, parameters=json.dumps(vars(txt2imgreq)), info=processed.info) def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI): sampler_index = sampler_to_index(img2imgreq.sampler_index) @@ -98,7 +96,7 @@ class Api: mask = img2imgreq.mask if mask: - mask = self.__base64_to_image(mask) + mask = processing_utils.decode_base64_to_image(mask) populate = img2imgreq.copy(update={ # Override __init__ params @@ -113,7 +111,7 @@ class Api: imgs = [] for img in init_images: - img = self.__base64_to_image(img) + img = processing_utils.decode_base64_to_image(img) imgs = [img] * p.batch_size p.init_images = imgs @@ -121,13 +119,12 @@ class Api: with self.queue_lock: processed = process_images(p) - b64images = [] - for i in processed.images: - buffer = io.BytesIO() - i.save(buffer, format="png") - b64images.append(base64.b64encode(buffer.getvalue())) - - return ImageToImageResponse(images=b64images, parameters=json.dumps(vars(img2imgreq)), info=json.dumps(processed.info)) + b64images = list(map(processing_utils.encode_pil_to_base64, processed.images)) + # for i in processed.images: + # buffer = io.BytesIO() + # i.save(buffer, format="png") + # b64images.append(base64.b64encode(buffer.getvalue())) + return ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.info) def extras_single_image_api(self, req: ExtrasSingleImageRequest): upscaler1Index = upscaler_to_index(req.upscaler_1) diff --git a/modules/api/models.py b/modules/api/models.py index bbd0ef53..209f8af5 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -4,8 +4,8 @@ from modules.shared import sd_upscalers class TextToImageResponse(BaseModel): images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") - parameters: Json - info: Json + parameters: str + info: str class ExtrasBaseRequest(BaseModel): resize_mode: Literal[0, 1] = Field(default=0, title="Resize Mode", description="Sets the resize mode: 0 to upscale by upscaling_resize amount, 1 to upscale up to upscaling_resize_h x upscaling_resize_w.") From 866b36d705a338d299aba385788729d60f7d48c8 Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sun, 23 Oct 2022 15:35:49 -0300 Subject: [PATCH 0192/1118] Move processing's models into models.py It didn't make sense to have two differente files for the same and "models" is a more descriptive name. --- modules/api/api.py | 59 ++++---------------- modules/api/models.py | 112 +++++++++++++++++++++++++++++++++++++- modules/api/processing.py | 106 ------------------------------------ 3 files changed, 120 insertions(+), 157 deletions(-) delete mode 100644 modules/api/processing.py diff --git a/modules/api/api.py b/modules/api/api.py index 3acb1f36..20e85e82 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -1,16 +1,11 @@ -from modules.api.processing import StableDiffusionTxt2ImgProcessingAPI, StableDiffusionImg2ImgProcessingAPI +import uvicorn +from gradio import processing_utils +from fastapi import APIRouter, HTTPException +import modules.shared as shared +from modules.api.models import * from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images from modules.sd_samplers import all_samplers -import modules.shared as shared -import uvicorn -from fastapi import APIRouter, HTTPException -import json -import io -import base64 -from modules.api.models import * -from PIL import Image from modules.extras import run_extras -from gradio import processing_utils def upscaler_to_index(name: str): try: @@ -20,29 +15,6 @@ def upscaler_to_index(name: str): sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None) -# def img_to_base64(img: str): -# buffer = io.BytesIO() -# img.save(buffer, format="png") -# return base64.b64encode(buffer.getvalue()) - -# def base64_to_bytes(base64Img: str): -# if "," in base64Img: -# base64Img = base64Img.split(",")[1] -# return io.BytesIO(base64.b64decode(base64Img)) - -# def base64_to_images(base64Imgs: list[str]): -# imgs = [] -# for img in base64Imgs: -# img = Image.open(base64_to_bytes(img)) -# imgs.append(img) -# return imgs - -class ImageToImageResponse(BaseModel): - images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") - parameters: dict - info: str - - class Api: def __init__(self, app, queue_lock): self.router = APIRouter() @@ -51,15 +23,7 @@ class Api: self.app.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=TextToImageResponse) self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=ImageToImageResponse) self.app.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse) - self.app.add_api_route("/sdapi/v1/extra-batch-image", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) - - # def __base64_to_image(self, base64_string): - # # if has a comma, deal with prefix - # if "," in base64_string: - # base64_string = base64_string.split(",")[1] - # imgdata = base64.b64decode(base64_string) - # # convert base64 to PIL image - # return Image.open(io.BytesIO(imgdata)) + self.app.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): sampler_index = sampler_to_index(txt2imgreq.sampler_index) @@ -81,7 +45,7 @@ class Api: b64images = list(map(processing_utils.encode_pil_to_base64, processed.images)) - return TextToImageResponse(images=b64images, parameters=json.dumps(vars(txt2imgreq)), info=processed.info) + return TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.info) def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI): sampler_index = sampler_to_index(img2imgreq.sampler_index) @@ -120,10 +84,7 @@ class Api: processed = process_images(p) b64images = list(map(processing_utils.encode_pil_to_base64, processed.images)) - # for i in processed.images: - # buffer = io.BytesIO() - # i.save(buffer, format="png") - # b64images.append(base64.b64encode(buffer.getvalue())) + return ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.info) def extras_single_image_api(self, req: ExtrasSingleImageRequest): @@ -134,12 +95,12 @@ class Api: reqDict.pop('upscaler_1') reqDict.pop('upscaler_2') - reqDict['image'] = processing_utils.decode_base64_to_file(reqDict['image']) + reqDict['image'] = processing_utils.decode_base64_to_image(reqDict['image']) with self.queue_lock: result = run_extras(**reqDict, extras_upscaler_1=upscaler1Index, extras_upscaler_2=upscaler2Index, extras_mode=0, image_folder="", input_dir="", output_dir="") - return ExtrasSingleImageResponse(image=processing_utils.encode_pil_to_base64(result[0]), html_info_x=result[1], html_info=result[2]) + return ExtrasSingleImageResponse(image=processing_utils.encode_pil_to_base64(result[0][0]), html_info_x=result[1], html_info=result[2]) def extras_batch_images_api(self, req: ExtrasBatchImagesRequest): upscaler1Index = upscaler_to_index(req.upscaler_1) diff --git a/modules/api/models.py b/modules/api/models.py index 209f8af5..362e6277 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -1,10 +1,118 @@ -from pydantic import BaseModel, Field, Json +import inspect +from pydantic import BaseModel, Field, Json, create_model +from typing import Any, Optional from typing_extensions import Literal +from inflection import underscore +from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img from modules.shared import sd_upscalers +API_NOT_ALLOWED = [ + "self", + "kwargs", + "sd_model", + "outpath_samples", + "outpath_grids", + "sampler_index", + "do_not_save_samples", + "do_not_save_grid", + "extra_generation_params", + "overlay_images", + "do_not_reload_embeddings", + "seed_enable_extras", + "prompt_for_display", + "sampler_noise_scheduler_override", + "ddim_discretize" +] + +class ModelDef(BaseModel): + """Assistance Class for Pydantic Dynamic Model Generation""" + + field: str + field_alias: str + field_type: Any + field_value: Any + + +class PydanticModelGenerator: + """ + Takes in created classes and stubs them out in a way FastAPI/Pydantic is happy about: + source_data is a snapshot of the default values produced by the class + params are the names of the actual keys required by __init__ + """ + + def __init__( + self, + model_name: str = None, + class_instance = None, + additional_fields = None, + ): + def field_type_generator(k, v): + # field_type = str if not overrides.get(k) else overrides[k]["type"] + # print(k, v.annotation, v.default) + field_type = v.annotation + + return Optional[field_type] + + def merge_class_params(class_): + all_classes = list(filter(lambda x: x is not object, inspect.getmro(class_))) + parameters = {} + for classes in all_classes: + parameters = {**parameters, **inspect.signature(classes.__init__).parameters} + return parameters + + + self._model_name = model_name + self._class_data = merge_class_params(class_instance) + self._model_def = [ + ModelDef( + field=underscore(k), + field_alias=k, + field_type=field_type_generator(k, v), + field_value=v.default + ) + for (k,v) in self._class_data.items() if k not in API_NOT_ALLOWED + ] + + for fields in additional_fields: + self._model_def.append(ModelDef( + field=underscore(fields["key"]), + field_alias=fields["key"], + field_type=fields["type"], + field_value=fields["default"])) + + def generate_model(self): + """ + Creates a pydantic BaseModel + from the json and overrides provided at initialization + """ + fields = { + d.field: (d.field_type, Field(default=d.field_value, alias=d.field_alias)) for d in self._model_def + } + DynamicModel = create_model(self._model_name, **fields) + DynamicModel.__config__.allow_population_by_field_name = True + DynamicModel.__config__.allow_mutation = True + return DynamicModel + +StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( + "StableDiffusionProcessingTxt2Img", + StableDiffusionProcessingTxt2Img, + [{"key": "sampler_index", "type": str, "default": "Euler"}] +).generate_model() + +StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( + "StableDiffusionProcessingImg2Img", + StableDiffusionProcessingImg2Img, + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}] +).generate_model() + class TextToImageResponse(BaseModel): images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") - parameters: str + parameters: dict + info: str + +class ImageToImageResponse(BaseModel): + images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") + parameters: dict info: str class ExtrasBaseRequest(BaseModel): diff --git a/modules/api/processing.py b/modules/api/processing.py deleted file mode 100644 index f551fa35..00000000 --- a/modules/api/processing.py +++ /dev/null @@ -1,106 +0,0 @@ -from array import array -from inflection import underscore -from typing import Any, Dict, Optional -from pydantic import BaseModel, Field, create_model -from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img -import inspect - - -API_NOT_ALLOWED = [ - "self", - "kwargs", - "sd_model", - "outpath_samples", - "outpath_grids", - "sampler_index", - "do_not_save_samples", - "do_not_save_grid", - "extra_generation_params", - "overlay_images", - "do_not_reload_embeddings", - "seed_enable_extras", - "prompt_for_display", - "sampler_noise_scheduler_override", - "ddim_discretize" -] - -class ModelDef(BaseModel): - """Assistance Class for Pydantic Dynamic Model Generation""" - - field: str - field_alias: str - field_type: Any - field_value: Any - - -class PydanticModelGenerator: - """ - Takes in created classes and stubs them out in a way FastAPI/Pydantic is happy about: - source_data is a snapshot of the default values produced by the class - params are the names of the actual keys required by __init__ - """ - - def __init__( - self, - model_name: str = None, - class_instance = None, - additional_fields = None, - ): - def field_type_generator(k, v): - # field_type = str if not overrides.get(k) else overrides[k]["type"] - # print(k, v.annotation, v.default) - field_type = v.annotation - - return Optional[field_type] - - def merge_class_params(class_): - all_classes = list(filter(lambda x: x is not object, inspect.getmro(class_))) - parameters = {} - for classes in all_classes: - parameters = {**parameters, **inspect.signature(classes.__init__).parameters} - return parameters - - - self._model_name = model_name - self._class_data = merge_class_params(class_instance) - self._model_def = [ - ModelDef( - field=underscore(k), - field_alias=k, - field_type=field_type_generator(k, v), - field_value=v.default - ) - for (k,v) in self._class_data.items() if k not in API_NOT_ALLOWED - ] - - for fields in additional_fields: - self._model_def.append(ModelDef( - field=underscore(fields["key"]), - field_alias=fields["key"], - field_type=fields["type"], - field_value=fields["default"])) - - def generate_model(self): - """ - Creates a pydantic BaseModel - from the json and overrides provided at initialization - """ - fields = { - d.field: (d.field_type, Field(default=d.field_value, alias=d.field_alias)) for d in self._model_def - } - DynamicModel = create_model(self._model_name, **fields) - DynamicModel.__config__.allow_population_by_field_name = True - DynamicModel.__config__.allow_mutation = True - return DynamicModel - -StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( - "StableDiffusionProcessingTxt2Img", - StableDiffusionProcessingTxt2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}] -).generate_model() - -StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( - "StableDiffusionProcessingImg2Img", - StableDiffusionProcessingImg2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}] -).generate_model() \ No newline at end of file From 1e625624ba6ab3dfc167f0a5226780bb9b50fb58 Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sun, 23 Oct 2022 16:01:16 -0300 Subject: [PATCH 0193/1118] Add folder processing endpoint Also minor refactor --- modules/api/api.py | 56 +++++++++++++++++++++++-------------------- modules/api/models.py | 6 ++++- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 20e85e82..7b4fbe29 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -1,5 +1,5 @@ import uvicorn -from gradio import processing_utils +from gradio.processing_utils import encode_pil_to_base64, decode_base64_to_file, decode_base64_to_image from fastapi import APIRouter, HTTPException import modules.shared as shared from modules.api.models import * @@ -11,10 +11,18 @@ def upscaler_to_index(name: str): try: return [x.name.lower() for x in shared.sd_upscalers].index(name.lower()) except: - raise HTTPException(status_code=400, detail="Upscaler not found") + raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be on of these: {' , '.join([x.name for x in sd_upscalers])}") sampler_to_index = lambda name: next(filter(lambda row: name.lower() == row[1].name.lower(), enumerate(all_samplers)), None) +def setUpscalers(req: dict): + reqDict = vars(req) + reqDict['extras_upscaler_1'] = upscaler_to_index(req.upscaler_1) + reqDict['extras_upscaler_2'] = upscaler_to_index(req.upscaler_2) + reqDict.pop('upscaler_1') + reqDict.pop('upscaler_2') + return reqDict + class Api: def __init__(self, app, queue_lock): self.router = APIRouter() @@ -24,6 +32,7 @@ class Api: self.app.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=ImageToImageResponse) self.app.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse) self.app.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) + self.app.add_api_route("/sdapi/v1/extra-folder-images", self.extras_folder_processing_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): sampler_index = sampler_to_index(txt2imgreq.sampler_index) @@ -43,7 +52,7 @@ class Api: with self.queue_lock: processed = process_images(p) - b64images = list(map(processing_utils.encode_pil_to_base64, processed.images)) + b64images = list(map(encode_pil_to_base64, processed.images)) return TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.info) @@ -60,7 +69,7 @@ class Api: mask = img2imgreq.mask if mask: - mask = processing_utils.decode_base64_to_image(mask) + mask = decode_base64_to_image(mask) populate = img2imgreq.copy(update={ # Override __init__ params @@ -75,7 +84,7 @@ class Api: imgs = [] for img in init_images: - img = processing_utils.decode_base64_to_image(img) + img = decode_base64_to_image(img) imgs = [img] * p.batch_size p.init_images = imgs @@ -83,43 +92,38 @@ class Api: with self.queue_lock: processed = process_images(p) - b64images = list(map(processing_utils.encode_pil_to_base64, processed.images)) + b64images = list(map(encode_pil_to_base64, processed.images)) return ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.info) def extras_single_image_api(self, req: ExtrasSingleImageRequest): - upscaler1Index = upscaler_to_index(req.upscaler_1) - upscaler2Index = upscaler_to_index(req.upscaler_2) + reqDict = setUpscalers(req) - reqDict = vars(req) - reqDict.pop('upscaler_1') - reqDict.pop('upscaler_2') - - reqDict['image'] = processing_utils.decode_base64_to_image(reqDict['image']) + reqDict['image'] = decode_base64_to_image(reqDict['image']) with self.queue_lock: - result = run_extras(**reqDict, extras_upscaler_1=upscaler1Index, extras_upscaler_2=upscaler2Index, extras_mode=0, image_folder="", input_dir="", output_dir="") + result = run_extras(extras_mode=0, image_folder="", input_dir="", output_dir="", **reqDict) - return ExtrasSingleImageResponse(image=processing_utils.encode_pil_to_base64(result[0][0]), html_info_x=result[1], html_info=result[2]) + return ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info_x=result[1], html_info=result[2]) def extras_batch_images_api(self, req: ExtrasBatchImagesRequest): - upscaler1Index = upscaler_to_index(req.upscaler_1) - upscaler2Index = upscaler_to_index(req.upscaler_2) + reqDict = setUpscalers(req) - reqDict = vars(req) - reqDict.pop('upscaler_1') - reqDict.pop('upscaler_2') - - reqDict['image_folder'] = list(map(processing_utils.decode_base64_to_file, reqDict['imageList'])) + reqDict['image_folder'] = list(map(decode_base64_to_file, reqDict['imageList'])) reqDict.pop('imageList') with self.queue_lock: - result = run_extras(**reqDict, extras_upscaler_1=upscaler1Index, extras_upscaler_2=upscaler2Index, extras_mode=1, image="", input_dir="", output_dir="") + result = run_extras(extras_mode=1, image="", input_dir="", output_dir="", **reqDict) - return ExtrasBatchImagesResponse(images=list(map(processing_utils.encode_pil_to_base64, result[0])), html_info_x=result[1], html_info=result[2]) + return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info_x=result[1], html_info=result[2]) - def extras_folder_processing_api(self): - raise NotImplementedError + def extras_folder_processing_api(self, req:ExtrasFoldersRequest): + reqDict = setUpscalers(req) + + with self.queue_lock: + result = run_extras(extras_mode=2, image=None, image_folder=None, **reqDict) + + return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info_x=result[1], html_info=result[2]) def pnginfoapi(self): raise NotImplementedError diff --git a/modules/api/models.py b/modules/api/models.py index 362e6277..6f096807 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -150,4 +150,8 @@ class ExtrasBatchImagesRequest(ExtrasBaseRequest): imageList: list[str] = Field(title="Images", description="List of images to work on. Must be Base64 strings") class ExtrasBatchImagesResponse(ExtraBaseResponse): - images: list[str] = Field(title="Images", description="The generated images in base64 format.") \ No newline at end of file + images: list[str] = Field(title="Images", description="The generated images in base64 format.") + +class ExtrasFoldersRequest(ExtrasBaseRequest): + input_dir: str = Field(title="Input directory", description="Directory path from where to take the images") + output_dir: str = Field(title="Output directory", description="Directory path to put the processsed images into") From 90f02c75220d187e075203a4e3b450bfba392c4d Mon Sep 17 00:00:00 2001 From: Bruno Seoane Date: Sun, 23 Oct 2022 16:03:30 -0300 Subject: [PATCH 0194/1118] Remove unused field and class --- modules/api/api.py | 6 +++--- modules/api/models.py | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 7b4fbe29..799e3701 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -104,7 +104,7 @@ class Api: with self.queue_lock: result = run_extras(extras_mode=0, image_folder="", input_dir="", output_dir="", **reqDict) - return ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info_x=result[1], html_info=result[2]) + return ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info=result[1]) def extras_batch_images_api(self, req: ExtrasBatchImagesRequest): reqDict = setUpscalers(req) @@ -115,7 +115,7 @@ class Api: with self.queue_lock: result = run_extras(extras_mode=1, image="", input_dir="", output_dir="", **reqDict) - return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info_x=result[1], html_info=result[2]) + return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1]) def extras_folder_processing_api(self, req:ExtrasFoldersRequest): reqDict = setUpscalers(req) @@ -123,7 +123,7 @@ class Api: with self.queue_lock: result = run_extras(extras_mode=2, image=None, image_folder=None, **reqDict) - return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info_x=result[1], html_info=result[2]) + return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1]) def pnginfoapi(self): raise NotImplementedError diff --git a/modules/api/models.py b/modules/api/models.py index 6f096807..e461d397 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -130,8 +130,7 @@ class ExtrasBaseRequest(BaseModel): extras_upscaler_2_visibility: float = Field(default=0, title="Secondary upscaler visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of secondary upscaler, values should be between 0 and 1.") class ExtraBaseResponse(BaseModel): - html_info_x: str - html_info: str + html_info: str = Field(title="HTML info", description="A series of HTML tags containing the process info.") class ExtrasSingleImageRequest(ExtrasBaseRequest): image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") @@ -139,9 +138,6 @@ class ExtrasSingleImageRequest(ExtrasBaseRequest): class ExtrasSingleImageResponse(ExtraBaseResponse): image: str = Field(default=None, title="Image", description="The generated image in base64 format.") -class SerializableImage(BaseModel): - path: str = Field(title="Path", description="The image's path ()") - class ImageItem(BaseModel): data: str = Field(title="image data") name: str = Field(title="filename") From 6124575e1892259bf706db186de303acc9de47bf Mon Sep 17 00:00:00 2001 From: Dynamic Date: Mon, 24 Oct 2022 04:29:19 +0900 Subject: [PATCH 0195/1118] Translation complete --- localizations/ko_KR.json | 302 +++++++++++++++++++++------------------ 1 file changed, 160 insertions(+), 142 deletions(-) diff --git a/localizations/ko_KR.json b/localizations/ko_KR.json index a48ece87..6889de46 100644 --- a/localizations/ko_KR.json +++ b/localizations/ko_KR.json @@ -15,23 +15,24 @@ "A merger of the two checkpoints will be generated in your": "체크포인트들이 병합된 결과물이 당신의", "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", - "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", + "Add a second progress bar to the console that shows progress for an entire job.": "콘솔에 전체 작업의 진행도를 보여주는 2번째 프로그레스 바 추가하기", "Add difference": "차이점 추가", - "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", + "Add extended info (seed, prompt) to filename when saving grid": "그리드 저장 시 파일명에 추가 정보(시드, 프롬프트) 기입", "Add layer normalization": "레이어 정규화(normalization) 추가", - "Add model hash to generation information": "Add model hash to generation information", - "Add model name to generation information": "Add model name to generation information", + "Add model hash to generation information": "생성 정보에 모델 해시 추가", + "Add model name to generation information": "생성 정보에 모델 이름 추가", "Aesthetic imgs embedding": "스타일 이미지 임베딩", "Aesthetic learning rate": "스타일 학습 수", "Aesthetic steps": "스타일 스텝 수", "Aesthetic text for imgs": "스타일 텍스트", "Aesthetic weight": "스타일 가중치", - "Always print all generation info to standard output": "Always print all generation info to standard output", - "Always save all generated image grids": "Always save all generated image grids", + "Allowed categories for random artists selection when using the Roll button": "랜덤 버튼을 눌러 무작위 작가를 선택할 때 허용된 카테고리", + "Always print all generation info to standard output": "기본 아웃풋에 모든 생성 정보 항상 출력하기", + "Always save all generated image grids": "생성된 이미지 그리드 항상 저장하기", "Always save all generated images": "생성된 이미지 항상 저장하기", "api": "", "append": "뒤에 삽입", - "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", + "Apply color correction to img2img results to match original colors.": "이미지→이미지 결과물이 기존 색상과 일치하도록 색상 보정 적용하기", "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", "Apply settings": "설정 적용하기", "Batch count": "배치 수", @@ -43,29 +44,29 @@ "built with gradio": "gradio로 제작되었습니다", "Cancel generate forever": "반복 생성 취소", "CFG Scale": "CFG 스케일", - "Check progress": "Check progress", - "Check progress (first)": "Check progress (first)", + "Check progress": "진행도 체크", + "Check progress (first)": "진행도 체크 (처음)", "checkpoint": " 체크포인트 ", "Checkpoint Merger": "체크포인트 병합", "Checkpoint name": "체크포인트 이름", - "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", + "Checkpoints to cache in RAM": "RAM에 캐싱할 체크포인트 수", "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", - "Click to Upload": "Click to Upload", + "Click to Upload": "클릭해서 업로드하기", "Clip skip": "클립 건너뛰기", - "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP : 텍스트 파일 최대 라인 수 (0 = 제한 없음)", "CodeFormer visibility": "CodeFormer 가시성", "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer 가중치 (0 = 최대 효과, 1 = 최소 효과)", - "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", + "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer 가중치 설정값 (0 = 최대 효과, 1 = 최소 효과)", "Color variation": "색깔 다양성", "Collect": "즐겨찾기", "copy": "복사", "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", - "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", - "Create aesthetic images embedding": "Create aesthetic images embedding", + "Create a text file next to every image with generation parameters.": "생성된 이미지마다 생성 설정값을 담은 텍스트 파일 생성하기", + "Create aesthetic images embedding": "스타일 이미지 임베딩 생성하기", "Create embedding": "임베딩 생성", "Create flipped copies": "좌우로 뒤집은 복사본 생성", "Create hypernetwork": "하이퍼네트워크 생성", - "Create images embedding": "Create images embedding", + "Create images embedding": "이미지 임베딩 생성하기", "Crop and resize": "잘라낸 후 리사이징", "Crop to fit": "잘라내서 맞추기", "Custom Name (Optional)": "병합 모델 이름 (선택사항)", @@ -80,15 +81,15 @@ "Denoising strength change factor": "디노이즈 강도 변경 배수", "Destination directory": "결과물 저장 경로", "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", - "Directory for saving images using the Save button": "Directory for saving images using the Save button", - "Directory name pattern": "Directory name pattern", + "Directory for saving images using the Save button": "저장 버튼을 이용해 저장하는 이미지들의 저장 경로", + "Directory name pattern": "디렉토리명 패턴", "directory.": "저장 경로에 저장됩니다.", - "Do not add watermark to images": "Do not add watermark to images", + "Do not add watermark to images": "이미지에 워터마크 추가하지 않기", "Do not do anything special": "아무것도 하지 않기", - "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", - "Do not show any images in results for web": "Do not show any images in results for web", + "Do not save grids consisting of one picture": "이미지가 1개뿐인 그리드는 저장하지 않기", + "Do not show any images in results for web": "웹에서 결과창에 아무 이미지도 보여주지 않기", "down": "아래쪽", - "Download localization template": "Download localization template", + "Download localization template": "현지화 템플릿 다운로드", "Download": "다운로드", "DPM adaptive": "DPM adaptive", "DPM fast": "DPM fast", @@ -98,65 +99,67 @@ "DPM2 Karras": "DPM2 Karras", "Draw legend": "범례 그리기", "Draw mask": "마스크 직접 그리기", - "Drop File Here": "Drop File Here", - "Drop Image Here": "Drop Image Here", + "Drop File Here": "파일을 끌어 놓으세요", + "Drop Image Here": "이미지를 끌어 놓으세요", "Embedding": "임베딩", "Embedding Learning rate": "임베딩 학습률", - "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", - "Enable full page image viewer": "Enable full page image viewer", - "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", + "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "강조 : (텍스트)를 이용해 모델의 텍스트에 대한 가중치를 더 강하게 주고 [텍스트]를 이용해 더 약하게 줍니다.", + "Enable full page image viewer": "전체 페이지 이미지 뷰어 활성화", + "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "더 예리하고 깔끔한 결과물을 위해 K 샘플러들에 양자화를 적용합니다. 존재하는 시드가 변경될 수 있습니다. 재시작이 필요합니다.", "End Page": "마지막 페이지", "Enter hypernetwork layer structure": "하이퍼네트워크 레이어 구조 입력", "Error": "오류", - "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "deepbooru에서 괄호를 역슬래시(\\)로 이스케이프 처리하기(가중치 강조가 아니라 실제 괄호로 사용되게 하기 위해)", "ESRGAN_4x": "ESRGAN_4x", "Eta": "Eta", - "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", - "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", - "Eta noise seed delta": "Eta noise seed delta", + "eta (noise multiplier) for ancestral samplers": "ancestral 샘플러를 위한 eta(노이즈 배수)값", + "eta (noise multiplier) for DDIM": "DDIM을 위한 eta(노이즈 배수)값", + "Eta noise seed delta": "Eta 노이즈 시드 변화", "Euler": "Euler", "Euler a": "Euler a", "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", "Existing Caption txt Action": "이미 존재하는 캡션 텍스트 처리", "Extra": "고급", "Extras": "부가기능", - "extras history": "extras history", - "Face restoration": "Face restoration", + "extras history": "부가기능 기록", + "Face restoration": "얼굴 보정", + "Face restoration model": "얼굴 보정 모델", "Fall-off exponent (lower=higher detail)": "감쇠 지수 (낮을수록 디테일이 올라감)", "favorites": "즐겨찾기", - "File": "File", - "File format for grids": "File format for grids", - "File format for images": "File format for images", + "File": "파일", + "File format for grids": "그리드 이미지 파일 형식", + "File format for images": "이미지 파일 형식", "File Name": "파일 이름", "File with inputs": "설정값 파일", - "Filename join string": "Filename join string", - "Filename word regex": "Filename word regex", + "Filename join string": "파일명 병합 문자열", + "Filename word regex": "파일명 정규표현식", "fill": "채우기", "fill it with colors of the image": "이미지의 색상으로 채우기", "fill it with latent space noise": "잠재 공간 노이즈로 채우기", "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", - "Filter NSFW content": "Filter NSFW content", + "Filter NSFW content": "성인 컨텐츠 필터링하기", "First Page": "처음 페이지", "Firstpass height": "초기 세로길이", "Firstpass width": "초기 가로길이", - "Font for image grids that have text": "Font for image grids that have text", + "Font for image grids that have text": "텍스트가 존재하는 그리드 이미지의 폰트", "for detailed explanation.": "를 참조하십시오.", "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "SD 업스케일링에서 타일 간 몇 픽셀을 겹치게 할지 결정하는 설정값입니다. 타일들이 다시 한 이미지로 합쳐질 때, 눈에 띄는 이음매가 없도록 서로 겹치게 됩니다.", "Generate": "생성", "Generate forever": "반복 생성", "Generate Info": "생성 정보", "GFPGAN visibility": "GFPGAN 가시성", - "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", + "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "그리드 세로줄 수 : -1로 설정 시 자동 감지/0으로 설정 시 배치 크기와 동일", "Height": "세로", "Heun": "Heun", "hide": "api 숨기기", - "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", + "Hide samplers in user interface (requires restart)": "사용자 인터페이스에서 숨길 샘플러 선택(재시작 필요)", "Highres. fix": "고해상도 보정", "History": "기록", "Image Browser": "이미지 브라우저", + "Images Browser": "이미지 브라우저", "Images directory": "이미지 경로", "extras": "부가기능", - "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", + "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "훈련이 얼마나 빨리 이루어질지 정하는 값입니다. 값이 낮을수록 훈련 시간이 길어지고, 높은 값일수록 정확한 결과를 내는 데 실패하고 임베딩을 망가뜨릴 수 있습니다(임베딩이 망가진 경우에는 훈련 정보 텍스트박스에 손실(Loss) : nan 이라고 출력되게 됩니다. 이 경우에는 망가지지 않은 이전 백업본을 불러와야 합니다).\n\n학습률은 하나의 값으로 설정할 수도 있고, 다음 문법을 사용해 여러 값을 사용할 수도 있습니다 :\n\n학습률_1:최대 스텝수_1, 학습률_2:최대 스텝수_2, ...\n\n예 : 0.005:100, 1e-3:1000, 1e-5\n\n예의 설정값은 첫 100스텝동안 0.005의 학습률로, 그 이후 1000스텝까지는 1e-3으로, 남은 스텝은 1e-5로 훈련하게 됩니다.", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", @@ -166,111 +169,114 @@ "Hypernet str.": "하이퍼네트워크 강도", "Hypernetwork": "하이퍼네트워크", "Hypernetwork Learning rate": "하이퍼네트워크 학습률", - "Hypernetwork strength": "Hypernetwork strength", - "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", - "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", - "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "Hypernetwork strength": "하이퍼네트워크 강도", + "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "PNG 이미지가 4MB보다 크거나 가로 또는 세로길이가 4000보다 클 경우, 다운스케일 후 JPG로 복사본 저장하기", + "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "이 옵션이 활성화되면 생성된 이미지에 워터마크가 추가되지 않습니다. 경고 : 워터마크를 추가하지 않는다면, 비윤리적인 행동을 하는 중일지도 모릅니다.", + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "이 값이 0이 아니라면, 시드에 해당 값이 더해지고, Eta가 있는 샘플러를 사용할 때 노이즈의 RNG 조정을 위해 해당 값이 사용됩니다. 이 설정으로 더 다양한 이미지를 생성하거나, 잘 알고 계시다면 특정 소프트웨어의 결과값을 재현할 수도 있습니다.", "ignore": "무시", - "Image": "Image", + "Image": "이미지", "Image for img2img": "Image for img2img", - "Image for inpainting with mask": "Image for inpainting with mask", - "Images filename pattern": "Images filename pattern", + "Image for inpainting with mask": "마스크로 인페인팅할 이미지", + "Images filename pattern": "이미지 파일명 패턴", "img2img": "이미지→이미지", "img2img alternative test": "이미지→이미지 대체버전 테스트", - "img2img DDIM discretize": "img2img DDIM discretize", - "img2img history": "img2img history", + "img2img DDIM discretize": "이미지→이미지 DDIM 이산화", + "img2img history": "이미지→이미지 기록", "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "루프백 모드에서는 매 루프마다 디노이즈 강도에 이 값이 곱해집니다. 1보다 작을 경우 다양성이 낮아져 결과 이미지들이 고정된 형태로 모일 겁니다. 1보다 클 경우 다양성이 높아져 결과 이미지들이 갈수록 혼란스러워지겠죠.", "Include Separate Images": "분리된 이미지 포함하기", - "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", + "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "75개보다 많은 토큰을 사용시 마지막 쉼표로부터 N개의 토큰 이내에 패딩을 추가해 통일성 증가시키기", "Initialization text": "초기화 텍스트", "Inpaint": "인페인트", "Inpaint at full resolution": "전체 해상도로 인페인트하기", - "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", + "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트시 패딩값(픽셀 단위)", "Inpaint masked": "마스크만 처리", "Inpaint not masked": "마스크 이외만 처리", "Input directory": "인풋 이미지 경로", "Interpolation Method": "보간 방법", "Interrogate\nCLIP": "CLIP\n분석", "Interrogate\nDeepBooru": "DeepBooru\n분석", - "Interrogate Options": "Interrogate Options", - "Interrogate: deepbooru score threshold": "Interrogate: deepbooru score threshold", - "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooru sort alphabetically", - "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).", - "Interrogate: keep models in VRAM": "Interrogate: keep models in VRAM", - "Interrogate: maximum description length": "Interrogate: maximum description length", - "Interrogate: minimum description length (excluding artists, etc..)": "Interrogate: minimum description length (excluding artists, etc..)", - "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", - "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", + "Interrogate Options": "분석 설정", + "Interrogate: deepbooru score threshold": "분석 : deepbooru 점수 임계값", + "Interrogate: deepbooru sort alphabetically": "분석 : deepbooru 알파벳 순서로 정렬하기", + "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "분석 : 결과물에 모델 태그의 랭크 포함하기 (캡션 바탕의 분석기에는 효과 없음)", + "Interrogate: keep models in VRAM": "분석 : VRAM에 모델 유지하기", + "Interrogate: maximum description length": "분석 : 설명 최대 길이", + "Interrogate: minimum description length (excluding artists, etc..)": "분석 : 설명 최소 길이(작가 등등..제외)", + "Interrogate: num_beams for BLIP": "분석 : BLIP의 num_beams값", + "Interrogate: use artists from artists.csv": "분석 : artists.csv의 작가들 사용하기", "Interrupt": "중단", "Is negative text": "네거티브 텍스트일시 체크", "Just resize": "리사이징", "Keep -1 for seeds": "시드값 -1로 유지", "keep whatever was there originally": "이미지 원본 유지", - "Label": "Label", + "Label": "라벨", "Lanczos": "Lanczos", - "Last prompt:": "Last prompt:", - "Last saved hypernetwork:": "Last saved hypernetwork:", - "Last saved image:": "Last saved image:", + "Last prompt:": "마지막 프롬프트 : ", + "Last saved hypernetwork:": "마지막으로 저장된 하이퍼네트워크 : ", + "Last saved image:": "마지막으로 저장된 이미지 : ", "latent noise": "잠재 노이즈", "latent nothing": "잠재 공백", "LDSR": "LDSR", - "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", + "LDSR processing steps. Lower = faster": "LDSR 스텝 수. 낮은 값 = 빠른 속도", "leakyrelu": "leakyrelu", "Leave blank to save images to the default path.": "기존 저장 경로에 이미지들을 저장하려면 비워두세요.", "left": "왼쪽", "linear": "linear", - "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", + "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "설정 탭이 아니라 상단의 빠른 설정 바에 위치시킬 설정 이름을 쉼표로 분리해서 입력하십시오. 설정 이름은 modules/shared.py에서 찾을 수 있습니다. 재시작이 필요합니다.", "LMS": "LMS", "LMS Karras": "LMS Karras", "Load": "불러오기", "Loading...": "로딩 중...", - "Localization (requires restart)": "Localization (requires restart)", + "Localization (requires restart)": "현지화 (재시작 필요)", "Log directory": "로그 경로", "Loopback": "루프백", "Loops": "루프 수", - "Loss:": "Loss:", + "Loss:": "손실(Loss) : ", "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", - "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", + "Make K-diffusion samplers produce same images in a batch as when making a single image": "K-diffusion 샘플러들이 단일 이미지를 생성하는 것처럼 배치에서도 동일한 이미지를 생성하게 하기", "Make Zip when Save?": "저장 시 Zip 생성하기", "Mask": "마스크", "Mask blur": "마스크 블러", - "Mask mode": "Mask mode", + "Mask mode": "마스크 모드", "Masked content": "마스크된 부분", - "Masking mode": "Masking mode", - "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", + "Masking mode": "마스킹 모드", + "Max prompt words for [prompt_words] pattern": "[prompt_words] 패턴의 최대 프롬프트 단어 수", "Max steps": "최대 스텝 수", "Modules": "모듈", - "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", - "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.", + "Move face restoration model from VRAM into RAM after processing": "처리가 완료되면 얼굴 보정 모델을 VRAM에서 RAM으로 옮기기", + "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "하이퍼네트워크 훈련 진행 시 VAE와 CLIP을 RAM으로 옮기기. VRAM이 절약됩니다.", "Multiplier (M) - set to 0 to get model A": "배율 (M) - 0으로 적용하면 모델 A를 얻게 됩니다", "Name": "이름", "Negative prompt": "네거티브 프롬프트", "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", "Next batch": "다음 묶음", "Next Page": "다음 페이지", - "None": "None", + "None": "없음", "Nothing": "없음", "Nothing found in the image.": "Nothing found in the image.", "number of images to delete consecutively next": "연속적으로 삭제할 이미지 수", - "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", + "Number of pictures displayed on each page": "각 페이지에 표시될 이미지 수", + "Minimum number of pages per load": "한번 불러올 때마다 불러올 최소 페이지 수", + "Number of grids in each row": "각 세로줄마다 표시될 그리드 수", + "Number of repeats for a single input image per epoch; used only for displaying epoch number": "세대(Epoch)당 단일 인풋 이미지의 반복 횟수 - 세대(Epoch) 숫자를 표시하는 데에만 사용됩니다. ", "Number of vectors per token": "토큰별 벡터 수", "Open for Clip Aesthetic!": "클립 스타일 기능을 활성화하려면 클릭!", "Open images output directory": "이미지 저장 경로 열기", "Open output directory": "저장 경로 열기", - "or": "or", + "or": "또는", "original": "원본 유지", "Original negative prompt": "기존 네거티브 프롬프트", "Original prompt": "기존 프롬프트", "Outpainting direction": "아웃페인팅 방향", "Outpainting mk2": "아웃페인팅 마크 2", "Output directory": "이미지 저장 경로", - "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", - "Output directory for images from extras tab": "Output directory for images from extras tab", - "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", - "Output directory for img2img grids": "Output directory for img2img grids", - "Output directory for img2img images": "Output directory for img2img images", - "Output directory for txt2img grids": "Output directory for txt2img grids", - "Output directory for txt2img images": "Output directory for txt2img images", + "Output directory for grids; if empty, defaults to two directories below": "그리드 이미지 저장 경로 - 비워둘 시 하단의 2가지 기본 경로로 설정됨", + "Output directory for images from extras tab": "부가기능 탭 저장 경로", + "Output directory for images; if empty, defaults to three directories below": "이미지 저장 경로 - 비워둘 시 하단의 3가지 기본 경로로 설정됨", + "Output directory for img2img grids": "이미지→이미지 그리드 저장 경로", + "Output directory for img2img images": "이미지→이미지 저장 경로", + "Output directory for txt2img grids": "텍스트→이미지 그리드 저장 경로", + "Output directory for txt2img images": "텍스트→이미지 저장 경로", "Override `Denoising strength` to 1?": "디노이즈 강도를 1로 적용할까요?", "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "프롬프트 값을 기존 프롬프트와 동일하게 적용할까요?(네거티브 프롬프트 포함)", "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", @@ -279,20 +285,21 @@ "Overwrite Old Hypernetwork": "기존 하이퍼네트워크 덮어쓰기", "Page Index": "페이지 인덱스", "parameters": "설정값", - "Path to directory where to write outputs": "Path to directory where to write outputs", + "Path to directory where to write outputs": "결과물을 출력할 경로", "Path to directory with input images": "인풋 이미지가 있는 경로", - "Paths for saving": "Paths for saving", + "Paths for saving": "저장 경로", "Pixels to expand": "확장할 픽셀 수", "PLMS": "PLMS", "PNG Info": "PNG 정보", "Poor man's outpainting": "가난뱅이의 아웃페인팅", - "Preparing dataset from": "Preparing dataset from", + "Preload images at startup": "WebUI 가동 시 이미지 프리로드하기", + "Preparing dataset from": "준비된 데이터셋 경로 : ", "prepend": "앞에 삽입", "Preprocess": "전처리", "Preprocess images": "이미지 전처리", "Prev batch": "이전 묶음", "Prev Page": "이전 페이지", - "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", + "Prevent empty spots in grid (when set to autodetect)": "(자동 감지 사용시)그리드에 빈칸이 생기는 것 방지하기", "Primary model (A)": "주 모델 (A)", "Process an image, use it as an input, repeat.": "이미지를 생성하고, 생성한 이미지를 다시 원본으로 사용하는 과정을 반복합니다.", "Process images in a directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리의 이미지들을 처리합니다.", @@ -307,26 +314,26 @@ "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", "quad": "quad", - "Quality for saved jpeg images": "Quality for saved jpeg images", - "Quicksettings list": "Quicksettings list", + "Quality for saved jpeg images": "저장된 jpeg 이미지들의 품질", + "Quicksettings list": "빠른 설정 리스트", "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", "Randomness": "랜덤성", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", "Read parameters (prompt, etc...) from txt2img tab when making previews": "프리뷰 이미지 생성 시 텍스트→이미지 탭에서 설정값(프롬프트 등) 읽어오기", "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "추천 설정값 - 샘플링 스텝 수 : 80-100 , 샘플러 : Euler a, 디노이즈 강도 : 0.8", - "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", + "Reload custom script bodies (No ui updates, No restart)": "커스텀 스크립트 리로드하기(UI 업데이트 없음, 재시작 없음)", "relu": "relu", "Renew Page": "Renew Page", - "Request browser notifications": "Request browser notifications", + "Request browser notifications": "브라우저 알림 권한 요청", "Resize": "리사이징 배수", "Resize and fill": "리사이징 후 채우기", "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "설정된 해상도로 이미지 리사이징을 진행합니다. 원본과 가로/세로 길이가 일치하지 않을 경우, 부정확한 화면비의 이미지를 얻게 됩니다.", - "Resize mode": "Resize mode", + "Resize mode": "리사이징 모드", "Resize seed from height": "시드 리사이징 가로길이", "Resize seed from width": "시드 리사이징 세로길이", "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "이미지 전체가 설정된 해상도 내부에 들어가게 리사이징을 진행합니다. 빈 공간은 이미지의 색상으로 채웁니다.", "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "설정된 해상도 전체가 이미지로 가득차게 리사이징을 진행합니다. 튀어나오는 부분은 잘라냅니다.", - "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", + "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Gradio를 재시작하고 컴포넌트 새로고침하기 (커스텀 스크립트, ui.py, js, css만 해당됨)", "Restore faces": "얼굴 보정", "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", "Result = A * (1 - M) + B * M": "결과물 = A * (1 - M) + B * M", @@ -335,23 +342,23 @@ "right": "오른쪽", "Run": "가동", "Sampler": "샘플러", - "Sampler parameters": "Sampler parameters", + "Sampler parameters": "샘플러 설정값", "Sampling method": "샘플링 방법", "Sampling Steps": "샘플링 스텝 수", "Save": "저장", "Save a copy of embedding to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 임베딩을 저장합니다, 비활성화하려면 0으로 설정하십시오.", - "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", - "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", - "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", + "Save a copy of image before applying color correction to img2img results": "이미지→이미지 결과물에 색상 보정을 진행하기 전 이미지의 복사본을 저장하기", + "Save a copy of image before doing face restoration.": "얼굴 보정을 진행하기 전 이미지의 복사본을 저장하기", + "Save an csv containing the loss to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 손실(Loss)을 포함하는 csv 파일을 저장합니다, 비활성화하려면 0으로 설정하십시오.", "Save an image to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 이미지를 저장합니다, 비활성화하려면 0으로 설정하십시오.", "Save as float16": "float16으로 저장", - "Save grids to a subdirectory": "Save grids to a subdirectory", - "Save images to a subdirectory": "Save images to a subdirectory", + "Save grids to a subdirectory": "그리드 이미지를 하위 디렉토리에 저장하기", + "Save images to a subdirectory": "이미지를 하위 디렉토리에 저장하기", "Save images with embedding in PNG chunks": "PNG 청크로 이미지에 임베딩을 포함시켜 저장", "Save style": "스타일 저장", - "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", - "Saving images/grids": "Saving images/grids", - "Saving to a directory": "Saving to a directory", + "Save text information about generation parameters as chunks to png files": "이미지 생성 설정값을 PNG 청크에 텍스트로 저장", + "Saving images/grids": "이미지/그리드 저장", + "Saving to a directory": "디렉토리에 저장", "Scale by": "스케일링 배수 지정", "Scale to": "스케일링 사이즈 지정", "Script": "스크립트", @@ -363,6 +370,7 @@ "Seed": "시드", "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", "Select activation function of hypernetwork": "하이퍼네트워크 활성화 함수 선택", + "Select which Real-ESRGAN models to show in the web UI. (Requires restart)": "WebUI에 표시할 Real-ESRGAN 모델을 선택하십시오. (재시작 필요)", "Send to extras": "부가기능으로 전송", "Send to img2img": "이미지→이미지로 전송", "Send to inpaint": "인페인트로 전송", @@ -374,29 +382,30 @@ "set_index": "set_index", "Settings": "설정", "should be 2 or lower.": "이 2 이하여야 합니다.", - "Show generation progress in window title.": "Show generation progress in window title.", - "Show grid in results for web": "Show grid in results for web", - "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", - "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", - "Show progressbar": "Show progressbar", + "Show generation progress in window title.": "창 타이틀에 생성 진행도 보여주기", + "Show grid in results for web": "웹에서 결과창에 그리드 보여주기", + "Show image creation progress every N sampling steps. Set 0 to disable.": "N번째 샘플링 스텝마다 이미지 생성 과정 보이기 - 비활성화하려면 0으로 설정", + "Show images zoomed in by default in full page image viewer": "전체 페이지 이미지 뷰어에서 기본값으로 이미지 확대해서 보여주기", + "Show progressbar": "프로그레스 바 보이기", "Show result images": "이미지 결과 보이기", "Show Textbox": "텍스트박스 보이기", + "Show previews of all images generated in a batch as a grid": "배치에서 생성된 모든 이미지의 미리보기를 그리드 형식으로 보여주기", "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", "Sigma Churn": "시그마 섞기", - "sigma churn": "sigma churn", + "sigma churn": "시그마 섞기", "Sigma max": "시그마 최댓값", "Sigma min": "시그마 최솟값", "Sigma noise": "시그마 노이즈", - "sigma noise": "sigma noise", - "sigma tmin": "sigma tmin", + "sigma noise": "시그마 노이즈", + "sigma tmin": "시그마 tmin", "Single Image": "단일 이미지", "Skip": "건너뛰기", "Slerp angle": "구면 선형 보간 각도", "Slerp interpolation": "구면 선형 보간", "Source": "원본", "Source directory": "원본 경로", - "Split image threshold": "Split image threshold", - "Split image overlap ratio": "Split image overlap ratio", + "Split image threshold": "이미지 분할 임계값", + "Split image overlap ratio": "이미지 분할 겹침 비율", "Split oversized images": "사이즈가 큰 이미지 분할하기", "Stable Diffusion": "Stable Diffusion", "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", @@ -407,20 +416,20 @@ "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", "Style 1": "스타일 1", "Style 2": "스타일 2", - "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", + "Style to apply; styles have components for both positive and negative prompts and apply to both": "적용할 스타일 - 스타일은 긍정/부정 프롬프트 모두에 대한 설정값을 가지고 있고 양쪽 모두에 적용 가능합니다.", "SwinIR 4x": "SwinIR 4x", "Sys VRAM:": "시스템 VRAM : ", - "System": "System", + "System": "시스템", "Tertiary model (C)": "3차 모델 (C)", - "Textbox": "Textbox", - "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", - "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", + "Textbox": "텍스트박스", + "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "이 정규표현식은 파일명으로부터 단어를 추출하는 데 사용됩니다. 추출된 단어들은 하단의 설정을 이용해 라벨 텍스트로 변환되어 훈련에 사용됩니다. 파일명 텍스트를 유지하려면 비워두십시오.", + "This string will be used to join split words into a single line if the option above is enabled.": "이 문자열은 상단 설정이 활성화되어있을 때 분리된 단어들을 한 줄로 합치는 데 사용됩니다.", "This text is used to rotate the feature space of the imgs embs": "이 텍스트는 이미지 임베딩의 특징 공간을 회전하는 데 사용됩니다.", "Tile overlap": "타일 겹침", - "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", - "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", - "Tile size for all SwinIR.": "Tile size for all SwinIR.", - "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", + "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "ESRGAN 업스케일러들의 타일 중첩 수치, 픽셀 단위. 낮은 값 = 눈에 띄는 이음매.", + "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "SwinIR의 타일 중첩 수치, 픽셀 단위. 낮은 값 = 눈에 띄는 이음매.", + "Tile size for all SwinIR.": "SwinIR의 타일 사이즈.", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "ESRGAN 업스케일러들의 타일 사이즈. 0 = 타일링 없음.", "Tiling": "타일링", "Time taken:": "소요 시간 : ", "Torch active/reserved:": "활성화/예약된 Torch 양 : ", @@ -429,51 +438,60 @@ "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "임베딩이나 하이퍼네트워크를 훈련시킵니다. 1:1 비율의 이미지가 있는 경로를 지정해야 합니다.", "Train Embedding": "임베딩 훈련", "Train Hypernetwork": "하이퍼네트워크 훈련", - "Training": "Training", + "Training": "훈련", "txt2img": "텍스트→이미지", - "txt2img history": "txt2img history", + "txt2img history": "텍스트→이미지 기록", "uniform": "uniform", "up": "위쪽", "Upload mask": "마스크 업로드하기", - "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", + "Upscale latent space image when doing hires. fix": "고해상도 보정 사용시 잠재 공간 이미지 업스케일하기", "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "마스크된 부분을 설정된 해상도로 업스케일하고, 인페인팅을 진행한 뒤, 다시 다운스케일 후 원본 이미지에 붙여넣습니다.", "Upscaler": "업스케일러", "Upscaler 1": "업스케일러 1", "Upscaler 2": "업스케일러 2", "Upscaler 2 visibility": "업스케일러 2 가시성", - "Upscaler for img2img": "Upscaler for img2img", - "Upscaling": "Upscaling", + "Upscaler for img2img": "이미지→이미지 업스케일러", + "Upscaling": "업스케일링", "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", "Use an empty output directory to save pictures normally instead of writing to the output directory.": "저장 경로를 비워두면 기본 저장 폴더에 이미지들이 저장됩니다.", "Use BLIP for caption": "캡션에 BLIP 사용", "Use deepbooru for caption": "캡션에 deepbooru 사용", "Use dropout": "드롭아웃 사용", - "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", - "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", - "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", - "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", - "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", - "User interface": "User interface", + "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "다음 태그들을 사용해 이미지 파일명 형식을 결정하세요 : [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]. 비워두면 기본값으로 설정됩니다.", + "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "다음 태그들을 사용해 이미지와 그리드의 하위 디렉토리명의 형식을 결정하세요 : [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]. 비워두면 기본값으로 설정됩니다.", + "Use old emphasis implementation. Can be useful to reproduce old seeds.": "옛 방식의 강조 구현을 사용합니다. 옛 시드를 재현하는 데 효과적일 수 있습니다.", + "Use original name for output filename during batch process in extras tab": "부가기능 탭에서 이미지를 여러장 처리 시 결과물 파일명에 기존 파일명 사용하기", + "use spaces for tags in deepbooru": "deepbooru에서 태그에 공백 사용", + "User interface": "사용자 인터페이스", "Var. seed": "바리에이션 시드", "Var. strength": "바리에이션 강도", "Variation seed": "바리에이션 시드", "Variation strength": "바리에이션 강도", "view": "api 보이기", - "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", + "VRAM usage polls per second during generation. Set to 0 to disable.": "생성 도중 초당 VRAM 사용량 폴링 수. 비활성화하려면 0으로 설정하십시오.", "Weighted sum": "가중 합", "What to put inside the masked area before processing it with Stable Diffusion.": "Stable Diffusion으로 이미지를 생성하기 전 마스크된 부분에 무엇을 채울지 결정하는 설정값", - "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", - "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", - "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", + "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "PNG 정보나 붙여넣은 텍스트로부터 생성 설정값을 읽어올 때, 선택된 모델/체크포인트는 변경하지 않기.", + "When using \"Save\" button, save images to a subdirectory": "저장 버튼 사용시, 이미지를 하위 디렉토리에 저장하기", + "When using 'Save' button, only save a single selected image": "저장 버튼 사용시, 선택된 이미지 1개만 저장하기", "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", "Width": "가로", "wiki": " 위키", "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "이미지를 설정된 사이즈의 2배로 업스케일합니다. 상단의 가로와 세로 슬라이더를 이용해 타일 사이즈를 지정하세요.", - "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", + "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "이미지→이미지 진행 시, 슬라이더로 설정한 스텝 수를 정확히 실행하기 (일반적으로 디노이즈 강도가 낮을수록 실제 설정된 스텝 수보다 적게 진행됨)", "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", "X type": "X축", "X values": "X 설정값", "X/Y plot": "X/Y 플롯", "Y type": "Y축", - "Y values": "Y 설정값" + "Y values": "Y 설정값", + "step1 min/max": "스텝1 최소/최대", + "step2 min/max": "스텝2 최소/최대", + "step count": "스텝 변화 횟수", + "cfg1 min/max": "CFG1 최소/최대", + "cfg2 min/max": "CFG2 최소/최대", + "cfg count": "CFG 변화 횟수", + "x/y change": "X/Y축 변경", + "Random": "랜덤", + "Random grid": "랜덤 그리드" } \ No newline at end of file From 2ce44fc48e3ee6c73042ea83748772fe3eb45b1e Mon Sep 17 00:00:00 2001 From: Dynamic Date: Mon, 24 Oct 2022 04:38:16 +0900 Subject: [PATCH 0196/1118] Finalize ko_KR.json --- localizations/ko_KR.json | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/localizations/ko_KR.json b/localizations/ko_KR.json index 6889de46..ab12c37e 100644 --- a/localizations/ko_KR.json +++ b/localizations/ko_KR.json @@ -5,10 +5,10 @@ "❮": "❮", "❯": "❯", "⤡": "⤡", - " images in this directory. Loaded ": "개의 이미지가 이 경로에 존재합니다. ", " images during ": "개의 이미지를 불러왔고, 생성 기간은 ", - ", divided into ": "입니다. ", + " images in this directory. Loaded ": "개의 이미지가 이 경로에 존재합니다. ", " pages": "페이지로 나뉘어 표시합니다.", + ", divided into ": "입니다. ", "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", "[wiki]": " [위키] 참조", "A directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리를 선택해 주세요.", @@ -43,7 +43,10 @@ "BSRGAN 4x": "BSRGAN 4x", "built with gradio": "gradio로 제작되었습니다", "Cancel generate forever": "반복 생성 취소", + "cfg count": "CFG 변화 횟수", "CFG Scale": "CFG 스케일", + "cfg1 min/max": "CFG1 최소/최대", + "cfg2 min/max": "CFG2 최소/최대", "Check progress": "진행도 체크", "Check progress (first)": "진행도 체크 (처음)", "checkpoint": " 체크포인트 ", @@ -57,8 +60,8 @@ "CodeFormer visibility": "CodeFormer 가시성", "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer 가중치 (0 = 최대 효과, 1 = 최소 효과)", "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer 가중치 설정값 (0 = 최대 효과, 1 = 최소 효과)", - "Color variation": "색깔 다양성", "Collect": "즐겨찾기", + "Color variation": "색깔 다양성", "copy": "복사", "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", "Create a text file next to every image with generation parameters.": "생성된 이미지마다 생성 설정값을 담은 텍스트 파일 생성하기", @@ -89,8 +92,8 @@ "Do not save grids consisting of one picture": "이미지가 1개뿐인 그리드는 저장하지 않기", "Do not show any images in results for web": "웹에서 결과창에 아무 이미지도 보여주지 않기", "down": "아래쪽", - "Download localization template": "현지화 템플릿 다운로드", "Download": "다운로드", + "Download localization template": "현지화 템플릿 다운로드", "DPM adaptive": "DPM adaptive", "DPM fast": "DPM fast", "DPM2": "DPM2", @@ -121,6 +124,7 @@ "Existing Caption txt Action": "이미 존재하는 캡션 텍스트 처리", "Extra": "고급", "Extras": "부가기능", + "extras": "부가기능", "extras history": "부가기능 기록", "Face restoration": "얼굴 보정", "Face restoration model": "얼굴 보정 모델", @@ -155,10 +159,6 @@ "Hide samplers in user interface (requires restart)": "사용자 인터페이스에서 숨길 샘플러 선택(재시작 필요)", "Highres. fix": "고해상도 보정", "History": "기록", - "Image Browser": "이미지 브라우저", - "Images Browser": "이미지 브라우저", - "Images directory": "이미지 경로", - "extras": "부가기능", "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "훈련이 얼마나 빨리 이루어질지 정하는 값입니다. 값이 낮을수록 훈련 시간이 길어지고, 높은 값일수록 정확한 결과를 내는 데 실패하고 임베딩을 망가뜨릴 수 있습니다(임베딩이 망가진 경우에는 훈련 정보 텍스트박스에 손실(Loss) : nan 이라고 출력되게 됩니다. 이 경우에는 망가지지 않은 이전 백업본을 불러와야 합니다).\n\n학습률은 하나의 값으로 설정할 수도 있고, 다음 문법을 사용해 여러 값을 사용할 수도 있습니다 :\n\n학습률_1:최대 스텝수_1, 학습률_2:최대 스텝수_2, ...\n\n예 : 0.005:100, 1e-3:1000, 1e-5\n\n예의 설정값은 첫 100스텝동안 0.005의 학습률로, 그 이후 1000스텝까지는 1e-3으로, 남은 스텝은 1e-5로 훈련하게 됩니다.", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", @@ -175,8 +175,11 @@ "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "이 값이 0이 아니라면, 시드에 해당 값이 더해지고, Eta가 있는 샘플러를 사용할 때 노이즈의 RNG 조정을 위해 해당 값이 사용됩니다. 이 설정으로 더 다양한 이미지를 생성하거나, 잘 알고 계시다면 특정 소프트웨어의 결과값을 재현할 수도 있습니다.", "ignore": "무시", "Image": "이미지", + "Image Browser": "이미지 브라우저", "Image for img2img": "Image for img2img", "Image for inpainting with mask": "마스크로 인페인팅할 이미지", + "Images Browser": "이미지 브라우저", + "Images directory": "이미지 경로", "Images filename pattern": "이미지 파일명 패턴", "img2img": "이미지→이미지", "img2img alternative test": "이미지→이미지 대체버전 테스트", @@ -242,6 +245,7 @@ "Masking mode": "마스킹 모드", "Max prompt words for [prompt_words] pattern": "[prompt_words] 패턴의 최대 프롬프트 단어 수", "Max steps": "최대 스텝 수", + "Minimum number of pages per load": "한번 불러올 때마다 불러올 최소 페이지 수", "Modules": "모듈", "Move face restoration model from VRAM into RAM after processing": "처리가 완료되면 얼굴 보정 모델을 VRAM에서 RAM으로 옮기기", "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "하이퍼네트워크 훈련 진행 시 VAE와 CLIP을 RAM으로 옮기기. VRAM이 절약됩니다.", @@ -254,10 +258,9 @@ "None": "없음", "Nothing": "없음", "Nothing found in the image.": "Nothing found in the image.", + "Number of grids in each row": "각 세로줄마다 표시될 그리드 수", "number of images to delete consecutively next": "연속적으로 삭제할 이미지 수", "Number of pictures displayed on each page": "각 페이지에 표시될 이미지 수", - "Minimum number of pages per load": "한번 불러올 때마다 불러올 최소 페이지 수", - "Number of grids in each row": "각 세로줄마다 표시될 그리드 수", "Number of repeats for a single input image per epoch; used only for displaying epoch number": "세대(Epoch)당 단일 인풋 이미지의 반복 횟수 - 세대(Epoch) 숫자를 표시하는 데에만 사용됩니다. ", "Number of vectors per token": "토큰별 벡터 수", "Open for Clip Aesthetic!": "클립 스타일 기능을 활성화하려면 클릭!", @@ -317,6 +320,8 @@ "Quality for saved jpeg images": "저장된 jpeg 이미지들의 품질", "Quicksettings list": "빠른 설정 리스트", "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", + "Random": "랜덤", + "Random grid": "랜덤 그리드", "Randomness": "랜덤성", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", "Read parameters (prompt, etc...) from txt2img tab when making previews": "프리뷰 이미지 생성 시 텍스트→이미지 탭에서 설정값(프롬프트 등) 읽어오기", @@ -386,10 +391,10 @@ "Show grid in results for web": "웹에서 결과창에 그리드 보여주기", "Show image creation progress every N sampling steps. Set 0 to disable.": "N번째 샘플링 스텝마다 이미지 생성 과정 보이기 - 비활성화하려면 0으로 설정", "Show images zoomed in by default in full page image viewer": "전체 페이지 이미지 뷰어에서 기본값으로 이미지 확대해서 보여주기", + "Show previews of all images generated in a batch as a grid": "배치에서 생성된 모든 이미지의 미리보기를 그리드 형식으로 보여주기", "Show progressbar": "프로그레스 바 보이기", "Show result images": "이미지 결과 보이기", "Show Textbox": "텍스트박스 보이기", - "Show previews of all images generated in a batch as a grid": "배치에서 생성된 모든 이미지의 미리보기를 그리드 형식으로 보여주기", "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", "Sigma Churn": "시그마 섞기", "sigma churn": "시그마 섞기", @@ -404,11 +409,14 @@ "Slerp interpolation": "구면 선형 보간", "Source": "원본", "Source directory": "원본 경로", - "Split image threshold": "이미지 분할 임계값", "Split image overlap ratio": "이미지 분할 겹침 비율", + "Split image threshold": "이미지 분할 임계값", "Split oversized images": "사이즈가 큰 이미지 분할하기", "Stable Diffusion": "Stable Diffusion", "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "step count": "스텝 변화 횟수", + "step1 min/max": "스텝1 최소/최대", + "step2 min/max": "스텝2 최소/최대", "Step:": "Step:", "Steps": "스텝 수", "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", @@ -482,16 +490,8 @@ "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", "X type": "X축", "X values": "X 설정값", + "x/y change": "X/Y축 변경", "X/Y plot": "X/Y 플롯", "Y type": "Y축", - "Y values": "Y 설정값", - "step1 min/max": "스텝1 최소/최대", - "step2 min/max": "스텝2 최소/최대", - "step count": "스텝 변화 횟수", - "cfg1 min/max": "CFG1 최소/최대", - "cfg2 min/max": "CFG2 최소/최대", - "cfg count": "CFG 변화 횟수", - "x/y change": "X/Y축 변경", - "Random": "랜덤", - "Random grid": "랜덤 그리드" + "Y values": "Y 설정값" } \ No newline at end of file From 124e44cf1eed1edc68954f63a2a9bc428aabbcec Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 24 Oct 2022 09:51:56 +0800 Subject: [PATCH 0197/1118] remove browser to extension --- .gitignore | 1 - javascript/images_history.js | 200 ----------------- javascript/inspiration.js | 48 ---- modules/images_history.py | 424 ----------------------------------- modules/inspiration.py | 193 ---------------- modules/script_callbacks.py | 2 - modules/shared.py | 15 -- modules/ui.py | 20 +- 8 files changed, 4 insertions(+), 899 deletions(-) delete mode 100644 javascript/images_history.js delete mode 100644 javascript/inspiration.js delete mode 100644 modules/images_history.py delete mode 100644 modules/inspiration.py diff --git a/.gitignore b/.gitignore index 8d01bc6a..70660c51 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,4 @@ notification.mp3 /textual_inversion .vscode /extensions - /inspiration diff --git a/javascript/images_history.js b/javascript/images_history.js deleted file mode 100644 index c9aa76f8..00000000 --- a/javascript/images_history.js +++ /dev/null @@ -1,200 +0,0 @@ -var images_history_click_image = function(){ - if (!this.classList.contains("transform")){ - var gallery = images_history_get_parent_by_class(this, "images_history_cantainor"); - var buttons = gallery.querySelectorAll(".gallery-item"); - var i = 0; - var hidden_list = []; - buttons.forEach(function(e){ - if (e.style.display == "none"){ - hidden_list.push(i); - } - i += 1; - }) - if (hidden_list.length > 0){ - setTimeout(images_history_hide_buttons, 10, hidden_list, gallery); - } - } - images_history_set_image_info(this); -} - -function images_history_disabled_del(){ - gradioApp().querySelectorAll(".images_history_del_button").forEach(function(btn){ - btn.setAttribute('disabled','disabled'); - }); -} - -function images_history_get_parent_by_class(item, class_name){ - var parent = item.parentElement; - while(!parent.classList.contains(class_name)){ - parent = parent.parentElement; - } - return parent; -} - -function images_history_get_parent_by_tagname(item, tagname){ - var parent = item.parentElement; - tagname = tagname.toUpperCase() - while(parent.tagName != tagname){ - parent = parent.parentElement; - } - return parent; -} - -function images_history_hide_buttons(hidden_list, gallery){ - var buttons = gallery.querySelectorAll(".gallery-item"); - var num = 0; - buttons.forEach(function(e){ - if (e.style.display == "none"){ - num += 1; - } - }); - if (num == hidden_list.length){ - setTimeout(images_history_hide_buttons, 10, hidden_list, gallery); - } - for( i in hidden_list){ - buttons[hidden_list[i]].style.display = "none"; - } -} - -function images_history_set_image_info(button){ - var buttons = images_history_get_parent_by_tagname(button, "DIV").querySelectorAll(".gallery-item"); - var index = -1; - var i = 0; - buttons.forEach(function(e){ - if(e == button){ - index = i; - } - if(e.style.display != "none"){ - i += 1; - } - }); - var gallery = images_history_get_parent_by_class(button, "images_history_cantainor"); - var set_btn = gallery.querySelector(".images_history_set_index"); - var curr_idx = set_btn.getAttribute("img_index", index); - if (curr_idx != index) { - set_btn.setAttribute("img_index", index); - images_history_disabled_del(); - } - set_btn.click(); - -} - -function images_history_get_current_img(tabname, img_index, files){ - return [ - tabname, - gradioApp().getElementById(tabname + '_images_history_set_index').getAttribute("img_index"), - files - ]; -} - -function images_history_delete(del_num, tabname, image_index){ - image_index = parseInt(image_index); - var tab = gradioApp().getElementById(tabname + '_images_history'); - var set_btn = tab.querySelector(".images_history_set_index"); - var buttons = []; - tab.querySelectorAll(".gallery-item").forEach(function(e){ - if (e.style.display != 'none'){ - buttons.push(e); - } - }); - var img_num = buttons.length / 2; - del_num = Math.min(img_num - image_index, del_num) - if (img_num <= del_num){ - setTimeout(function(tabname){ - gradioApp().getElementById(tabname + '_images_history_renew_page').click(); - }, 30, tabname); - } else { - var next_img - for (var i = 0; i < del_num; i++){ - buttons[image_index + i].style.display = 'none'; - buttons[image_index + i + img_num].style.display = 'none'; - next_img = image_index + i + 1 - } - var bnt; - if (next_img >= img_num){ - btn = buttons[image_index - 1]; - } else { - btn = buttons[next_img]; - } - setTimeout(function(btn){btn.click()}, 30, btn); - } - images_history_disabled_del(); - -} - -function images_history_turnpage(tabname){ - gradioApp().getElementById(tabname + '_images_history_del_button').setAttribute('disabled','disabled'); - var buttons = gradioApp().getElementById(tabname + '_images_history').querySelectorAll(".gallery-item"); - buttons.forEach(function(elem) { - elem.style.display = 'block'; - }) -} - -function images_history_enable_del_buttons(){ - gradioApp().querySelectorAll(".images_history_del_button").forEach(function(btn){ - btn.removeAttribute('disabled'); - }) -} - -function images_history_init(){ - var tabnames = gradioApp().getElementById("images_history_tabnames_list") - if (tabnames){ - images_history_tab_list = tabnames.querySelector("textarea").value.split(",") - for (var i in images_history_tab_list ){ - var tab = images_history_tab_list[i]; - gradioApp().getElementById(tab + '_images_history').classList.add("images_history_cantainor"); - gradioApp().getElementById(tab + '_images_history_set_index').classList.add("images_history_set_index"); - gradioApp().getElementById(tab + '_images_history_del_button').classList.add("images_history_del_button"); - gradioApp().getElementById(tab + '_images_history_gallery').classList.add("images_history_gallery"); - gradioApp().getElementById(tab + "_images_history_start").setAttribute("style","padding:20px;font-size:25px"); - } - - //preload - if (gradioApp().getElementById("images_history_preload").querySelector("input").checked ){ - var tabs_box = gradioApp().getElementById("tab_images_history").querySelector("div").querySelector("div").querySelector("div"); - tabs_box.setAttribute("id", "images_history_tab"); - var tab_btns = tabs_box.querySelectorAll("button"); - for (var i in images_history_tab_list){ - var tabname = images_history_tab_list[i] - tab_btns[i].setAttribute("tabname", tabname); - tab_btns[i].addEventListener('click', function(){ - var tabs_box = gradioApp().getElementById("images_history_tab"); - if (!tabs_box.classList.contains(this.getAttribute("tabname"))) { - gradioApp().getElementById(this.getAttribute("tabname") + "_images_history_start").click(); - tabs_box.classList.add(this.getAttribute("tabname")) - } - }); - } - tab_btns[0].click() - } - } else { - setTimeout(images_history_init, 500); - } -} - -var images_history_tab_list = ""; -setTimeout(images_history_init, 500); -document.addEventListener("DOMContentLoaded", function() { - var mutationObserver = new MutationObserver(function(m){ - if (images_history_tab_list != ""){ - for (var i in images_history_tab_list ){ - let tabname = images_history_tab_list[i] - var buttons = gradioApp().querySelectorAll('#' + tabname + '_images_history .gallery-item'); - buttons.forEach(function(bnt){ - bnt.addEventListener('click', images_history_click_image, true); - }); - - var cls_btn = gradioApp().getElementById(tabname + '_images_history_gallery').querySelector("svg"); - if (cls_btn){ - cls_btn.addEventListener('click', function(){ - gradioApp().getElementById(tabname + '_images_history_renew_page').click(); - }, false); - } - - } - } - }); - mutationObserver.observe(gradioApp(), { childList:true, subtree:true }); -}); - - diff --git a/javascript/inspiration.js b/javascript/inspiration.js deleted file mode 100644 index 39844544..00000000 --- a/javascript/inspiration.js +++ /dev/null @@ -1,48 +0,0 @@ -function public_image_index_in_gallery(item, gallery){ - var imgs = gallery.querySelectorAll("img.h-full") - var index; - var i = 0; - imgs.forEach(function(e){ - if (e == item) - index = i; - i += 1; - }); - var all_imgs = gallery.querySelectorAll("img") - if (all_imgs.length > imgs.length){ - var num = imgs.length / 2 - index = (index < num) ? index : (index - num) - } - return index; -} - -function inspiration_selected(name, name_list){ - var btn = gradioApp().getElementById("inspiration_select_button") - return [gradioApp().getElementById("inspiration_select_button").getAttribute("img-index")]; -} - -function inspiration_click_get_button(){ - gradioApp().getElementById("inspiration_get_button").click(); -} - -var inspiration_image_click = function(){ - var index = public_image_index_in_gallery(this, gradioApp().getElementById("inspiration_gallery")); - var btn = gradioApp().getElementById("inspiration_select_button"); - btn.setAttribute("img-index", index); - setTimeout(function(btn){btn.click();}, 10, btn); -} - -document.addEventListener("DOMContentLoaded", function() { - var mutationObserver = new MutationObserver(function(m){ - var gallery = gradioApp().getElementById("inspiration_gallery") - if (gallery) { - var node = gallery.querySelector(".absolute.backdrop-blur.h-full") - if (node) { - node.style.display = "None"; - } - gallery.querySelectorAll('img').forEach(function(e){ - e.onclick = inspiration_image_click - }); - } - }); - mutationObserver.observe( gradioApp(), { childList:true, subtree:true }); -}); diff --git a/modules/images_history.py b/modules/images_history.py deleted file mode 100644 index bc5cf11f..00000000 --- a/modules/images_history.py +++ /dev/null @@ -1,424 +0,0 @@ -import os -import shutil -import time -import hashlib -import gradio -system_bak_path = "webui_log_and_bak" -custom_tab_name = "custom fold" -faverate_tab_name = "favorites" -tabs_list = ["txt2img", "img2img", "extras", faverate_tab_name] -def is_valid_date(date): - try: - time.strptime(date, "%Y%m%d") - return True - except: - return False - -def reduplicative_file_move(src, dst): - def same_name_file(basename, path): - name, ext = os.path.splitext(basename) - f_list = os.listdir(path) - max_num = 0 - for f in f_list: - if len(f) <= len(basename): - continue - f_ext = f[-len(ext):] if len(ext) > 0 else "" - if f[:len(name)] == name and f_ext == ext: - if f[len(name)] == "(" and f[-len(ext)-1] == ")": - number = f[len(name)+1:-len(ext)-1] - if number.isdigit(): - if int(number) > max_num: - max_num = int(number) - return f"{name}({max_num + 1}){ext}" - name = os.path.basename(src) - save_name = os.path.join(dst, name) - if not os.path.exists(save_name): - shutil.move(src, dst) - else: - name = same_name_file(name, dst) - shutil.move(src, os.path.join(dst, name)) - -def traverse_all_files(curr_path, image_list, all_type=False): - try: - f_list = os.listdir(curr_path) - except: - if all_type or (curr_path[-10:].rfind(".") > 0 and curr_path[-4:] != ".txt" and curr_path[-4:] != ".csv"): - image_list.append(curr_path) - return image_list - for file in f_list: - file = os.path.join(curr_path, file) - if (not all_type) and (file[-4:] == ".txt" or file[-4:] == ".csv"): - pass - elif os.path.isfile(file) and file[-10:].rfind(".") > 0: - image_list.append(file) - else: - image_list = traverse_all_files(file, image_list) - return image_list - -def auto_sorting(dir_name): - bak_path = os.path.join(dir_name, system_bak_path) - if not os.path.exists(bak_path): - os.mkdir(bak_path) - log_file = None - files_list = [] - f_list = os.listdir(dir_name) - for file in f_list: - if file == system_bak_path: - continue - file_path = os.path.join(dir_name, file) - if not is_valid_date(file): - if file[-10:].rfind(".") > 0: - files_list.append(file_path) - else: - files_list = traverse_all_files(file_path, files_list, all_type=True) - - for file in files_list: - date_str = time.strftime("%Y%m%d",time.localtime(os.path.getmtime(file))) - file_path = os.path.dirname(file) - hash_path = hashlib.md5(file_path.encode()).hexdigest() - path = os.path.join(dir_name, date_str, hash_path) - if not os.path.exists(path): - os.makedirs(path) - if log_file is None: - log_file = open(os.path.join(bak_path,"path_mapping.csv"),"a") - log_file.write(f"{hash_path},{file_path}\n") - reduplicative_file_move(file, path) - - date_list = [] - f_list = os.listdir(dir_name) - for f in f_list: - if is_valid_date(f): - date_list.append(f) - elif f == system_bak_path: - continue - else: - try: - reduplicative_file_move(os.path.join(dir_name, f), bak_path) - except: - pass - - today = time.strftime("%Y%m%d",time.localtime(time.time())) - if today not in date_list: - date_list.append(today) - return sorted(date_list, reverse=True) - -def archive_images(dir_name, date_to): - filenames = [] - batch_size =int(opts.images_history_num_per_page * opts.images_history_pages_num) - if batch_size <= 0: - batch_size = opts.images_history_num_per_page * 6 - today = time.strftime("%Y%m%d",time.localtime(time.time())) - date_to = today if date_to is None or date_to == "" else date_to - date_to_bak = date_to - if False: #opts.images_history_reconstruct_directory: - date_list = auto_sorting(dir_name) - for date in date_list: - if date <= date_to: - path = os.path.join(dir_name, date) - if date == today and not os.path.exists(path): - continue - filenames = traverse_all_files(path, filenames) - if len(filenames) > batch_size: - break - filenames = sorted(filenames, key=lambda file: -os.path.getmtime(file)) - else: - filenames = traverse_all_files(dir_name, filenames) - total_num = len(filenames) - tmparray = [(os.path.getmtime(file), file) for file in filenames ] - date_stamp = time.mktime(time.strptime(date_to, "%Y%m%d")) + 86400 - filenames = [] - date_list = {date_to:None} - date = time.strftime("%Y%m%d",time.localtime(time.time())) - for t, f in tmparray: - date = time.strftime("%Y%m%d",time.localtime(t)) - date_list[date] = None - if t <= date_stamp: - filenames.append((t, f ,date)) - date_list = sorted(list(date_list.keys()), reverse=True) - sort_array = sorted(filenames, key=lambda x:-x[0]) - if len(sort_array) > batch_size: - date = sort_array[batch_size][2] - filenames = [x[1] for x in sort_array] - else: - date = date_to if len(sort_array) == 0 else sort_array[-1][2] - filenames = [x[1] for x in sort_array] - filenames = [x[1] for x in sort_array if x[2]>= date] - num = len(filenames) - last_date_from = date_to_bak if num == 0 else time.strftime("%Y%m%d", time.localtime(time.mktime(time.strptime(date, "%Y%m%d")) - 1000)) - date = date[:4] + "/" + date[4:6] + "/" + date[6:8] - date_to_bak = date_to_bak[:4] + "/" + date_to_bak[4:6] + "/" + date_to_bak[6:8] - load_info = "
" - load_info += f"{total_num} images in this directory. Loaded {num} images during {date} - {date_to_bak}, divided into {int((num + 1) // opts.images_history_num_per_page + 1)} pages" - load_info += "
" - _, image_list, _, _, visible_num = get_recent_images(1, 0, filenames) - return ( - date_to, - load_info, - filenames, - 1, - image_list, - "", - "", - visible_num, - last_date_from, - gradio.update(visible=total_num > num) - ) - -def delete_image(delete_num, name, filenames, image_index, visible_num): - if name == "": - return filenames, delete_num - else: - delete_num = int(delete_num) - visible_num = int(visible_num) - image_index = int(image_index) - index = list(filenames).index(name) - i = 0 - new_file_list = [] - for name in filenames: - if i >= index and i < index + delete_num: - if os.path.exists(name): - if visible_num == image_index: - new_file_list.append(name) - i += 1 - continue - print(f"Delete file {name}") - os.remove(name) - visible_num -= 1 - txt_file = os.path.splitext(name)[0] + ".txt" - if os.path.exists(txt_file): - os.remove(txt_file) - else: - print(f"Not exists file {name}") - else: - new_file_list.append(name) - i += 1 - return new_file_list, 1, visible_num - -def save_image(file_name): - if file_name is not None and os.path.exists(file_name): - shutil.copy(file_name, opts.outdir_save) - -def get_recent_images(page_index, step, filenames): - page_index = int(page_index) - num_of_imgs_per_page = int(opts.images_history_num_per_page) - max_page_index = len(filenames) // num_of_imgs_per_page + 1 - page_index = max_page_index if page_index == -1 else page_index + step - page_index = 1 if page_index < 1 else page_index - page_index = max_page_index if page_index > max_page_index else page_index - idx_frm = (page_index - 1) * num_of_imgs_per_page - image_list = filenames[idx_frm:idx_frm + num_of_imgs_per_page] - length = len(filenames) - visible_num = num_of_imgs_per_page if idx_frm + num_of_imgs_per_page <= length else length % num_of_imgs_per_page - visible_num = num_of_imgs_per_page if visible_num == 0 else visible_num - return page_index, image_list, "", "", visible_num - -def loac_batch_click(date_to): - if date_to is None: - return time.strftime("%Y%m%d",time.localtime(time.time())), [] - else: - return None, [] -def forward_click(last_date_from, date_to_recorder): - if len(date_to_recorder) == 0: - return None, [] - if last_date_from == date_to_recorder[-1]: - date_to_recorder = date_to_recorder[:-1] - if len(date_to_recorder) == 0: - return None, [] - return date_to_recorder[-1], date_to_recorder[:-1] - -def backward_click(last_date_from, date_to_recorder): - if last_date_from is None or last_date_from == "": - return time.strftime("%Y%m%d",time.localtime(time.time())), [] - if len(date_to_recorder) == 0 or last_date_from != date_to_recorder[-1]: - date_to_recorder.append(last_date_from) - return last_date_from, date_to_recorder - - -def first_page_click(page_index, filenames): - return get_recent_images(1, 0, filenames) - -def end_page_click(page_index, filenames): - return get_recent_images(-1, 0, filenames) - -def prev_page_click(page_index, filenames): - return get_recent_images(page_index, -1, filenames) - -def next_page_click(page_index, filenames): - return get_recent_images(page_index, 1, filenames) - -def page_index_change(page_index, filenames): - return get_recent_images(page_index, 0, filenames) - -def show_image_info(tabname_box, num, page_index, filenames): - file = filenames[int(num) + int((page_index - 1) * int(opts.images_history_num_per_page))] - tm = "
" + time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(file))) + "
" - return file, tm, num, file - -def enable_page_buttons(): - return gradio.update(visible=True) - -def change_dir(img_dir, date_to): - warning = None - try: - if os.path.exists(img_dir): - try: - f = os.listdir(img_dir) - except: - warning = f"'{img_dir} is not a directory" - else: - warning = "The directory is not exist" - except: - warning = "The format of the directory is incorrect" - if warning is None: - today = time.strftime("%Y%m%d",time.localtime(time.time())) - return gradio.update(visible=False), gradio.update(visible=True), None, None if date_to != today else today, gradio.update(visible=True), gradio.update(visible=True) - else: - return gradio.update(visible=True), gradio.update(visible=False), warning, date_to, gradio.update(visible=False), gradio.update(visible=False) - -def show_images_history(gr, opts, tabname, run_pnginfo, switch_dict): - custom_dir = False - if tabname == "txt2img": - dir_name = opts.outdir_txt2img_samples - elif tabname == "img2img": - dir_name = opts.outdir_img2img_samples - elif tabname == "extras": - dir_name = opts.outdir_extras_samples - elif tabname == faverate_tab_name: - dir_name = opts.outdir_save - else: - custom_dir = True - dir_name = None - - if not custom_dir: - d = dir_name.split("/") - dir_name = d[0] - for p in d[1:]: - dir_name = os.path.join(dir_name, p) - if not os.path.exists(dir_name): - os.makedirs(dir_name) - - with gr.Column() as page_panel: - with gr.Row(): - with gr.Column(scale=1, visible=not custom_dir) as load_batch_box: - load_batch = gr.Button('Load', elem_id=tabname + "_images_history_start", full_width=True) - with gr.Column(scale=4): - with gr.Row(): - img_path = gr.Textbox(dir_name, label="Images directory", placeholder="Input images directory", interactive=custom_dir) - with gr.Row(): - with gr.Column(visible=False, scale=1) as batch_panel: - with gr.Row(): - forward = gr.Button('Prev batch') - backward = gr.Button('Next batch') - with gr.Column(scale=3): - load_info = gr.HTML(visible=not custom_dir) - with gr.Row(visible=False) as warning: - warning_box = gr.Textbox("Message", interactive=False) - - with gr.Row(visible=not custom_dir, elem_id=tabname + "_images_history") as main_panel: - with gr.Column(scale=2): - with gr.Row(visible=True) as turn_page_buttons: - #date_to = gr.Dropdown(label="Date to") - first_page = gr.Button('First Page') - prev_page = gr.Button('Prev Page') - page_index = gr.Number(value=1, label="Page Index") - next_page = gr.Button('Next Page') - end_page = gr.Button('End Page') - - history_gallery = gr.Gallery(show_label=False, elem_id=tabname + "_images_history_gallery").style(grid=opts.images_history_grid_num) - with gr.Row(): - delete_num = gr.Number(value=1, interactive=True, label="number of images to delete consecutively next") - delete = gr.Button('Delete', elem_id=tabname + "_images_history_del_button") - - with gr.Column(): - with gr.Row(): - with gr.Column(): - img_file_info = gr.Textbox(label="Generate Info", interactive=False, lines=6) - gr.HTML("
") - img_file_name = gr.Textbox(value="", label="File Name", interactive=False) - img_file_time= gr.HTML() - with gr.Row(): - if tabname != faverate_tab_name: - save_btn = gr.Button('Collect') - pnginfo_send_to_txt2img = gr.Button('Send to txt2img') - pnginfo_send_to_img2img = gr.Button('Send to img2img') - - - # hiden items - with gr.Row(visible=False): - renew_page = gr.Button('Refresh page', elem_id=tabname + "_images_history_renew_page") - batch_date_to = gr.Textbox(label="Date to") - visible_img_num = gr.Number() - date_to_recorder = gr.State([]) - last_date_from = gr.Textbox() - tabname_box = gr.Textbox(tabname) - image_index = gr.Textbox(value=-1) - set_index = gr.Button('set_index', elem_id=tabname + "_images_history_set_index") - filenames = gr.State() - all_images_list = gr.State() - hidden = gr.Image(type="pil") - info1 = gr.Textbox() - info2 = gr.Textbox() - - img_path.submit(change_dir, inputs=[img_path, batch_date_to], outputs=[warning, main_panel, warning_box, batch_date_to, load_batch_box, load_info]) - - #change batch - change_date_output = [batch_date_to, load_info, filenames, page_index, history_gallery, img_file_name, img_file_time, visible_img_num, last_date_from, batch_panel] - - batch_date_to.change(archive_images, inputs=[img_path, batch_date_to], outputs=change_date_output) - batch_date_to.change(enable_page_buttons, inputs=None, outputs=[turn_page_buttons]) - batch_date_to.change(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - - load_batch.click(loac_batch_click, inputs=[batch_date_to], outputs=[batch_date_to, date_to_recorder]) - forward.click(forward_click, inputs=[last_date_from, date_to_recorder], outputs=[batch_date_to, date_to_recorder]) - backward.click(backward_click, inputs=[last_date_from, date_to_recorder], outputs=[batch_date_to, date_to_recorder]) - - - #delete - delete.click(delete_image, inputs=[delete_num, img_file_name, filenames, image_index, visible_img_num], outputs=[filenames, delete_num, visible_img_num]) - delete.click(fn=None, _js="images_history_delete", inputs=[delete_num, tabname_box, image_index], outputs=None) - if tabname != faverate_tab_name: - save_btn.click(save_image, inputs=[img_file_name], outputs=None) - - #turn page - gallery_inputs = [page_index, filenames] - gallery_outputs = [page_index, history_gallery, img_file_name, img_file_time, visible_img_num] - first_page.click(first_page_click, inputs=gallery_inputs, outputs=gallery_outputs) - next_page.click(next_page_click, inputs=gallery_inputs, outputs=gallery_outputs) - prev_page.click(prev_page_click, inputs=gallery_inputs, outputs=gallery_outputs) - end_page.click(end_page_click, inputs=gallery_inputs, outputs=gallery_outputs) - page_index.submit(page_index_change, inputs=gallery_inputs, outputs=gallery_outputs) - renew_page.click(page_index_change, inputs=gallery_inputs, outputs=gallery_outputs) - - first_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - next_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - prev_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - end_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - page_index.submit(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - renew_page.click(fn=None, inputs=[tabname_box], outputs=None, _js="images_history_turnpage") - - # other funcitons - set_index.click(show_image_info, _js="images_history_get_current_img", inputs=[tabname_box, image_index, page_index, filenames], outputs=[img_file_name, img_file_time, image_index, hidden]) - img_file_name.change(fn=None, _js="images_history_enable_del_buttons", inputs=None, outputs=None) - hidden.change(fn=run_pnginfo, inputs=[hidden], outputs=[info1, img_file_info, info2]) - switch_dict["fn"](pnginfo_send_to_txt2img, switch_dict["t2i"], img_file_info, 'switch_to_txt2img') - switch_dict["fn"](pnginfo_send_to_img2img, switch_dict["i2i"], img_file_info, 'switch_to_img2img_img2img') - - - -def create_history_tabs(gr, sys_opts, cmp_ops, run_pnginfo, switch_dict): - global opts; - opts = sys_opts - loads_files_num = int(opts.images_history_num_per_page) - num_of_imgs_per_page = int(opts.images_history_num_per_page * opts.images_history_pages_num) - if cmp_ops.browse_all_images: - tabs_list.append(custom_tab_name) - with gr.Blocks(analytics_enabled=False) as images_history: - with gr.Tabs() as tabs: - for tab in tabs_list: - with gr.Tab(tab): - with gr.Blocks(analytics_enabled=False) : - show_images_history(gr, opts, tab, run_pnginfo, switch_dict) - gradio.Checkbox(opts.images_history_preload, elem_id="images_history_preload", visible=False) - gradio.Textbox(",".join(tabs_list), elem_id="images_history_tabnames_list", visible=False) - - return images_history diff --git a/modules/inspiration.py b/modules/inspiration.py deleted file mode 100644 index 29cf8297..00000000 --- a/modules/inspiration.py +++ /dev/null @@ -1,193 +0,0 @@ -import os -import random -import gradio -from modules.shared import opts -inspiration_system_path = os.path.join(opts.inspiration_dir, "system") -def read_name_list(file, types=None, keyword=None): - if not os.path.exists(file): - return [] - ret = [] - f = open(file, "r") - line = f.readline() - while len(line) > 0: - line = line.rstrip("\n") - if types is not None: - dirname = os.path.split(line) - if dirname[0] in types and keyword in dirname[1].lower(): - ret.append(line) - else: - ret.append(line) - line = f.readline() - return ret - -def save_name_list(file, name): - name_list = read_name_list(file) - if name not in name_list: - with open(file, "a") as f: - f.write(name + "\n") - -def get_types_list(): - files = os.listdir(opts.inspiration_dir) - types = [] - for x in files: - path = os.path.join(opts.inspiration_dir, x) - if x[0] == ".": - continue - if not os.path.isdir(path): - continue - if path == inspiration_system_path: - continue - types.append(x) - return types - -def get_inspiration_images(source, types, keyword): - keyword = keyword.strip(" ").lower() - get_num = int(opts.inspiration_rows_num * opts.inspiration_cols_num) - if source == "Favorites": - names = read_name_list(os.path.join(inspiration_system_path, "faverites.txt"), types, keyword) - names = random.sample(names, get_num) if len(names) > get_num else names - elif source == "Abandoned": - names = read_name_list(os.path.join(inspiration_system_path, "abandoned.txt"), types, keyword) - names = random.sample(names, get_num) if len(names) > get_num else names - elif source == "Exclude abandoned": - abandoned = read_name_list(os.path.join(inspiration_system_path, "abandoned.txt"), types, keyword) - all_names = [] - for tp in types: - name_list = os.listdir(os.path.join(opts.inspiration_dir, tp)) - all_names += [os.path.join(tp, x) for x in name_list if keyword in x.lower()] - - if len(all_names) > get_num: - names = [] - while len(names) < get_num: - name = random.choice(all_names) - if name not in abandoned: - names.append(name) - else: - names = all_names - else: - all_names = [] - for tp in types: - name_list = os.listdir(os.path.join(opts.inspiration_dir, tp)) - all_names += [os.path.join(tp, x) for x in name_list if keyword in x.lower()] - names = random.sample(all_names, get_num) if len(all_names) > get_num else all_names - image_list = [] - for a in names: - image_path = os.path.join(opts.inspiration_dir, a) - images = os.listdir(image_path) - if len(images) > 0: - image_list.append((os.path.join(image_path, random.choice(images)), a)) - else: - print(image_path) - return image_list, names - -def select_click(index, name_list): - name = name_list[int(index)] - path = os.path.join(opts.inspiration_dir, name) - images = os.listdir(path) - return name, [os.path.join(path, x) for x in images], "" - -def give_up_click(name): - file = os.path.join(inspiration_system_path, "abandoned.txt") - save_name_list(file, name) - return "Added to abandoned list" - -def collect_click(name): - file = os.path.join(inspiration_system_path, "faverites.txt") - save_name_list(file, name) - return "Added to faverite list" - -def moveout_click(name, source): - if source == "Abandoned": - file = os.path.join(inspiration_system_path, "abandoned.txt") - elif source == "Favorites": - file = os.path.join(inspiration_system_path, "faverites.txt") - else: - return None - name_list = read_name_list(file) - os.remove(file) - with open(file, "a") as f: - for a in name_list: - if a != name: - f.write(a + "\n") - return f"Moved out {name} from {source} list" - -def source_change(source): - if source in ["Abandoned", "Favorites"]: - return gradio.update(visible=True), [] - else: - return gradio.update(visible=False), [] -def add_to_prompt(name, prompt): - name = os.path.basename(name) - return prompt + "," + name - -def clear_keyword(): - return "" - -def ui(gr, opts, txt2img_prompt, img2img_prompt): - with gr.Blocks(analytics_enabled=False) as inspiration: - flag = os.path.exists(opts.inspiration_dir) - if flag: - types = get_types_list() - flag = len(types) > 0 - else: - os.makedirs(opts.inspiration_dir) - if not flag: - gr.HTML(""" -

To activate inspiration function, you need get "inspiration" images first.


- You can create these images by run "Create inspiration images" script in txt2img page,
you can get the artists or art styles list from here
- https://github.com/pharmapsychotic/clip-interrogator/tree/main/data
- download these files, and select these files in the "Create inspiration images" script UI
- There about 6000 artists and art styles in these files.
This takes server hours depending on your GPU type and how many pictures you generate for each artist/style -
I suggest at least four images for each


-

You can also download generated pictures from here:


- https://huggingface.co/datasets/yfszzx/inspiration
- unzip the file to the project directory of webui
- and restart webui, and enjoy the joy of creation!
- """) - return inspiration - if not os.path.exists(inspiration_system_path): - os.mkdir(inspiration_system_path) - with gr.Row(): - with gr.Column(scale=2): - inspiration_gallery = gr.Gallery(show_label=False, elem_id="inspiration_gallery").style(grid=opts.inspiration_cols_num, height='auto') - with gr.Column(scale=1): - types = gr.CheckboxGroup(choices=types, value=types) - with gr.Row(): - source = gr.Dropdown(choices=["All", "Favorites", "Exclude abandoned", "Abandoned"], value="Exclude abandoned", label="Source") - keyword = gr.Textbox("", label="Key word") - get_inspiration = gr.Button("Get inspiration", elem_id="inspiration_get_button") - name = gr.Textbox(show_label=False, interactive=False) - with gr.Row(): - send_to_txt2img = gr.Button('to txt2img') - send_to_img2img = gr.Button('to img2img') - collect = gr.Button('Collect') - give_up = gr.Button("Don't show again") - moveout = gr.Button("Move out", visible=False) - warning = gr.HTML() - style_gallery = gr.Gallery(show_label=False).style(grid=2, height='auto') - - - - with gr.Row(visible=False): - select_button = gr.Button('set button', elem_id="inspiration_select_button") - name_list = gr.State() - - get_inspiration.click(get_inspiration_images, inputs=[source, types, keyword], outputs=[inspiration_gallery, name_list]) - keyword.submit(fn=None, _js="inspiration_click_get_button", inputs=None, outputs=None) - source.change(source_change, inputs=[source], outputs=[moveout, style_gallery]) - source.change(fn=clear_keyword, _js="inspiration_click_get_button", inputs=None, outputs=[keyword]) - types.change(fn=clear_keyword, _js="inspiration_click_get_button", inputs=None, outputs=[keyword]) - - select_button.click(select_click, _js="inspiration_selected", inputs=[name, name_list], outputs=[name, style_gallery, warning]) - give_up.click(give_up_click, inputs=[name], outputs=[warning]) - collect.click(collect_click, inputs=[name], outputs=[warning]) - moveout.click(moveout_click, inputs=[name, source], outputs=[warning]) - moveout.click(fn=None, _js="inspiration_click_get_button", inputs=None, outputs=None) - - send_to_txt2img.click(add_to_prompt, inputs=[name, txt2img_prompt], outputs=[txt2img_prompt]) - send_to_img2img.click(add_to_prompt, inputs=[name, img2img_prompt], outputs=[img2img_prompt]) - send_to_txt2img.click(collect_click, inputs=[name], outputs=[warning]) - send_to_img2img.click(collect_click, inputs=[name], outputs=[warning]) - send_to_txt2img.click(None, _js='switch_to_txt2img', inputs=None, outputs=None) - send_to_img2img.click(None, _js="switch_to_img2img_img2img", inputs=None, outputs=None) - return inspiration diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 5bcccd67..66666a56 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -1,4 +1,3 @@ - callbacks_model_loaded = [] callbacks_ui_tabs = [] callbacks_ui_settings = [] @@ -16,7 +15,6 @@ def model_loaded_callback(sd_model): def ui_tabs_callback(): res = [] - for callback in callbacks_ui_tabs: res += callback() or [] diff --git a/modules/shared.py b/modules/shared.py index 0aaaadac..5dfd7927 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -321,21 +321,6 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}), })) -options_templates.update(options_section(('inspiration', "Inspiration"), { - "inspiration_dir": OptionInfo("inspiration", "Directory of inspiration", component_args=hide_dirs), - "inspiration_max_samples": OptionInfo(4, "Maximum number of samples, used to determine which folders to skip when continue running the create script", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1}), - "inspiration_rows_num": OptionInfo(4, "Rows of inspiration interface frame", gr.Slider, {"minimum": 4, "maximum": 16, "step": 1}), - "inspiration_cols_num": OptionInfo(8, "Columns of inspiration interface frame", gr.Slider, {"minimum": 4, "maximum": 16, "step": 1}), -})) - -options_templates.update(options_section(('images-history', "Images Browser"), { - #"images_history_reconstruct_directory": OptionInfo(False, "Reconstruct output directory structure.This can greatly improve the speed of loading , but will change the original output directory structure"), - "images_history_preload": OptionInfo(False, "Preload images at startup"), - "images_history_num_per_page": OptionInfo(36, "Number of pictures displayed on each page"), - "images_history_pages_num": OptionInfo(6, "Minimum number of pages per load "), - "images_history_grid_num": OptionInfo(6, "Number of grids in each row"), - -})) class Options: data = None diff --git a/modules/ui.py b/modules/ui.py index a73175f5..fa42712e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -49,14 +49,12 @@ from modules.sd_hijack import model_hijack from modules.sd_samplers import samplers, samplers_for_img2img import modules.textual_inversion.ui import modules.hypernetworks.ui -import modules.images_history as images_history -import modules.inspiration as inspiration - - # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() mimetypes.add_type('application/javascript', '.js') +txt2img_paste_fields = [] +img2img_paste_fields = [] if not cmd_opts.share and not cmd_opts.listen: @@ -1193,16 +1191,7 @@ def create_ui(wrap_gradio_gpu_call): inputs=[image], outputs=[html, generation_info, html2], ) - #images history - images_history_switch_dict = { - "fn": modules.generation_parameters_copypaste.connect_paste, - "t2i": txt2img_paste_fields, - "i2i": img2img_paste_fields - } - - browser_interface = images_history.create_history_tabs(gr, opts, cmd_opts, wrap_gradio_call(modules.extras.run_pnginfo), images_history_switch_dict) - inspiration_interface = inspiration.ui(gr, opts, txt2img_prompt, img2img_prompt) - + with gr.Blocks() as modelmerger_interface: with gr.Row().style(equal_height=False): with gr.Column(variant='panel'): @@ -1651,8 +1640,6 @@ Requested path was: {f} (img2img_interface, "img2img", "img2img"), (extras_interface, "Extras", "extras"), (pnginfo_interface, "PNG Info", "pnginfo"), - (inspiration_interface, "Inspiration", "inspiration"), - (browser_interface , "Image Browser", "images_history"), (modelmerger_interface, "Checkpoint Merger", "modelmerger"), (train_interface, "Train", "ti"), ] @@ -1896,6 +1883,7 @@ def load_javascript(raw_response): javascript = f'' scripts_list = modules.scripts.list_scripts("javascript", ".js") + scripts_list += modules.scripts.list_scripts("scripts", ".js") for basedir, filename, path in scripts_list: with open(path, "r", encoding="utf8") as jsfile: javascript += f"\n" From cef1b89aa2e6c7647db7e93a4cd4ec020da3f2da Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 24 Oct 2022 10:10:33 +0800 Subject: [PATCH 0198/1118] remove browser to extension --- modules/script_callbacks.py | 2 + modules/shared.py | 1 - modules/ui.py | 2 +- scripts/create_inspiration_images.py | 57 ---------------------------- 4 files changed, 3 insertions(+), 59 deletions(-) delete mode 100644 scripts/create_inspiration_images.py diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 66666a56..f46d3d9a 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -1,3 +1,4 @@ + callbacks_model_loaded = [] callbacks_ui_tabs = [] callbacks_ui_settings = [] @@ -15,6 +16,7 @@ def model_loaded_callback(sd_model): def ui_tabs_callback(): res = [] + for callback in callbacks_ui_tabs: res += callback() or [] diff --git a/modules/shared.py b/modules/shared.py index 5dfd7927..6541e679 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -82,7 +82,6 @@ parser.add_argument("--api", action='store_true', help="use api=True to launch t parser.add_argument("--nowebui", action='store_true', help="use api=True to launch the api instead of the webui") parser.add_argument("--ui-debug-mode", action='store_true', help="Don't load model to quickly launch UI") parser.add_argument("--device-id", type=str, help="Select the default CUDA device to use (export CUDA_VISIBLE_DEVICES=0,1,etc might be needed before)", default=None) -parser.add_argument("--browse-all-images", action='store_true', help="Allow browsing all images by Image Browser", default=False) cmd_opts = parser.parse_args() restricted_opts = [ diff --git a/modules/ui.py b/modules/ui.py index fa42712e..a32f7259 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1104,7 +1104,7 @@ def create_ui(wrap_gradio_gpu_call): upscaling_crop = gr.Checkbox(label='Crop to fit', value=True) with gr.Group(): - extras_upscaler_1 = gr.Radio(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers] , value=shared.sd_upscalers[0].name, type="index") + extras_upscaler_1 = gr.Radio(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index") with gr.Group(): extras_upscaler_2 = gr.Radio(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index") diff --git a/scripts/create_inspiration_images.py b/scripts/create_inspiration_images.py deleted file mode 100644 index 2fd30578..00000000 --- a/scripts/create_inspiration_images.py +++ /dev/null @@ -1,57 +0,0 @@ -import csv, os, shutil -import modules.scripts as scripts -from modules import processing, shared, sd_samplers, images -from modules.processing import Processed -from modules.shared import opts -import gradio -class Script(scripts.Script): - def title(self): - return "Create inspiration images" - - def show(self, is_img2img): - return True - - def ui(self, is_img2img): - file = gradio.Files(label="Artist or styles name list. '.txt' files with one name per line",) - with gradio.Row(): - prefix = gradio.Textbox("a painting in", label="Prompt words before artist or style name", file_count="multiple") - suffix= gradio.Textbox("style", label="Prompt words after artist or style name") - negative_prompt = gradio.Textbox("picture frame, portrait photo", label="Negative Prompt") - with gradio.Row(): - batch_size = gradio.Number(1, label="Batch size") - batch_count = gradio.Number(2, label="Batch count") - return [batch_size, batch_count, prefix, suffix, negative_prompt, file] - - def run(self, p, batch_size, batch_count, prefix, suffix, negative_prompt, files): - p.batch_size = int(batch_size) - p.n_iterint = int(batch_count) - p.negative_prompt = negative_prompt - p.do_not_save_samples = True - p.do_not_save_grid = True - for file in files: - tp = file.orig_name.split(".")[0] - print(tp) - path = os.path.join(opts.inspiration_dir, tp) - if not os.path.exists(path): - os.makedirs(path) - f = open(file.name, "r") - line = f.readline() - while len(line) > 0: - name = line.rstrip("\n").split(",")[0] - line = f.readline() - artist_path = os.path.join(path, name) - if not os.path.exists(artist_path): - os.mkdir(artist_path) - if len(os.listdir(artist_path)) >= opts.inspiration_max_samples: - continue - p.prompt = f"{prefix} {name} {suffix}" - print(p.prompt) - processed = processing.process_images(p) - for img in processed.images: - i = 0 - filename = os.path.join(artist_path, format(0, "03d") + ".jpg") - while os.path.exists(filename): - i += 1 - filename = os.path.join(artist_path, format(i, "03d") + ".jpg") - img.save(filename, quality=80) - return processed From d7987ef9da2d89f146e091f0c727444a522245d9 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 24 Oct 2022 11:06:58 +0800 Subject: [PATCH 0199/1118] add paste_fields to global --- extensions/inspiration | 1 + extensions/put extension here.txt | 0 extensions/stable-diffusion-webui-aesthetic-gradients | 1 + extensions/stable-diffusion-webui-images-browse | 1 + extensions/stable-diffusion-webui-inspiration | 1 + extensions/stable-diffusion-webui-wildcards | 1 + 6 files changed, 5 insertions(+) create mode 160000 extensions/inspiration create mode 100644 extensions/put extension here.txt create mode 160000 extensions/stable-diffusion-webui-aesthetic-gradients create mode 160000 extensions/stable-diffusion-webui-images-browse create mode 160000 extensions/stable-diffusion-webui-inspiration create mode 160000 extensions/stable-diffusion-webui-wildcards diff --git a/extensions/inspiration b/extensions/inspiration new file mode 160000 index 00000000..4cff5855 --- /dev/null +++ b/extensions/inspiration @@ -0,0 +1 @@ +Subproject commit 4cff5855f3ca658fb5c9dd9745e5f2ae7bcc7074 diff --git a/extensions/put extension here.txt b/extensions/put extension here.txt new file mode 100644 index 00000000..e69de29b diff --git a/extensions/stable-diffusion-webui-aesthetic-gradients b/extensions/stable-diffusion-webui-aesthetic-gradients new file mode 160000 index 00000000..411889ca --- /dev/null +++ b/extensions/stable-diffusion-webui-aesthetic-gradients @@ -0,0 +1 @@ +Subproject commit 411889ca602f20b8bb5e4d1af2b9686eab1913b1 diff --git a/extensions/stable-diffusion-webui-images-browse b/extensions/stable-diffusion-webui-images-browse new file mode 160000 index 00000000..6b8e158d --- /dev/null +++ b/extensions/stable-diffusion-webui-images-browse @@ -0,0 +1 @@ +Subproject commit 6b8e158dc174f31f0bb73d74547917f5a6fba507 diff --git a/extensions/stable-diffusion-webui-inspiration b/extensions/stable-diffusion-webui-inspiration new file mode 160000 index 00000000..4cff5855 --- /dev/null +++ b/extensions/stable-diffusion-webui-inspiration @@ -0,0 +1 @@ +Subproject commit 4cff5855f3ca658fb5c9dd9745e5f2ae7bcc7074 diff --git a/extensions/stable-diffusion-webui-wildcards b/extensions/stable-diffusion-webui-wildcards new file mode 160000 index 00000000..2c0e7d7e --- /dev/null +++ b/extensions/stable-diffusion-webui-wildcards @@ -0,0 +1 @@ +Subproject commit 2c0e7d7e19e6c2b76b83189013aadb822776301f From a889c93f23f1e80d0dac4e5ddbc3a26207e8cdf1 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 24 Oct 2022 11:13:16 +0800 Subject: [PATCH 0200/1118] paste_fields add to public --- modules/ui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ui.py b/modules/ui.py index a32f7259..a73b9ff0 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -784,6 +784,7 @@ def create_ui(wrap_gradio_gpu_call): ] ) + global txt2img_paste_fields txt2img_paste_fields = [ (txt2img_prompt, "Prompt"), (txt2img_negative_prompt, "Negative prompt"), @@ -1054,6 +1055,7 @@ def create_ui(wrap_gradio_gpu_call): outputs=[prompt, negative_prompt, style1, style2], ) + global img2img_paste_fields img2img_paste_fields = [ (img2img_prompt, "Prompt"), (img2img_negative_prompt, "Negative prompt"), From 9dd17b86017e26ccf58897142bdcaa0297f8db8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E4=BC=9A=E7=94=BB=E7=94=BB=E7=9A=84=E4=B8=AD?= =?UTF-8?q?=E5=8C=BB=E4=B8=8D=E6=98=AF=E5=A5=BD=E7=A8=8B=E5=BA=8F=E5=91=98?= Date: Mon, 24 Oct 2022 11:19:49 +0800 Subject: [PATCH 0201/1118] fix add git add mistake --- extensions/inspiration | 1 - extensions/put extension here.txt | 0 extensions/stable-diffusion-webui-aesthetic-gradients | 1 - extensions/stable-diffusion-webui-images-browse | 1 - extensions/stable-diffusion-webui-inspiration | 1 - extensions/stable-diffusion-webui-wildcards | 1 - 6 files changed, 5 deletions(-) delete mode 160000 extensions/inspiration delete mode 100644 extensions/put extension here.txt delete mode 160000 extensions/stable-diffusion-webui-aesthetic-gradients delete mode 160000 extensions/stable-diffusion-webui-images-browse delete mode 160000 extensions/stable-diffusion-webui-inspiration delete mode 160000 extensions/stable-diffusion-webui-wildcards diff --git a/extensions/inspiration b/extensions/inspiration deleted file mode 160000 index 4cff5855..00000000 --- a/extensions/inspiration +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4cff5855f3ca658fb5c9dd9745e5f2ae7bcc7074 diff --git a/extensions/put extension here.txt b/extensions/put extension here.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/extensions/stable-diffusion-webui-aesthetic-gradients b/extensions/stable-diffusion-webui-aesthetic-gradients deleted file mode 160000 index 411889ca..00000000 --- a/extensions/stable-diffusion-webui-aesthetic-gradients +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 411889ca602f20b8bb5e4d1af2b9686eab1913b1 diff --git a/extensions/stable-diffusion-webui-images-browse b/extensions/stable-diffusion-webui-images-browse deleted file mode 160000 index 6b8e158d..00000000 --- a/extensions/stable-diffusion-webui-images-browse +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6b8e158dc174f31f0bb73d74547917f5a6fba507 diff --git a/extensions/stable-diffusion-webui-inspiration b/extensions/stable-diffusion-webui-inspiration deleted file mode 160000 index 4cff5855..00000000 --- a/extensions/stable-diffusion-webui-inspiration +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4cff5855f3ca658fb5c9dd9745e5f2ae7bcc7074 diff --git a/extensions/stable-diffusion-webui-wildcards b/extensions/stable-diffusion-webui-wildcards deleted file mode 160000 index 2c0e7d7e..00000000 --- a/extensions/stable-diffusion-webui-wildcards +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c0e7d7e19e6c2b76b83189013aadb822776301f From 394c4986211df4f7d9d8c9c26180edf8b9946d51 Mon Sep 17 00:00:00 2001 From: yfszzx Date: Mon, 24 Oct 2022 11:29:45 +0800 Subject: [PATCH 0202/1118] test --- extensions/stable-diffusion-webui-inspiration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/stable-diffusion-webui-inspiration b/extensions/stable-diffusion-webui-inspiration index 4cff5855..a0b96664 160000 --- a/extensions/stable-diffusion-webui-inspiration +++ b/extensions/stable-diffusion-webui-inspiration @@ -1 +1 @@ -Subproject commit 4cff5855f3ca658fb5c9dd9745e5f2ae7bcc7074 +Subproject commit a0b96664d2524b87916ae463fbb65411b13a569b From fe9740d2f5fa057e02529f8a81de21333adf4234 Mon Sep 17 00:00:00 2001 From: judgeou Date: Sun, 23 Oct 2022 20:40:23 +0800 Subject: [PATCH 0203/1118] update deepdanbooru version --- launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch.py b/launch.py index 333f308a..8affd410 100644 --- a/launch.py +++ b/launch.py @@ -111,7 +111,7 @@ def prepare_enviroment(): gfpgan_package = os.environ.get('GFPGAN_PACKAGE', "git+https://github.com/TencentARC/GFPGAN.git@8d2447a2d918f8eba5a4a01463fd48e45126a379") clip_package = os.environ.get('CLIP_PACKAGE', "git+https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1") - deepdanbooru_package = os.environ.get('DEEPDANBOORU_PACKAGE', "git+https://github.com/KichangKim/DeepDanbooru.git@edf73df4cdaeea2cf00e9ac08bd8a9026b7a7b26") + deepdanbooru_package = os.environ.get('DEEPDANBOORU_PACKAGE', "git+https://github.com/KichangKim/DeepDanbooru.git@d91a2963bf87c6a770d74894667e9ffa9f6de7ff") xformers_windows_package = os.environ.get('XFORMERS_WINDOWS_PACKAGE', 'https://github.com/C43H66N12O12S2/stable-diffusion-webui/releases/download/f/xformers-0.0.14.dev0-cp310-cp310-win_amd64.whl') From 68e9e978996c24772016ba9e4937367e91540681 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 19:07:17 +0900 Subject: [PATCH 0204/1118] Initial KR support - WIP Localization WIP --- ko-KR.json | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 ko-KR.json diff --git a/ko-KR.json b/ko-KR.json new file mode 100644 index 00000000..f93b3e16 --- /dev/null +++ b/ko-KR.json @@ -0,0 +1,76 @@ +{ + "txt2img": "텍스트→이미지", + "img2img": "이미지→이미지", + "Extras": "부가기능", + "PNG Info": "PNG 정보", + "History": "기록", + "Checkpoint Merger": "체크포인트 병합", + "Train": "훈련", + "Settings": "설정", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Hypernetwork": "하이퍼네트워크", + "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", + "Generate": "생성", + "Style 1": "스타일 1", + "Style 2": "스타일 2", + "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", + "Save style": "스타일 저장", + "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", + "Do not do anything special": "아무것도 하지 않기", + "Generate forever": "반복 생성", + "Cancel generate forever": "반복 생성 취소", + "Interrupt": "중단", + "Skip": "건너뛰기", + "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", + "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", + "Prompt": "프롬프트", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Negative prompt": "네거티브 프롬프트", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Sampling Steps": "샘플링 스텝 수", + "Sampling method": "샘플링 방법", + "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", + "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Width": "가로", + "Height": "세로", + "Restore faces": "얼굴 보정", + "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", + "Tiling": "타일링", + "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Highres. fix": "고해상도 보정", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Firstpass width": "초기 가로길이", + "Firstpass height": "초기 세로길이", + "Denoising strength": "디노이즈 강도", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Batch count": "배치 수", + "Batch size": "배치 크기", + "How many batches of images to create": "생성할 이미지 배치 수", + "How many image to create in a single batch": "한 배치당 이미지 수", + "CFG Scale": "CFG 스케일", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Seed": "시드", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", + "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", + "Extra": "고급", + "Variation seed": "바리에이션 시드", + "Variation strength": "바리에이션 강도", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", + "Resize seed from height": "시드 리사이징 가로길이", + "Resize seed from width": "시드 리사이징 세로길이", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Script": "스크립트", + "Save": "저장", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "Send to img2img": "이미지→이미지로 전송", + "Send to inpaint": "인페인트로 전송", + "Send to extras": "부가기능으로 전송", + "Open images output directory": "이미지 저장 경로 열기", + "Make Zip when Save?": "저장 시 Zip 생성하기", + "Always save all generated images": "생성된 이미지 항상 저장하기" +} \ No newline at end of file From e7eea555715320a7b1977bf0e12c5ca1e2774a09 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 20:11:17 +0900 Subject: [PATCH 0205/1118] Update ko-KR.json --- localizations/ko-KR.json | 85 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 localizations/ko-KR.json diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json new file mode 100644 index 00000000..a4367dc5 --- /dev/null +++ b/localizations/ko-KR.json @@ -0,0 +1,85 @@ +{ + "⤡": "⤡", + "⊞": "⊞", + "×": "×", + "❮": "❮", + "❯": "❯", + "Loading...": "로딩중...", + "view": "", + "api": "api", + "•": "•", + "txt2img": "텍스트→이미지", + "img2img": "이미지→이미지", + "Extras": "부가기능", + "PNG Info": "PNG 정보", + "History": "기록", + "Checkpoint Merger": "체크포인트 병합", + "Train": "훈련", + "Settings": "설정", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Hypernetwork": "하이퍼네트워크", + "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", + "Generate": "생성", + "Style 1": "스타일 1", + "Style 2": "스타일 2", + "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", + "Save style": "스타일 저장", + "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", + "Do not do anything special": "아무것도 하지 않기", + "Generate forever": "반복 생성", + "Cancel generate forever": "반복 생성 취소", + "Interrupt": "중단", + "Skip": "건너뛰기", + "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", + "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", + "Prompt": "프롬프트", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Negative prompt": "네거티브 프롬프트", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Sampling Steps": "샘플링 스텝 수", + "Sampling method": "샘플링 방법", + "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", + "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Width": "가로", + "Height": "세로", + "Restore faces": "얼굴 보정", + "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", + "Tiling": "타일링", + "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Highres. fix": "고해상도 보정", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Firstpass width": "초기 가로길이", + "Firstpass height": "초기 세로길이", + "Denoising strength": "디노이즈 강도", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Batch count": "배치 수", + "Batch size": "배치 크기", + "How many batches of images to create": "생성할 이미지 배치 수", + "How many image to create in a single batch": "한 배치당 이미지 수", + "CFG Scale": "CFG 스케일", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Seed": "시드", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", + "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", + "Extra": "고급", + "Variation seed": "바리에이션 시드", + "Variation strength": "바리에이션 강도", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", + "Resize seed from height": "시드 리사이징 가로길이", + "Resize seed from width": "시드 리사이징 세로길이", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Script": "스크립트", + "Save": "저장", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "Send to img2img": "이미지→이미지로 전송", + "Send to inpaint": "인페인트로 전송", + "Send to extras": "부가기능으로 전송", + "Open images output directory": "이미지 저장 경로 열기", + "Make Zip when Save?": "저장 시 Zip 생성하기", + "Always save all generated images": "생성된 이미지 항상 저장하기" +} \ No newline at end of file From 021b02751ef08f8f5fc7cc2a3d7e40c599657dc4 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 20:12:54 +0900 Subject: [PATCH 0206/1118] Move ko-KR.json --- ko-KR.json | 76 ------------------------------------------------------ 1 file changed, 76 deletions(-) delete mode 100644 ko-KR.json diff --git a/ko-KR.json b/ko-KR.json deleted file mode 100644 index f93b3e16..00000000 --- a/ko-KR.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "txt2img": "텍스트→이미지", - "img2img": "이미지→이미지", - "Extras": "부가기능", - "PNG Info": "PNG 정보", - "History": "기록", - "Checkpoint Merger": "체크포인트 병합", - "Train": "훈련", - "Settings": "설정", - "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", - "Hypernetwork": "하이퍼네트워크", - "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", - "Generate": "생성", - "Style 1": "스타일 1", - "Style 2": "스타일 2", - "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", - "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", - "Save style": "스타일 저장", - "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", - "Do not do anything special": "아무것도 하지 않기", - "Generate forever": "반복 생성", - "Cancel generate forever": "반복 생성 취소", - "Interrupt": "중단", - "Skip": "건너뛰기", - "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", - "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", - "Prompt": "프롬프트", - "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Negative prompt": "네거티브 프롬프트", - "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Sampling Steps": "샘플링 스텝 수", - "Sampling method": "샘플링 방법", - "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", - "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", - "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", - "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", - "Width": "가로", - "Height": "세로", - "Restore faces": "얼굴 보정", - "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", - "Tiling": "타일링", - "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", - "Highres. fix": "고해상도 보정", - "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", - "Firstpass width": "초기 가로길이", - "Firstpass height": "초기 세로길이", - "Denoising strength": "디노이즈 강도", - "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", - "Batch count": "배치 수", - "Batch size": "배치 크기", - "How many batches of images to create": "생성할 이미지 배치 수", - "How many image to create in a single batch": "한 배치당 이미지 수", - "CFG Scale": "CFG 스케일", - "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", - "Seed": "시드", - "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", - "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", - "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", - "Extra": "고급", - "Variation seed": "바리에이션 시드", - "Variation strength": "바리에이션 강도", - "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", - "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", - "Resize seed from height": "시드 리사이징 가로길이", - "Resize seed from width": "시드 리사이징 세로길이", - "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", - "Script": "스크립트", - "Save": "저장", - "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", - "Send to img2img": "이미지→이미지로 전송", - "Send to inpaint": "인페인트로 전송", - "Send to extras": "부가기능으로 전송", - "Open images output directory": "이미지 저장 경로 열기", - "Make Zip when Save?": "저장 시 Zip 생성하기", - "Always save all generated images": "생성된 이미지 항상 저장하기" -} \ No newline at end of file From 1a96f856c4c3348708974b80d0de5a8ac18c1799 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 21:50:34 +0900 Subject: [PATCH 0207/1118] update ko-KR.json Translated all text on txt2img window, plus some extra --- localizations/ko-KR.json | 42 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json index a4367dc5..c6e55bb1 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko-KR.json @@ -4,9 +4,10 @@ "×": "×", "❮": "❮", "❯": "❯", - "Loading...": "로딩중...", - "view": "", - "api": "api", + "Loading...": "", + "view": "api 보이기", + "hide": "api 숨기기", + "api": "", "•": "•", "txt2img": "텍스트→이미지", "img2img": "이미지→이미지", @@ -50,7 +51,7 @@ "Tiling": "타일링", "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", "Highres. fix": "고해상도 보정", - "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", "Firstpass width": "초기 가로길이", "Firstpass height": "초기 세로길이", "Denoising strength": "디노이즈 강도", @@ -81,5 +82,38 @@ "Send to extras": "부가기능으로 전송", "Open images output directory": "이미지 저장 경로 열기", "Make Zip when Save?": "저장 시 Zip 생성하기", + "Prompt matrix": "프롬프트 매트릭스", + "Separate prompts into parts using vertical pipe character (|) and the script will create a picture for every combination of them (except for the first part, which will be present in all combinations)": "(|)를 이용해 프롬프트를 분리할 시 첫 프롬프트를 제외하고 모든 프롬프트의 조합마다 이미지를 생성합니다. 첫 프롬프트는 모든 조합에 포함되게 됩니다.", + "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", + "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", + "Show Textbox": "텍스트박스 보이기", + "File with inputs": "설정값 파일", + "Prompts": "프롬프트", + "X/Y plot": "X/Y 플롯", + "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", + "X type": "X축", + "Y type": "Y축", + "X values": "X 설정값", + "Y values": "Y 설정값", + "Separate values for X axis using commas.": "쉼표로 X축에 적용할 값 분리", + "Separate values for Y axis using commas.": "쉼표로 Y축에 적용할 값 분리", + "Draw legend": "범례 그리기", + "Include Separate Images": "분리된 이미지 포함하기", + "Keep -1 for seeds": "시드값 -1로 유지", + "Var. seed": "바리에이션 시드", + "Var. strength": "바리에이션 강도", + "Steps": "스텝 수", + "Prompt S/R": "프롬프트 스타일 변경", + "Prompt order": "프롬프트 순서", + "Sampler": "샘플러", + "Checkpoint name": "체크포인트 이름", + "Hypernet str.": "하이퍼네트워크 강도", + "Sigma Churn": "시그마 섞기", + "Sigma min": "시그마 최솟값", + "Sigma max": "시그마 최댓값", + "Sigma noise": "시그마 노이즈", + "Clip skip": "클립 건너뛰기", + "Denoising": "디노이징", + "Nothing": "없음", "Always save all generated images": "생성된 이미지 항상 저장하기" } \ No newline at end of file From e210b61d6a4189817e27b7a4f3c1028cdb67a868 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Tue, 18 Oct 2022 22:12:41 +0900 Subject: [PATCH 0208/1118] update ko-KR.json --- localizations/ko-KR.json | 1 + 1 file changed, 1 insertion(+) diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json index c6e55bb1..b263b13c 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko-KR.json @@ -115,5 +115,6 @@ "Clip skip": "클립 건너뛰기", "Denoising": "디노이징", "Nothing": "없음", + "Apply settings": "설정 적용하기", "Always save all generated images": "생성된 이미지 항상 저장하기" } \ No newline at end of file From 499713c54697ae7ccdb264316307f4aa2c39faea Mon Sep 17 00:00:00 2001 From: Dynamic Date: Thu, 20 Oct 2022 19:20:39 +0900 Subject: [PATCH 0209/1118] Updated file with basic template and added new translations Translation done in txt2img-img2img windows and following scripts --- localizations/ko-KR.json | 498 +++++++++++++++++++++++++++++++-------- 1 file changed, 400 insertions(+), 98 deletions(-) diff --git a/localizations/ko-KR.json b/localizations/ko-KR.json index b263b13c..7cc431c6 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko-KR.json @@ -1,120 +1,422 @@ { - "⤡": "⤡", - "⊞": "⊞", "×": "×", + "•": "•", + "⊞": "⊞", "❮": "❮", "❯": "❯", - "Loading...": "", - "view": "api 보이기", - "hide": "api 숨기기", - "api": "", - "•": "•", - "txt2img": "텍스트→이미지", - "img2img": "이미지→이미지", - "Extras": "부가기능", - "PNG Info": "PNG 정보", - "History": "기록", - "Checkpoint Merger": "체크포인트 병합", - "Train": "훈련", - "Settings": "설정", - "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", - "Hypernetwork": "하이퍼네트워크", - "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", - "Generate": "생성", - "Style 1": "스타일 1", - "Style 2": "스타일 2", + "⤡": "⤡", + "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", + "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", + "A merger of the two checkpoints will be generated in your": "A merger of the two checkpoints will be generated in your", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", - "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", - "Save style": "스타일 저장", + "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", + "Add difference": "Add difference", + "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", + "Add layer normalization": "Add layer normalization", + "Add model hash to generation information": "Add model hash to generation information", + "Add model name to generation information": "Add model name to generation information", + "Always print all generation info to standard output": "Always print all generation info to standard output", + "Always save all generated image grids": "Always save all generated image grids", + "Always save all generated images": "생성된 이미지 항상 저장하기", + "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", - "Do not do anything special": "아무것도 하지 않기", - "Generate forever": "반복 생성", - "Cancel generate forever": "반복 생성 취소", - "Interrupt": "중단", - "Skip": "건너뛰기", - "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", - "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", - "Prompt": "프롬프트", - "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Negative prompt": "네거티브 프롬프트", - "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Sampling Steps": "샘플링 스텝 수", - "Sampling method": "샘플링 방법", - "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", - "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", - "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", - "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", - "Width": "가로", - "Height": "세로", - "Restore faces": "얼굴 보정", - "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", - "Tiling": "타일링", - "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", - "Highres. fix": "고해상도 보정", - "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", - "Firstpass width": "초기 가로길이", - "Firstpass height": "초기 세로길이", - "Denoising strength": "디노이즈 강도", - "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Apply settings": "설정 적용하기", + "BSRGAN 4x": "BSRGAN 4x", + "Batch Process": "Batch Process", "Batch count": "배치 수", + "Batch from Directory": "Batch from Directory", + "Batch img2img": "이미지→이미지 배치", "Batch size": "배치 크기", + "CFG Scale": "CFG 스케일", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "Cancel generate forever": "반복 생성 취소", + "Check progress (first)": "Check progress (first)", + "Check progress": "Check progress", + "Checkpoint Merger": "체크포인트 병합", + "Checkpoint name": "체크포인트 이름", + "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Clip skip": "클립 건너뛰기", + "CodeFormer visibility": "CodeFormer visibility", + "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", + "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", + "Color variation": "색깔 다양성", + "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", + "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", + "Create embedding": "Create embedding", + "Create flipped copies": "Create flipped copies", + "Create hypernetwork": "Create hypernetwork", + "Crop and resize": "잘라낸 후 리사이징", + "Crop to fit": "Crop to fit", + "Custom Name (Optional)": "Custom Name (Optional)", + "DDIM": "DDIM", + "DPM adaptive": "DPM adaptive", + "DPM fast": "DPM fast", + "DPM2 Karras": "DPM2 Karras", + "DPM2 a Karras": "DPM2 a Karras", + "DPM2 a": "DPM2 a", + "DPM2": "DPM2", + "Dataset directory": "Dataset directory", + "Decode CFG scale": "디코딩 CFG 스케일", + "Decode steps": "디코딩 스텝 수", + "Delete": "Delete", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Denoising strength change factor": "디노이즈 강도 변경 배수", + "Denoising strength": "디노이즈 강도", + "Denoising": "디노이징", + "Destination directory": "Destination directory", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", + "Directory for saving images using the Save button": "Directory for saving images using the Save button", + "Directory name pattern": "Directory name pattern", + "Do not add watermark to images": "Do not add watermark to images", + "Do not do anything special": "아무것도 하지 않기", + "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", + "Do not show any images in results for web": "Do not show any images in results for web", + "Download localization template": "Download localization template", + "Draw legend": "범례 그리기", + "Draw mask": "마스크 직접 그리기", + "Drop File Here": "Drop File Here", + "Drop Image Here": "Drop Image Here", + "ESRGAN_4x": "ESRGAN_4x", + "Embedding": "Embedding", + "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", + "Enable full page image viewer": "Enable full page image viewer", + "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", + "End Page": "End Page", + "Enter hypernetwork layer structure": "Enter hypernetwork layer structure", + "Eta noise seed delta": "Eta noise seed delta", + "Eta": "Eta", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Euler a": "Euler a", + "Euler": "Euler", + "Extra": "고급", + "Extras": "부가기능", + "Face restoration": "Face restoration", + "Fall-off exponent (lower=higher detail)": "감쇠 지수 (낮을수록 디테일이 올라감)", + "File Name": "File Name", + "File format for grids": "File format for grids", + "File format for images": "File format for images", + "File with inputs": "설정값 파일", + "File": "File", + "Filename join string": "Filename join string", + "Filename word regex": "Filename word regex", + "Filter NSFW content": "Filter NSFW content", + "First Page": "First Page", + "Firstpass height": "초기 세로길이", + "Firstpass width": "초기 가로길이", + "Font for image grids that have text": "Font for image grids that have text", + "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "SD 업스케일링에서 타일 간 몇 픽셀을 겹치게 할지 결정하는 설정값입니다. 타일들이 다시 한 이미지로 합쳐질 때, 눈에 띄는 이음매가 없도록 서로 겹치게 됩니다.", + "GFPGAN visibility": "GFPGAN visibility", + "Generate Info": "Generate Info", + "Generate forever": "반복 생성", + "Generate": "생성", + "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", + "Height": "세로", + "Heun": "Heun", + "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", + "Highres. fix": "고해상도 보정", + "History": "기록", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", - "CFG Scale": "CFG 스케일", - "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", - "Seed": "시드", - "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", - "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", - "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", - "Extra": "고급", - "Variation seed": "바리에이션 시드", - "Variation strength": "바리에이션 강도", - "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", + "How many times to repeat processing an image and using it as input for the next iteration": "이미지를 생성 후 원본으로 몇 번 반복해서 사용할지 결정하는 값", + "How much to blur the mask before processing, in pixels.": "이미지 생성 전 마스크를 얼마나 블러처리할지 결정하는 값. 픽셀 단위", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", + "Hypernet str.": "하이퍼네트워크 강도", + "Hypernetwork strength": "Hypernetwork strength", + "Hypernetwork": "하이퍼네트워크", + "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", + "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "Image for img2img": "Image for img2img", + "Image for inpainting with mask": "Image for inpainting with mask", + "Image": "Image", + "Images filename pattern": "Images filename pattern", + "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "루프백 모드에서는 매 루프마다 디노이즈 강도에 이 값이 곱해집니다. 1보다 작을 경우 다양성이 낮아져 결과 이미지들이 고정된 형태로 모일 겁니다. 1보다 클 경우 다양성이 높아져 결과 이미지들이 갈수록 혼란스러워지겠죠.", + "Include Separate Images": "분리된 이미지 포함하기", + "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", + "Initialization text": "Initialization text", + "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", + "Inpaint at full resolution": "전체 해상도로 인페인트하기", + "Inpaint masked": "마스크만 처리", + "Inpaint not masked": "마스크 이외만 처리", + "Inpaint": "인페인트", + "Input directory": "인풋 이미지 경로", + "Interpolation Method": "Interpolation Method", + "Interrogate\nCLIP": "CLIP\n분석", + "Interrogate\nDeepBooru": "DeepBooru\n분석", + "Interrogate Options": "Interrogate Options", + "Interrogate: deepbooru score threshold": "Interrogate: deepbooru score threshold", + "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooru sort alphabetically", + "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).", + "Interrogate: keep models in VRAM": "Interrogate: keep models in VRAM", + "Interrogate: maximum description length": "Interrogate: maximum description length", + "Interrogate: minimum description length (excluding artists, etc..)": "Interrogate: minimum description length (excluding artists, etc..)", + "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", + "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", + "Interrupt": "중단", + "Just resize": "리사이징", + "Keep -1 for seeds": "시드값 -1로 유지", + "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", + "LDSR": "LDSR", + "LMS Karras": "LMS Karras", + "LMS": "LMS", + "Label": "Label", + "Lanczos": "Lanczos", + "Learning rate": "Learning rate", + "Leave blank to save images to the default path.": "Leave blank to save images to the default path.", + "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", + "Loading...": "로딩 중...", + "Localization (requires restart)": "Localization (requires restart)", + "Log directory": "Log directory", + "Loopback": "루프백", + "Loops": "루프 수", + "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", + "Make Zip when Save?": "저장 시 Zip 생성하기", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Mask blur": "마스크 블러", + "Mask mode": "Mask mode", + "Mask": "마스크", + "Masked content": "마스크된 부분", + "Masking mode": "Masking mode", + "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", + "Max steps": "Max steps", + "Modules": "Modules", + "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", + "Multiplier (M) - set to 0 to get model A": "Multiplier (M) - set to 0 to get model A", + "Name": "Name", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Negative prompt": "네거티브 프롬프트", + "Next Page": "Next Page", + "None": "None", + "Nothing": "없음", + "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", + "Number of vectors per token": "Number of vectors per token", + "Open images output directory": "이미지 저장 경로 열기", + "Open output directory": "Open output directory", + "Original negative prompt": "기존 네거티브 프롬프트", + "Original prompt": "기존 프롬프트", + "Outpainting direction": "아웃페인팅 방향", + "Outpainting mk2": "아웃페인팅 마크 2", + "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", + "Output directory for images from extras tab": "Output directory for images from extras tab", + "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", + "Output directory for img2img grids": "Output directory for img2img grids", + "Output directory for img2img images": "Output directory for img2img images", + "Output directory for txt2img grids": "Output directory for txt2img grids", + "Output directory for txt2img images": "Output directory for txt2img images", + "Output directory": "이미지 저장 경로", + "Override `Denoising strength` to 1?": "디노이즈 강도를 1로 적용할까요?", + "Override `Sampling Steps` to the same value as `Decode steps`?": "샘플링 스텝 수를 디코딩 스텝 수와 동일하게 적용할까요?", + "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", + "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "프롬프트 값을 기존 프롬프트와 동일하게 적용할까요?(네거티브 프롬프트 포함)", + "PLMS": "PLMS", + "PNG Info": "PNG 정보", + "Page Index": "Page Index", + "Path to directory where to write outputs": "Path to directory where to write outputs", + "Path to directory with input images": "Path to directory with input images", + "Paths for saving": "Paths for saving", + "Pixels to expand": "확장할 픽셀 수", + "Poor man's outpainting": "가난뱅이의 아웃페인팅", + "Preprocess images": "Preprocess images", + "Preprocess": "Preprocess", + "Prev Page": "Prev Page", + "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", + "Primary model (A)": "Primary model (A)", + "Process an image, use it as an input, repeat.": "이미지를 생성하고, 생성한 이미지를 다시 원본으로 사용하는 과정을 반복합니다.", + "Process images in a directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리의 이미지들을 처리합니다.", + "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Prompt S/R": "프롬프트 스타일 변경", + "Prompt matrix": "프롬프트 매트릭스", + "Prompt order": "프롬프트 순서", + "Prompt template file": "Prompt template file", + "Prompt": "프롬프트", + "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", + "Prompts": "프롬프트", + "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", + "Quality for saved jpeg images": "Quality for saved jpeg images", + "Quicksettings list": "Quicksettings list", + "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", + "Randomness": "랜덤성", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", + "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", + "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "추천 설정값 - 샘플링 스텝 수 : 80-100 , 샘플러 : Euler a, 디노이즈 강도 : 0.8", + "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", + "Renew Page": "Renew Page", + "Request browser notifications": "Request browser notifications", + "Resize and fill": "리사이징 후 채우기", + "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "설정된 해상도로 이미지 리사이징을 진행합니다. 원본과 가로/세로 길이가 일치하지 않을 경우, 부정확한 화면비의 이미지를 얻게 됩니다.", + "Resize mode": "Resize mode", "Resize seed from height": "시드 리사이징 가로길이", "Resize seed from width": "시드 리사이징 세로길이", - "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", - "Script": "스크립트", + "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "이미지 전체가 설정된 해상도 내부에 들어가게 리사이징을 진행합니다. 빈 공간은 이미지의 색상으로 채웁니다.", + "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "설정된 해상도 전체가 이미지로 가득차게 리사이징을 진행합니다. 튀어나오는 부분은 잘라냅니다.", + "Resize": "Resize", + "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", + "Restore faces": "얼굴 보정", + "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", + "Result = A * (1 - M) + B * M": "Result = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "Result = A + (B - C) * M", + "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", + "Run": "Run", + "SD upscale": "SD 업스케일링", + "Sampler parameters": "Sampler parameters", + "Sampler": "샘플러", + "Sampling Steps": "샘플링 스텝 수", + "Sampling method": "샘플링 방법", + "Save a copy of embedding to log directory every N steps, 0 to disable": "Save a copy of embedding to log directory every N steps, 0 to disable", + "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", + "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", + "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", + "Save an image to log directory every N steps, 0 to disable": "Save an image to log directory every N steps, 0 to disable", + "Save as float16": "Save as float16", + "Save grids to a subdirectory": "Save grids to a subdirectory", + "Save images to a subdirectory": "Save images to a subdirectory", + "Save images with embedding in PNG chunks": "Save images with embedding in PNG chunks", + "Save style": "스타일 저장", + "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", "Save": "저장", - "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "Saving images/grids": "Saving images/grids", + "Saving to a directory": "Saving to a directory", + "Scale by": "Scale by", + "Scale to": "Scale to", + "Script": "스크립트", + "ScuNET GAN": "ScuNET GAN", + "ScuNET PSNR": "ScuNET PSNR", + "Secondary model (B)": "Secondary model (B)", + "See": "See", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "Seed": "시드", + "Send to extras": "부가기능으로 전송", "Send to img2img": "이미지→이미지로 전송", "Send to inpaint": "인페인트로 전송", - "Send to extras": "부가기능으로 전송", - "Open images output directory": "이미지 저장 경로 열기", - "Make Zip when Save?": "저장 시 Zip 생성하기", - "Prompt matrix": "프롬프트 매트릭스", + "Send to txt2img": "텍스트→이미지로 전송", "Separate prompts into parts using vertical pipe character (|) and the script will create a picture for every combination of them (except for the first part, which will be present in all combinations)": "(|)를 이용해 프롬프트를 분리할 시 첫 프롬프트를 제외하고 모든 프롬프트의 조합마다 이미지를 생성합니다. 첫 프롬프트는 모든 조합에 포함되게 됩니다.", - "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", - "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", - "Show Textbox": "텍스트박스 보이기", - "File with inputs": "설정값 파일", - "Prompts": "프롬프트", - "X/Y plot": "X/Y 플롯", - "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", - "X type": "X축", - "Y type": "Y축", - "X values": "X 설정값", - "Y values": "Y 설정값", "Separate values for X axis using commas.": "쉼표로 X축에 적용할 값 분리", "Separate values for Y axis using commas.": "쉼표로 Y축에 적용할 값 분리", - "Draw legend": "범례 그리기", - "Include Separate Images": "분리된 이미지 포함하기", - "Keep -1 for seeds": "시드값 -1로 유지", + "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "Settings": "설정", + "Show Textbox": "텍스트박스 보이기", + "Show generation progress in window title.": "Show generation progress in window title.", + "Show grid in results for web": "Show grid in results for web", + "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", + "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", + "Show progressbar": "Show progressbar", + "Show result images": "Show result images", + "Sigma Churn": "시그마 섞기", + "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", + "Sigma max": "시그마 최댓값", + "Sigma min": "시그마 최솟값", + "Sigma noise": "시그마 노이즈", + "Single Image": "Single Image", + "Skip": "건너뛰기", + "Source directory": "Source directory", + "Source": "Source", + "Split oversized images into two": "Split oversized images into two", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Stable Diffusion": "Stable Diffusion", + "Steps": "스텝 수", + "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", + "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", + "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", + "Style 1": "스타일 1", + "Style 2": "스타일 2", + "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", + "SwinIR 4x": "SwinIR 4x", + "System": "System", + "Tertiary model (C)": "Tertiary model (C)", + "Textbox": "Textbox", + "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", + "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", + "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", + "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", + "Tile overlap": "타일 겹침", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", + "Tile size for all SwinIR.": "Tile size for all SwinIR.", + "Tiling": "타일링", + "Train Embedding": "Train Embedding", + "Train Hypernetwork": "Train Hypernetwork", + "Train an embedding; must specify a directory with a set of 1:1 ratio images": "Train an embedding; must specify a directory with a set of 1:1 ratio images", + "Train": "훈련", + "Training": "Training", + "Unload VAE and CLIP from VRAM when training": "Unload VAE and CLIP from VRAM when training", + "Upload mask": "마스크 업로드하기", + "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", + "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "마스크된 부분을 설정된 해상도로 업스케일하고, 인페인팅을 진행한 뒤, 다시 다운스케일 후 원본 이미지에 붙여넣습니다.", + "Upscaler 2 visibility": "Upscaler 2 visibility", + "Upscaler for img2img": "Upscaler for img2img", + "Upscaler": "업스케일러", + "Upscaling": "Upscaling", + "Use BLIP for caption": "Use BLIP for caption", + "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", + "Use an empty output directory to save pictures normally instead of writing to the output directory.": "저장 경로를 비워두면 기본 저장 폴더에 이미지들이 저장됩니다.", + "Use deepbooru for caption": "Use deepbooru for caption", + "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", + "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", + "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", + "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", + "User interface": "User interface", + "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", "Var. seed": "바리에이션 시드", "Var. strength": "바리에이션 강도", - "Steps": "스텝 수", - "Prompt S/R": "프롬프트 스타일 변경", - "Prompt order": "프롬프트 순서", - "Sampler": "샘플러", - "Checkpoint name": "체크포인트 이름", - "Hypernet str.": "하이퍼네트워크 강도", - "Sigma Churn": "시그마 섞기", - "Sigma min": "시그마 최솟값", - "Sigma max": "시그마 최댓값", - "Sigma noise": "시그마 노이즈", - "Clip skip": "클립 건너뛰기", - "Denoising": "디노이징", - "Nothing": "없음", - "Apply settings": "설정 적용하기", - "Always save all generated images": "생성된 이미지 항상 저장하기" + "Variation seed": "바리에이션 시드", + "Variation strength": "바리에이션 강도", + "Weighted sum": "Weighted sum", + "What to put inside the masked area before processing it with Stable Diffusion.": "Stable Diffusion으로 이미지를 생성하기 전 마스크된 부분에 무엇을 채울지 결정하는 설정값", + "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", + "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", + "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", + "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", + "Width": "가로", + "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "이미지를 설정된 사이즈의 2배로 업스케일합니다. 상단의 가로와 세로 슬라이더를 이용해 타일 사이즈를 지정하세요.", + "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", + "X type": "X축", + "X values": "X 설정값", + "X/Y plot": "X/Y 플롯", + "Y type": "Y축", + "Y values": "Y 설정값", + "api": "", + "built with gradio": "gradio로 제작되었습니다", + "checkpoint": "checkpoint", + "directory.": "directory.", + "down": "아래쪽", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", + "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", + "extras history": "extras history", + "fill it with colors of the image": "이미지의 색상으로 채우기", + "fill it with latent space noise": "잠재 공간 노이즈로 채우기", + "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", + "fill": "채우기", + "for detailed explanation.": "for detailed explanation.", + "hide": "api 숨기기", + "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", + "img2img DDIM discretize": "img2img DDIM discretize", + "img2img alternative test": "이미지→이미지 대체버전 테스트", + "img2img history": "img2img history", + "img2img": "이미지→이미지", + "keep whatever was there originally": "이미지 원본 유지", + "latent noise": "잠재 노이즈", + "latent nothing": "잠재 공백", + "left": "왼쪽", + "number of images to delete consecutively next": "number of images to delete consecutively next", + "or": "or", + "original": "원본 유지", + "quad": "quad", + "right": "오른쪽", + "set_index": "set_index", + "should be 2 or lower.": "이 2 이하여야 합니다.", + "sigma churn": "sigma churn", + "sigma noise": "sigma noise", + "sigma tmin": "sigma tmin", + "txt2img history": "txt2img history", + "txt2img": "텍스트→이미지", + "uniform": "uniform", + "up": "위쪽", + "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", + "view": "api 보이기", + "wiki": "wiki" } \ No newline at end of file From 6cfe23a6f183be58746feb7d7d58f83e877ed630 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Sun, 23 Oct 2022 22:37:40 +0900 Subject: [PATCH 0210/1118] Rename ko-KR.json to ko_KR.json --- localizations/{ko-KR.json => ko_KR.json} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename localizations/{ko-KR.json => ko_KR.json} (98%) diff --git a/localizations/ko-KR.json b/localizations/ko_KR.json similarity index 98% rename from localizations/ko-KR.json rename to localizations/ko_KR.json index 7cc431c6..f665042e 100644 --- a/localizations/ko-KR.json +++ b/localizations/ko_KR.json @@ -419,4 +419,4 @@ "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", "view": "api 보이기", "wiki": "wiki" -} \ No newline at end of file +} From 016712fc4cd523fb18123eed4281245f0dcc5bc3 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Sun, 23 Oct 2022 22:38:49 +0900 Subject: [PATCH 0211/1118] Update ko_KR.json Updated translation for everything except the Settings tab --- localizations/ko_KR.json | 381 ++++++++++++++++++++++----------------- 1 file changed, 219 insertions(+), 162 deletions(-) diff --git a/localizations/ko_KR.json b/localizations/ko_KR.json index f665042e..a48ece87 100644 --- a/localizations/ko_KR.json +++ b/localizations/ko_KR.json @@ -5,118 +5,158 @@ "❮": "❮", "❯": "❯", "⤡": "⤡", + " images in this directory. Loaded ": "개의 이미지가 이 경로에 존재합니다. ", + " images during ": "개의 이미지를 불러왔고, 생성 기간은 ", + ", divided into ": "입니다. ", + " pages": "페이지로 나뉘어 표시합니다.", "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", - "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", - "A merger of the two checkpoints will be generated in your": "A merger of the two checkpoints will be generated in your", + "[wiki]": " [위키] 참조", + "A directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리를 선택해 주세요.", + "A merger of the two checkpoints will be generated in your": "체크포인트들이 병합된 결과물이 당신의", "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", - "Add difference": "Add difference", + "Add difference": "차이점 추가", "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", - "Add layer normalization": "Add layer normalization", + "Add layer normalization": "레이어 정규화(normalization) 추가", "Add model hash to generation information": "Add model hash to generation information", "Add model name to generation information": "Add model name to generation information", + "Aesthetic imgs embedding": "스타일 이미지 임베딩", + "Aesthetic learning rate": "스타일 학습 수", + "Aesthetic steps": "스타일 스텝 수", + "Aesthetic text for imgs": "스타일 텍스트", + "Aesthetic weight": "스타일 가중치", "Always print all generation info to standard output": "Always print all generation info to standard output", "Always save all generated image grids": "Always save all generated image grids", "Always save all generated images": "생성된 이미지 항상 저장하기", + "api": "", + "append": "뒤에 삽입", "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", "Apply settings": "설정 적용하기", - "BSRGAN 4x": "BSRGAN 4x", - "Batch Process": "Batch Process", "Batch count": "배치 수", - "Batch from Directory": "Batch from Directory", + "Batch from Directory": "저장 경로로부터 여러장 처리", "Batch img2img": "이미지→이미지 배치", + "Batch Process": "이미지 여러장 처리", "Batch size": "배치 크기", - "CFG Scale": "CFG 스케일", - "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "BSRGAN 4x": "BSRGAN 4x", + "built with gradio": "gradio로 제작되었습니다", "Cancel generate forever": "반복 생성 취소", - "Check progress (first)": "Check progress (first)", + "CFG Scale": "CFG 스케일", "Check progress": "Check progress", + "Check progress (first)": "Check progress (first)", + "checkpoint": " 체크포인트 ", "Checkpoint Merger": "체크포인트 병합", "Checkpoint name": "체크포인트 이름", "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", + "Click to Upload": "Click to Upload", "Clip skip": "클립 건너뛰기", - "CodeFormer visibility": "CodeFormer visibility", - "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "CodeFormer visibility": "CodeFormer 가시성", + "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer 가중치 (0 = 최대 효과, 1 = 최소 효과)", "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", "Color variation": "색깔 다양성", + "Collect": "즐겨찾기", + "copy": "복사", "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", - "Create embedding": "Create embedding", - "Create flipped copies": "Create flipped copies", - "Create hypernetwork": "Create hypernetwork", + "Create aesthetic images embedding": "Create aesthetic images embedding", + "Create embedding": "임베딩 생성", + "Create flipped copies": "좌우로 뒤집은 복사본 생성", + "Create hypernetwork": "하이퍼네트워크 생성", + "Create images embedding": "Create images embedding", "Crop and resize": "잘라낸 후 리사이징", - "Crop to fit": "Crop to fit", - "Custom Name (Optional)": "Custom Name (Optional)", + "Crop to fit": "잘라내서 맞추기", + "Custom Name (Optional)": "병합 모델 이름 (선택사항)", + "Dataset directory": "데이터셋 경로", "DDIM": "DDIM", - "DPM adaptive": "DPM adaptive", - "DPM fast": "DPM fast", - "DPM2 Karras": "DPM2 Karras", - "DPM2 a Karras": "DPM2 a Karras", - "DPM2 a": "DPM2 a", - "DPM2": "DPM2", - "Dataset directory": "Dataset directory", "Decode CFG scale": "디코딩 CFG 스케일", "Decode steps": "디코딩 스텝 수", - "Delete": "Delete", - "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", - "Denoising strength change factor": "디노이즈 강도 변경 배수", - "Denoising strength": "디노이즈 강도", + "Delete": "삭제", "Denoising": "디노이징", - "Destination directory": "Destination directory", + "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 인페이팅에 뛰어남", + "Denoising strength": "디노이즈 강도", + "Denoising strength change factor": "디노이즈 강도 변경 배수", + "Destination directory": "결과물 저장 경로", "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", "Directory for saving images using the Save button": "Directory for saving images using the Save button", "Directory name pattern": "Directory name pattern", + "directory.": "저장 경로에 저장됩니다.", "Do not add watermark to images": "Do not add watermark to images", "Do not do anything special": "아무것도 하지 않기", "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", "Do not show any images in results for web": "Do not show any images in results for web", + "down": "아래쪽", "Download localization template": "Download localization template", + "Download": "다운로드", + "DPM adaptive": "DPM adaptive", + "DPM fast": "DPM fast", + "DPM2": "DPM2", + "DPM2 a": "DPM2 a", + "DPM2 a Karras": "DPM2 a Karras", + "DPM2 Karras": "DPM2 Karras", "Draw legend": "범례 그리기", "Draw mask": "마스크 직접 그리기", "Drop File Here": "Drop File Here", "Drop Image Here": "Drop Image Here", - "ESRGAN_4x": "ESRGAN_4x", - "Embedding": "Embedding", + "Embedding": "임베딩", + "Embedding Learning rate": "임베딩 학습률", "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", "Enable full page image viewer": "Enable full page image viewer", "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", - "End Page": "End Page", - "Enter hypernetwork layer structure": "Enter hypernetwork layer structure", - "Eta noise seed delta": "Eta noise seed delta", + "End Page": "마지막 페이지", + "Enter hypernetwork layer structure": "하이퍼네트워크 레이어 구조 입력", + "Error": "오류", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "ESRGAN_4x": "ESRGAN_4x", "Eta": "Eta", - "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", - "Euler a": "Euler a", + "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", + "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", + "Eta noise seed delta": "Eta noise seed delta", "Euler": "Euler", + "Euler a": "Euler a", + "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", + "Existing Caption txt Action": "이미 존재하는 캡션 텍스트 처리", "Extra": "고급", "Extras": "부가기능", + "extras history": "extras history", "Face restoration": "Face restoration", "Fall-off exponent (lower=higher detail)": "감쇠 지수 (낮을수록 디테일이 올라감)", - "File Name": "File Name", + "favorites": "즐겨찾기", + "File": "File", "File format for grids": "File format for grids", "File format for images": "File format for images", + "File Name": "파일 이름", "File with inputs": "설정값 파일", - "File": "File", "Filename join string": "Filename join string", "Filename word regex": "Filename word regex", + "fill": "채우기", + "fill it with colors of the image": "이미지의 색상으로 채우기", + "fill it with latent space noise": "잠재 공간 노이즈로 채우기", + "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", "Filter NSFW content": "Filter NSFW content", - "First Page": "First Page", + "First Page": "처음 페이지", "Firstpass height": "초기 세로길이", "Firstpass width": "초기 가로길이", "Font for image grids that have text": "Font for image grids that have text", + "for detailed explanation.": "를 참조하십시오.", "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "SD 업스케일링에서 타일 간 몇 픽셀을 겹치게 할지 결정하는 설정값입니다. 타일들이 다시 한 이미지로 합쳐질 때, 눈에 띄는 이음매가 없도록 서로 겹치게 됩니다.", - "GFPGAN visibility": "GFPGAN visibility", - "Generate Info": "Generate Info", - "Generate forever": "반복 생성", "Generate": "생성", + "Generate forever": "반복 생성", + "Generate Info": "생성 정보", + "GFPGAN visibility": "GFPGAN 가시성", "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", "Height": "세로", "Heun": "Heun", + "hide": "api 숨기기", "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", "Highres. fix": "고해상도 보정", "History": "기록", + "Image Browser": "이미지 브라우저", + "Images directory": "이미지 경로", + "extras": "부가기능", + "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", @@ -124,26 +164,32 @@ "How much to blur the mask before processing, in pixels.": "이미지 생성 전 마스크를 얼마나 블러처리할지 결정하는 값. 픽셀 단위", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "바리에이션을 얼마나 줄지 정하는 수치 - 0일 경우 아무것도 바뀌지 않고, 1일 경우 바리에이션 시드로부터 생성된 이미지를 얻게 됩니다. (Ancestral 샘플러 제외 - 이 경우에는 좀 다른 무언가를 얻게 됩니다)", "Hypernet str.": "하이퍼네트워크 강도", - "Hypernetwork strength": "Hypernetwork strength", "Hypernetwork": "하이퍼네트워크", + "Hypernetwork Learning rate": "하이퍼네트워크 학습률", + "Hypernetwork strength": "Hypernetwork strength", "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "ignore": "무시", + "Image": "Image", "Image for img2img": "Image for img2img", "Image for inpainting with mask": "Image for inpainting with mask", - "Image": "Image", "Images filename pattern": "Images filename pattern", + "img2img": "이미지→이미지", + "img2img alternative test": "이미지→이미지 대체버전 테스트", + "img2img DDIM discretize": "img2img DDIM discretize", + "img2img history": "img2img history", "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "루프백 모드에서는 매 루프마다 디노이즈 강도에 이 값이 곱해집니다. 1보다 작을 경우 다양성이 낮아져 결과 이미지들이 고정된 형태로 모일 겁니다. 1보다 클 경우 다양성이 높아져 결과 이미지들이 갈수록 혼란스러워지겠죠.", "Include Separate Images": "분리된 이미지 포함하기", "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", - "Initialization text": "Initialization text", - "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", + "Initialization text": "초기화 텍스트", + "Inpaint": "인페인트", "Inpaint at full resolution": "전체 해상도로 인페인트하기", + "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", "Inpaint masked": "마스크만 처리", "Inpaint not masked": "마스크 이외만 처리", - "Inpaint": "인페인트", "Input directory": "인풋 이미지 경로", - "Interpolation Method": "Interpolation Method", + "Interpolation Method": "보간 방법", "Interrogate\nCLIP": "CLIP\n분석", "Interrogate\nDeepBooru": "DeepBooru\n분석", "Interrogate Options": "Interrogate Options", @@ -156,49 +202,68 @@ "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", "Interrupt": "중단", + "Is negative text": "네거티브 텍스트일시 체크", "Just resize": "리사이징", "Keep -1 for seeds": "시드값 -1로 유지", - "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", - "LDSR": "LDSR", - "LMS Karras": "LMS Karras", - "LMS": "LMS", + "keep whatever was there originally": "이미지 원본 유지", "Label": "Label", "Lanczos": "Lanczos", - "Learning rate": "Learning rate", - "Leave blank to save images to the default path.": "Leave blank to save images to the default path.", + "Last prompt:": "Last prompt:", + "Last saved hypernetwork:": "Last saved hypernetwork:", + "Last saved image:": "Last saved image:", + "latent noise": "잠재 노이즈", + "latent nothing": "잠재 공백", + "LDSR": "LDSR", + "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", + "leakyrelu": "leakyrelu", + "Leave blank to save images to the default path.": "기존 저장 경로에 이미지들을 저장하려면 비워두세요.", + "left": "왼쪽", + "linear": "linear", "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", + "LMS": "LMS", + "LMS Karras": "LMS Karras", + "Load": "불러오기", "Loading...": "로딩 중...", "Localization (requires restart)": "Localization (requires restart)", - "Log directory": "Log directory", + "Log directory": "로그 경로", "Loopback": "루프백", "Loops": "루프 수", + "Loss:": "Loss:", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", "Make Zip when Save?": "저장 시 Zip 생성하기", - "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", + "Mask": "마스크", "Mask blur": "마스크 블러", "Mask mode": "Mask mode", - "Mask": "마스크", "Masked content": "마스크된 부분", "Masking mode": "Masking mode", "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", - "Max steps": "Max steps", - "Modules": "Modules", + "Max steps": "최대 스텝 수", + "Modules": "모듈", "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", - "Multiplier (M) - set to 0 to get model A": "Multiplier (M) - set to 0 to get model A", - "Name": "Name", - "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.", + "Multiplier (M) - set to 0 to get model A": "배율 (M) - 0으로 적용하면 모델 A를 얻게 됩니다", + "Name": "이름", "Negative prompt": "네거티브 프롬프트", - "Next Page": "Next Page", + "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", + "Next batch": "다음 묶음", + "Next Page": "다음 페이지", "None": "None", "Nothing": "없음", + "Nothing found in the image.": "Nothing found in the image.", + "number of images to delete consecutively next": "연속적으로 삭제할 이미지 수", "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", - "Number of vectors per token": "Number of vectors per token", + "Number of vectors per token": "토큰별 벡터 수", + "Open for Clip Aesthetic!": "클립 스타일 기능을 활성화하려면 클릭!", "Open images output directory": "이미지 저장 경로 열기", - "Open output directory": "Open output directory", + "Open output directory": "저장 경로 열기", + "or": "or", + "original": "원본 유지", "Original negative prompt": "기존 네거티브 프롬프트", "Original prompt": "기존 프롬프트", "Outpainting direction": "아웃페인팅 방향", "Outpainting mk2": "아웃페인팅 마크 2", + "Output directory": "이미지 저장 경로", "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", "Output directory for images from extras tab": "Output directory for images from extras tab", "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", @@ -206,46 +271,54 @@ "Output directory for img2img images": "Output directory for img2img images", "Output directory for txt2img grids": "Output directory for txt2img grids", "Output directory for txt2img images": "Output directory for txt2img images", - "Output directory": "이미지 저장 경로", "Override `Denoising strength` to 1?": "디노이즈 강도를 1로 적용할까요?", - "Override `Sampling Steps` to the same value as `Decode steps`?": "샘플링 스텝 수를 디코딩 스텝 수와 동일하게 적용할까요?", - "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "프롬프트 값을 기존 프롬프트와 동일하게 적용할까요?(네거티브 프롬프트 포함)", - "PLMS": "PLMS", - "PNG Info": "PNG 정보", - "Page Index": "Page Index", + "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", + "Override `Sampling Steps` to the same value as `Decode steps`?": "샘플링 스텝 수를 디코딩 스텝 수와 동일하게 적용할까요?", + "Overwrite Old Embedding": "기존 임베딩 덮어쓰기", + "Overwrite Old Hypernetwork": "기존 하이퍼네트워크 덮어쓰기", + "Page Index": "페이지 인덱스", + "parameters": "설정값", "Path to directory where to write outputs": "Path to directory where to write outputs", - "Path to directory with input images": "Path to directory with input images", + "Path to directory with input images": "인풋 이미지가 있는 경로", "Paths for saving": "Paths for saving", "Pixels to expand": "확장할 픽셀 수", + "PLMS": "PLMS", + "PNG Info": "PNG 정보", "Poor man's outpainting": "가난뱅이의 아웃페인팅", - "Preprocess images": "Preprocess images", - "Preprocess": "Preprocess", - "Prev Page": "Prev Page", + "Preparing dataset from": "Preparing dataset from", + "prepend": "앞에 삽입", + "Preprocess": "전처리", + "Preprocess images": "이미지 전처리", + "Prev batch": "이전 묶음", + "Prev Page": "이전 페이지", "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", - "Primary model (A)": "Primary model (A)", + "Primary model (A)": "주 모델 (A)", "Process an image, use it as an input, repeat.": "이미지를 생성하고, 생성한 이미지를 다시 원본으로 사용하는 과정을 반복합니다.", "Process images in a directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리의 이미지들을 처리합니다.", "Produce an image that can be tiled.": "타일링 가능한 이미지를 생성합니다.", + "Prompt": "프롬프트", "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", - "Prompt S/R": "프롬프트 스타일 변경", "Prompt matrix": "프롬프트 매트릭스", "Prompt order": "프롬프트 순서", - "Prompt template file": "Prompt template file", - "Prompt": "프롬프트", - "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", + "Prompt S/R": "프롬프트 스타일 변경", + "Prompt template file": "프롬프트 템플릿 파일 경로", "Prompts": "프롬프트", + "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", + "quad": "quad", "Quality for saved jpeg images": "Quality for saved jpeg images", "Quicksettings list": "Quicksettings list", "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", "Randomness": "랜덤성", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", - "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", + "Read parameters (prompt, etc...) from txt2img tab when making previews": "프리뷰 이미지 생성 시 텍스트→이미지 탭에서 설정값(프롬프트 등) 읽어오기", "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "추천 설정값 - 샘플링 스텝 수 : 80-100 , 샘플러 : Euler a, 디노이즈 강도 : 0.8", "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", + "relu": "relu", "Renew Page": "Renew Page", "Request browser notifications": "Request browser notifications", + "Resize": "리사이징 배수", "Resize and fill": "리사이징 후 채우기", "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "설정된 해상도로 이미지 리사이징을 진행합니다. 원본과 가로/세로 길이가 일치하지 않을 경우, 부정확한 화면비의 이미지를 얻게 됩니다.", "Resize mode": "Resize mode", @@ -253,42 +326,43 @@ "Resize seed from width": "시드 리사이징 세로길이", "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "이미지 전체가 설정된 해상도 내부에 들어가게 리사이징을 진행합니다. 빈 공간은 이미지의 색상으로 채웁니다.", "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "설정된 해상도 전체가 이미지로 가득차게 리사이징을 진행합니다. 튀어나오는 부분은 잘라냅니다.", - "Resize": "Resize", "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", "Restore faces": "얼굴 보정", "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", - "Result = A * (1 - M) + B * M": "Result = A * (1 - M) + B * M", - "Result = A + (B - C) * M": "Result = A + (B - C) * M", + "Result = A * (1 - M) + B * M": "결과물 = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "결과물 = A + (B - C) * M", "Reuse seed from last generation, mostly useful if it was randomed": "이전 생성에서 사용된 시드를 불러옵니다. 랜덤하게 생성했을 시 도움됨", - "Run": "Run", - "SD upscale": "SD 업스케일링", - "Sampler parameters": "Sampler parameters", + "right": "오른쪽", + "Run": "가동", "Sampler": "샘플러", - "Sampling Steps": "샘플링 스텝 수", + "Sampler parameters": "Sampler parameters", "Sampling method": "샘플링 방법", - "Save a copy of embedding to log directory every N steps, 0 to disable": "Save a copy of embedding to log directory every N steps, 0 to disable", + "Sampling Steps": "샘플링 스텝 수", + "Save": "저장", + "Save a copy of embedding to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 임베딩을 저장합니다, 비활성화하려면 0으로 설정하십시오.", "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", - "Save an image to log directory every N steps, 0 to disable": "Save an image to log directory every N steps, 0 to disable", - "Save as float16": "Save as float16", + "Save an image to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 이미지를 저장합니다, 비활성화하려면 0으로 설정하십시오.", + "Save as float16": "float16으로 저장", "Save grids to a subdirectory": "Save grids to a subdirectory", "Save images to a subdirectory": "Save images to a subdirectory", - "Save images with embedding in PNG chunks": "Save images with embedding in PNG chunks", + "Save images with embedding in PNG chunks": "PNG 청크로 이미지에 임베딩을 포함시켜 저장", "Save style": "스타일 저장", "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", - "Save": "저장", "Saving images/grids": "Saving images/grids", "Saving to a directory": "Saving to a directory", - "Scale by": "Scale by", - "Scale to": "Scale to", + "Scale by": "스케일링 배수 지정", + "Scale to": "스케일링 사이즈 지정", "Script": "스크립트", "ScuNET GAN": "ScuNET GAN", "ScuNET PSNR": "ScuNET PSNR", - "Secondary model (B)": "Secondary model (B)", - "See": "See", - "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "SD upscale": "SD 업스케일링", + "Secondary model (B)": "2차 모델 (B)", + "See": "자세한 설명은", "Seed": "시드", + "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", + "Select activation function of hypernetwork": "하이퍼네트워크 활성화 함수 선택", "Send to extras": "부가기능으로 전송", "Send to img2img": "이미지→이미지로 전송", "Send to inpaint": "인페인트로 전송", @@ -297,26 +371,36 @@ "Separate values for X axis using commas.": "쉼표로 X축에 적용할 값 분리", "Separate values for Y axis using commas.": "쉼표로 Y축에 적용할 값 분리", "Set seed to -1, which will cause a new random number to be used every time": "시드를 -1로 적용 - 매번 랜덤한 시드가 적용되게 됩니다.", + "set_index": "set_index", "Settings": "설정", - "Show Textbox": "텍스트박스 보이기", + "should be 2 or lower.": "이 2 이하여야 합니다.", "Show generation progress in window title.": "Show generation progress in window title.", "Show grid in results for web": "Show grid in results for web", "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", "Show progressbar": "Show progressbar", - "Show result images": "Show result images", - "Sigma Churn": "시그마 섞기", + "Show result images": "이미지 결과 보이기", + "Show Textbox": "텍스트박스 보이기", "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", + "Sigma Churn": "시그마 섞기", + "sigma churn": "sigma churn", "Sigma max": "시그마 최댓값", "Sigma min": "시그마 최솟값", "Sigma noise": "시그마 노이즈", - "Single Image": "Single Image", + "sigma noise": "sigma noise", + "sigma tmin": "sigma tmin", + "Single Image": "단일 이미지", "Skip": "건너뛰기", - "Source directory": "Source directory", - "Source": "Source", - "Split oversized images into two": "Split oversized images into two", - "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Slerp angle": "구면 선형 보간 각도", + "Slerp interpolation": "구면 선형 보간", + "Source": "원본", + "Source directory": "원본 경로", + "Split image threshold": "Split image threshold", + "Split image overlap ratio": "Split image overlap ratio", + "Split oversized images": "사이즈가 큰 이미지 분할하기", "Stable Diffusion": "Stable Diffusion", + "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "Step:": "Step:", "Steps": "스텝 수", "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", "Stop processing current image and continue processing.": "현재 진행중인 이미지 생성을 중단하고 작업을 계속하기", @@ -325,51 +409,65 @@ "Style 2": "스타일 2", "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", "SwinIR 4x": "SwinIR 4x", + "Sys VRAM:": "시스템 VRAM : ", "System": "System", - "Tertiary model (C)": "Tertiary model (C)", + "Tertiary model (C)": "3차 모델 (C)", "Textbox": "Textbox", "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", + "This text is used to rotate the feature space of the imgs embs": "이 텍스트는 이미지 임베딩의 특징 공간을 회전하는 데 사용됩니다.", + "Tile overlap": "타일 겹침", "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", - "Tile overlap": "타일 겹침", - "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", "Tile size for all SwinIR.": "Tile size for all SwinIR.", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", "Tiling": "타일링", - "Train Embedding": "Train Embedding", - "Train Hypernetwork": "Train Hypernetwork", - "Train an embedding; must specify a directory with a set of 1:1 ratio images": "Train an embedding; must specify a directory with a set of 1:1 ratio images", + "Time taken:": "소요 시간 : ", + "Torch active/reserved:": "활성화/예약된 Torch 양 : ", + "Torch active: Peak amount of VRAM used by Torch during generation, excluding cached data.\nTorch reserved: Peak amount of VRAM allocated by Torch, including all active and cached data.\nSys VRAM: Peak amount of VRAM allocation across all applications / total GPU VRAM (peak utilization%).": "활성화된 Torch : 생성 도중 캐시된 데이터를 포함해 사용된 VRAM의 최대량\n예약된 Torch : 활성화되고 캐시된 모든 데이터를 포함해 Torch에게 할당된 VRAM의 최대량\n시스템 VRAM : 모든 어플리케이션에 할당된 VRAM 최대량 / 총 GPU VRAM (최고 이용도%)", "Train": "훈련", + "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "임베딩이나 하이퍼네트워크를 훈련시킵니다. 1:1 비율의 이미지가 있는 경로를 지정해야 합니다.", + "Train Embedding": "임베딩 훈련", + "Train Hypernetwork": "하이퍼네트워크 훈련", "Training": "Training", - "Unload VAE and CLIP from VRAM when training": "Unload VAE and CLIP from VRAM when training", + "txt2img": "텍스트→이미지", + "txt2img history": "txt2img history", + "uniform": "uniform", + "up": "위쪽", "Upload mask": "마스크 업로드하기", "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "마스크된 부분을 설정된 해상도로 업스케일하고, 인페인팅을 진행한 뒤, 다시 다운스케일 후 원본 이미지에 붙여넣습니다.", - "Upscaler 2 visibility": "Upscaler 2 visibility", - "Upscaler for img2img": "Upscaler for img2img", "Upscaler": "업스케일러", + "Upscaler 1": "업스케일러 1", + "Upscaler 2": "업스케일러 2", + "Upscaler 2 visibility": "업스케일러 2 가시성", + "Upscaler for img2img": "Upscaler for img2img", "Upscaling": "Upscaling", - "Use BLIP for caption": "Use BLIP for caption", "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", "Use an empty output directory to save pictures normally instead of writing to the output directory.": "저장 경로를 비워두면 기본 저장 폴더에 이미지들이 저장됩니다.", - "Use deepbooru for caption": "Use deepbooru for caption", + "Use BLIP for caption": "캡션에 BLIP 사용", + "Use deepbooru for caption": "캡션에 deepbooru 사용", + "Use dropout": "드롭아웃 사용", "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", + "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", "User interface": "User interface", - "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", "Var. seed": "바리에이션 시드", "Var. strength": "바리에이션 강도", "Variation seed": "바리에이션 시드", "Variation strength": "바리에이션 강도", - "Weighted sum": "Weighted sum", + "view": "api 보이기", + "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", + "Weighted sum": "가중 합", "What to put inside the masked area before processing it with Stable Diffusion.": "Stable Diffusion으로 이미지를 생성하기 전 마스크된 부분에 무엇을 채울지 결정하는 설정값", "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", "Width": "가로", + "wiki": " 위키", "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "이미지를 설정된 사이즈의 2배로 업스케일합니다. 상단의 가로와 세로 슬라이더를 이용해 타일 사이즈를 지정하세요.", "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", @@ -377,46 +475,5 @@ "X values": "X 설정값", "X/Y plot": "X/Y 플롯", "Y type": "Y축", - "Y values": "Y 설정값", - "api": "", - "built with gradio": "gradio로 제작되었습니다", - "checkpoint": "checkpoint", - "directory.": "directory.", - "down": "아래쪽", - "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", - "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", - "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", - "extras history": "extras history", - "fill it with colors of the image": "이미지의 색상으로 채우기", - "fill it with latent space noise": "잠재 공간 노이즈로 채우기", - "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", - "fill": "채우기", - "for detailed explanation.": "for detailed explanation.", - "hide": "api 숨기기", - "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", - "img2img DDIM discretize": "img2img DDIM discretize", - "img2img alternative test": "이미지→이미지 대체버전 테스트", - "img2img history": "img2img history", - "img2img": "이미지→이미지", - "keep whatever was there originally": "이미지 원본 유지", - "latent noise": "잠재 노이즈", - "latent nothing": "잠재 공백", - "left": "왼쪽", - "number of images to delete consecutively next": "number of images to delete consecutively next", - "or": "or", - "original": "원본 유지", - "quad": "quad", - "right": "오른쪽", - "set_index": "set_index", - "should be 2 or lower.": "이 2 이하여야 합니다.", - "sigma churn": "sigma churn", - "sigma noise": "sigma noise", - "sigma tmin": "sigma tmin", - "txt2img history": "txt2img history", - "txt2img": "텍스트→이미지", - "uniform": "uniform", - "up": "위쪽", - "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", - "view": "api 보이기", - "wiki": "wiki" -} + "Y values": "Y 설정값" +} \ No newline at end of file From ae7c830c3ae7bb1ebe9b0d935cb33c254354b649 Mon Sep 17 00:00:00 2001 From: Dynamic Date: Mon, 24 Oct 2022 04:29:19 +0900 Subject: [PATCH 0212/1118] Translation complete --- localizations/ko_KR.json | 302 +++++++++++++++++++++------------------ 1 file changed, 160 insertions(+), 142 deletions(-) diff --git a/localizations/ko_KR.json b/localizations/ko_KR.json index a48ece87..6889de46 100644 --- a/localizations/ko_KR.json +++ b/localizations/ko_KR.json @@ -15,23 +15,24 @@ "A merger of the two checkpoints will be generated in your": "체크포인트들이 병합된 결과물이 당신의", "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", - "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", + "Add a second progress bar to the console that shows progress for an entire job.": "콘솔에 전체 작업의 진행도를 보여주는 2번째 프로그레스 바 추가하기", "Add difference": "차이점 추가", - "Add extended info (seed, prompt) to filename when saving grid": "Add extended info (seed, prompt) to filename when saving grid", + "Add extended info (seed, prompt) to filename when saving grid": "그리드 저장 시 파일명에 추가 정보(시드, 프롬프트) 기입", "Add layer normalization": "레이어 정규화(normalization) 추가", - "Add model hash to generation information": "Add model hash to generation information", - "Add model name to generation information": "Add model name to generation information", + "Add model hash to generation information": "생성 정보에 모델 해시 추가", + "Add model name to generation information": "생성 정보에 모델 이름 추가", "Aesthetic imgs embedding": "스타일 이미지 임베딩", "Aesthetic learning rate": "스타일 학습 수", "Aesthetic steps": "스타일 스텝 수", "Aesthetic text for imgs": "스타일 텍스트", "Aesthetic weight": "스타일 가중치", - "Always print all generation info to standard output": "Always print all generation info to standard output", - "Always save all generated image grids": "Always save all generated image grids", + "Allowed categories for random artists selection when using the Roll button": "랜덤 버튼을 눌러 무작위 작가를 선택할 때 허용된 카테고리", + "Always print all generation info to standard output": "기본 아웃풋에 모든 생성 정보 항상 출력하기", + "Always save all generated image grids": "생성된 이미지 그리드 항상 저장하기", "Always save all generated images": "생성된 이미지 항상 저장하기", "api": "", "append": "뒤에 삽입", - "Apply color correction to img2img results to match original colors.": "Apply color correction to img2img results to match original colors.", + "Apply color correction to img2img results to match original colors.": "이미지→이미지 결과물이 기존 색상과 일치하도록 색상 보정 적용하기", "Apply selected styles to current prompt": "현재 프롬프트에 선택된 스타일 적용", "Apply settings": "설정 적용하기", "Batch count": "배치 수", @@ -43,29 +44,29 @@ "built with gradio": "gradio로 제작되었습니다", "Cancel generate forever": "반복 생성 취소", "CFG Scale": "CFG 스케일", - "Check progress": "Check progress", - "Check progress (first)": "Check progress (first)", + "Check progress": "진행도 체크", + "Check progress (first)": "진행도 체크 (처음)", "checkpoint": " 체크포인트 ", "Checkpoint Merger": "체크포인트 병합", "Checkpoint name": "체크포인트 이름", - "Checkpoints to cache in RAM": "Checkpoints to cache in RAM", + "Checkpoints to cache in RAM": "RAM에 캐싱할 체크포인트 수", "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 이미지가 주어진 프롬프트를 얼마나 따를지를 정해주는 수치 - 낮은 값일수록 더 창의적인 결과물이 나옴", - "Click to Upload": "Click to Upload", + "Click to Upload": "클릭해서 업로드하기", "Clip skip": "클립 건너뛰기", - "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP: maximum number of lines in text file (0 = No limit)", + "CLIP: maximum number of lines in text file (0 = No limit)": "CLIP : 텍스트 파일 최대 라인 수 (0 = 제한 없음)", "CodeFormer visibility": "CodeFormer 가시성", "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer 가중치 (0 = 최대 효과, 1 = 최소 효과)", - "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", + "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer 가중치 설정값 (0 = 최대 효과, 1 = 최소 효과)", "Color variation": "색깔 다양성", "Collect": "즐겨찾기", "copy": "복사", "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", - "Create a text file next to every image with generation parameters.": "Create a text file next to every image with generation parameters.", - "Create aesthetic images embedding": "Create aesthetic images embedding", + "Create a text file next to every image with generation parameters.": "생성된 이미지마다 생성 설정값을 담은 텍스트 파일 생성하기", + "Create aesthetic images embedding": "스타일 이미지 임베딩 생성하기", "Create embedding": "임베딩 생성", "Create flipped copies": "좌우로 뒤집은 복사본 생성", "Create hypernetwork": "하이퍼네트워크 생성", - "Create images embedding": "Create images embedding", + "Create images embedding": "이미지 임베딩 생성하기", "Crop and resize": "잘라낸 후 리사이징", "Crop to fit": "잘라내서 맞추기", "Custom Name (Optional)": "병합 모델 이름 (선택사항)", @@ -80,15 +81,15 @@ "Denoising strength change factor": "디노이즈 강도 변경 배수", "Destination directory": "결과물 저장 경로", "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "알고리즘이 얼마나 원본 이미지를 반영할지를 결정하는 수치입니다. 0일 경우 아무것도 바뀌지 않고, 1일 경우 원본 이미지와 전혀 관련없는 결과물을 얻게 됩니다. 1.0 아래의 값일 경우, 설정된 샘플링 스텝 수보다 적은 스텝 수를 거치게 됩니다.", - "Directory for saving images using the Save button": "Directory for saving images using the Save button", - "Directory name pattern": "Directory name pattern", + "Directory for saving images using the Save button": "저장 버튼을 이용해 저장하는 이미지들의 저장 경로", + "Directory name pattern": "디렉토리명 패턴", "directory.": "저장 경로에 저장됩니다.", - "Do not add watermark to images": "Do not add watermark to images", + "Do not add watermark to images": "이미지에 워터마크 추가하지 않기", "Do not do anything special": "아무것도 하지 않기", - "Do not save grids consisting of one picture": "Do not save grids consisting of one picture", - "Do not show any images in results for web": "Do not show any images in results for web", + "Do not save grids consisting of one picture": "이미지가 1개뿐인 그리드는 저장하지 않기", + "Do not show any images in results for web": "웹에서 결과창에 아무 이미지도 보여주지 않기", "down": "아래쪽", - "Download localization template": "Download localization template", + "Download localization template": "현지화 템플릿 다운로드", "Download": "다운로드", "DPM adaptive": "DPM adaptive", "DPM fast": "DPM fast", @@ -98,65 +99,67 @@ "DPM2 Karras": "DPM2 Karras", "Draw legend": "범례 그리기", "Draw mask": "마스크 직접 그리기", - "Drop File Here": "Drop File Here", - "Drop Image Here": "Drop Image Here", + "Drop File Here": "파일을 끌어 놓으세요", + "Drop Image Here": "이미지를 끌어 놓으세요", "Embedding": "임베딩", "Embedding Learning rate": "임베딩 학습률", - "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", - "Enable full page image viewer": "Enable full page image viewer", - "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", + "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "강조 : (텍스트)를 이용해 모델의 텍스트에 대한 가중치를 더 강하게 주고 [텍스트]를 이용해 더 약하게 줍니다.", + "Enable full page image viewer": "전체 페이지 이미지 뷰어 활성화", + "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "더 예리하고 깔끔한 결과물을 위해 K 샘플러들에 양자화를 적용합니다. 존재하는 시드가 변경될 수 있습니다. 재시작이 필요합니다.", "End Page": "마지막 페이지", "Enter hypernetwork layer structure": "하이퍼네트워크 레이어 구조 입력", "Error": "오류", - "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)", + "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)": "deepbooru에서 괄호를 역슬래시(\\)로 이스케이프 처리하기(가중치 강조가 아니라 실제 괄호로 사용되게 하기 위해)", "ESRGAN_4x": "ESRGAN_4x", "Eta": "Eta", - "eta (noise multiplier) for ancestral samplers": "eta (noise multiplier) for ancestral samplers", - "eta (noise multiplier) for DDIM": "eta (noise multiplier) for DDIM", - "Eta noise seed delta": "Eta noise seed delta", + "eta (noise multiplier) for ancestral samplers": "ancestral 샘플러를 위한 eta(노이즈 배수)값", + "eta (noise multiplier) for DDIM": "DDIM을 위한 eta(노이즈 배수)값", + "Eta noise seed delta": "Eta 노이즈 시드 변화", "Euler": "Euler", "Euler a": "Euler a", "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 매우 창의적, 스텝 수에 따라 완전히 다른 결과물이 나올 수 있음. 30~40보다 높은 스텝 수는 효과가 미미함", "Existing Caption txt Action": "이미 존재하는 캡션 텍스트 처리", "Extra": "고급", "Extras": "부가기능", - "extras history": "extras history", - "Face restoration": "Face restoration", + "extras history": "부가기능 기록", + "Face restoration": "얼굴 보정", + "Face restoration model": "얼굴 보정 모델", "Fall-off exponent (lower=higher detail)": "감쇠 지수 (낮을수록 디테일이 올라감)", "favorites": "즐겨찾기", - "File": "File", - "File format for grids": "File format for grids", - "File format for images": "File format for images", + "File": "파일", + "File format for grids": "그리드 이미지 파일 형식", + "File format for images": "이미지 파일 형식", "File Name": "파일 이름", "File with inputs": "설정값 파일", - "Filename join string": "Filename join string", - "Filename word regex": "Filename word regex", + "Filename join string": "파일명 병합 문자열", + "Filename word regex": "파일명 정규표현식", "fill": "채우기", "fill it with colors of the image": "이미지의 색상으로 채우기", "fill it with latent space noise": "잠재 공간 노이즈로 채우기", "fill it with latent space zeroes": "잠재 공간의 0값으로 채우기", - "Filter NSFW content": "Filter NSFW content", + "Filter NSFW content": "성인 컨텐츠 필터링하기", "First Page": "처음 페이지", "Firstpass height": "초기 세로길이", "Firstpass width": "초기 가로길이", - "Font for image grids that have text": "Font for image grids that have text", + "Font for image grids that have text": "텍스트가 존재하는 그리드 이미지의 폰트", "for detailed explanation.": "를 참조하십시오.", "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "SD 업스케일링에서 타일 간 몇 픽셀을 겹치게 할지 결정하는 설정값입니다. 타일들이 다시 한 이미지로 합쳐질 때, 눈에 띄는 이음매가 없도록 서로 겹치게 됩니다.", "Generate": "생성", "Generate forever": "반복 생성", "Generate Info": "생성 정보", "GFPGAN visibility": "GFPGAN 가시성", - "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", + "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "그리드 세로줄 수 : -1로 설정 시 자동 감지/0으로 설정 시 배치 크기와 동일", "Height": "세로", "Heun": "Heun", "hide": "api 숨기기", - "Hide samplers in user interface (requires restart)": "Hide samplers in user interface (requires restart)", + "Hide samplers in user interface (requires restart)": "사용자 인터페이스에서 숨길 샘플러 선택(재시작 필요)", "Highres. fix": "고해상도 보정", "History": "기록", "Image Browser": "이미지 브라우저", + "Images Browser": "이미지 브라우저", "Images directory": "이미지 경로", "extras": "부가기능", - "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", + "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "훈련이 얼마나 빨리 이루어질지 정하는 값입니다. 값이 낮을수록 훈련 시간이 길어지고, 높은 값일수록 정확한 결과를 내는 데 실패하고 임베딩을 망가뜨릴 수 있습니다(임베딩이 망가진 경우에는 훈련 정보 텍스트박스에 손실(Loss) : nan 이라고 출력되게 됩니다. 이 경우에는 망가지지 않은 이전 백업본을 불러와야 합니다).\n\n학습률은 하나의 값으로 설정할 수도 있고, 다음 문법을 사용해 여러 값을 사용할 수도 있습니다 :\n\n학습률_1:최대 스텝수_1, 학습률_2:최대 스텝수_2, ...\n\n예 : 0.005:100, 1e-3:1000, 1e-5\n\n예의 설정값은 첫 100스텝동안 0.005의 학습률로, 그 이후 1000스텝까지는 1e-3으로, 남은 스텝은 1e-5로 훈련하게 됩니다.", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results": "생성된 이미지를 향상할 횟수; 매우 낮은 값은 만족스럽지 못한 결과물을 출력할 수 있음", @@ -166,111 +169,114 @@ "Hypernet str.": "하이퍼네트워크 강도", "Hypernetwork": "하이퍼네트워크", "Hypernetwork Learning rate": "하이퍼네트워크 학습률", - "Hypernetwork strength": "Hypernetwork strength", - "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG", - "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.", - "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "Hypernetwork strength": "하이퍼네트워크 강도", + "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "PNG 이미지가 4MB보다 크거나 가로 또는 세로길이가 4000보다 클 경우, 다운스케일 후 JPG로 복사본 저장하기", + "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "이 옵션이 활성화되면 생성된 이미지에 워터마크가 추가되지 않습니다. 경고 : 워터마크를 추가하지 않는다면, 비윤리적인 행동을 하는 중일지도 모릅니다.", + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "이 값이 0이 아니라면, 시드에 해당 값이 더해지고, Eta가 있는 샘플러를 사용할 때 노이즈의 RNG 조정을 위해 해당 값이 사용됩니다. 이 설정으로 더 다양한 이미지를 생성하거나, 잘 알고 계시다면 특정 소프트웨어의 결과값을 재현할 수도 있습니다.", "ignore": "무시", - "Image": "Image", + "Image": "이미지", "Image for img2img": "Image for img2img", - "Image for inpainting with mask": "Image for inpainting with mask", - "Images filename pattern": "Images filename pattern", + "Image for inpainting with mask": "마스크로 인페인팅할 이미지", + "Images filename pattern": "이미지 파일명 패턴", "img2img": "이미지→이미지", "img2img alternative test": "이미지→이미지 대체버전 테스트", - "img2img DDIM discretize": "img2img DDIM discretize", - "img2img history": "img2img history", + "img2img DDIM discretize": "이미지→이미지 DDIM 이산화", + "img2img history": "이미지→이미지 기록", "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "루프백 모드에서는 매 루프마다 디노이즈 강도에 이 값이 곱해집니다. 1보다 작을 경우 다양성이 낮아져 결과 이미지들이 고정된 형태로 모일 겁니다. 1보다 클 경우 다양성이 높아져 결과 이미지들이 갈수록 혼란스러워지겠죠.", "Include Separate Images": "분리된 이미지 포함하기", - "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", + "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "75개보다 많은 토큰을 사용시 마지막 쉼표로부터 N개의 토큰 이내에 패딩을 추가해 통일성 증가시키기", "Initialization text": "초기화 텍스트", "Inpaint": "인페인트", "Inpaint at full resolution": "전체 해상도로 인페인트하기", - "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트 패딩값(픽셀 단위)", + "Inpaint at full resolution padding, pixels": "전체 해상도로 인페인트시 패딩값(픽셀 단위)", "Inpaint masked": "마스크만 처리", "Inpaint not masked": "마스크 이외만 처리", "Input directory": "인풋 이미지 경로", "Interpolation Method": "보간 방법", "Interrogate\nCLIP": "CLIP\n분석", "Interrogate\nDeepBooru": "DeepBooru\n분석", - "Interrogate Options": "Interrogate Options", - "Interrogate: deepbooru score threshold": "Interrogate: deepbooru score threshold", - "Interrogate: deepbooru sort alphabetically": "Interrogate: deepbooru sort alphabetically", - "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).", - "Interrogate: keep models in VRAM": "Interrogate: keep models in VRAM", - "Interrogate: maximum description length": "Interrogate: maximum description length", - "Interrogate: minimum description length (excluding artists, etc..)": "Interrogate: minimum description length (excluding artists, etc..)", - "Interrogate: num_beams for BLIP": "Interrogate: num_beams for BLIP", - "Interrogate: use artists from artists.csv": "Interrogate: use artists from artists.csv", + "Interrogate Options": "분석 설정", + "Interrogate: deepbooru score threshold": "분석 : deepbooru 점수 임계값", + "Interrogate: deepbooru sort alphabetically": "분석 : deepbooru 알파벳 순서로 정렬하기", + "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators).": "분석 : 결과물에 모델 태그의 랭크 포함하기 (캡션 바탕의 분석기에는 효과 없음)", + "Interrogate: keep models in VRAM": "분석 : VRAM에 모델 유지하기", + "Interrogate: maximum description length": "분석 : 설명 최대 길이", + "Interrogate: minimum description length (excluding artists, etc..)": "분석 : 설명 최소 길이(작가 등등..제외)", + "Interrogate: num_beams for BLIP": "분석 : BLIP의 num_beams값", + "Interrogate: use artists from artists.csv": "분석 : artists.csv의 작가들 사용하기", "Interrupt": "중단", "Is negative text": "네거티브 텍스트일시 체크", "Just resize": "리사이징", "Keep -1 for seeds": "시드값 -1로 유지", "keep whatever was there originally": "이미지 원본 유지", - "Label": "Label", + "Label": "라벨", "Lanczos": "Lanczos", - "Last prompt:": "Last prompt:", - "Last saved hypernetwork:": "Last saved hypernetwork:", - "Last saved image:": "Last saved image:", + "Last prompt:": "마지막 프롬프트 : ", + "Last saved hypernetwork:": "마지막으로 저장된 하이퍼네트워크 : ", + "Last saved image:": "마지막으로 저장된 이미지 : ", "latent noise": "잠재 노이즈", "latent nothing": "잠재 공백", "LDSR": "LDSR", - "LDSR processing steps. Lower = faster": "LDSR processing steps. Lower = faster", + "LDSR processing steps. Lower = faster": "LDSR 스텝 수. 낮은 값 = 빠른 속도", "leakyrelu": "leakyrelu", "Leave blank to save images to the default path.": "기존 저장 경로에 이미지들을 저장하려면 비워두세요.", "left": "왼쪽", "linear": "linear", - "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", + "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "설정 탭이 아니라 상단의 빠른 설정 바에 위치시킬 설정 이름을 쉼표로 분리해서 입력하십시오. 설정 이름은 modules/shared.py에서 찾을 수 있습니다. 재시작이 필요합니다.", "LMS": "LMS", "LMS Karras": "LMS Karras", "Load": "불러오기", "Loading...": "로딩 중...", - "Localization (requires restart)": "Localization (requires restart)", + "Localization (requires restart)": "현지화 (재시작 필요)", "Log directory": "로그 경로", "Loopback": "루프백", "Loops": "루프 수", - "Loss:": "Loss:", + "Loss:": "손실(Loss) : ", "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "동일한 시드 값으로 생성되었을 이미지를 주어진 해상도로 최대한 유사하게 재현합니다.", - "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", + "Make K-diffusion samplers produce same images in a batch as when making a single image": "K-diffusion 샘플러들이 단일 이미지를 생성하는 것처럼 배치에서도 동일한 이미지를 생성하게 하기", "Make Zip when Save?": "저장 시 Zip 생성하기", "Mask": "마스크", "Mask blur": "마스크 블러", - "Mask mode": "Mask mode", + "Mask mode": "마스크 모드", "Masked content": "마스크된 부분", - "Masking mode": "Masking mode", - "Max prompt words for [prompt_words] pattern": "Max prompt words for [prompt_words] pattern", + "Masking mode": "마스킹 모드", + "Max prompt words for [prompt_words] pattern": "[prompt_words] 패턴의 최대 프롬프트 단어 수", "Max steps": "최대 스텝 수", "Modules": "모듈", - "Move face restoration model from VRAM into RAM after processing": "Move face restoration model from VRAM into RAM after processing", - "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.", + "Move face restoration model from VRAM into RAM after processing": "처리가 완료되면 얼굴 보정 모델을 VRAM에서 RAM으로 옮기기", + "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "하이퍼네트워크 훈련 진행 시 VAE와 CLIP을 RAM으로 옮기기. VRAM이 절약됩니다.", "Multiplier (M) - set to 0 to get model A": "배율 (M) - 0으로 적용하면 모델 A를 얻게 됩니다", "Name": "이름", "Negative prompt": "네거티브 프롬프트", "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "네거티브 프롬프트 입력(Ctrl+Enter나 Alt+Enter로 생성 시작)", "Next batch": "다음 묶음", "Next Page": "다음 페이지", - "None": "None", + "None": "없음", "Nothing": "없음", "Nothing found in the image.": "Nothing found in the image.", "number of images to delete consecutively next": "연속적으로 삭제할 이미지 수", - "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", + "Number of pictures displayed on each page": "각 페이지에 표시될 이미지 수", + "Minimum number of pages per load": "한번 불러올 때마다 불러올 최소 페이지 수", + "Number of grids in each row": "각 세로줄마다 표시될 그리드 수", + "Number of repeats for a single input image per epoch; used only for displaying epoch number": "세대(Epoch)당 단일 인풋 이미지의 반복 횟수 - 세대(Epoch) 숫자를 표시하는 데에만 사용됩니다. ", "Number of vectors per token": "토큰별 벡터 수", "Open for Clip Aesthetic!": "클립 스타일 기능을 활성화하려면 클릭!", "Open images output directory": "이미지 저장 경로 열기", "Open output directory": "저장 경로 열기", - "or": "or", + "or": "또는", "original": "원본 유지", "Original negative prompt": "기존 네거티브 프롬프트", "Original prompt": "기존 프롬프트", "Outpainting direction": "아웃페인팅 방향", "Outpainting mk2": "아웃페인팅 마크 2", "Output directory": "이미지 저장 경로", - "Output directory for grids; if empty, defaults to two directories below": "Output directory for grids; if empty, defaults to two directories below", - "Output directory for images from extras tab": "Output directory for images from extras tab", - "Output directory for images; if empty, defaults to three directories below": "Output directory for images; if empty, defaults to three directories below", - "Output directory for img2img grids": "Output directory for img2img grids", - "Output directory for img2img images": "Output directory for img2img images", - "Output directory for txt2img grids": "Output directory for txt2img grids", - "Output directory for txt2img images": "Output directory for txt2img images", + "Output directory for grids; if empty, defaults to two directories below": "그리드 이미지 저장 경로 - 비워둘 시 하단의 2가지 기본 경로로 설정됨", + "Output directory for images from extras tab": "부가기능 탭 저장 경로", + "Output directory for images; if empty, defaults to three directories below": "이미지 저장 경로 - 비워둘 시 하단의 3가지 기본 경로로 설정됨", + "Output directory for img2img grids": "이미지→이미지 그리드 저장 경로", + "Output directory for img2img images": "이미지→이미지 저장 경로", + "Output directory for txt2img grids": "텍스트→이미지 그리드 저장 경로", + "Output directory for txt2img images": "텍스트→이미지 저장 경로", "Override `Denoising strength` to 1?": "디노이즈 강도를 1로 적용할까요?", "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "프롬프트 값을 기존 프롬프트와 동일하게 적용할까요?(네거티브 프롬프트 포함)", "Override `Sampling method` to Euler?(this method is built for it)": "샘플링 방법을 Euler로 적용할까요?(이 기능은 해당 샘플러를 위해 만들어져 있습니다)", @@ -279,20 +285,21 @@ "Overwrite Old Hypernetwork": "기존 하이퍼네트워크 덮어쓰기", "Page Index": "페이지 인덱스", "parameters": "설정값", - "Path to directory where to write outputs": "Path to directory where to write outputs", + "Path to directory where to write outputs": "결과물을 출력할 경로", "Path to directory with input images": "인풋 이미지가 있는 경로", - "Paths for saving": "Paths for saving", + "Paths for saving": "저장 경로", "Pixels to expand": "확장할 픽셀 수", "PLMS": "PLMS", "PNG Info": "PNG 정보", "Poor man's outpainting": "가난뱅이의 아웃페인팅", - "Preparing dataset from": "Preparing dataset from", + "Preload images at startup": "WebUI 가동 시 이미지 프리로드하기", + "Preparing dataset from": "준비된 데이터셋 경로 : ", "prepend": "앞에 삽입", "Preprocess": "전처리", "Preprocess images": "이미지 전처리", "Prev batch": "이전 묶음", "Prev Page": "이전 페이지", - "Prevent empty spots in grid (when set to autodetect)": "Prevent empty spots in grid (when set to autodetect)", + "Prevent empty spots in grid (when set to autodetect)": "(자동 감지 사용시)그리드에 빈칸이 생기는 것 방지하기", "Primary model (A)": "주 모델 (A)", "Process an image, use it as an input, repeat.": "이미지를 생성하고, 생성한 이미지를 다시 원본으로 사용하는 과정을 반복합니다.", "Process images in a directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리의 이미지들을 처리합니다.", @@ -307,26 +314,26 @@ "Prompts from file or textbox": "파일이나 텍스트박스로부터 프롬프트 불러오기", "Put variable parts at start of prompt": "변경되는 프롬프트를 앞에 위치시키기", "quad": "quad", - "Quality for saved jpeg images": "Quality for saved jpeg images", - "Quicksettings list": "Quicksettings list", + "Quality for saved jpeg images": "저장된 jpeg 이미지들의 품질", + "Quicksettings list": "빠른 설정 리스트", "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", "Randomness": "랜덤성", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", "Read parameters (prompt, etc...) from txt2img tab when making previews": "프리뷰 이미지 생성 시 텍스트→이미지 탭에서 설정값(프롬프트 등) 읽어오기", "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "추천 설정값 - 샘플링 스텝 수 : 80-100 , 샘플러 : Euler a, 디노이즈 강도 : 0.8", - "Reload custom script bodies (No ui updates, No restart)": "Reload custom script bodies (No ui updates, No restart)", + "Reload custom script bodies (No ui updates, No restart)": "커스텀 스크립트 리로드하기(UI 업데이트 없음, 재시작 없음)", "relu": "relu", "Renew Page": "Renew Page", - "Request browser notifications": "Request browser notifications", + "Request browser notifications": "브라우저 알림 권한 요청", "Resize": "리사이징 배수", "Resize and fill": "리사이징 후 채우기", "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "설정된 해상도로 이미지 리사이징을 진행합니다. 원본과 가로/세로 길이가 일치하지 않을 경우, 부정확한 화면비의 이미지를 얻게 됩니다.", - "Resize mode": "Resize mode", + "Resize mode": "리사이징 모드", "Resize seed from height": "시드 리사이징 가로길이", "Resize seed from width": "시드 리사이징 세로길이", "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "이미지 전체가 설정된 해상도 내부에 들어가게 리사이징을 진행합니다. 빈 공간은 이미지의 색상으로 채웁니다.", "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "설정된 해상도 전체가 이미지로 가득차게 리사이징을 진행합니다. 튀어나오는 부분은 잘라냅니다.", - "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)", + "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Gradio를 재시작하고 컴포넌트 새로고침하기 (커스텀 스크립트, ui.py, js, css만 해당됨)", "Restore faces": "얼굴 보정", "Restore low quality faces using GFPGAN neural network": "GFPGAN 신경망을 이용해 저품질의 얼굴을 보정합니다.", "Result = A * (1 - M) + B * M": "결과물 = A * (1 - M) + B * M", @@ -335,23 +342,23 @@ "right": "오른쪽", "Run": "가동", "Sampler": "샘플러", - "Sampler parameters": "Sampler parameters", + "Sampler parameters": "샘플러 설정값", "Sampling method": "샘플링 방법", "Sampling Steps": "샘플링 스텝 수", "Save": "저장", "Save a copy of embedding to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 임베딩을 저장합니다, 비활성화하려면 0으로 설정하십시오.", - "Save a copy of image before applying color correction to img2img results": "Save a copy of image before applying color correction to img2img results", - "Save a copy of image before doing face restoration.": "Save a copy of image before doing face restoration.", - "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", + "Save a copy of image before applying color correction to img2img results": "이미지→이미지 결과물에 색상 보정을 진행하기 전 이미지의 복사본을 저장하기", + "Save a copy of image before doing face restoration.": "얼굴 보정을 진행하기 전 이미지의 복사본을 저장하기", + "Save an csv containing the loss to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 손실(Loss)을 포함하는 csv 파일을 저장합니다, 비활성화하려면 0으로 설정하십시오.", "Save an image to log directory every N steps, 0 to disable": "N스텝마다 로그 경로에 이미지를 저장합니다, 비활성화하려면 0으로 설정하십시오.", "Save as float16": "float16으로 저장", - "Save grids to a subdirectory": "Save grids to a subdirectory", - "Save images to a subdirectory": "Save images to a subdirectory", + "Save grids to a subdirectory": "그리드 이미지를 하위 디렉토리에 저장하기", + "Save images to a subdirectory": "이미지를 하위 디렉토리에 저장하기", "Save images with embedding in PNG chunks": "PNG 청크로 이미지에 임베딩을 포함시켜 저장", "Save style": "스타일 저장", - "Save text information about generation parameters as chunks to png files": "Save text information about generation parameters as chunks to png files", - "Saving images/grids": "Saving images/grids", - "Saving to a directory": "Saving to a directory", + "Save text information about generation parameters as chunks to png files": "이미지 생성 설정값을 PNG 청크에 텍스트로 저장", + "Saving images/grids": "이미지/그리드 저장", + "Saving to a directory": "디렉토리에 저장", "Scale by": "스케일링 배수 지정", "Scale to": "스케일링 사이즈 지정", "Script": "스크립트", @@ -363,6 +370,7 @@ "Seed": "시드", "Seed of a different picture to be mixed into the generation.": "결과물에 섞일 다른 그림의 시드", "Select activation function of hypernetwork": "하이퍼네트워크 활성화 함수 선택", + "Select which Real-ESRGAN models to show in the web UI. (Requires restart)": "WebUI에 표시할 Real-ESRGAN 모델을 선택하십시오. (재시작 필요)", "Send to extras": "부가기능으로 전송", "Send to img2img": "이미지→이미지로 전송", "Send to inpaint": "인페인트로 전송", @@ -374,29 +382,30 @@ "set_index": "set_index", "Settings": "설정", "should be 2 or lower.": "이 2 이하여야 합니다.", - "Show generation progress in window title.": "Show generation progress in window title.", - "Show grid in results for web": "Show grid in results for web", - "Show image creation progress every N sampling steps. Set 0 to disable.": "Show image creation progress every N sampling steps. Set 0 to disable.", - "Show images zoomed in by default in full page image viewer": "Show images zoomed in by default in full page image viewer", - "Show progressbar": "Show progressbar", + "Show generation progress in window title.": "창 타이틀에 생성 진행도 보여주기", + "Show grid in results for web": "웹에서 결과창에 그리드 보여주기", + "Show image creation progress every N sampling steps. Set 0 to disable.": "N번째 샘플링 스텝마다 이미지 생성 과정 보이기 - 비활성화하려면 0으로 설정", + "Show images zoomed in by default in full page image viewer": "전체 페이지 이미지 뷰어에서 기본값으로 이미지 확대해서 보여주기", + "Show progressbar": "프로그레스 바 보이기", "Show result images": "이미지 결과 보이기", "Show Textbox": "텍스트박스 보이기", + "Show previews of all images generated in a batch as a grid": "배치에서 생성된 모든 이미지의 미리보기를 그리드 형식으로 보여주기", "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", "Sigma Churn": "시그마 섞기", - "sigma churn": "sigma churn", + "sigma churn": "시그마 섞기", "Sigma max": "시그마 최댓값", "Sigma min": "시그마 최솟값", "Sigma noise": "시그마 노이즈", - "sigma noise": "sigma noise", - "sigma tmin": "sigma tmin", + "sigma noise": "시그마 노이즈", + "sigma tmin": "시그마 tmin", "Single Image": "단일 이미지", "Skip": "건너뛰기", "Slerp angle": "구면 선형 보간 각도", "Slerp interpolation": "구면 선형 보간", "Source": "원본", "Source directory": "원본 경로", - "Split image threshold": "Split image threshold", - "Split image overlap ratio": "Split image overlap ratio", + "Split image threshold": "이미지 분할 임계값", + "Split image overlap ratio": "이미지 분할 겹침 비율", "Split oversized images": "사이즈가 큰 이미지 분할하기", "Stable Diffusion": "Stable Diffusion", "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", @@ -407,20 +416,20 @@ "Stop processing images and return any results accumulated so far.": "이미지 생성을 중단하고 지금까지 진행된 결과물 출력", "Style 1": "스타일 1", "Style 2": "스타일 2", - "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", + "Style to apply; styles have components for both positive and negative prompts and apply to both": "적용할 스타일 - 스타일은 긍정/부정 프롬프트 모두에 대한 설정값을 가지고 있고 양쪽 모두에 적용 가능합니다.", "SwinIR 4x": "SwinIR 4x", "Sys VRAM:": "시스템 VRAM : ", - "System": "System", + "System": "시스템", "Tertiary model (C)": "3차 모델 (C)", - "Textbox": "Textbox", - "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", - "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", + "Textbox": "텍스트박스", + "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "이 정규표현식은 파일명으로부터 단어를 추출하는 데 사용됩니다. 추출된 단어들은 하단의 설정을 이용해 라벨 텍스트로 변환되어 훈련에 사용됩니다. 파일명 텍스트를 유지하려면 비워두십시오.", + "This string will be used to join split words into a single line if the option above is enabled.": "이 문자열은 상단 설정이 활성화되어있을 때 분리된 단어들을 한 줄로 합치는 데 사용됩니다.", "This text is used to rotate the feature space of the imgs embs": "이 텍스트는 이미지 임베딩의 특징 공간을 회전하는 데 사용됩니다.", "Tile overlap": "타일 겹침", - "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", - "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "Tile overlap, in pixels for SwinIR. Low values = visible seam.", - "Tile size for all SwinIR.": "Tile size for all SwinIR.", - "Tile size for ESRGAN upscalers. 0 = no tiling.": "Tile size for ESRGAN upscalers. 0 = no tiling.", + "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.": "ESRGAN 업스케일러들의 타일 중첩 수치, 픽셀 단위. 낮은 값 = 눈에 띄는 이음매.", + "Tile overlap, in pixels for SwinIR. Low values = visible seam.": "SwinIR의 타일 중첩 수치, 픽셀 단위. 낮은 값 = 눈에 띄는 이음매.", + "Tile size for all SwinIR.": "SwinIR의 타일 사이즈.", + "Tile size for ESRGAN upscalers. 0 = no tiling.": "ESRGAN 업스케일러들의 타일 사이즈. 0 = 타일링 없음.", "Tiling": "타일링", "Time taken:": "소요 시간 : ", "Torch active/reserved:": "활성화/예약된 Torch 양 : ", @@ -429,51 +438,60 @@ "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "임베딩이나 하이퍼네트워크를 훈련시킵니다. 1:1 비율의 이미지가 있는 경로를 지정해야 합니다.", "Train Embedding": "임베딩 훈련", "Train Hypernetwork": "하이퍼네트워크 훈련", - "Training": "Training", + "Training": "훈련", "txt2img": "텍스트→이미지", - "txt2img history": "txt2img history", + "txt2img history": "텍스트→이미지 기록", "uniform": "uniform", "up": "위쪽", "Upload mask": "마스크 업로드하기", - "Upscale latent space image when doing hires. fix": "Upscale latent space image when doing hires. fix", + "Upscale latent space image when doing hires. fix": "고해상도 보정 사용시 잠재 공간 이미지 업스케일하기", "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "마스크된 부분을 설정된 해상도로 업스케일하고, 인페인팅을 진행한 뒤, 다시 다운스케일 후 원본 이미지에 붙여넣습니다.", "Upscaler": "업스케일러", "Upscaler 1": "업스케일러 1", "Upscaler 2": "업스케일러 2", "Upscaler 2 visibility": "업스케일러 2 가시성", - "Upscaler for img2img": "Upscaler for img2img", - "Upscaling": "Upscaling", + "Upscaler for img2img": "이미지→이미지 업스케일러", + "Upscaling": "업스케일링", "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "저해상도 이미지를 1차적으로 생성 후 업스케일을 진행하여, 이미지의 전체적인 구성을 바꾸지 않고 세부적인 디테일을 향상시킵니다.", "Use an empty output directory to save pictures normally instead of writing to the output directory.": "저장 경로를 비워두면 기본 저장 폴더에 이미지들이 저장됩니다.", "Use BLIP for caption": "캡션에 BLIP 사용", "Use deepbooru for caption": "캡션에 deepbooru 사용", "Use dropout": "드롭아웃 사용", - "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", - "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", - "Use old emphasis implementation. Can be useful to reproduce old seeds.": "Use old emphasis implementation. Can be useful to reproduce old seeds.", - "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", - "use spaces for tags in deepbooru": "use spaces for tags in deepbooru", - "User interface": "User interface", + "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "다음 태그들을 사용해 이미지 파일명 형식을 결정하세요 : [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]. 비워두면 기본값으로 설정됩니다.", + "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "다음 태그들을 사용해 이미지와 그리드의 하위 디렉토리명의 형식을 결정하세요 : [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]. 비워두면 기본값으로 설정됩니다.", + "Use old emphasis implementation. Can be useful to reproduce old seeds.": "옛 방식의 강조 구현을 사용합니다. 옛 시드를 재현하는 데 효과적일 수 있습니다.", + "Use original name for output filename during batch process in extras tab": "부가기능 탭에서 이미지를 여러장 처리 시 결과물 파일명에 기존 파일명 사용하기", + "use spaces for tags in deepbooru": "deepbooru에서 태그에 공백 사용", + "User interface": "사용자 인터페이스", "Var. seed": "바리에이션 시드", "Var. strength": "바리에이션 강도", "Variation seed": "바리에이션 시드", "Variation strength": "바리에이션 강도", "view": "api 보이기", - "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", + "VRAM usage polls per second during generation. Set to 0 to disable.": "생성 도중 초당 VRAM 사용량 폴링 수. 비활성화하려면 0으로 설정하십시오.", "Weighted sum": "가중 합", "What to put inside the masked area before processing it with Stable Diffusion.": "Stable Diffusion으로 이미지를 생성하기 전 마스크된 부분에 무엇을 채울지 결정하는 설정값", - "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", - "When using \"Save\" button, save images to a subdirectory": "When using \"Save\" button, save images to a subdirectory", - "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", + "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "PNG 정보나 붙여넣은 텍스트로부터 생성 설정값을 읽어올 때, 선택된 모델/체크포인트는 변경하지 않기.", + "When using \"Save\" button, save images to a subdirectory": "저장 버튼 사용시, 이미지를 하위 디렉토리에 저장하기", + "When using 'Save' button, only save a single selected image": "저장 버튼 사용시, 선택된 이미지 1개만 저장하기", "Which algorithm to use to produce the image": "이미지를 생성할 때 사용할 알고리즘", "Width": "가로", "wiki": " 위키", "Will upscale the image to twice the dimensions; use width and height sliders to set tile size": "이미지를 설정된 사이즈의 2배로 업스케일합니다. 상단의 가로와 세로 슬라이더를 이용해 타일 사이즈를 지정하세요.", - "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).", + "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "이미지→이미지 진행 시, 슬라이더로 설정한 스텝 수를 정확히 실행하기 (일반적으로 디노이즈 강도가 낮을수록 실제 설정된 스텝 수보다 적게 진행됨)", "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", "X type": "X축", "X values": "X 설정값", "X/Y plot": "X/Y 플롯", "Y type": "Y축", - "Y values": "Y 설정값" + "Y values": "Y 설정값", + "step1 min/max": "스텝1 최소/최대", + "step2 min/max": "스텝2 최소/최대", + "step count": "스텝 변화 횟수", + "cfg1 min/max": "CFG1 최소/최대", + "cfg2 min/max": "CFG2 최소/최대", + "cfg count": "CFG 변화 횟수", + "x/y change": "X/Y축 변경", + "Random": "랜덤", + "Random grid": "랜덤 그리드" } \ No newline at end of file From dd25722d6c3f9d9a5f7d76307822bf7558386a0f Mon Sep 17 00:00:00 2001 From: Dynamic Date: Mon, 24 Oct 2022 04:38:16 +0900 Subject: [PATCH 0213/1118] Finalize ko_KR.json --- localizations/ko_KR.json | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/localizations/ko_KR.json b/localizations/ko_KR.json index 6889de46..ab12c37e 100644 --- a/localizations/ko_KR.json +++ b/localizations/ko_KR.json @@ -5,10 +5,10 @@ "❮": "❮", "❯": "❯", "⤡": "⤡", - " images in this directory. Loaded ": "개의 이미지가 이 경로에 존재합니다. ", " images during ": "개의 이미지를 불러왔고, 생성 기간은 ", - ", divided into ": "입니다. ", + " images in this directory. Loaded ": "개의 이미지가 이 경로에 존재합니다. ", " pages": "페이지로 나뉘어 표시합니다.", + ", divided into ": "입니다. ", "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", "[wiki]": " [위키] 참조", "A directory on the same machine where the server is running.": "WebUI 서버가 돌아가고 있는 디바이스에 존재하는 디렉토리를 선택해 주세요.", @@ -43,7 +43,10 @@ "BSRGAN 4x": "BSRGAN 4x", "built with gradio": "gradio로 제작되었습니다", "Cancel generate forever": "반복 생성 취소", + "cfg count": "CFG 변화 횟수", "CFG Scale": "CFG 스케일", + "cfg1 min/max": "CFG1 최소/최대", + "cfg2 min/max": "CFG2 최소/최대", "Check progress": "진행도 체크", "Check progress (first)": "진행도 체크 (처음)", "checkpoint": " 체크포인트 ", @@ -57,8 +60,8 @@ "CodeFormer visibility": "CodeFormer 가시성", "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer 가중치 (0 = 최대 효과, 1 = 최소 효과)", "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormer 가중치 설정값 (0 = 최대 효과, 1 = 최소 효과)", - "Color variation": "색깔 다양성", "Collect": "즐겨찾기", + "Color variation": "색깔 다양성", "copy": "복사", "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows": "서로 다른 설정값으로 생성된 이미지의 그리드를 만듭니다. 아래의 설정으로 가로/세로에 어떤 설정값을 적용할지 선택하세요.", "Create a text file next to every image with generation parameters.": "생성된 이미지마다 생성 설정값을 담은 텍스트 파일 생성하기", @@ -89,8 +92,8 @@ "Do not save grids consisting of one picture": "이미지가 1개뿐인 그리드는 저장하지 않기", "Do not show any images in results for web": "웹에서 결과창에 아무 이미지도 보여주지 않기", "down": "아래쪽", - "Download localization template": "현지화 템플릿 다운로드", "Download": "다운로드", + "Download localization template": "현지화 템플릿 다운로드", "DPM adaptive": "DPM adaptive", "DPM fast": "DPM fast", "DPM2": "DPM2", @@ -121,6 +124,7 @@ "Existing Caption txt Action": "이미 존재하는 캡션 텍스트 처리", "Extra": "고급", "Extras": "부가기능", + "extras": "부가기능", "extras history": "부가기능 기록", "Face restoration": "얼굴 보정", "Face restoration model": "얼굴 보정 모델", @@ -155,10 +159,6 @@ "Hide samplers in user interface (requires restart)": "사용자 인터페이스에서 숨길 샘플러 선택(재시작 필요)", "Highres. fix": "고해상도 보정", "History": "기록", - "Image Browser": "이미지 브라우저", - "Images Browser": "이미지 브라우저", - "Images directory": "이미지 경로", - "extras": "부가기능", "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.": "훈련이 얼마나 빨리 이루어질지 정하는 값입니다. 값이 낮을수록 훈련 시간이 길어지고, 높은 값일수록 정확한 결과를 내는 데 실패하고 임베딩을 망가뜨릴 수 있습니다(임베딩이 망가진 경우에는 훈련 정보 텍스트박스에 손실(Loss) : nan 이라고 출력되게 됩니다. 이 경우에는 망가지지 않은 이전 백업본을 불러와야 합니다).\n\n학습률은 하나의 값으로 설정할 수도 있고, 다음 문법을 사용해 여러 값을 사용할 수도 있습니다 :\n\n학습률_1:최대 스텝수_1, 학습률_2:최대 스텝수_2, ...\n\n예 : 0.005:100, 1e-3:1000, 1e-5\n\n예의 설정값은 첫 100스텝동안 0.005의 학습률로, 그 이후 1000스텝까지는 1e-3으로, 남은 스텝은 1e-5로 훈련하게 됩니다.", "How many batches of images to create": "생성할 이미지 배치 수", "How many image to create in a single batch": "한 배치당 이미지 수", @@ -175,8 +175,11 @@ "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "이 값이 0이 아니라면, 시드에 해당 값이 더해지고, Eta가 있는 샘플러를 사용할 때 노이즈의 RNG 조정을 위해 해당 값이 사용됩니다. 이 설정으로 더 다양한 이미지를 생성하거나, 잘 알고 계시다면 특정 소프트웨어의 결과값을 재현할 수도 있습니다.", "ignore": "무시", "Image": "이미지", + "Image Browser": "이미지 브라우저", "Image for img2img": "Image for img2img", "Image for inpainting with mask": "마스크로 인페인팅할 이미지", + "Images Browser": "이미지 브라우저", + "Images directory": "이미지 경로", "Images filename pattern": "이미지 파일명 패턴", "img2img": "이미지→이미지", "img2img alternative test": "이미지→이미지 대체버전 테스트", @@ -242,6 +245,7 @@ "Masking mode": "마스킹 모드", "Max prompt words for [prompt_words] pattern": "[prompt_words] 패턴의 최대 프롬프트 단어 수", "Max steps": "최대 스텝 수", + "Minimum number of pages per load": "한번 불러올 때마다 불러올 최소 페이지 수", "Modules": "모듈", "Move face restoration model from VRAM into RAM after processing": "처리가 완료되면 얼굴 보정 모델을 VRAM에서 RAM으로 옮기기", "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "하이퍼네트워크 훈련 진행 시 VAE와 CLIP을 RAM으로 옮기기. VRAM이 절약됩니다.", @@ -254,10 +258,9 @@ "None": "없음", "Nothing": "없음", "Nothing found in the image.": "Nothing found in the image.", + "Number of grids in each row": "각 세로줄마다 표시될 그리드 수", "number of images to delete consecutively next": "연속적으로 삭제할 이미지 수", "Number of pictures displayed on each page": "각 페이지에 표시될 이미지 수", - "Minimum number of pages per load": "한번 불러올 때마다 불러올 최소 페이지 수", - "Number of grids in each row": "각 세로줄마다 표시될 그리드 수", "Number of repeats for a single input image per epoch; used only for displaying epoch number": "세대(Epoch)당 단일 인풋 이미지의 반복 횟수 - 세대(Epoch) 숫자를 표시하는 데에만 사용됩니다. ", "Number of vectors per token": "토큰별 벡터 수", "Open for Clip Aesthetic!": "클립 스타일 기능을 활성화하려면 클릭!", @@ -317,6 +320,8 @@ "Quality for saved jpeg images": "저장된 jpeg 이미지들의 품질", "Quicksettings list": "빠른 설정 리스트", "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", + "Random": "랜덤", + "Random grid": "랜덤 그리드", "Randomness": "랜덤성", "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "클립보드에 복사된 정보로부터 설정값 읽어오기/프롬프트창이 비어있을경우 제일 최근 설정값 불러오기", "Read parameters (prompt, etc...) from txt2img tab when making previews": "프리뷰 이미지 생성 시 텍스트→이미지 탭에서 설정값(프롬프트 등) 읽어오기", @@ -386,10 +391,10 @@ "Show grid in results for web": "웹에서 결과창에 그리드 보여주기", "Show image creation progress every N sampling steps. Set 0 to disable.": "N번째 샘플링 스텝마다 이미지 생성 과정 보이기 - 비활성화하려면 0으로 설정", "Show images zoomed in by default in full page image viewer": "전체 페이지 이미지 뷰어에서 기본값으로 이미지 확대해서 보여주기", + "Show previews of all images generated in a batch as a grid": "배치에서 생성된 모든 이미지의 미리보기를 그리드 형식으로 보여주기", "Show progressbar": "프로그레스 바 보이기", "Show result images": "이미지 결과 보이기", "Show Textbox": "텍스트박스 보이기", - "Show previews of all images generated in a batch as a grid": "배치에서 생성된 모든 이미지의 미리보기를 그리드 형식으로 보여주기", "Sigma adjustment for finding noise for image": "이미지 노이즈를 찾기 위해 시그마 조정", "Sigma Churn": "시그마 섞기", "sigma churn": "시그마 섞기", @@ -404,11 +409,14 @@ "Slerp interpolation": "구면 선형 보간", "Source": "원본", "Source directory": "원본 경로", - "Split image threshold": "이미지 분할 임계값", "Split image overlap ratio": "이미지 분할 겹침 비율", + "Split image threshold": "이미지 분할 임계값", "Split oversized images": "사이즈가 큰 이미지 분할하기", "Stable Diffusion": "Stable Diffusion", "Stable Diffusion checkpoint": "Stable Diffusion 체크포인트", + "step count": "스텝 변화 횟수", + "step1 min/max": "스텝1 최소/최대", + "step2 min/max": "스텝2 최소/최대", "Step:": "Step:", "Steps": "스텝 수", "Stop At last layers of CLIP model": "CLIP 모델의 n번째 레이어에서 멈추기", @@ -482,16 +490,8 @@ "Write image to a directory (default - log/images) and generation parameters into csv file.": "이미지를 경로에 저장하고, 설정값들을 csv 파일로 저장합니다. (기본 경로 - log/images)", "X type": "X축", "X values": "X 설정값", + "x/y change": "X/Y축 변경", "X/Y plot": "X/Y 플롯", "Y type": "Y축", - "Y values": "Y 설정값", - "step1 min/max": "스텝1 최소/최대", - "step2 min/max": "스텝2 최소/최대", - "step count": "스텝 변화 횟수", - "cfg1 min/max": "CFG1 최소/최대", - "cfg2 min/max": "CFG2 최소/최대", - "cfg count": "CFG 변화 횟수", - "x/y change": "X/Y축 변경", - "Random": "랜덤", - "Random grid": "랜덤 그리드" + "Y values": "Y 설정값" } \ No newline at end of file From 974196932583b96b6b76632052fc0d7e70820bf3 Mon Sep 17 00:00:00 2001 From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com> Date: Sun, 23 Oct 2022 22:38:42 +0300 Subject: [PATCH 0214/1118] Save properly processed image before color correction --- modules/processing.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index ff83023c..15b639e1 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -46,6 +46,20 @@ def apply_color_correction(correction, image): return image +def apply_overlay(overlay_exists, overlay, paste_loc, image): + if overlay_exists: + if paste_loc is not None: + x, y, w, h = paste_loc + base_image = Image.new('RGBA', (overlay.width, overlay.height)) + image = images.resize_image(1, image, w, h) + base_image.paste(image, (x, y)) + image = base_image + + image = image.convert('RGBA') + image.alpha_composite(overlay) + image = image.convert('RGB') + + return image def get_correct_sampler(p): if isinstance(p, modules.processing.StableDiffusionProcessingTxt2Img): @@ -446,25 +460,14 @@ def process_images(p: StableDiffusionProcessing) -> Processed: devices.torch_gc() image = Image.fromarray(x_sample) - + if p.color_corrections is not None and i < len(p.color_corrections): if opts.save and not p.do_not_save_samples and opts.save_images_before_color_correction: - images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-color-correction") + image_without_cc = apply_overlay(p.overlay_images is not None and i < len(p.overlay_images), p.overlay_images[i], p.paste_to, image) + images.save_image(image_without_cc, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-color-correction") image = apply_color_correction(p.color_corrections[i], image) - if p.overlay_images is not None and i < len(p.overlay_images): - overlay = p.overlay_images[i] - - if p.paste_to is not None: - x, y, w, h = p.paste_to - base_image = Image.new('RGBA', (overlay.width, overlay.height)) - image = images.resize_image(1, image, w, h) - base_image.paste(image, (x, y)) - image = base_image - - image = image.convert('RGBA') - image.alpha_composite(overlay) - image = image.convert('RGB') + image = apply_overlay(p.overlay_images is not None and i < len(p.overlay_images), p.overlay_images[i], p.paste_to, image) if opts.samples_save and not p.do_not_save_samples: images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p) From f2cc3f32d5bc8538e95edec54d7dc1b9efdf769a Mon Sep 17 00:00:00 2001 From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com> Date: Sun, 23 Oct 2022 22:44:46 +0300 Subject: [PATCH 0215/1118] fix whitespaces --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 15b639e1..2a332514 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -460,7 +460,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: devices.torch_gc() image = Image.fromarray(x_sample) - + if p.color_corrections is not None and i < len(p.color_corrections): if opts.save and not p.do_not_save_samples and opts.save_images_before_color_correction: image_without_cc = apply_overlay(p.overlay_images is not None and i < len(p.overlay_images), p.overlay_images[i], p.paste_to, image) From b297cc3324979ec78d69b2d11dd18030dfad7bcc Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:06:42 +0900 Subject: [PATCH 0216/1118] Hypernetworks - fix KeyError in statistics caching Statistics logging has changed to {filename : list[losses]}, so it has to use loss_info[key].pop() --- modules/hypernetworks/hypernetwork.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 98a7b62e..33827210 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -274,8 +274,8 @@ def log_statistics(loss_info:dict, key, value): loss_info[key] = [value] else: loss_info[key].append(value) - if len(loss_info) > 1024: - loss_info.pop(0) + if len(loss_info[key]) > 1024: + loss_info[key].pop(0) def statistics(data): From 40b56c9289bf9458ae5ef3c1990ccea851c6c3e2 Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:07:07 +0900 Subject: [PATCH 0217/1118] cleanup some code --- modules/hypernetworks/hypernetwork.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 33827210..4072bf54 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -16,6 +16,7 @@ from modules.textual_inversion import textual_inversion from modules.textual_inversion.learn_schedule import LearnRateScheduler from torch import einsum +from collections import defaultdict, deque from statistics import stdev, mean class HypernetworkModule(torch.nn.Module): @@ -269,15 +270,6 @@ def stack_conds(conds): return torch.stack(conds) -def log_statistics(loss_info:dict, key, value): - if key not in loss_info: - loss_info[key] = [value] - else: - loss_info[key].append(value) - if len(loss_info[key]) > 1024: - loss_info[key].pop(0) - - def statistics(data): total_information = f"loss:{mean(data):.3f}"+u"\u00B1"+f"({stdev(data)/ (len(data)**0.5):.3f})" recent_data = data[-32:] @@ -341,7 +333,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log weight.requires_grad = True size = len(ds.indexes) - loss_dict = {} + loss_dict = defaultdict(lambda : deque(maxlen = 1024)) losses = torch.zeros((size,)) previous_mean_loss = 0 print("Mean loss of {} elements".format(size)) @@ -383,7 +375,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log losses[hypernetwork.step % losses.shape[0]] = loss.item() for entry in entries: - log_statistics(loss_dict, entry.filename, loss.item()) + loss_dict[entry.filename].append(loss.item()) optimizer.zero_grad() weights[0].grad = None From 348f89c8d40397c1875cff4a7331018785f9c3b8 Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:29:53 +0900 Subject: [PATCH 0218/1118] statistics for pbar --- modules/hypernetworks/hypernetwork.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 4072bf54..48b56029 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -335,6 +335,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log size = len(ds.indexes) loss_dict = defaultdict(lambda : deque(maxlen = 1024)) losses = torch.zeros((size,)) + previous_mean_losses = [0] previous_mean_loss = 0 print("Mean loss of {} elements".format(size)) @@ -356,7 +357,8 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log for i, entries in pbar: hypernetwork.step = i + ititial_step if len(loss_dict) > 0: - previous_mean_loss = sum(i[-1] for i in loss_dict.values()) / len(loss_dict) + previous_mean_losses = [i[-1] for i in loss_dict.values()] + previous_mean_loss = mean(previous_mean_losses) scheduler.apply(optimizer, hypernetwork.step) if scheduler.finished: @@ -391,7 +393,13 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log if torch.isnan(losses[hypernetwork.step % losses.shape[0]]): raise RuntimeError("Loss diverged.") - pbar.set_description(f"dataset loss: {previous_mean_loss:.7f}") + + if len(previous_mean_losses) > 1: + std = stdev(previous_mean_losses) + else: + std = 0 + dataset_loss_info = f"dataset loss:{mean(previous_mean_losses):.3f}" + u"\u00B1" + f"({std / (len(previous_mean_losses) ** 0.5):.3f})" + pbar.set_description(dataset_loss_info) if hypernetwork.step > 0 and hypernetwork_dir is not None and hypernetwork.step % save_hypernetwork_every == 0: # Before saving, change name to match current checkpoint. From 0d2e1dac407a0e2f5b148d314715f0457b2525b7 Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:41:39 +0900 Subject: [PATCH 0219/1118] convert deque -> list I don't feel this being efficient --- modules/hypernetworks/hypernetwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 48b56029..fb510fa7 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -282,7 +282,7 @@ def report_statistics(loss_info:dict): for key in keys: try: print("Loss statistics for file " + key) - info, recent = statistics(loss_info[key]) + info, recent = statistics(list(loss_info[key])) print(info) print(recent) except Exception as e: From e9a410b5357612f63528015c5533c2185dcff92e Mon Sep 17 00:00:00 2001 From: AngelBottomless <35677394+aria1th@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:47:39 +0900 Subject: [PATCH 0220/1118] check length for variance --- modules/hypernetworks/hypernetwork.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index fb510fa7..d647ea55 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -271,9 +271,17 @@ def stack_conds(conds): def statistics(data): - total_information = f"loss:{mean(data):.3f}"+u"\u00B1"+f"({stdev(data)/ (len(data)**0.5):.3f})" + if len(data) < 2: + std = 0 + else: + std = stdev(data) + total_information = f"loss:{mean(data):.3f}" + u"\u00B1" + f"({std/ (len(data) ** 0.5):.3f})" recent_data = data[-32:] - recent_information = f"recent 32 loss:{mean(recent_data):.3f}"+u"\u00B1"+f"({stdev(recent_data)/ (len(recent_data)**0.5):.3f})" + if len(recent_data) < 2: + std = 0 + else: + std = stdev(recent_data) + recent_information = f"recent 32 loss:{mean(recent_data):.3f}" + u"\u00B1" + f"({std / (len(recent_data) ** 0.5):.3f})" return total_information, recent_information From 6cbb04f7a5e675cf1f6dfc247aa9c9e8df7dc5ce Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 24 Oct 2022 09:15:26 +0300 Subject: [PATCH 0221/1118] fix #3517 breaking txt2img --- modules/processing.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 2a332514..c61bbfbd 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -46,18 +46,23 @@ def apply_color_correction(correction, image): return image -def apply_overlay(overlay_exists, overlay, paste_loc, image): - if overlay_exists: - if paste_loc is not None: - x, y, w, h = paste_loc - base_image = Image.new('RGBA', (overlay.width, overlay.height)) - image = images.resize_image(1, image, w, h) - base_image.paste(image, (x, y)) - image = base_image - image = image.convert('RGBA') - image.alpha_composite(overlay) - image = image.convert('RGB') +def apply_overlay(image, paste_loc, index, overlays): + if overlays is None or index >= len(overlays): + return image + + overlay = overlays[index] + + if paste_loc is not None: + x, y, w, h = paste_loc + base_image = Image.new('RGBA', (overlay.width, overlay.height)) + image = images.resize_image(1, image, w, h) + base_image.paste(image, (x, y)) + image = base_image + + image = image.convert('RGBA') + image.alpha_composite(overlay) + image = image.convert('RGB') return image @@ -463,11 +468,11 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if p.color_corrections is not None and i < len(p.color_corrections): if opts.save and not p.do_not_save_samples and opts.save_images_before_color_correction: - image_without_cc = apply_overlay(p.overlay_images is not None and i < len(p.overlay_images), p.overlay_images[i], p.paste_to, image) + image_without_cc = apply_overlay(image, p.paste_to, i, p.overlay_images) images.save_image(image_without_cc, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-color-correction") image = apply_color_correction(p.color_corrections[i], image) - image = apply_overlay(p.overlay_images is not None and i < len(p.overlay_images), p.overlay_images[i], p.paste_to, image) + image = apply_overlay(image, p.paste_to, i, p.overlay_images) if opts.samples_save and not p.do_not_save_samples: images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p) From c6459986cb98211565c5a4d7596f9617e82b6d12 Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Sun, 23 Oct 2022 02:41:17 +0900 Subject: [PATCH 0222/1118] update ja translation --- localizations/ja_JP.json | 91 ++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 13 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index 514b579e..f9987473 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -14,9 +14,10 @@ "img2img": "img2img", "Extras": "その他", "PNG Info": "PNG内の情報を表示", - "History": "履歴", + "Image Browser": "画像閲覧", "Checkpoint Merger": "Checkpointの統合", "Train": "学習", + "Create aesthetic embedding": "Create aesthetic embedding", "Settings": "設定", "Prompt": "プロンプト", "Negative prompt": "ネガティブ プロンプト", @@ -67,8 +68,18 @@ "Variation strength": "Variation 強度", "Resize seed from width": "Resize seed from width", "Resize seed from height": "Resize seed from height", - "Script": "スクリプト", + "Open for Clip Aesthetic!": "Open for Clip Aesthetic!", + "▼": "▼", + "Aesthetic weight": "Aesthetic weight", + "Aesthetic steps": "Aesthetic steps", + "Aesthetic learning rate": "Aesthetic learning rate", + "Slerp interpolation": "Slerp interpolation", + "Aesthetic imgs embedding": "Aesthetic imgs embedding", "None": "なし", + "Aesthetic text for imgs": "Aesthetic text for imgs", + "Slerp angle": "Slerp angle", + "Is negative text": "Is negative text", + "Script": "スクリプト", "Prompt matrix": "Prompt matrix", "Prompts from file or textbox": "Prompts from file or textbox", "X/Y plot": "X/Y plot", @@ -76,6 +87,7 @@ "Show Textbox": "Show Textbox", "File with inputs": "File with inputs", "Prompts": "プロンプト", + "Save images to path": "Save images to path", "X type": "X軸の種類", "Nothing": "なし", "Var. seed": "Var. seed", @@ -86,7 +98,7 @@ "Sampler": "サンプラー", "Checkpoint name": "Checkpoint名", "Hypernetwork": "Hypernetwork", - "Hypernet str.": "Hypernet強度", + "Hypernet str.": "Hypernetの強度", "Sigma Churn": "Sigma Churn", "Sigma min": "Sigma min", "Sigma max": "Sigma max", @@ -141,6 +153,7 @@ "Outpainting mk2": "Outpainting mk2", "Poor man's outpainting": "Poor man's outpainting", "SD upscale": "SD アップスケール", + "[C] Video to video": "[C] Video to video", "should be 2 or lower.": "2以下にすること", "Override `Sampling method` to Euler?(this method is built for it)": "サンプリングアルゴリズムをEulerに上書きする(そうすることを前提に設計されています)", "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)", @@ -170,9 +183,22 @@ "LDSR": "LDSR", "BSRGAN 4x": "BSRGAN 4x", "ESRGAN_4x": "ESRGAN_4x", + "R-ESRGAN General 4xV3": "R-ESRGAN General 4xV3", + "R-ESRGAN General WDN 4xV3": "R-ESRGAN General WDN 4xV3", + "R-ESRGAN AnimeVideo": "R-ESRGAN AnimeVideo", + "R-ESRGAN 4x+": "R-ESRGAN 4x+", + "R-ESRGAN 4x+ Anime6B": "R-ESRGAN 4x+ Anime6B", + "R-ESRGAN 2x+": "R-ESRGAN 2x+", "ScuNET GAN": "ScuNET GAN", "ScuNET PSNR": "ScuNET PSNR", "SwinIR 4x": "SwinIR 4x", + "Input file path": "Input file path", + "CRF (quality, less is better, x264 param)": "CRF (quality, less is better, x264 param)", + "FPS": "FPS", + "Seed step size": "Seed step size", + "Seed max distance": "Seed max distance", + "Start time": "Start time", + "End time": "End time", "Single Image": "単一画像", "Batch Process": "バッチ処理", "Batch from Directory": "フォルダからバッチ処理", @@ -182,17 +208,18 @@ "Scale to": "解像度指定", "Resize": "倍率", "Crop to fit": "合うように切り抜き", - "Upscaler 2": "アップスケーラー 2", "Upscaler 2 visibility": "Upscaler 2 visibility", "GFPGAN visibility": "GFPGAN visibility", "CodeFormer visibility": "CodeFormer visibility", "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", "Open output directory": "出力フォルダを開く", "Send to txt2img": "txt2imgに送る", - "txt2img history": "txt2imgの履歴", - "img2img history": "img2imgの履歴", - "extras history": "その他タブの履歴", - "Renew Page": "更新", + "extras": "その他タブ", + "favorites": "お気に入り", + "Load": "読み込み", + "Images directory": "フォルダ", + "Prev batch": "前の batch", + "Next batch": "次の batch", "First Page": "最初のぺージへ", "Prev Page": "前ページへ", "Page Index": "ページ番号", @@ -202,7 +229,12 @@ "Delete": "削除", "Generate Info": "生成情報", "File Name": "ファイル名", + "Collect": "保存(お気に入り)", + "Refresh page": "Refresh page", + "Date to": "Date to", + "Number": "Number", "set_index": "set_index", + "Checkbox": "Checkbox", "A merger of the two checkpoints will be generated in your": "統合されたチェックポイントはあなたの", "checkpoint": "checkpoint", "directory.": "フォルダに保存されます.", @@ -224,17 +256,37 @@ "Name": "ファイル名", "Initialization text": "Initialization text", "Number of vectors per token": "Number of vectors per token", + "Overwrite Old Embedding": "Overwrite Old Embedding", "Modules": "Modules", + "Enter hypernetwork layer structure": "Enter hypernetwork layer structure", + "Select activation function of hypernetwork": "Select activation function of hypernetwork", + "linear": "linear", + "relu": "relu", + "leakyrelu": "leakyrelu", + "elu": "elu", + "swish": "swish", + "Add layer normalization": "Add layer normalization", + "Use dropout": "Use dropout", + "Overwrite Old Hypernetwork": "Overwrite Old Hypernetwork", "Source directory": "入力フォルダ", "Destination directory": "出力フォルダ", + "Existing Caption txt Action": "Existing Caption txt Action", + "ignore": "ignore", + "copy": "copy", + "prepend": "prepend", + "append": "append", "Create flipped copies": "反転画像を生成する", - "Split oversized images into two": "大きすぎる画像を2分割する", + "Split oversized images": "大きすぎる画像を分割する", "Use BLIP for caption": "BLIPで説明をつける", "Use deepbooru for caption": "deepbooruで説明をつける", + "Split image threshold": "分割する大きさの閾値", + "Split image overlap ratio": "Split image overlap ratio", "Preprocess": "前処理開始", - "Train an embedding; must specify a directory with a set of 1:1 ratio images": "embeddingの学習をします;データセット内の画像は正方形でなければなりません。", + "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images", + "[wiki]": "[wiki]", "Embedding": "Embedding", - "Learning rate": "学習率", + "Embedding Learning rate": "Embedding Learning rate", + "Hypernetwork Learning rate": "Hypernetwork Learning rate", "Dataset directory": "データセットフォルダ", "Log directory": "ログフォルダ", "Prompt template file": "Prompt template file", @@ -245,6 +297,8 @@ "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", "Train Hypernetwork": "Hypernetworkの学習を開始", "Train Embedding": "Embeddingの学習を開始", + "Create an aesthetic embedding out of any number of images": "Create an aesthetic embedding out of any number of images", + "Create images embedding": "Create images embedding", "Apply settings": "Apply settings", "Saving images/grids": "画像/グリッドの保存", "Always save all generated images": "生成された画像をすべて保存する", @@ -295,7 +349,7 @@ "Always print all generation info to standard output": "常にすべての生成に関する情報を標準出力(stdout)に出力する", "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", "Training": "学習", - "Unload VAE and CLIP from VRAM when training": "学習を行う際、VAEとCLIPをVRAMから削除する", + "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "hypernetworkの学習をするとき、VAEとCLIPをRAMへ退避します。VRAMが節約できます。", "Filename word regex": "Filename word regex", "Filename join string": "Filename join string", "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", @@ -332,6 +386,7 @@ "Do not show any images in results for web": "WebUI上で一切画像を表示しない", "Add model hash to generation information": "モデルのハッシュ値を生成情報に追加", "Add model name to generation information": "モデルの名称を生成情報に追加", + "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", "Font for image grids that have text": "画像グリッド内のテキストフォント", "Enable full page image viewer": "フルページの画像ビューワーを有効化", "Show images zoomed in by default in full page image viewer": "フルページ画像ビューアでデフォルトで画像を拡大して表示する", @@ -350,10 +405,16 @@ "sigma tmin": "sigma tmin", "sigma noise": "sigma noise", "Eta noise seed delta": "Eta noise seed delta", + "Images Browser": "画像閲覧", + "Preload images at startup": "起動時に画像を読み込んでおく", + "Number of pictures displayed on each page": "各ページに表示される画像の枚数", + "Minimum number of pages per load": "Minimum number of pages per load", + "Number of grids in each row": "Number of grids in each row", "Request browser notifications": "ブラウザ通知の許可を要求する", "Download localization template": "ローカライゼーション用のテンプレートをダウンロードする", "Reload custom script bodies (No ui updates, No restart)": "カスタムスクリプトを再読み込み (UIは変更されず、再起動もしません。)", "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Gradioを再起動してコンポーネントをリフレッシュする (Custom Scripts, ui.py, js, cssのみ影響を受ける)", + "Audio": "Audio", "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "ネガティブ プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", "Add a random artist to the prompt.": "芸術家などの名称をプロンプトに追加", @@ -379,6 +440,7 @@ "Seed of a different picture to be mixed into the generation.": "Seed of a different picture to be mixed into the generation.", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).", "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution", + "This text is used to rotate the feature space of the imgs embs": "This text is used to rotate the feature space of the imgs embs", "Separate values for X axis using commas.": "X軸に用いる値をカンマ(,)で区切って入力してください。", "Separate values for Y axis using commas.": "Y軸に用いる値をカンマ(,)で区切って入力してください。", "Write image to a directory (default - log/images) and generation parameters into csv file.": "Write image to a directory (default - log/images) and generation parameters into csv file.", @@ -398,8 +460,10 @@ "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.", "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", "Leave blank to save images to the default path.": "空欄でデフォルトの場所へ画像を保存", + "Input images directory": "Input images directory", "Result = A * (1 - M) + B * M": "結果モデル = A * (1 - M) + B * M", "Result = A + (B - C) * M": "結果モデル = A + (B - C) * M", + "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", "Path to directory with input images": "Path to directory with input images", "Path to directory where to write outputs": "Path to directory where to write outputs", "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", @@ -409,5 +473,6 @@ "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", - "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing." + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "Enable Autocomplete": "自動補完を有効化" } \ No newline at end of file From a921badac3df177ab4bd8f6469dceb0342269cb7 Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Sun, 23 Oct 2022 18:12:21 +0900 Subject: [PATCH 0223/1118] update ja translation --- localizations/ja_JP.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index f9987473..a790b0a6 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -366,7 +366,7 @@ "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", "Filter NSFW content": "NSFW(≒R-18)なコンテンツを検閲する", - "Stop At last layers of CLIP model": "最後から何層目でCLIPを止めるか(stop…layers of CLIP model)", + "Stop At last layers of CLIP model": "最後から何層目でCLIPを止めるか", "Interrogate Options": "Interrogate 設定", "Interrogate: keep models in VRAM": "Interrogate: モデルをVRAMに保持する", "Interrogate: use artists from artists.csv": "Interrogate: artists.csvにある芸術家などの名称を利用する", From e33a05f263ff39f2750e4aa51b04d463c55cea4c Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:16:44 +0900 Subject: [PATCH 0224/1118] update ja translation --- localizations/ja_JP.json | 209 ++++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 101 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index a790b0a6..741875c3 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -10,6 +10,7 @@ "•": "•", "gradioで作ろう": "gradioで作ろう", "Stable Diffusion checkpoint": "Stable Diffusion checkpoint", + "Stop At last layers of CLIP model": "最後から何層目でCLIPを止めるか", "txt2img": "txt2img", "img2img": "img2img", "Extras": "その他", @@ -17,7 +18,7 @@ "Image Browser": "画像閲覧", "Checkpoint Merger": "Checkpointの統合", "Train": "学習", - "Create aesthetic embedding": "Create aesthetic embedding", + "Create aesthetic embedding": "aesthetic embeddingを作る", "Settings": "設定", "Prompt": "プロンプト", "Negative prompt": "ネガティブ プロンプト", @@ -58,7 +59,7 @@ "Highres. fix": "高解像度 fix(マウスオーバーで詳細)", "Firstpass width": "Firstpass width", "Firstpass height": "Firstpass height", - "Denoising strength": "ノイズ除去 強度", + "Denoising strength": "ノイズ除去強度", "Batch count": "バッチ生成回数", "Batch size": "バッチあたり生成枚数", "CFG Scale": "CFG Scale", @@ -80,13 +81,17 @@ "Slerp angle": "Slerp angle", "Is negative text": "Is negative text", "Script": "スクリプト", + "nai2SD Prompt Converter": "nai2SD Prompt Converter", "Prompt matrix": "Prompt matrix", "Prompts from file or textbox": "Prompts from file or textbox", + "Save steps of the sampling process to files": "Save steps of the sampling process to files", "X/Y plot": "X/Y plot", + "Prompts": "プロンプト", + "convert": "convert", + "Converted Prompts": "Converted Prompts", "Put variable parts at start of prompt": "Put variable parts at start of prompt", "Show Textbox": "Show Textbox", "File with inputs": "File with inputs", - "Prompts": "プロンプト", "Save images to path": "Save images to path", "X type": "X軸の種類", "Nothing": "なし", @@ -125,23 +130,23 @@ "Batch img2img": "Batch img2img", "Image for img2img": "Image for img2img", "Image for inpainting with mask": "Image for inpainting with mask", - "Mask": "Mask", - "Mask blur": "Mask blur", - "Mask mode": "Mask mode", - "Draw mask": "Draw mask", - "Upload mask": "Upload mask", - "Masking mode": "Masking mode", - "Inpaint masked": "Inpaint masked", - "Inpaint not masked": "Inpaint not masked", - "Masked content": "Masked content", - "fill": "fill", + "Mask": "マスク", + "Mask blur": "マスクぼかし", + "Mask mode": "マスクモード", + "Draw mask": "マスクをかける", + "Upload mask": "マスクをアップロードする", + "Masking mode": "マスキング方法", + "Inpaint masked": "マスクされた場所を描き直す", + "Inpaint not masked": "マスクされていない場所を描き直す", + "Masked content": "マスクされたコンテンツ", + "fill": "埋める", "original": "オリジナル", - "latent noise": "latent noise", - "latent nothing": "latent nothing", - "Inpaint at full resolution": "Inpaint at full resolution", - "Inpaint at full resolution padding, pixels": "Inpaint at full resolution padding, pixels", - "Process images in a directory on the same machine where the server is running.": "Process images in a directory on the same machine where the server is running.", - "Use an empty output directory to save pictures normally instead of writing to the output directory.": "Use an empty output directory to save pictures normally instead of writing to the output directory.", + "latent noise": "潜在空間でのノイズ", + "latent nothing": "潜在空間での無", + "Inpaint at full resolution": "フル解像度で描き直す", + "Inpaint at full resolution padding, pixels": "フル解像度で描き直す際のパディング数。px単位。", + "Process images in a directory on the same machine where the server is running.": "サーバーが稼働しているマシンと同じフォルダにある画像を処理します", + "Use an empty output directory to save pictures normally instead of writing to the output directory.": "\"出力フォルダ\"を空にすると、通常の画像と同様に保存されます。", "Input directory": "入力フォルダ", "Output directory": "出力フォルダ", "Resize mode": "リサイズモード", @@ -156,18 +161,18 @@ "[C] Video to video": "[C] Video to video", "should be 2 or lower.": "2以下にすること", "Override `Sampling method` to Euler?(this method is built for it)": "サンプリングアルゴリズムをEulerに上書きする(そうすることを前提に設計されています)", - "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)", - "Original prompt": "Original prompt", - "Original negative prompt": "Original negative prompt", - "Override `Sampling Steps` to the same value as `Decode steps`?": "Override `Sampling Steps` to the same value as `Decode steps`?", - "Decode steps": "Decode steps", - "Override `Denoising strength` to 1?": "Override `Denoising strength` to 1?", + "Override `prompt` to the same value as `original prompt`?(and `negative prompt`)": "プロンプトをオリジナルプロンプトと同じ値に上書きする(ネガティブプロンプトも同様)", + "Original prompt": "オリジナルのプロンプト", + "Original negative prompt": "オリジナルのネガティブプロンプト", + "Override `Sampling Steps` to the same value as `Decode steps`?": "サンプリング数をデコードステップ数と同じ値に上書きする", + "Decode steps": "デコードステップ数", + "Override `Denoising strength` to 1?": "ノイズ除去強度を1に上書きする", "Decode CFG scale": "Decode CFG scale", - "Randomness": "Randomness", + "Randomness": "ランダム性", "Sigma adjustment for finding noise for image": "Sigma adjustment for finding noise for image", - "Loops": "Loops", + "Loops": "ループ数", "Denoising strength change factor": "Denoising strength change factor", - "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8", + "Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8": "推奨設定: サンプリング回数: 80-100, サンプリングアルゴリズム: Euler a, ノイズ除去強度: 0.8", "Pixels to expand": "Pixels to expand", "Outpainting direction": "Outpainting direction", "left": "左", @@ -181,7 +186,6 @@ "Upscaler": "アップスケーラー", "Lanczos": "Lanczos", "LDSR": "LDSR", - "BSRGAN 4x": "BSRGAN 4x", "ESRGAN_4x": "ESRGAN_4x", "R-ESRGAN General 4xV3": "R-ESRGAN General 4xV3", "R-ESRGAN General WDN 4xV3": "R-ESRGAN General WDN 4xV3", @@ -211,7 +215,7 @@ "Upscaler 2 visibility": "Upscaler 2 visibility", "GFPGAN visibility": "GFPGAN visibility", "CodeFormer visibility": "CodeFormer visibility", - "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormer weight (0 = maximum effect, 1 = minimum effect)", + "CodeFormer weight (0 = maximum effect, 1 = minimum effect)": "CodeFormerの重み (注:0で最大、1で最小)", "Open output directory": "出力フォルダを開く", "Send to txt2img": "txt2imgに送る", "extras": "その他タブ", @@ -225,12 +229,12 @@ "Page Index": "ページ番号", "Next Page": "次ページへ", "End Page": "最後のページへ", - "number of images to delete consecutively next": "number of images to delete consecutively next", + "number of images to delete consecutively next": "次の削除で一度に削除する画像数", "Delete": "削除", "Generate Info": "生成情報", "File Name": "ファイル名", "Collect": "保存(お気に入り)", - "Refresh page": "Refresh page", + "Refresh page": "ページを更新", "Date to": "Date to", "Number": "Number", "set_index": "set_index", @@ -253,13 +257,13 @@ "Create embedding": "Embeddingを作る", "Create hypernetwork": "Hypernetworkを作る", "Preprocess images": "画像の前処理", - "Name": "ファイル名", + "Name": "名称", "Initialization text": "Initialization text", "Number of vectors per token": "Number of vectors per token", - "Overwrite Old Embedding": "Overwrite Old Embedding", - "Modules": "Modules", - "Enter hypernetwork layer structure": "Enter hypernetwork layer structure", - "Select activation function of hypernetwork": "Select activation function of hypernetwork", + "Overwrite Old Embedding": "古いEmbeddingを上書き", + "Modules": "モジュール", + "Enter hypernetwork layer structure": "Hypernetworkのレイヤー構造を入力", + "Select activation function of hypernetwork": "Hypernetworkの活性化関数", "linear": "linear", "relu": "relu", "leakyrelu": "leakyrelu", @@ -267,14 +271,14 @@ "swish": "swish", "Add layer normalization": "Add layer normalization", "Use dropout": "Use dropout", - "Overwrite Old Hypernetwork": "Overwrite Old Hypernetwork", + "Overwrite Old Hypernetwork": "古いHypernetworkを上書きする", "Source directory": "入力フォルダ", "Destination directory": "出力フォルダ", - "Existing Caption txt Action": "Existing Caption txt Action", - "ignore": "ignore", - "copy": "copy", - "prepend": "prepend", - "append": "append", + "Existing Caption txt Action": "既存のキャプションの取り扱い", + "ignore": "無視する", + "copy": "コピーする", + "prepend": "先頭に加える", + "append": "末尾に加える", "Create flipped copies": "反転画像を生成する", "Split oversized images": "大きすぎる画像を分割する", "Use BLIP for caption": "BLIPで説明をつける", @@ -282,24 +286,24 @@ "Split image threshold": "分割する大きさの閾値", "Split image overlap ratio": "Split image overlap ratio", "Preprocess": "前処理開始", - "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images", + "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "EmbeddingまたはHypernetworkを学習します。1:1の比率の画像セットを含むフォルダを指定する必要があります。", "[wiki]": "[wiki]", "Embedding": "Embedding", - "Embedding Learning rate": "Embedding Learning rate", - "Hypernetwork Learning rate": "Hypernetwork Learning rate", + "Embedding Learning rate": "Embeddingの学習率(Learning rate)", + "Hypernetwork Learning rate": "Hypernetworkの学習率(Learning rate)", "Dataset directory": "データセットフォルダ", "Log directory": "ログフォルダ", - "Prompt template file": "Prompt template file", + "Prompt template file": "プロンプトのテンプレートファイル", "Max steps": "最大ステップ数", "Save an image to log directory every N steps, 0 to disable": "指定したステップ数ごとに画像を生成し、ログに保存する。0で無効化。", "Save a copy of embedding to log directory every N steps, 0 to disable": "指定したステップ数ごとにEmbeddingのコピーをログに保存する。0で無効化。", "Save images with embedding in PNG chunks": "保存する画像にembeddingを埋め込む", - "Read parameters (prompt, etc...) from txt2img tab when making previews": "Read parameters (prompt, etc...) from txt2img tab when making previews", + "Read parameters (prompt, etc...) from txt2img tab when making previews": "プレビューの作成にtxt2imgタブから読み込んだパラメータ(プロンプトなど)を使う", "Train Hypernetwork": "Hypernetworkの学習を開始", "Train Embedding": "Embeddingの学習を開始", "Create an aesthetic embedding out of any number of images": "Create an aesthetic embedding out of any number of images", "Create images embedding": "Create images embedding", - "Apply settings": "Apply settings", + "Apply settings": "設定を適用", "Saving images/grids": "画像/グリッドの保存", "Always save all generated images": "生成された画像をすべて保存する", "File format for images": "画像ファイルの保存形式", @@ -310,15 +314,15 @@ "Do not save grids consisting of one picture": "1画像からなるグリッド画像は保存しない", "Prevent empty spots in grid (when set to autodetect)": "(自動設定のとき)グリッドに空隙が生じるのを防ぐ", "Grid row count; use -1 for autodetect and 0 for it to be same as batch size": "グリッドの列数; -1で自動設定、0でバッチ生成回数と同じにする", - "Save text information about generation parameters as chunks to png files": "生成に関するパラメーターをpng画像に含める", + "Save text information about generation parameters as chunks to png files": "生成に関するパラメーターをPNG画像に含める", "Create a text file next to every image with generation parameters.": "保存する画像とともに生成パラメータをテキストファイルで保存する", "Save a copy of image before doing face restoration.": "顔修復を行う前にコピーを保存しておく。", "Quality for saved jpeg images": "JPG保存時の画質", "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG": "PNG画像が4MBを超えるか、どちらか1辺の長さが4000を超えたなら、ダウンスケールしてコピーを別にJPGで保存する", - "Use original name for output filename during batch process in extras tab": "Use original name for output filename during batch process in extras tab", - "When using 'Save' button, only save a single selected image": "When using 'Save' button, only save a single selected image", + "Use original name for output filename during batch process in extras tab": "その他タブでバッチ処理をする際、元のファイル名を出力ファイル名に使う", + "When using 'Save' button, only save a single selected image": "\"保存\"ボタンを使うとき、単一の選択された画像のみを保存する", "Do not add watermark to images": "電子透かしを画像に追加しない", - "Paths for saving": "Paths for saving", + "Paths for saving": "保存する場所", "Output directory for images; if empty, defaults to three directories below": "画像の保存先フォルダ(下項目のデフォルト値になります)", "Output directory for txt2img images": "txt2imgで作った画像の保存先フォルダ", "Output directory for img2img images": "img2imgで作った画像の保存先フォルダ", @@ -345,13 +349,13 @@ "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect": "CodeFormerの重みパラメーター;0が最大で1が最小", "Move face restoration model from VRAM into RAM after processing": "処理終了後、顔修復モデルをVRAMからRAMへと移動する", "System": "システム設定", - "VRAM usage polls per second during generation. Set to 0 to disable.": "VRAM usage polls per second during generation. Set to 0 to disable.", + "VRAM usage polls per second during generation. Set to 0 to disable.": "生成中のVRAM使用率の取得間隔。0にすると取得しない。", "Always print all generation info to standard output": "常にすべての生成に関する情報を標準出力(stdout)に出力する", - "Add a second progress bar to the console that shows progress for an entire job.": "Add a second progress bar to the console that shows progress for an entire job.", + "Add a second progress bar to the console that shows progress for an entire job.": "ジョブ全体の進捗をコンソールに表示する2つ目のプログレスバーを追加する", "Training": "学習", - "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "hypernetworkの学習をするとき、VAEとCLIPをRAMへ退避します。VRAMが節約できます。", - "Filename word regex": "Filename word regex", - "Filename join string": "Filename join string", + "Move VAE and CLIP to RAM when training hypernetwork. Saves VRAM.": "hypernetworkの学習をするとき、VAEとCLIPをRAMへ退避する。VRAMが節約できます。", + "Filename word regex": "ファイル名の正規表現(学習用)", + "Filename join string": "ファイル名の結合子", "Number of repeats for a single input image per epoch; used only for displaying epoch number": "Number of repeats for a single input image per epoch; used only for displaying epoch number", "Save an csv containing the loss to log directory every N steps, 0 to disable": "Save an csv containing the loss to log directory every N steps, 0 to disable", "Stable Diffusion": "Stable Diffusion", @@ -360,13 +364,12 @@ "Apply color correction to img2img results to match original colors.": "元画像に合わせてimg2imgの結果を色補正する", "Save a copy of image before applying color correction to img2img results": "色補正をする前の画像も保存する", "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "img2imgでスライダーで指定されたステップ数を正確に実行する(通常は、ノイズ除去を少なくするためにより少ないステップ数で実行します)。", - "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.", + "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply.": "より良い結果を得るために、Kサンプラーで量子化を有効にします。これにより既存のシードが変更される可能性があります。適用するには再起動が必要です。", "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention": "強調: (text)とするとモデルはtextをより強く扱い、[text]とするとモデルはtextをより弱く扱います。", "Use old emphasis implementation. Can be useful to reproduce old seeds.": "古い強調の実装を使う。古い生成物を再現するのに使えます。", - "Make K-diffusion samplers produce same images in a batch as when making a single image": "Make K-diffusion samplers produce same images in a batch as when making a single image", - "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", + "Make K-diffusion samplers produce same images in a batch as when making a single image": "K-diffusionサンプラーによるバッチ生成時に、単一画像生成時と同じ画像を生成する", + "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens": "75トークン以上を使用する場合、nトークン内の最後のカンマからパディングして一貫性を高める", "Filter NSFW content": "NSFW(≒R-18)なコンテンツを検閲する", - "Stop At last layers of CLIP model": "最後から何層目でCLIPを止めるか", "Interrogate Options": "Interrogate 設定", "Interrogate: keep models in VRAM": "Interrogate: モデルをVRAMに保持する", "Interrogate: use artists from artists.csv": "Interrogate: artists.csvにある芸術家などの名称を利用する", @@ -382,18 +385,20 @@ "User interface": "UI設定", "Show progressbar": "プログレスバーを表示", "Show image creation progress every N sampling steps. Set 0 to disable.": "指定したステップ数ごとに画像の生成過程を表示する。0で無効化。", + "Show previews of all images generated in a batch as a grid": "Show previews of all images generated in a batch as a grid", "Show grid in results for web": "WebUI上でグリッド表示", "Do not show any images in results for web": "WebUI上で一切画像を表示しない", "Add model hash to generation information": "モデルのハッシュ値を生成情報に追加", "Add model name to generation information": "モデルの名称を生成情報に追加", - "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.", + "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint.": "テキストからUIに生成パラメータを読み込む場合(PNG情報または貼り付けられたテキストから)、選択されたモデル/チェックポイントは変更しない。", "Font for image grids that have text": "画像グリッド内のテキストフォント", "Enable full page image viewer": "フルページの画像ビューワーを有効化", "Show images zoomed in by default in full page image viewer": "フルページ画像ビューアでデフォルトで画像を拡大して表示する", "Show generation progress in window title.": "ウィンドウのタイトルで生成の進捗を表示", - "Quicksettings list": "Quicksettings list", + "Quicksettings list": "クイック設定", "Localization (requires restart)": "言語 (プログラムの再起動が必要)", "ja_JP": "ja_JP", + "ru_RU": "ru_RU", "Sampler parameters": "サンプラー parameters", "Hide samplers in user interface (requires restart)": "使わないサンプリングアルゴリズムを隠す (再起動が必要)", "eta (noise multiplier) for DDIM": "DDIMで用いるeta (noise multiplier)", @@ -414,65 +419,67 @@ "Download localization template": "ローカライゼーション用のテンプレートをダウンロードする", "Reload custom script bodies (No ui updates, No restart)": "カスタムスクリプトを再読み込み (UIは変更されず、再起動もしません。)", "Restart Gradio and Refresh components (Custom Scripts, ui.py, js and css only)": "Gradioを再起動してコンポーネントをリフレッシュする (Custom Scripts, ui.py, js, cssのみ影響を受ける)", - "Audio": "Audio", + "Audio": "音声", "Prompt (press Ctrl+Enter or Alt+Enter to generate)": "プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", "Negative prompt (press Ctrl+Enter or Alt+Enter to generate)": "ネガティブ プロンプト (Ctrl+Enter か Alt+Enter を押して生成)", "Add a random artist to the prompt.": "芸術家などの名称をプロンプトに追加", - "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", + "Read generation parameters from prompt or last generation if prompt is empty into user interface.": "プロンプトから生成パラメータを読み込むか、プロンプトが空の場合は最後の生成パラメータをユーザーインターフェースに読み込む。", "Save style": "スタイルを保存する", "Apply selected styles to current prompt": "現在のプロンプトに選択したスタイルを適用する", "Stop processing current image and continue processing.": "現在の処理を中断し、その後の処理は続ける", "Stop processing images and return any results accumulated so far.": "処理を中断し、それまでに出来た結果を表示する", - "Style to apply; styles have components for both positive and negative prompts and apply to both": "Style to apply; styles have components for both positive and negative prompts and apply to both", + "Style to apply; styles have components for both positive and negative prompts and apply to both": "適用するスタイル。スタイルは、ポジティブプロンプトとネガティブプロンプトの両方のコンポーネントを持ち、両方に適用される。", "Do not do anything special": "特別なことをなにもしない", "Which algorithm to use to produce the image": "どのアルゴリズムを使って生成するか", "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help": "Euler Ancestral - 非常に独創的で、ステップ数によって全く異なる画像が得られる、ステップ数を30~40より高く設定しても効果がない。", "Denoising Diffusion Implicit Models - best at inpainting": "Denoising Diffusion Implicit Models - 描き直しには最適", - "Produce an image that can be tiled.": "Produce an image that can be tiled.", + "Produce an image that can be tiled.": "タイルとして扱える画像を生成する", "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition": "2ステップで、まず部分的に小さい解像度で画像を作成し、その後アップスケールすることで、構図を変えずにディテールが改善されます。", - "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", + "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.": "アルゴリズムが画像の内容をどの程度参考にするかを決定します。0 にすると何も変わりませんし、 1 にすると全く無関係な画像になります。1.0未満の値ではスライダーで指定したサンプリングステップ数よりも少ないステップ数で処理が行われます。", "How many batches of images to create": "バッチ処理を何回行うか", "How many image to create in a single batch": "1回のバッチ処理で何枚の画像を生成するか", - "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results", - "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result", + "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 生成する画像がどの程度プロンプトに沿ったものになるか。 - 低い値の方がよりクリエイティブな結果を生み出します。", + "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "乱数発生器の出力を決定する値。同じパラメータとシードで画像を作成すれば、同じ結果が得られます。", "Set seed to -1, which will cause a new random number to be used every time": "シード値を -1 に設定するとランダムに生成します。", "Reuse seed from last generation, mostly useful if it was randomed": "前回生成時のシード値を読み出す。(ランダム生成時に便利)", - "Seed of a different picture to be mixed into the generation.": "Seed of a different picture to be mixed into the generation.", - "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).", - "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution", + "Seed of a different picture to be mixed into the generation.": "生成時に混合されることになる画像のシード値", + "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "Variationの強度。0の場合、何の効果もありません。1では、バリエーションシードで完全な画像を得ることができます(Ancestalなアルゴリズム以外では、何か(?)を得るだけです)。", + "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution": "同じシードで指定された解像度の似た画像を生成することを試みる。", "This text is used to rotate the feature space of the imgs embs": "This text is used to rotate the feature space of the imgs embs", "Separate values for X axis using commas.": "X軸に用いる値をカンマ(,)で区切って入力してください。", "Separate values for Y axis using commas.": "Y軸に用いる値をカンマ(,)で区切って入力してください。", - "Write image to a directory (default - log/images) and generation parameters into csv file.": "Write image to a directory (default - log/images) and generation parameters into csv file.", + "Write image to a directory (default - log/images) and generation parameters into csv file.": "画像はフォルダ(デフォルト:log/images)に、生成パラメータはcsvファイルに書き出します。", "Open images output directory": "画像の出力フォルダを開く", - "How much to blur the mask before processing, in pixels.": "How much to blur the mask before processing, in pixels.", - "What to put inside the masked area before processing it with Stable Diffusion.": "What to put inside the masked area before processing it with Stable Diffusion.", - "fill it with colors of the image": "fill it with colors of the image", - "keep whatever was there originally": "keep whatever was there originally", - "fill it with latent space noise": "fill it with latent space noise", - "fill it with latent space zeroes": "fill it with latent space zeroes", - "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image", - "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.", - "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.", - "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.", - "How many times to repeat processing an image and using it as input for the next iteration": "How many times to repeat processing an image and using it as input for the next iteration", - "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.", - "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.", - "A directory on the same machine where the server is running.": "A directory on the same machine where the server is running.", + "How much to blur the mask before processing, in pixels.": "処理前にどれだけマスクをぼかすか。px単位。", + "What to put inside the masked area before processing it with Stable Diffusion.": "Stable Diffusionにわたす前にマスクされたエリアに何を書き込むか", + "fill it with colors of the image": "元画像の色で埋める", + "keep whatever was there originally": "もともとあったものをそのままにする", + "fill it with latent space noise": "潜在空間(latent space)におけるノイズで埋める", + "fill it with latent space zeroes": "潜在空間(latent space)における0で埋める", + "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "マスクされた領域をターゲット解像度にアップスケールし、インペイントを行い、元の解像度にダウンスケールして元の画像に貼り付けます。", + "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "画像をターゲット解像度にリサイズします。高さと幅が一致しない場合、アスペクト比が正しくなくなります。", + "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "対象の解像度に画像をフィットさせます。はみ出た部分は切り取られます。", + "Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors.": "画像をリサイズして、ターゲット解像度の中に収まるようにします。空白部分は画像の色で埋めます。", + "How many times to repeat processing an image and using it as input for the next iteration": "何回画像処理を繰り返し、次の反復処理の入力として使用するか", + "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.": "ループバックモードにおいて、各ループでのノイズ除去の強度はこの値によって乗算されます。1より小さければ変化が小さくなっていって、生成される画像は1つの画像に収束します。1より大きいとどんどん変化が大きくなるので、生成される画像はよりカオスになります。", + "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.": "SDアップスケールで、どれだけタイル間の重なりを確保するか(px単位)。タイルの一部を重複させることで、1枚の画像にした時明らかな継ぎ目がなくなります。", + "A directory on the same machine where the server is running.": "サーバーが稼働しているのと同じマシンのあるフォルダ", "Leave blank to save images to the default path.": "空欄でデフォルトの場所へ画像を保存", "Input images directory": "Input images directory", - "Result = A * (1 - M) + B * M": "結果モデル = A * (1 - M) + B * M", - "Result = A + (B - C) * M": "結果モデル = A + (B - C) * M", - "1st and last digit must be 1. ex:'1, 2, 1'": "1st and last digit must be 1. ex:'1, 2, 1'", - "Path to directory with input images": "Path to directory with input images", - "Path to directory where to write outputs": "Path to directory where to write outputs", - "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", + "Result = A * (1 - M) + B * M": "出力されるモデル = A * (1 - M) + B * M", + "Result = A + (B - C) * M": "出力されるモデル = A + (B - C) * M", + "1st and last digit must be 1. ex:'1, 2, 1'": "最初と最後の数字は1でなければなりません。 例:'1, 2, 1'", + "Path to directory with input images": "入力ファイルのあるフォルダの場所", + "Path to directory where to write outputs": "出力を書き込むフォルダの場所", + "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "以下のタグを用いてファイル名パターンを決められます: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; 空白でデフォルト設定。", "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.": "このオプションを有効にすると、作成された画像にウォーターマークが追加されなくなります。警告:ウォーターマークを追加しない場合、非倫理的な行動とみなされる場合があります。", - "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.", - "Restore low quality faces using GFPGAN neural network": "GFPGANを用いて低クオリティーの画像を修復", - "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", - "This string will be used to join split words into a single line if the option above is enabled.": "This string will be used to join split words into a single line if the option above is enabled.", - "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.", - "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", + "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.": "以下のタグを用いてサブフォルダのフォルダ名パターンを決められます: [steps], [cfg], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; 空白でデフォルト設定", + "Restore low quality faces using GFPGAN neural network": "GFPGANを用いて低クオリティーな顔画像を修復", + "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.": "この正規表現を使ってファイル名から単語を抽出し、以下のオプションで結合して学習用のラベルテキストにします。ファイル名のテキストをそのまま使用する場合は、空白にしてください。", + "This string will be used to join split words into a single line if the option above is enabled.": "この文字列は、上記のオプションが有効な場合に、分割された単語を1行に結合するために使用されます。", + "List of setting names, separated by commas, for settings that should go to the quick access bar at the top, rather than the usual setting tab. See modules/shared.py for setting names. Requires restarting to apply.": "上部のクイックアクセスバーに置く設定の設定名をカンマで区切って入力。設定名については modules/shared.py を参照してください。適用するには再起動が必要です。", + "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.": "この値が0以外の場合、シードに追加され、Etaでサンプラーを使用する際のノイズ用の乱数生成器を初期化するのに使用されます。これを利用して、さらにバリエーション豊かな画像を作成したり、他のソフトの画像に合わせたりすることができます。", + "NAIConvert": "NAIから変換", + "History": "履歴", "Enable Autocomplete": "自動補完を有効化" } \ No newline at end of file From 71d14a4c40503f0788e2881bb406911c102af40d Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:25:25 +0900 Subject: [PATCH 0225/1118] cleanup ja translation --- localizations/ja_JP.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index 741875c3..7bb9db30 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -81,17 +81,14 @@ "Slerp angle": "Slerp angle", "Is negative text": "Is negative text", "Script": "スクリプト", - "nai2SD Prompt Converter": "nai2SD Prompt Converter", "Prompt matrix": "Prompt matrix", "Prompts from file or textbox": "Prompts from file or textbox", "Save steps of the sampling process to files": "Save steps of the sampling process to files", "X/Y plot": "X/Y plot", - "Prompts": "プロンプト", - "convert": "convert", - "Converted Prompts": "Converted Prompts", "Put variable parts at start of prompt": "Put variable parts at start of prompt", "Show Textbox": "Show Textbox", "File with inputs": "File with inputs", + "Prompts": "プロンプト", "Save images to path": "Save images to path", "X type": "X軸の種類", "Nothing": "なし", From edc0c907fa257f70d63dfdbb755e674cea08f4a7 Mon Sep 17 00:00:00 2001 From: Kris57 <49682577+yuuki76@users.noreply.github.com> Date: Sun, 23 Oct 2022 22:10:13 +0900 Subject: [PATCH 0226/1118] fix ja translation --- localizations/ja_JP.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localizations/ja_JP.json b/localizations/ja_JP.json index 7bb9db30..a6cc2477 100644 --- a/localizations/ja_JP.json +++ b/localizations/ja_JP.json @@ -437,7 +437,7 @@ "How many image to create in a single batch": "1回のバッチ処理で何枚の画像を生成するか", "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results": "Classifier Free Guidance Scale - 生成する画像がどの程度プロンプトに沿ったものになるか。 - 低い値の方がよりクリエイティブな結果を生み出します。", "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "乱数発生器の出力を決定する値。同じパラメータとシードで画像を作成すれば、同じ結果が得られます。", - "Set seed to -1, which will cause a new random number to be used every time": "シード値を -1 に設定するとランダムに生成します。", + "Set seed to -1, which will cause a new random number to be used every time": "シード値を-1に設定。つまり、毎回ランダムに生成します。", "Reuse seed from last generation, mostly useful if it was randomed": "前回生成時のシード値を読み出す。(ランダム生成時に便利)", "Seed of a different picture to be mixed into the generation.": "生成時に混合されることになる画像のシード値", "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).": "Variationの強度。0の場合、何の効果もありません。1では、バリエーションシードで完全な画像を得ることができます(Ancestalなアルゴリズム以外では、何か(?)を得るだけです)。", From 734986dde3231416813f827242c111da212b2ccb Mon Sep 17 00:00:00 2001 From: Trung Ngo Date: Mon, 24 Oct 2022 01:17:09 -0500 Subject: [PATCH 0227/1118] add callback after image is saved --- modules/images.py | 3 ++- modules/script_callbacks.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index b9589563..01c60f89 100644 --- a/modules/images.py +++ b/modules/images.py @@ -12,7 +12,7 @@ from PIL import Image, ImageFont, ImageDraw, PngImagePlugin from fonts.ttf import Roboto import string -from modules import sd_samplers, shared +from modules import sd_samplers, shared, script_callbacks from modules.shared import opts, cmd_opts LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) @@ -467,6 +467,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i else: txt_fullfn = None + script_callbacks.image_saved_callback(image, p, fullfn, txt_fullfn) return fullfn, txt_fullfn diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 5bcccd67..5836e4b9 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -2,11 +2,12 @@ callbacks_model_loaded = [] callbacks_ui_tabs = [] callbacks_ui_settings = [] - +callbacks_image_saved = [] def clear_callbacks(): callbacks_model_loaded.clear() callbacks_ui_tabs.clear() + callbacks_image_saved.clear() def model_loaded_callback(sd_model): @@ -28,6 +29,10 @@ def ui_settings_callback(): callback() +def image_saved_callback(image, p, fullfn, txt_fullfn): + for callback in callbacks_image_saved: + callback(image, p, fullfn, txt_fullfn) + def on_model_loaded(callback): """register a function to be called when the stable diffusion model is created; the model is passed as an argument""" @@ -51,3 +56,8 @@ def on_ui_settings(callback): """register a function to be called before UI settings are populated; add your settings by using shared.opts.add_option(shared.OptionInfo(...)) """ callbacks_ui_settings.append(callback) + + +def on_save_imaged(callback): + """register a function to call after modules.images.save_image is called returning same values, original image and p """ + callbacks_image_saved.append(callback) From 876a96f0f9843382ebc8984db3de5d8af0e9ce4c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 24 Oct 2022 09:39:46 +0300 Subject: [PATCH 0228/1118] remove erroneous dir in the extension directory remove loading .js files from scripts dir (they go into javascript) load scripts after models, for scripts that depend on loaded models --- extensions/stable-diffusion-webui-inspiration | 1 - modules/ui.py | 2 +- webui.py | 11 ++++++----- 3 files changed, 7 insertions(+), 7 deletions(-) delete mode 160000 extensions/stable-diffusion-webui-inspiration diff --git a/extensions/stable-diffusion-webui-inspiration b/extensions/stable-diffusion-webui-inspiration deleted file mode 160000 index a0b96664..00000000 --- a/extensions/stable-diffusion-webui-inspiration +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a0b96664d2524b87916ae463fbb65411b13a569b diff --git a/modules/ui.py b/modules/ui.py index a73b9ff0..03528968 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1885,7 +1885,7 @@ def load_javascript(raw_response): javascript = f'' scripts_list = modules.scripts.list_scripts("javascript", ".js") - scripts_list += modules.scripts.list_scripts("scripts", ".js") + for basedir, filename, path in scripts_list: with open(path, "r", encoding="utf8") as jsfile: javascript += f"\n" diff --git a/webui.py b/webui.py index a0f3757f..ade7334b 100644 --- a/webui.py +++ b/webui.py @@ -9,7 +9,7 @@ from fastapi.middleware.gzip import GZipMiddleware from modules.paths import script_path -from modules import devices, sd_samplers +from modules import devices, sd_samplers, upscaler import modules.codeformer_model as codeformer import modules.extras import modules.face_restoration @@ -73,12 +73,11 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): def initialize(): - modules.scripts.load_scripts() if cmd_opts.ui_debug_mode: - class enmpty(): - name = None - shared.sd_upscalers = [enmpty()] + shared.sd_upscalers = upscaler.UpscalerLanczos().scalers + modules.scripts.load_scripts() return + modelloader.cleanup_models() modules.sd_models.setup_model() codeformer.setup_model(cmd_opts.codeformer_models_path) @@ -86,6 +85,8 @@ def initialize(): shared.face_restorers.append(modules.face_restoration.FaceRestoration()) modelloader.load_upscalers() + modules.scripts.load_scripts() + modules.sd_models.load_model() shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights(shared.sd_model))) shared.opts.onchange("sd_hypernetwork", wrap_queued_call(lambda: modules.hypernetworks.hypernetwork.load_hypernetwork(shared.opts.sd_hypernetwork))) From c623fa1f0b9a3936a29f1d1bd65f4e0fadf1c9c4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 24 Oct 2022 09:51:17 +0300 Subject: [PATCH 0229/1118] add extensions dir --- extensions/put extensions here.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 extensions/put extensions here.txt diff --git a/extensions/put extensions here.txt b/extensions/put extensions here.txt new file mode 100644 index 00000000..e69de29b From 3be6b29d81408d2adb741bff5b11c80214aa621e Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:14:34 +0900 Subject: [PATCH 0230/1118] indent=4 config.json --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 6541e679..d6ddfe59 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -348,7 +348,7 @@ class Options: def save(self, filename): with open(filename, "w", encoding="utf8") as file: - json.dump(self.data, file) + json.dump(self.data, file, indent=4) def same_type(self, x, y): if x is None or y is None: From c5d90628a4058bf49c2fdabf620a24db73407f31 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 22 Oct 2022 17:16:55 +0900 Subject: [PATCH 0231/1118] move "file_decoration" initialize section into "if forced_filename is None:" no need to initialize it if it's not going to be used --- modules/images.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/images.py b/modules/images.py index b9589563..50a59cff 100644 --- a/modules/images.py +++ b/modules/images.py @@ -386,18 +386,6 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i txt_fullfn (`str` or None): If a text file is saved for this image, this will be its full path. Otherwise None. ''' - if short_filename or prompt is None or seed is None: - file_decoration = "" - elif opts.save_to_dirs: - file_decoration = opts.samples_filename_pattern or "[seed]" - else: - file_decoration = opts.samples_filename_pattern or "[seed]-[prompt_spaces]" - - if file_decoration != "": - file_decoration = "-" + file_decoration.lower() - - file_decoration = apply_filename_pattern(file_decoration, p, seed, prompt) + suffix - if extension == 'png' and opts.enable_pnginfo and info is not None: pnginfo = PngImagePlugin.PngInfo() @@ -419,6 +407,18 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i os.makedirs(path, exist_ok=True) if forced_filename is None: + if short_filename or prompt is None or seed is None: + file_decoration = "" + elif opts.save_to_dirs: + file_decoration = opts.samples_filename_pattern or "[seed]" + else: + file_decoration = opts.samples_filename_pattern or "[seed]-[prompt_spaces]" + + if file_decoration != "": + file_decoration = "-" + file_decoration.lower() + + file_decoration = apply_filename_pattern(file_decoration, p, seed, prompt) + suffix + basecount = get_next_sequence_number(path, basename) fullfn = "a.png" fullfn_without_extension = "a" From 7d4a4db9ea7543c079f4a4a702c2945f4b66cd11 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 22 Oct 2022 17:48:59 +0900 Subject: [PATCH 0232/1118] modify unnecessary sting assignment as it's going to get overwritten --- modules/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index 50a59cff..cc5066b1 100644 --- a/modules/images.py +++ b/modules/images.py @@ -420,8 +420,8 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i file_decoration = apply_filename_pattern(file_decoration, p, seed, prompt) + suffix basecount = get_next_sequence_number(path, basename) - fullfn = "a.png" - fullfn_without_extension = "a" + fullfn = None + fullfn_without_extension = None for i in range(500): fn = f"{basecount + i:05}" if basename == '' else f"{basename}-{basecount + i:04}" fullfn = os.path.join(path, f"{fn}{file_decoration}.{extension}") From 37dd6deafb831a809eaf7ae8d232937a8c7998e7 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 22 Oct 2022 21:11:15 +0900 Subject: [PATCH 0233/1118] filename pattern [datetime], extended customizable Format and Time Zone format: [datetime] [datetime] [datetime
+
+
+{versions} +
diff --git a/launch.py b/launch.py index af0d418b..49b91b1f 100644 --- a/launch.py +++ b/launch.py @@ -13,6 +13,21 @@ dir_extensions = "extensions" python = sys.executable git = os.environ.get('GIT', "git") index_url = os.environ.get('INDEX_URL', "") +stored_commit_hash = None + + +def commit_hash(): + global stored_commit_hash + + if stored_commit_hash is not None: + return stored_commit_hash + + try: + stored_commit_hash = run(f"{git} rev-parse HEAD").strip() + except Exception: + stored_commit_hash = "" + + return stored_commit_hash def extract_arg(args, name): @@ -194,10 +209,7 @@ def prepare_environment(): xformers = '--xformers' in sys.argv ngrok = '--ngrok' in sys.argv - try: - commit = run(f"{git} rev-parse HEAD").strip() - except Exception: - commit = "" + commit = commit_hash() print(f"Python {sys.version}") print(f"Commit hash: {commit}") diff --git a/modules/ui.py b/modules/ui.py index bb64fe20..81d96c5b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1696,7 +1696,9 @@ def create_ui(): if os.path.exists("html/footer.html"): with open("html/footer.html", encoding="utf8") as file: - gr.HTML(file.read(), elem_id="footer") + footer = file.read() + footer = footer.format(versions=versions_html()) + gr.HTML(footer, elem_id="footer") text_settings = gr.Textbox(elem_id="settings_json", value=lambda: opts.dumpjson(), visible=False) settings_submit.click( @@ -1857,3 +1859,30 @@ def reload_javascript(): if not hasattr(shared, 'GradioTemplateResponseOriginal'): shared.GradioTemplateResponseOriginal = gradio.routes.templates.TemplateResponse + + +def versions_html(): + import torch + import launch + + python_version = ".".join([str(x) for x in sys.version_info[0:3]]) + commit = launch.commit_hash() + short_commit = commit[0:8] + + if shared.xformers_available: + import xformers + xformers_version = xformers.__version__ + else: + xformers_version = "N/A" + + return f""" +python: {python_version} + •  +torch: {torch.__version__} + •  +xformers: {xformers_version} + •  +gradio: {gr.__version__} + •  +commit: {short_commit} +""" diff --git a/style.css b/style.css index 09ee540b..ee74d79e 100644 --- a/style.css +++ b/style.css @@ -628,6 +628,11 @@ footer { display: inline-block; } +#footer .versions{ + font-size: 85%; + opacity: 0.85; +} + /* The following handles localization for right-to-left (RTL) languages like Arabic. The rtl media type will only be activated by the logic in javascript/localization.js. If you change anything above, you need to make sure it is RTL compliant by just running From f8d0cf6a6ec4911559cfecb9a9d1d46b547b38e8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 5 Jan 2023 12:08:11 +0300 Subject: [PATCH 1017/1118] rework #6329 to remove duplicate code and add prevent tab names for showing in ids for scripts that only exist on one tab --- modules/scripts.py | 10 ++++++++++ scripts/custom_code.py | 6 ------ scripts/img2imgalt.py | 6 ------ scripts/loopback.py | 6 ------ scripts/outpainting_mk_2.py | 6 ------ scripts/poor_mans_outpainting.py | 6 ------ scripts/prompt_matrix.py | 6 ------ scripts/prompts_from_file.py | 6 ------ scripts/sd_upscale.py | 6 ------ scripts/xy_grid.py | 5 ----- 10 files changed, 10 insertions(+), 53 deletions(-) diff --git a/modules/scripts.py b/modules/scripts.py index 722f8685..0c44f191 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -1,4 +1,5 @@ import os +import re import sys import traceback from collections import namedtuple @@ -128,6 +129,15 @@ class Script: """unused""" return "" + def elem_id(self, item_id): + """helper function to generate id for a HTML element, constructs final id out of script name, tab and user-supplied item_id""" + + need_tabname = self.show(True) == self.show(False) + tabname = ('img2img' if self.is_img2img else 'txt2txt') + "_" if need_tabname else "" + title = re.sub(r'[^a-z_0-9]', '', re.sub(r'\s', '_', self.title().lower())) + + return f'script_{tabname}{title}_{item_id}' + current_basedir = paths.script_path diff --git a/scripts/custom_code.py b/scripts/custom_code.py index 9ce1f650..d29113e6 100644 --- a/scripts/custom_code.py +++ b/scripts/custom_code.py @@ -3,18 +3,12 @@ import gradio as gr from modules.processing import Processed from modules.shared import opts, cmd_opts, state -import re class Script(scripts.Script): def title(self): return "Custom code" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def show(self, is_img2img): return cmd_opts.allow_code diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index 7555e874..cbdfc6b3 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -16,7 +16,6 @@ import k_diffusion as K from PIL import Image from torch import autocast from einops import rearrange, repeat -import re def find_noise_for_image(p, cond, uncond, cfg_scale, steps): @@ -123,11 +122,6 @@ class Script(scripts.Script): def title(self): return "img2img alternative test" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def show(self, is_img2img): return is_img2img diff --git a/scripts/loopback.py b/scripts/loopback.py index 4df7b73f..1dab9476 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -8,18 +8,12 @@ from modules import processing, shared, sd_samplers, images from modules.processing import Processed from modules.sd_samplers import samplers from modules.shared import opts, cmd_opts, state -import re class Script(scripts.Script): def title(self): return "Loopback" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def show(self, is_img2img): return is_img2img diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index b4a0dc73..0906da6a 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -10,7 +10,6 @@ from PIL import Image, ImageDraw from modules import images, processing, devices from modules.processing import Processed, process_images from modules.shared import opts, cmd_opts, state -import re # this function is taken from https://github.com/parlance-zz/g-diffuser-bot @@ -123,11 +122,6 @@ class Script(scripts.Script): def title(self): return "Outpainting mk2" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def show(self, is_img2img): return is_img2img diff --git a/scripts/poor_mans_outpainting.py b/scripts/poor_mans_outpainting.py index 1c7dc467..d8feda00 100644 --- a/scripts/poor_mans_outpainting.py +++ b/scripts/poor_mans_outpainting.py @@ -7,18 +7,12 @@ from PIL import Image, ImageDraw from modules import images, processing, devices from modules.processing import Processed, process_images from modules.shared import opts, cmd_opts, state -import re class Script(scripts.Script): def title(self): return "Poor man's outpainting" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def show(self, is_img2img): return is_img2img diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index 278d2e68..dd95e588 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -10,7 +10,6 @@ from modules import images from modules.processing import process_images, Processed from modules.shared import opts, cmd_opts, state import modules.sd_samplers -import re def draw_xy_grid(xs, ys, x_label, y_label, cell): @@ -45,11 +44,6 @@ class Script(scripts.Script): def title(self): return "Prompt matrix" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def ui(self, is_img2img): put_at_start = gr.Checkbox(label='Put variable parts at start of prompt', value=False, elem_id=self.elem_id("put_at_start")) different_seeds = gr.Checkbox(label='Use different seed for each picture', value=False, elem_id=self.elem_id("different_seeds")) diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 5c84c3e9..2751f98a 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -13,7 +13,6 @@ from modules import sd_samplers from modules.processing import Processed, process_images from PIL import Image from modules.shared import opts, cmd_opts, state -import re def process_string_tag(tag): @@ -112,11 +111,6 @@ class Script(scripts.Script): def title(self): return "Prompts from file or textbox" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def ui(self, is_img2img): checkbox_iterate = gr.Checkbox(label="Iterate seed every line", value=False, elem_id=self.elem_id("checkbox_iterate")) checkbox_iterate_batch = gr.Checkbox(label="Use same random seed for all lines", value=False, elem_id=self.elem_id("checkbox_iterate_batch")) diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 247e755b..9b8ffd85 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -7,18 +7,12 @@ from PIL import Image from modules import processing, shared, sd_samplers, images, devices from modules.processing import Processed from modules.shared import opts, cmd_opts, state -import re class Script(scripts.Script): def title(self): return "SD upscale" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def show(self, is_img2img): return is_img2img diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index b277a439..f04d9b7e 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -290,11 +290,6 @@ class Script(scripts.Script): def title(self): return "X/Y plot" - def elem_id(self, item_id): - gen_elem_id = ('img2img' if self.is_img2img else 'txt2txt') + '_script_' + re.sub(r'\s', '_', self.title().lower()) + '_' + item_id - gen_elem_id = re.sub(r'[^a-z_0-9]', '', gen_elem_id) - return gen_elem_id - def ui(self, is_img2img): current_axis_options = [x for x in axis_options if type(x) == AxisOption or type(x) == AxisOptionImg2Img and is_img2img] From eea8fc40e16664ddc8a9aec77206da704a35dde0 Mon Sep 17 00:00:00 2001 From: timntorres Date: Thu, 5 Jan 2023 07:24:22 -0800 Subject: [PATCH 1018/1118] Add option to save ti settings to file. --- modules/shared.py | 1 + .../textual_inversion/textual_inversion.py | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index e0f44c6d..933cd738 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -362,6 +362,7 @@ options_templates.update(options_section(('training', "Training"), { "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."), "pin_memory": OptionInfo(False, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."), "save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training of embedding or HN can be resumed with the matching optim file."), + "save_train_settings_to_txt": OptionInfo(False, "Save textual inversion and hypernet settings to a text file when training starts."), "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 71e07bcc..2bed2ecb 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -1,6 +1,7 @@ import os import sys import traceback +import inspect import torch import tqdm @@ -229,6 +230,28 @@ def write_loss(log_directory, filename, step, epoch_len, values): **values, }) +def save_settings_to_file(initial_step, num_of_dataset_images, embedding_name, vectors_per_token, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): + checkpoint = sd_models.select_checkpoint() + model_name = checkpoint.model_name + model_hash = '[{}]'.format(checkpoint.hash) + + # Get a list of the argument names. + arg_names = inspect.getfullargspec(save_settings_to_file).args + + # Create a list of the argument names to include in the settings string. + names = arg_names[:16] # Include all arguments up until the preview-related ones. + if preview_from_txt2img: + names.extend(arg_names[16:]) # Include all remaining arguments if `preview_from_txt2img` is True. + + # Build the settings string. + settings_str = "datetime : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" + for name in names: + value = locals()[name] + settings_str += f"{name}: {value}\n" + + with open(os.path.join(log_directory, 'settings.txt'), "a+") as fout: + fout.write(settings_str + "\n\n") + def validate_train_inputs(model_name, learn_rate, batch_size, gradient_step, data_root, template_file, steps, save_model_every, create_image_every, log_directory, name="embedding"): assert model_name, f"{name} not selected" assert learn_rate, "Learning rate is empty or 0" @@ -292,13 +315,13 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_ if initial_step >= steps: shared.state.textinfo = "Model has already been trained beyond specified max steps" return embedding, filename + scheduler = LearnRateScheduler(learn_rate, steps, initial_step) - clip_grad = torch.nn.utils.clip_grad_value_ if clip_grad_mode == "value" else \ torch.nn.utils.clip_grad_norm_ if clip_grad_mode == "norm" else \ None if clip_grad: - clip_grad_sched = LearnRateScheduler(clip_grad_value, steps, ititial_step, verbose=False) + clip_grad_sched = LearnRateScheduler(clip_grad_value, steps, initial_step, verbose=False) # dataset loading may take a while, so input validations and early returns should be done before this shared.state.textinfo = f"Preparing dataset from {html.escape(data_root)}..." old_parallel_processing_allowed = shared.parallel_processing_allowed @@ -306,7 +329,8 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_ pin_memory = shared.opts.pin_memory ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method) - + if shared.opts.save_train_settings_to_txt: + save_settings_to_file(initial_step , len(ds) , embedding_name, len(embedding.vec) , learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) latent_sampling_method = ds.latent_sampling_method dl = modules.textual_inversion.dataset.PersonalizedDataLoader(ds, latent_sampling_method=latent_sampling_method, batch_size=ds.batch_size, pin_memory=pin_memory) From 19a81ac2871ec900fc8b7955bbc2554b6c5ac6b1 Mon Sep 17 00:00:00 2001 From: cat Date: Thu, 5 Jan 2023 20:17:39 +0500 Subject: [PATCH 1019/1118] hires-fix: add "nearest-exact" latent upscale mode. --- modules/shared.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/shared.py b/modules/shared.py index e0f44c6d..b7a3ce5c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -576,6 +576,7 @@ latent_upscale_modes = { "Latent (bicubic)": {"mode": "bicubic", "antialias": False}, "Latent (bicubic antialiased)": {"mode": "bicubic", "antialias": True}, "Latent (nearest)": {"mode": "nearest", "antialias": False}, + "Latent (nearest-exact)": {"mode": "nearest-exact", "antialias": False}, } sd_upscalers = [] From b85c2b5cf4a6809bc871718cf4680d49c3e95e94 Mon Sep 17 00:00:00 2001 From: timntorres Date: Thu, 5 Jan 2023 08:14:38 -0800 Subject: [PATCH 1020/1118] Clean up ti, add same behavior to hypernetwork. --- modules/hypernetworks/hypernetwork.py | 31 ++++++++++++++++++- modules/shared.py | 2 +- .../textual_inversion/textual_inversion.py | 14 ++++++--- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 6a9b1398..d5985263 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -401,7 +401,33 @@ def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, hypernet.save(fn) shared.reload_hypernetworks() +# Note: textual_inversion.py has a nearly identical function of the same name. +def save_settings_to_file(initial_step, num_of_dataset_images, hypernetwork_name, layer_structure, activation_func, weight_init, add_layer_norm, use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): + checkpoint = sd_models.select_checkpoint() + model_name = checkpoint.model_name + model_hash = '[{}]'.format(checkpoint.hash) + # Starting index of preview-related arguments. + border_index = 19 + # Get a list of the argument names, excluding default argument. + sig = inspect.signature(save_settings_to_file) + arg_names = [p.name for p in sig.parameters.values() if p.default == p.empty] + + # Create a list of the argument names to include in the settings string. + names = arg_names[:border_index] # Include all arguments up until the preview-related ones. + + # Include preview-related arguments if applicable. + if preview_from_txt2img: + names.extend(arg_names[border_index:]) + + # Build the settings string. + settings_str = "datetime : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" + for name in names: + value = locals()[name] + settings_str += f"{name}: {value}\n" + + with open(os.path.join(log_directory, 'settings.txt'), "a+") as fout: + fout.write(settings_str + "\n\n") def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # images allows training previews to have infotext. Importing it at the top causes a circular import problem. @@ -457,7 +483,10 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step, pin_memory = shared.opts.pin_memory ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method) - + + if shared.opts.save_training_settings_to_txt: + save_settings_to_file(initial_step, len(ds), hypernetwork_name, hypernetwork.layer_structure, hypernetwork.activation_func, hypernetwork.weight_init, hypernetwork.add_layer_norm, hypernetwork.use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) + latent_sampling_method = ds.latent_sampling_method dl = modules.textual_inversion.dataset.PersonalizedDataLoader(ds, latent_sampling_method=latent_sampling_method, batch_size=ds.batch_size, pin_memory=pin_memory) diff --git a/modules/shared.py b/modules/shared.py index 933cd738..10231a75 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -362,7 +362,7 @@ options_templates.update(options_section(('training', "Training"), { "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."), "pin_memory": OptionInfo(False, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."), "save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training of embedding or HN can be resumed with the matching optim file."), - "save_train_settings_to_txt": OptionInfo(False, "Save textual inversion and hypernet settings to a text file when training starts."), + "save_training_settings_to_txt": OptionInfo(False, "Save textual inversion and hypernet settings to a text file whenever training starts."), "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 2bed2ecb..68648550 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -230,18 +230,20 @@ def write_loss(log_directory, filename, step, epoch_len, values): **values, }) +# Note: hypernetwork.py has a nearly identical function of the same name. def save_settings_to_file(initial_step, num_of_dataset_images, embedding_name, vectors_per_token, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): checkpoint = sd_models.select_checkpoint() model_name = checkpoint.model_name model_hash = '[{}]'.format(checkpoint.hash) - + # Starting index of preview-related arguments. + border_index = 16 # Get a list of the argument names. arg_names = inspect.getfullargspec(save_settings_to_file).args # Create a list of the argument names to include in the settings string. - names = arg_names[:16] # Include all arguments up until the preview-related ones. + names = arg_names[:border_index] # Include all arguments up until the preview-related ones. if preview_from_txt2img: - names.extend(arg_names[16:]) # Include all remaining arguments if `preview_from_txt2img` is True. + names.extend(arg_names[border_index:]) # Include all remaining arguments if `preview_from_txt2img` is True. # Build the settings string. settings_str = "datetime : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" @@ -329,8 +331,10 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_ pin_memory = shared.opts.pin_memory ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method) - if shared.opts.save_train_settings_to_txt: - save_settings_to_file(initial_step , len(ds) , embedding_name, len(embedding.vec) , learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) + + if shared.opts.save_training_settings_to_txt: + save_settings_to_file(initial_step, len(ds), embedding_name, len(embedding.vec), learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) + latent_sampling_method = ds.latent_sampling_method dl = modules.textual_inversion.dataset.PersonalizedDataLoader(ds, latent_sampling_method=latent_sampling_method, batch_size=ds.batch_size, pin_memory=pin_memory) From b6bab2f052b32c0ffebe6aecc1819ccf20cf8c5d Mon Sep 17 00:00:00 2001 From: timntorres Date: Thu, 5 Jan 2023 09:14:56 -0800 Subject: [PATCH 1021/1118] Include model in log file. Exclude directory. --- modules/hypernetworks/hypernetwork.py | 28 +++++++------------ .../textual_inversion/textual_inversion.py | 22 ++++++--------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index d5985263..3237c37a 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -402,30 +402,22 @@ def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, shared.reload_hypernetworks() # Note: textual_inversion.py has a nearly identical function of the same name. -def save_settings_to_file(initial_step, num_of_dataset_images, hypernetwork_name, layer_structure, activation_func, weight_init, add_layer_norm, use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): - checkpoint = sd_models.select_checkpoint() - model_name = checkpoint.model_name - model_hash = '[{}]'.format(checkpoint.hash) +def save_settings_to_file(model_name, model_hash, initial_step, num_of_dataset_images, hypernetwork_name, layer_structure, activation_func, weight_init, add_layer_norm, use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # Starting index of preview-related arguments. - border_index = 19 - - # Get a list of the argument names, excluding default argument. - sig = inspect.signature(save_settings_to_file) - arg_names = [p.name for p in sig.parameters.values() if p.default == p.empty] - + border_index = 21 + # Get a list of the argument names. + arg_names = inspect.getfullargspec(save_settings_to_file).args # Create a list of the argument names to include in the settings string. names = arg_names[:border_index] # Include all arguments up until the preview-related ones. - - # Include preview-related arguments if applicable. if preview_from_txt2img: - names.extend(arg_names[border_index:]) - + names.extend(arg_names[border_index:]) # Include preview-related arguments if applicable. # Build the settings string. settings_str = "datetime : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" for name in names: - value = locals()[name] - settings_str += f"{name}: {value}\n" - + if name != 'log_directory': # It's useless and redundant to save log_directory. + value = locals()[name] + settings_str += f"{name}: {value}\n" + # Create or append to the file. with open(os.path.join(log_directory, 'settings.txt'), "a+") as fout: fout.write(settings_str + "\n\n") @@ -485,7 +477,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step, ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method) if shared.opts.save_training_settings_to_txt: - save_settings_to_file(initial_step, len(ds), hypernetwork_name, hypernetwork.layer_structure, hypernetwork.activation_func, hypernetwork.weight_init, hypernetwork.add_layer_norm, hypernetwork.use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) + save_settings_to_file(checkpoint.model_name, '[{}]'.format(checkpoint.hash), initial_step, len(ds), hypernetwork_name, hypernetwork.layer_structure, hypernetwork.activation_func, hypernetwork.weight_init, hypernetwork.add_layer_norm, hypernetwork.use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) latent_sampling_method = ds.latent_sampling_method diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 68648550..ce7e4f5d 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -231,26 +231,22 @@ def write_loss(log_directory, filename, step, epoch_len, values): }) # Note: hypernetwork.py has a nearly identical function of the same name. -def save_settings_to_file(initial_step, num_of_dataset_images, embedding_name, vectors_per_token, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): - checkpoint = sd_models.select_checkpoint() - model_name = checkpoint.model_name - model_hash = '[{}]'.format(checkpoint.hash) +def save_settings_to_file(model_name, model_hash, initial_step, num_of_dataset_images, embedding_name, vectors_per_token, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # Starting index of preview-related arguments. - border_index = 16 + border_index = 18 # Get a list of the argument names. - arg_names = inspect.getfullargspec(save_settings_to_file).args - + arg_names = inspect.getfullargspec(save_settings_to_file).args # Create a list of the argument names to include in the settings string. names = arg_names[:border_index] # Include all arguments up until the preview-related ones. if preview_from_txt2img: - names.extend(arg_names[border_index:]) # Include all remaining arguments if `preview_from_txt2img` is True. - + names.extend(arg_names[border_index:]) # Include preview-related arguments if applicable. # Build the settings string. settings_str = "datetime : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" for name in names: - value = locals()[name] - settings_str += f"{name}: {value}\n" - + if name != 'log_directory': # It's useless and redundant to save log_directory. + value = locals()[name] + settings_str += f"{name}: {value}\n" + # Create or append to the file. with open(os.path.join(log_directory, 'settings.txt'), "a+") as fout: fout.write(settings_str + "\n\n") @@ -333,7 +329,7 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_ ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method) if shared.opts.save_training_settings_to_txt: - save_settings_to_file(initial_step, len(ds), embedding_name, len(embedding.vec), learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) + save_settings_to_file(checkpoint.model_name, '[{}]'.format(checkpoint.hash), initial_step, len(ds), embedding_name, len(embedding.vec), learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) latent_sampling_method = ds.latent_sampling_method From fda04e620d529031e2134520e74756d0efa30464 Mon Sep 17 00:00:00 2001 From: Kuma <36082288+KumiIT@users.noreply.github.com> Date: Thu, 5 Jan 2023 18:44:19 +0100 Subject: [PATCH 1022/1118] typo in TI --- modules/textual_inversion/textual_inversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 71e07bcc..24b43045 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -298,7 +298,7 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_ torch.nn.utils.clip_grad_norm_ if clip_grad_mode == "norm" else \ None if clip_grad: - clip_grad_sched = LearnRateScheduler(clip_grad_value, steps, ititial_step, verbose=False) + clip_grad_sched = LearnRateScheduler(clip_grad_value, steps, initial_step, verbose=False) # dataset loading may take a while, so input validations and early returns should be done before this shared.state.textinfo = f"Preparing dataset from {html.escape(data_root)}..." old_parallel_processing_allowed = shared.parallel_processing_allowed From 847f869c67c7108e3e792fc193331d0e6acca29c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 5 Jan 2023 21:00:52 +0300 Subject: [PATCH 1023/1118] experimental optimization --- modules/processing.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 61e97077..a408d622 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -544,6 +544,29 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: infotexts = [] output_images = [] + cached_uc = [None, None] + cached_c = [None, None] + + def get_conds_with_caching(function, required_prompts, steps, cache): + """ + Returns the result of calling function(shared.sd_model, required_prompts, steps) + using a cache to store the result if the same arguments have been used before. + + cache is an array containing two elements. The first element is a tuple + representing the previously used arguments, or None if no arguments + have been used before. The second element is where the previously + computed result is stored. + """ + + if cache[0] is not None and (required_prompts, steps) == cache[0]: + return cache[1] + + with devices.autocast(): + cache[1] = function(shared.sd_model, required_prompts, steps) + + cache[0] = (required_prompts, steps) + return cache[1] + with torch.no_grad(), p.sd_model.ema_scope(): with devices.autocast(): p.init(p.all_prompts, p.all_seeds, p.all_subseeds) @@ -571,9 +594,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.scripts is not None: p.scripts.process_batch(p, batch_number=n, prompts=prompts, seeds=seeds, subseeds=subseeds) - with devices.autocast(): - uc = prompt_parser.get_learned_conditioning(shared.sd_model, negative_prompts, p.steps) - c = prompt_parser.get_multicond_learned_conditioning(shared.sd_model, prompts, p.steps) + uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, cached_uc) + c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, cached_c) if len(model_hijack.comments) > 0: for comment in model_hijack.comments: From 81133d4168ae0bae9bf8bf1a1d4983319a589112 Mon Sep 17 00:00:00 2001 From: Faber Date: Fri, 6 Jan 2023 03:38:37 +0700 Subject: [PATCH 1024/1118] allow loading embeddings from subdirectories --- .../textual_inversion/textual_inversion.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 24b43045..0a059044 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -149,19 +149,20 @@ class EmbeddingDatabase: else: self.skipped_embeddings[name] = embedding - for fn in os.listdir(self.embeddings_dir): - try: - fullfn = os.path.join(self.embeddings_dir, fn) + for root, dirs, fns in os.walk(self.embeddings_dir): + for fn in fns: + try: + fullfn = os.path.join(root, fn) - if os.stat(fullfn).st_size == 0: + if os.stat(fullfn).st_size == 0: + continue + + process_file(fullfn, fn) + except Exception: + print(f"Error loading embedding {fn}:", file=sys.stderr) + print(traceback.format_exc(), file=sys.stderr) continue - process_file(fullfn, fn) - except Exception: - print(f"Error loading embedding {fn}:", file=sys.stderr) - print(traceback.format_exc(), file=sys.stderr) - continue - print(f"Textual inversion embeddings loaded({len(self.word_embeddings)}): {', '.join(self.word_embeddings.keys())}") if len(self.skipped_embeddings) > 0: print(f"Textual inversion embeddings skipped({len(self.skipped_embeddings)}): {', '.join(self.skipped_embeddings.keys())}") From b5253f0dab529707f1fe2e11211a10ce2f264617 Mon Sep 17 00:00:00 2001 From: noodleanon <122053346+noodleanon@users.noreply.github.com> Date: Thu, 5 Jan 2023 21:21:48 +0000 Subject: [PATCH 1025/1118] allow img2img api to run scripts --- modules/api/api.py | 27 ++++++++++++++++++++++++--- modules/api/models.py | 2 +- modules/processing.py | 4 ++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 2103709b..aa62a42e 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -11,7 +11,7 @@ from fastapi.security import HTTPBasic, HTTPBasicCredentials from secrets import compare_digest import modules.shared as shared -from modules import sd_samplers, deepbooru, sd_hijack, images +from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui from modules.api.models import * from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images from modules.extras import run_extras @@ -28,8 +28,13 @@ def upscaler_to_index(name: str): try: return [x.name.lower() for x in shared.sd_upscalers].index(name.lower()) except: - raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be on of these: {' , '.join([x.name for x in sd_upscalers])}") + raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be one of these: {' , '.join([x.name for x in sd_upscalers])}") +def script_name_to_index(name, scripts): + try: + return [script.title().lower() for script in scripts].index(name.lower()) + except: + raise HTTPException(status_code=422, detail=f"Script '{name}' not found") def validate_sampler_name(name): config = sd_samplers.all_samplers_map.get(name, None) @@ -170,6 +175,14 @@ class Api: if init_images is None: raise HTTPException(status_code=404, detail="Init image not found") + if img2imgreq.script_name is not None: + if scripts.scripts_img2img.scripts == []: + scripts.scripts_img2img.initialize_scripts(True) + ui.create_ui() + + script_idx = script_name_to_index(img2imgreq.script_name, scripts.scripts_img2img.selectable_scripts) + script = scripts.scripts_img2img.selectable_scripts[script_idx] + mask = img2imgreq.mask if mask: mask = decode_base64_to_image(mask) @@ -186,13 +199,21 @@ class Api: args = vars(populate) args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model, but it's for a reason that I cannot determine. + args.pop('script_name', None) with self.queue_lock: p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) p.init_images = [decode_base64_to_image(x) for x in init_images] shared.state.begin() - processed = process_images(p) + if 'script' in locals(): + p.outpath_grids = opts.outdir_img2img_grids + p.outpath_samples = opts.outdir_img2img_samples + p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args + processed = scripts.scripts_img2img.run(p, *p.script_args) + else: + processed = process_images(p) + shared.state.end() b64images = list(map(encode_pil_to_base64, processed.images)) diff --git a/modules/api/models.py b/modules/api/models.py index d8198a27..862477e7 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -106,7 +106,7 @@ StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( "StableDiffusionProcessingImg2Img", StableDiffusionProcessingImg2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}] + [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}] ).generate_model() class TextToImageResponse(BaseModel): diff --git a/modules/processing.py b/modules/processing.py index a408d622..d5ac7eb1 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -98,7 +98,7 @@ class StableDiffusionProcessing(): """ The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing """ - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, width: int = 512, height: int = 512, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None): + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, width: int = 512, height: int = 512, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): if sampler_index is not None: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) @@ -149,7 +149,7 @@ class StableDiffusionProcessing(): self.seed_resize_from_w = 0 self.scripts = None - self.script_args = None + self.script_args = script_args self.all_prompts = None self.all_negative_prompts = None self.all_seeds = None From eadd1bf06adbd7263875640a6446d3b0184d1561 Mon Sep 17 00:00:00 2001 From: noodleanon <122053346+noodleanon@users.noreply.github.com> Date: Thu, 5 Jan 2023 21:22:04 +0000 Subject: [PATCH 1026/1118] allow sdupscale to accept upscaler name --- scripts/sd_upscale.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 9b8ffd85..332d76d9 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -25,6 +25,8 @@ class Script(scripts.Script): return [info, overlap, upscaler_index, scale_factor] def run(self, p, _, overlap, upscaler_index, scale_factor): + if isinstance(upscaler_index, str): + upscaler_index = [x.name.lower() for x in shared.sd_upscalers].index(upscaler_index.lower()) processing.fix_seed(p) upscaler = shared.sd_upscalers[upscaler_index] From 8111b5569d07c7ac3b695e28171aede728b4ae56 Mon Sep 17 00:00:00 2001 From: brkirch Date: Tue, 3 Jan 2023 20:43:05 -0500 Subject: [PATCH 1027/1118] Add support for PyTorch nightly and local builds --- modules/devices.py | 28 +++++++++++++++++++++++----- webui.py | 7 ++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 800510b7..caeb0276 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -133,8 +133,26 @@ def numpy_fix(self, *args, **kwargs): return orig_tensor_numpy(self, *args, **kwargs) -# PyTorch 1.13 doesn't need these fixes but unfortunately is slower and has regressions that prevent training from working -if has_mps() and version.parse(torch.__version__) < version.parse("1.13"): - torch.Tensor.to = tensor_to_fix - torch.nn.functional.layer_norm = layer_norm_fix - torch.Tensor.numpy = numpy_fix +# MPS workaround for https://github.com/pytorch/pytorch/issues/89784 +orig_cumsum = torch.cumsum +orig_Tensor_cumsum = torch.Tensor.cumsum +def cumsum_fix(input, cumsum_func, *args, **kwargs): + if input.device.type == 'mps': + output_dtype = kwargs.get('dtype', input.dtype) + if any(output_dtype == broken_dtype for broken_dtype in [torch.bool, torch.int8, torch.int16, torch.int64]): + return cumsum_func(input.cpu(), *args, **kwargs).to(input.device) + return cumsum_func(input, *args, **kwargs) + + +if has_mps(): + if version.parse(torch.__version__) < version.parse("1.13"): + # PyTorch 1.13 doesn't need these fixes but unfortunately is slower and has regressions that prevent training from working + torch.Tensor.to = tensor_to_fix + torch.nn.functional.layer_norm = layer_norm_fix + torch.Tensor.numpy = numpy_fix + elif version.parse(torch.__version__) > version.parse("1.13.1"): + if not torch.Tensor([1,2]).to(torch.device("mps")).equal(torch.Tensor([1,1]).to(torch.device("mps")).cumsum(0, dtype=torch.int16)): + torch.cumsum = lambda input, *args, **kwargs: ( cumsum_fix(input, orig_cumsum, *args, **kwargs) ) + torch.Tensor.cumsum = lambda self, *args, **kwargs: ( cumsum_fix(self, orig_Tensor_cumsum, *args, **kwargs) ) + orig_narrow = torch.narrow + torch.narrow = lambda *args, **kwargs: ( orig_narrow(*args, **kwargs).clone() ) diff --git a/webui.py b/webui.py index 13375e71..ddfaea95 100644 --- a/webui.py +++ b/webui.py @@ -4,7 +4,7 @@ import threading import time import importlib import signal -import threading +import re from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware @@ -13,6 +13,11 @@ from modules import import_hook, errors from modules.call_queue import wrap_queued_call, queue_lock, wrap_gradio_gpu_call from modules.paths import script_path +import torch +# Truncate version number of nightly/local build of PyTorch to not cause exceptions with CodeFormer or Safetensors +if ".dev" in torch.__version__ or "+git" in torch.__version__: + torch.__version__ = re.search(r'[\d.]+', torch.__version__).group(0) + from modules import shared, devices, sd_samplers, upscaler, extensions, localization, ui_tempdir import modules.codeformer_model as codeformer import modules.extras From d61a5aa4f623f6630670241aca8fc5c2a6381769 Mon Sep 17 00:00:00 2001 From: acncagua Date: Fri, 6 Jan 2023 10:58:22 +0900 Subject: [PATCH 1028/1118] Add files via upload --- modules/ui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ui.py b/modules/ui.py index 81d96c5b..030f0685 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -550,6 +550,8 @@ Requested path was: {f} os.startfile(path) elif platform.system() == "Darwin": sp.Popen(["open", path]) + elif "microsoft-standard-WSL2" in platform.uname().release: + sp.Popen(["wsl-open", path]) else: sp.Popen(["xdg-open", path]) From d782a95967c9eea753df3333cd1954b6ec73eba0 Mon Sep 17 00:00:00 2001 From: brkirch Date: Tue, 27 Dec 2022 08:50:55 -0500 Subject: [PATCH 1029/1118] Add Birch-san's sub-quadratic attention implementation --- README.md | 1 + modules/sd_hijack.py | 15 +-- modules/sd_hijack_optimizations.py | 124 ++++++++++++++---- modules/shared.py | 4 + modules/sub_quadratic_attention.py | 201 +++++++++++++++++++++++++++++ requirements.txt | 2 +- 6 files changed, 312 insertions(+), 35 deletions(-) create mode 100644 modules/sub_quadratic_attention.py diff --git a/README.md b/README.md index 556000fb..1913caf3 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ The documentation was moved from this README over to the project's [wiki](https: - Ideas for optimizations - https://github.com/basujindal/stable-diffusion - Cross Attention layer optimization - Doggettx - https://github.com/Doggettx/stable-diffusion, original idea for prompt editing. - Cross Attention layer optimization - InvokeAI, lstein - https://github.com/invoke-ai/InvokeAI (originally http://github.com/lstein/stable-diffusion) +- Sub-quadratic Cross Attention layer optimization - Alex Birch (https://github.com/Birch-san), Amin Rezaei (https://github.com/AminRezaei0x443) - Textual Inversion - Rinon Gal - https://github.com/rinongal/textual_inversion (we're not using his code, but we are using his ideas). - Idea for SD upscale - https://github.com/jquesnelle/txt2imghd - Noise generation for outpainting mk2 - https://github.com/parlance-zz/g-diffuser-bot diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 690a9ec2..019a6f3f 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -7,8 +7,6 @@ from modules.hypernetworks import hypernetwork from modules.shared import cmd_opts from modules import sd_hijack_clip, sd_hijack_open_clip, sd_hijack_unet -from modules.sd_hijack_optimizations import invokeAI_mps_available - import ldm.modules.attention import ldm.modules.diffusionmodules.model import ldm.modules.diffusionmodules.openaimodel @@ -40,17 +38,16 @@ def apply_optimizations(): print("Applying xformers cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.xformers_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward + elif cmd_opts.opt_sub_quad_attention: + print("Applying sub-quadratic cross attention optimization.") + ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.sub_quad_attention_forward + ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.sub_quad_attnblock_forward elif cmd_opts.opt_split_attention_v1: print("Applying v1 cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_v1 elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention_invokeai or not torch.cuda.is_available()): - if not invokeAI_mps_available and shared.device.type == 'mps': - print("The InvokeAI cross attention optimization for MPS requires the psutil package which is not installed.") - print("Applying v1 cross attention optimization.") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_v1 - else: - print("Applying cross attention optimization (InvokeAI).") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_invokeAI + print("Applying cross attention optimization (InvokeAI).") + ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_invokeAI elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention or torch.cuda.is_available()): print("Applying cross attention optimization (Doggettx).") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 02c87f40..f5c153e8 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -1,7 +1,7 @@ import math import sys import traceback -import importlib +import psutil import torch from torch import einsum @@ -12,6 +12,8 @@ from einops import rearrange from modules import shared from modules.hypernetworks import hypernetwork +from .sub_quadratic_attention import efficient_dot_product_attention + if shared.cmd_opts.xformers or shared.cmd_opts.force_enable_xformers: try: @@ -22,6 +24,19 @@ if shared.cmd_opts.xformers or shared.cmd_opts.force_enable_xformers: print(traceback.format_exc(), file=sys.stderr) +def get_available_vram(): + if shared.device.type == 'cuda': + stats = torch.cuda.memory_stats(shared.device) + mem_active = stats['active_bytes.all.current'] + mem_reserved = stats['reserved_bytes.all.current'] + mem_free_cuda, _ = torch.cuda.mem_get_info(torch.cuda.current_device()) + mem_free_torch = mem_reserved - mem_active + mem_free_total = mem_free_cuda + mem_free_torch + return mem_free_total + else: + return psutil.virtual_memory().available + + # see https://github.com/basujindal/stable-diffusion/pull/117 for discussion def split_cross_attention_forward_v1(self, x, context=None, mask=None): h = self.heads @@ -76,12 +91,7 @@ def split_cross_attention_forward(self, x, context=None, mask=None): r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) - stats = torch.cuda.memory_stats(q.device) - mem_active = stats['active_bytes.all.current'] - mem_reserved = stats['reserved_bytes.all.current'] - mem_free_cuda, _ = torch.cuda.mem_get_info(torch.cuda.current_device()) - mem_free_torch = mem_reserved - mem_active - mem_free_total = mem_free_cuda + mem_free_torch + mem_free_total = get_available_vram() gb = 1024 ** 3 tensor_size = q.shape[0] * q.shape[1] * k.shape[1] * q.element_size() @@ -118,19 +128,8 @@ def split_cross_attention_forward(self, x, context=None, mask=None): return self.to_out(r2) -def check_for_psutil(): - try: - spec = importlib.util.find_spec('psutil') - return spec is not None - except ModuleNotFoundError: - return False - -invokeAI_mps_available = check_for_psutil() - # -- Taken from https://github.com/invoke-ai/InvokeAI and modified -- -if invokeAI_mps_available: - import psutil - mem_total_gb = psutil.virtual_memory().total // (1 << 30) +mem_total_gb = psutil.virtual_memory().total // (1 << 30) def einsum_op_compvis(q, k, v): s = einsum('b i d, b j d -> b i j', q, k) @@ -215,6 +214,70 @@ def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): # -- End of code from https://github.com/invoke-ai/InvokeAI -- + +# Based on Birch-san's modified implementation of sub-quadratic attention from https://github.com/Birch-san/diffusers/pull/1 +def sub_quad_attention_forward(self, x, context=None, mask=None): + assert mask is None, "attention-mask not currently implemented for SubQuadraticCrossAttnProcessor." + + h = self.heads + + q = self.to_q(x) + context = default(context, x) + + context_k, context_v = hypernetwork.apply_hypernetwork(shared.loaded_hypernetwork, context) + k = self.to_k(context_k) + v = self.to_v(context_v) + del context, context_k, context_v, x + + q = q.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1) + k = k.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1) + v = v.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1) + + x = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold_bytes=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training) + + x = x.unflatten(0, (-1, h)).transpose(1,2).flatten(start_dim=2) + + out_proj, dropout = self.to_out + x = out_proj(x) + x = dropout(x) + + return x + +def sub_quad_attention(q, k, v, q_chunk_size=1024, kv_chunk_size=None, kv_chunk_size_min=None, chunk_threshold_bytes=None, use_checkpoint=True): + bytes_per_token = torch.finfo(q.dtype).bits//8 + batch_x_heads, q_tokens, _ = q.shape + _, k_tokens, _ = k.shape + qk_matmul_size_bytes = batch_x_heads * bytes_per_token * q_tokens * k_tokens + + available_vram = int(get_available_vram() * 0.9) if q.device.type == 'mps' else int(get_available_vram() * 0.7) + + if chunk_threshold_bytes is None: + chunk_threshold_bytes = available_vram + elif chunk_threshold_bytes == 0: + chunk_threshold_bytes = None + + if kv_chunk_size_min is None: + kv_chunk_size_min = chunk_threshold_bytes // (batch_x_heads * bytes_per_token * (k.shape[2] + v.shape[2])) + elif kv_chunk_size_min == 0: + kv_chunk_size_min = None + + if chunk_threshold_bytes is not None and qk_matmul_size_bytes <= chunk_threshold_bytes: + # the big matmul fits into our memory limit; do everything in 1 chunk, + # i.e. send it down the unchunked fast-path + query_chunk_size = q_tokens + kv_chunk_size = k_tokens + + return efficient_dot_product_attention( + q, + k, + v, + query_chunk_size=q_chunk_size, + kv_chunk_size=kv_chunk_size, + kv_chunk_size_min = kv_chunk_size_min, + use_checkpoint=use_checkpoint, + ) + + def xformers_attention_forward(self, x, context=None, mask=None): h = self.heads q_in = self.to_q(x) @@ -252,12 +315,7 @@ def cross_attention_attnblock_forward(self, x): h_ = torch.zeros_like(k, device=q.device) - stats = torch.cuda.memory_stats(q.device) - mem_active = stats['active_bytes.all.current'] - mem_reserved = stats['reserved_bytes.all.current'] - mem_free_cuda, _ = torch.cuda.mem_get_info(torch.cuda.current_device()) - mem_free_torch = mem_reserved - mem_active - mem_free_total = mem_free_cuda + mem_free_torch + mem_free_total = get_available_vram() tensor_size = q.shape[0] * q.shape[1] * k.shape[2] * q.element_size() mem_required = tensor_size * 2.5 @@ -312,3 +370,19 @@ def xformers_attnblock_forward(self, x): return x + out except NotImplementedError: return cross_attention_attnblock_forward(self, x) + +def sub_quad_attnblock_forward(self, x): + h_ = x + h_ = self.norm(h_) + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + b, c, h, w = q.shape + q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + q = q.contiguous() + k = k.contiguous() + v = v.contiguous() + out = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold_bytes=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training) + out = rearrange(out, 'b (h w) c -> b c h w', h=h) + out = self.proj_out(out) + return x + out diff --git a/modules/shared.py b/modules/shared.py index d4ddeea0..487a7792 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -56,6 +56,10 @@ parser.add_argument("--xformers", action='store_true', help="enable xformers for parser.add_argument("--force-enable-xformers", action='store_true', help="enable xformers for cross attention layers regardless of whether the checking code thinks you can run it; do not make bug reports if this fails to work") parser.add_argument("--deepdanbooru", action='store_true', help="does not do anything") parser.add_argument("--opt-split-attention", action='store_true', help="force-enables Doggettx's cross-attention layer optimization. By default, it's on for torch cuda.") +parser.add_argument("--opt-sub-quad-attention", action='store_true', help="enable memory efficient sub-quadratic cross-attention layer optimization") +parser.add_argument("--sub-quad-q-chunk-size", type=int, help="query chunk size for the sub-quadratic cross-attention layer optimization to use", default=1024) +parser.add_argument("--sub-quad-kv-chunk-size", type=int, help="kv chunk size for the sub-quadratic cross-attention layer optimization to use", default=None) +parser.add_argument("--sub-quad-chunk-threshold", type=int, help="the size threshold in bytes for the sub-quadratic cross-attention layer optimization to use chunking", default=None) parser.add_argument("--opt-split-attention-invokeai", action='store_true', help="force-enables InvokeAI's cross-attention layer optimization. By default, it's on when cuda is unavailable.") parser.add_argument("--opt-split-attention-v1", action='store_true', help="enable older version of split attention optimization that does not consume all the VRAM it can find") parser.add_argument("--disable-opt-split-attention", action='store_true', help="force-disables cross-attention layer optimization") diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py new file mode 100644 index 00000000..b11dc1c7 --- /dev/null +++ b/modules/sub_quadratic_attention.py @@ -0,0 +1,201 @@ +# original source: +# https://github.com/AminRezaei0x443/memory-efficient-attention/blob/1bc0d9e6ac5f82ea43a375135c4e1d3896ee1694/memory_efficient_attention/attention_torch.py +# license: +# unspecified +# credit: +# Amin Rezaei (original author) +# Alex Birch (optimized algorithm for 3D tensors, at the expense of removing bias, masking and callbacks) +# implementation of: +# Self-attention Does Not Need O(n2) Memory": +# https://arxiv.org/abs/2112.05682v2 + +from functools import partial +import torch +from torch import Tensor +from torch.utils.checkpoint import checkpoint +import math +from typing import Optional, NamedTuple, Protocol, List + +def dynamic_slice( + x: Tensor, + starts: List[int], + sizes: List[int], +) -> Tensor: + slicing = [slice(start, start + size) for start, size in zip(starts, sizes)] + return x[slicing] + +class AttnChunk(NamedTuple): + exp_values: Tensor + exp_weights_sum: Tensor + max_score: Tensor + +class SummarizeChunk(Protocol): + @staticmethod + def __call__( + query: Tensor, + key: Tensor, + value: Tensor, + ) -> AttnChunk: ... + +class ComputeQueryChunkAttn(Protocol): + @staticmethod + def __call__( + query: Tensor, + key: Tensor, + value: Tensor, + ) -> Tensor: ... + +def _summarize_chunk( + query: Tensor, + key: Tensor, + value: Tensor, + scale: float, +) -> AttnChunk: + attn_weights = torch.baddbmm( + torch.empty(1, 1, 1, device=query.device, dtype=query.dtype), + query, + key.transpose(1,2), + alpha=scale, + beta=0, + ) + max_score, _ = torch.max(attn_weights, -1, keepdim=True) + max_score = max_score.detach() + exp_weights = torch.exp(attn_weights - max_score) + exp_values = torch.bmm(exp_weights, value) + max_score = max_score.squeeze(-1) + return AttnChunk(exp_values, exp_weights.sum(dim=-1), max_score) + +def _query_chunk_attention( + query: Tensor, + key: Tensor, + value: Tensor, + summarize_chunk: SummarizeChunk, + kv_chunk_size: int, +) -> Tensor: + batch_x_heads, k_tokens, k_channels_per_head = key.shape + _, _, v_channels_per_head = value.shape + + def chunk_scanner(chunk_idx: int) -> AttnChunk: + key_chunk = dynamic_slice( + key, + (0, chunk_idx, 0), + (batch_x_heads, kv_chunk_size, k_channels_per_head) + ) + value_chunk = dynamic_slice( + value, + (0, chunk_idx, 0), + (batch_x_heads, kv_chunk_size, v_channels_per_head) + ) + return summarize_chunk(query, key_chunk, value_chunk) + + chunks: List[AttnChunk] = [ + chunk_scanner(chunk) for chunk in torch.arange(0, k_tokens, kv_chunk_size) + ] + acc_chunk = AttnChunk(*map(torch.stack, zip(*chunks))) + chunk_values, chunk_weights, chunk_max = acc_chunk + + global_max, _ = torch.max(chunk_max, 0, keepdim=True) + max_diffs = torch.exp(chunk_max - global_max) + chunk_values *= torch.unsqueeze(max_diffs, -1) + chunk_weights *= max_diffs + + all_values = chunk_values.sum(dim=0) + all_weights = torch.unsqueeze(chunk_weights, -1).sum(dim=0) + return all_values / all_weights + +# TODO: refactor CrossAttention#get_attention_scores to share code with this +def _get_attention_scores_no_kv_chunking( + query: Tensor, + key: Tensor, + value: Tensor, + scale: float, +) -> Tensor: + attn_scores = torch.baddbmm( + torch.empty(1, 1, 1, device=query.device, dtype=query.dtype), + query, + key.transpose(1,2), + alpha=scale, + beta=0, + ) + attn_probs = attn_scores.softmax(dim=-1) + del attn_scores + hidden_states_slice = torch.bmm(attn_probs, value) + return hidden_states_slice + +class ScannedChunk(NamedTuple): + chunk_idx: int + attn_chunk: AttnChunk + +def efficient_dot_product_attention( + query: Tensor, + key: Tensor, + value: Tensor, + query_chunk_size=1024, + kv_chunk_size: Optional[int] = None, + kv_chunk_size_min: Optional[int] = None, + use_checkpoint=True, +): + """Computes efficient dot-product attention given query, key, and value. + This is efficient version of attention presented in + https://arxiv.org/abs/2112.05682v2 which comes with O(sqrt(n)) memory requirements. + Args: + query: queries for calculating attention with shape of + `[batch * num_heads, tokens, channels_per_head]`. + key: keys for calculating attention with shape of + `[batch * num_heads, tokens, channels_per_head]`. + value: values to be used in attention with shape of + `[batch * num_heads, tokens, channels_per_head]`. + query_chunk_size: int: query chunks size + kv_chunk_size: Optional[int]: key/value chunks size. if None: defaults to sqrt(key_tokens) + kv_chunk_size_min: Optional[int]: key/value minimum chunk size. only considered when kv_chunk_size is None. changes `sqrt(key_tokens)` into `max(sqrt(key_tokens), kv_chunk_size_min)`, to ensure our chunk sizes don't get too small (smaller chunks = more chunks = less concurrent work done). + use_checkpoint: bool: whether to use checkpointing (recommended True for training, False for inference) + Returns: + Output of shape `[batch * num_heads, query_tokens, channels_per_head]`. + """ + batch_x_heads, q_tokens, q_channels_per_head = query.shape + _, k_tokens, _ = key.shape + scale = q_channels_per_head ** -0.5 + + kv_chunk_size = min(kv_chunk_size or int(math.sqrt(k_tokens)), k_tokens) + if kv_chunk_size_min is not None: + kv_chunk_size = max(kv_chunk_size, kv_chunk_size_min) + + def get_query_chunk(chunk_idx: int) -> Tensor: + return dynamic_slice( + query, + (0, chunk_idx, 0), + (batch_x_heads, min(query_chunk_size, q_tokens), q_channels_per_head) + ) + + summarize_chunk: SummarizeChunk = partial(_summarize_chunk, scale=scale) + summarize_chunk: SummarizeChunk = partial(checkpoint, summarize_chunk) if use_checkpoint else summarize_chunk + compute_query_chunk_attn: ComputeQueryChunkAttn = partial( + _get_attention_scores_no_kv_chunking, + scale=scale + ) if k_tokens <= kv_chunk_size else ( + # fast-path for when there's just 1 key-value chunk per query chunk (this is just sliced attention btw) + partial( + _query_chunk_attention, + kv_chunk_size=kv_chunk_size, + summarize_chunk=summarize_chunk, + ) + ) + + if q_tokens <= query_chunk_size: + # fast-path for when there's just 1 query chunk + return compute_query_chunk_attn( + query=query, + key=key, + value=value, + ) + + # TODO: maybe we should use torch.empty_like(query) to allocate storage in-advance, + # and pass slices to be mutated, instead of torch.cat()ing the returned slices + res = torch.cat([ + compute_query_chunk_attn( + query=get_query_chunk(i * query_chunk_size), + key=key, + value=value, + ) for i in range(math.ceil(q_tokens / query_chunk_size)) + ], dim=1) + return res diff --git a/requirements.txt b/requirements.txt index 5bed694e..0dbea322 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,4 +30,4 @@ inflection GitPython torchsde safetensors -psutil; sys_platform == 'darwin' +psutil From b119815333026164f2bd7d1ca71f3e4f7a9afd0d Mon Sep 17 00:00:00 2001 From: brkirch Date: Thu, 5 Jan 2023 04:37:17 -0500 Subject: [PATCH 1030/1118] Use narrow instead of dynamic_slice --- modules/sub_quadratic_attention.py | 34 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index b11dc1c7..95924d24 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -5,6 +5,7 @@ # credit: # Amin Rezaei (original author) # Alex Birch (optimized algorithm for 3D tensors, at the expense of removing bias, masking and callbacks) +# brkirch (modified to use torch.narrow instead of dynamic_slice implementation) # implementation of: # Self-attention Does Not Need O(n2) Memory": # https://arxiv.org/abs/2112.05682v2 @@ -16,13 +17,13 @@ from torch.utils.checkpoint import checkpoint import math from typing import Optional, NamedTuple, Protocol, List -def dynamic_slice( - x: Tensor, - starts: List[int], - sizes: List[int], +def narrow_trunc( + input: Tensor, + dim: int, + start: int, + length: int ) -> Tensor: - slicing = [slice(start, start + size) for start, size in zip(starts, sizes)] - return x[slicing] + return torch.narrow(input, dim, start, length if input.shape[dim] >= start + length else input.shape[dim] - start) class AttnChunk(NamedTuple): exp_values: Tensor @@ -76,15 +77,17 @@ def _query_chunk_attention( _, _, v_channels_per_head = value.shape def chunk_scanner(chunk_idx: int) -> AttnChunk: - key_chunk = dynamic_slice( + key_chunk = narrow_trunc( key, - (0, chunk_idx, 0), - (batch_x_heads, kv_chunk_size, k_channels_per_head) + 1, + chunk_idx, + kv_chunk_size ) - value_chunk = dynamic_slice( + value_chunk = narrow_trunc( value, - (0, chunk_idx, 0), - (batch_x_heads, kv_chunk_size, v_channels_per_head) + 1, + chunk_idx, + kv_chunk_size ) return summarize_chunk(query, key_chunk, value_chunk) @@ -161,10 +164,11 @@ def efficient_dot_product_attention( kv_chunk_size = max(kv_chunk_size, kv_chunk_size_min) def get_query_chunk(chunk_idx: int) -> Tensor: - return dynamic_slice( + return narrow_trunc( query, - (0, chunk_idx, 0), - (batch_x_heads, min(query_chunk_size, q_tokens), q_channels_per_head) + 1, + chunk_idx, + min(query_chunk_size, q_tokens) ) summarize_chunk: SummarizeChunk = partial(_summarize_chunk, scale=scale) From 683287d87f6401083a8d63eedc00ca7410214ca1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 6 Jan 2023 08:52:06 +0300 Subject: [PATCH 1031/1118] rework saving training params to file #6372 --- modules/hypernetworks/hypernetwork.py | 28 +++++-------------- modules/shared.py | 2 +- modules/textual_inversion/logging.py | 24 ++++++++++++++++ .../textual_inversion/textual_inversion.py | 23 ++------------- 4 files changed, 35 insertions(+), 42 deletions(-) create mode 100644 modules/textual_inversion/logging.py diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 3237c37a..b0cfbe71 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -13,7 +13,7 @@ import tqdm from einops import rearrange, repeat from ldm.util import default from modules import devices, processing, sd_models, shared, sd_samplers -from modules.textual_inversion import textual_inversion +from modules.textual_inversion import textual_inversion, logging from modules.textual_inversion.learn_schedule import LearnRateScheduler from torch import einsum from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_ @@ -401,25 +401,7 @@ def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, hypernet.save(fn) shared.reload_hypernetworks() -# Note: textual_inversion.py has a nearly identical function of the same name. -def save_settings_to_file(model_name, model_hash, initial_step, num_of_dataset_images, hypernetwork_name, layer_structure, activation_func, weight_init, add_layer_norm, use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): - # Starting index of preview-related arguments. - border_index = 21 - # Get a list of the argument names. - arg_names = inspect.getfullargspec(save_settings_to_file).args - # Create a list of the argument names to include in the settings string. - names = arg_names[:border_index] # Include all arguments up until the preview-related ones. - if preview_from_txt2img: - names.extend(arg_names[border_index:]) # Include preview-related arguments if applicable. - # Build the settings string. - settings_str = "datetime : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" - for name in names: - if name != 'log_directory': # It's useless and redundant to save log_directory. - value = locals()[name] - settings_str += f"{name}: {value}\n" - # Create or append to the file. - with open(os.path.join(log_directory, 'settings.txt'), "a+") as fout: - fout.write(settings_str + "\n\n") + def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # images allows training previews to have infotext. Importing it at the top causes a circular import problem. @@ -477,7 +459,11 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step, ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method) if shared.opts.save_training_settings_to_txt: - save_settings_to_file(checkpoint.model_name, '[{}]'.format(checkpoint.hash), initial_step, len(ds), hypernetwork_name, hypernetwork.layer_structure, hypernetwork.activation_func, hypernetwork.weight_init, hypernetwork.add_layer_norm, hypernetwork.use_dropout, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_hypernetwork_every, template_file, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) + saved_params = dict( + model_name=checkpoint.model_name, model_hash=checkpoint.hash, num_of_dataset_images=len(ds), + **{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]} + ) + logging.save_settings_to_file(log_directory, {**saved_params, **locals()}) latent_sampling_method = ds.latent_sampling_method diff --git a/modules/shared.py b/modules/shared.py index f0e10b35..57e489d0 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -362,7 +362,7 @@ options_templates.update(options_section(('training', "Training"), { "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."), "pin_memory": OptionInfo(False, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."), "save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training of embedding or HN can be resumed with the matching optim file."), - "save_training_settings_to_txt": OptionInfo(False, "Save textual inversion and hypernet settings to a text file whenever training starts."), + "save_training_settings_to_txt": OptionInfo(True, "Save textual inversion and hypernet settings to a text file whenever training starts."), "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), diff --git a/modules/textual_inversion/logging.py b/modules/textual_inversion/logging.py new file mode 100644 index 00000000..8b1981d5 --- /dev/null +++ b/modules/textual_inversion/logging.py @@ -0,0 +1,24 @@ +import datetime +import json +import os + +saved_params_shared = {"model_name", "model_hash", "initial_step", "num_of_dataset_images", "learn_rate", "batch_size", "data_root", "log_directory", "training_width", "training_height", "steps", "create_image_every", "template_file"} +saved_params_ti = {"embedding_name", "num_vectors_per_token", "save_embedding_every", "save_image_with_stored_embedding"} +saved_params_hypernet = {"hypernetwork_name", "layer_structure", "activation_func", "weight_init", "add_layer_norm", "use_dropout", "save_hypernetwork_every"} +saved_params_all = saved_params_shared | saved_params_ti | saved_params_hypernet +saved_params_previews = {"preview_prompt", "preview_negative_prompt", "preview_steps", "preview_sampler_index", "preview_cfg_scale", "preview_seed", "preview_width", "preview_height"} + + +def save_settings_to_file(log_directory, all_params): + now = datetime.datetime.now() + params = {"datetime": now.strftime("%Y-%m-%d %H:%M:%S")} + + keys = saved_params_all + if all_params.get('preview_from_txt2img'): + keys = keys | saved_params_previews + + params.update({k: v for k, v in all_params.items() if k in keys}) + + filename = f'settings-{now.strftime("%Y-%m-%d-%H-%M-%S")}.json' + with open(os.path.join(log_directory, filename), "w") as file: + json.dump(params, file, indent=4) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index e9cf432f..f9f5e8cd 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -18,6 +18,8 @@ from modules.textual_inversion.learn_schedule import LearnRateScheduler from modules.textual_inversion.image_embedding import (embedding_to_b64, embedding_from_b64, insert_image_data_embed, extract_image_data_embed, caption_image_overlay) +from modules.textual_inversion.logging import save_settings_to_file + class Embedding: def __init__(self, vec, name, step=None): @@ -231,25 +233,6 @@ def write_loss(log_directory, filename, step, epoch_len, values): **values, }) -# Note: hypernetwork.py has a nearly identical function of the same name. -def save_settings_to_file(model_name, model_hash, initial_step, num_of_dataset_images, embedding_name, vectors_per_token, learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): - # Starting index of preview-related arguments. - border_index = 18 - # Get a list of the argument names. - arg_names = inspect.getfullargspec(save_settings_to_file).args - # Create a list of the argument names to include in the settings string. - names = arg_names[:border_index] # Include all arguments up until the preview-related ones. - if preview_from_txt2img: - names.extend(arg_names[border_index:]) # Include preview-related arguments if applicable. - # Build the settings string. - settings_str = "datetime : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" - for name in names: - if name != 'log_directory': # It's useless and redundant to save log_directory. - value = locals()[name] - settings_str += f"{name}: {value}\n" - # Create or append to the file. - with open(os.path.join(log_directory, 'settings.txt'), "a+") as fout: - fout.write(settings_str + "\n\n") def validate_train_inputs(model_name, learn_rate, batch_size, gradient_step, data_root, template_file, steps, save_model_every, create_image_every, log_directory, name="embedding"): assert model_name, f"{name} not selected" @@ -330,7 +313,7 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_ ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method) if shared.opts.save_training_settings_to_txt: - save_settings_to_file(checkpoint.model_name, '[{}]'.format(checkpoint.hash), initial_step, len(ds), embedding_name, len(embedding.vec), learn_rate, batch_size, data_root, log_directory, training_width, training_height, steps, create_image_every, save_embedding_every, template_file, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height) + save_settings_to_file(log_directory, {**dict(model_name=checkpoint.model_name, model_hash=checkpoint.hash, num_of_dataset_images=len(ds), num_vectors_per_token=len(embedding.vec)), **locals()}) latent_sampling_method = ds.latent_sampling_method From b95a4c0ce5ab9c414e0494193bfff665f45e9e65 Mon Sep 17 00:00:00 2001 From: brkirch Date: Fri, 6 Jan 2023 01:01:51 -0500 Subject: [PATCH 1032/1118] Change sub-quad chunk threshold to use percentage --- modules/sd_hijack_optimizations.py | 18 +++++++++--------- modules/shared.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index f5c153e8..b416e9ac 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -233,7 +233,7 @@ def sub_quad_attention_forward(self, x, context=None, mask=None): k = k.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1) v = v.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1) - x = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold_bytes=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training) + x = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training) x = x.unflatten(0, (-1, h)).transpose(1,2).flatten(start_dim=2) @@ -243,20 +243,20 @@ def sub_quad_attention_forward(self, x, context=None, mask=None): return x -def sub_quad_attention(q, k, v, q_chunk_size=1024, kv_chunk_size=None, kv_chunk_size_min=None, chunk_threshold_bytes=None, use_checkpoint=True): +def sub_quad_attention(q, k, v, q_chunk_size=1024, kv_chunk_size=None, kv_chunk_size_min=None, chunk_threshold=None, use_checkpoint=True): bytes_per_token = torch.finfo(q.dtype).bits//8 batch_x_heads, q_tokens, _ = q.shape _, k_tokens, _ = k.shape qk_matmul_size_bytes = batch_x_heads * bytes_per_token * q_tokens * k_tokens - available_vram = int(get_available_vram() * 0.9) if q.device.type == 'mps' else int(get_available_vram() * 0.7) - - if chunk_threshold_bytes is None: - chunk_threshold_bytes = available_vram - elif chunk_threshold_bytes == 0: + if chunk_threshold is None: + chunk_threshold_bytes = int(get_available_vram() * 0.9) if q.device.type == 'mps' else int(get_available_vram() * 0.7) + elif chunk_threshold == 0: chunk_threshold_bytes = None + else: + chunk_threshold_bytes = int(0.01 * chunk_threshold * get_available_vram()) - if kv_chunk_size_min is None: + if kv_chunk_size_min is None and chunk_threshold_bytes is not None: kv_chunk_size_min = chunk_threshold_bytes // (batch_x_heads * bytes_per_token * (k.shape[2] + v.shape[2])) elif kv_chunk_size_min == 0: kv_chunk_size_min = None @@ -382,7 +382,7 @@ def sub_quad_attnblock_forward(self, x): q = q.contiguous() k = k.contiguous() v = v.contiguous() - out = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold_bytes=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training) + out = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training) out = rearrange(out, 'b (h w) c -> b c h w', h=h) out = self.proj_out(out) return x + out diff --git a/modules/shared.py b/modules/shared.py index cb1dc312..d7a81db1 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -59,7 +59,7 @@ parser.add_argument("--opt-split-attention", action='store_true', help="force-en parser.add_argument("--opt-sub-quad-attention", action='store_true', help="enable memory efficient sub-quadratic cross-attention layer optimization") parser.add_argument("--sub-quad-q-chunk-size", type=int, help="query chunk size for the sub-quadratic cross-attention layer optimization to use", default=1024) parser.add_argument("--sub-quad-kv-chunk-size", type=int, help="kv chunk size for the sub-quadratic cross-attention layer optimization to use", default=None) -parser.add_argument("--sub-quad-chunk-threshold", type=int, help="the size threshold in bytes for the sub-quadratic cross-attention layer optimization to use chunking", default=None) +parser.add_argument("--sub-quad-chunk-threshold", type=int, help="the percentage of VRAM threshold for the sub-quadratic cross-attention layer optimization to use chunking", default=None) parser.add_argument("--opt-split-attention-invokeai", action='store_true', help="force-enables InvokeAI's cross-attention layer optimization. By default, it's on when cuda is unavailable.") parser.add_argument("--opt-split-attention-v1", action='store_true', help="enable older version of split attention optimization that does not consume all the VRAM it can find") parser.add_argument("--disable-opt-split-attention", action='store_true', help="force-disables cross-attention layer optimization") From 5deb2a19ccea57a50252e8fcb07b4d17c6599def Mon Sep 17 00:00:00 2001 From: brkirch Date: Fri, 6 Jan 2023 01:33:15 -0500 Subject: [PATCH 1033/1118] Allow Doggettx's cross attention opt without CUDA --- modules/sd_hijack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index ef25dadb..bd101e5b 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -50,7 +50,7 @@ def apply_optimizations(): print("Applying v1 cross attention optimization.") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_v1 optimization_method = 'V1' - elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention_invokeai or not torch.cuda.is_available()): + elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention_invokeai or not cmd_opts.opt_split_attention and not torch.cuda.is_available()): print("Applying cross attention optimization (InvokeAI).") ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_invokeAI optimization_method = 'InvokeAI' From c9bded39ee05bd0507ccd27d2b674d86d6c0c8e8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 6 Jan 2023 12:32:44 +0300 Subject: [PATCH 1034/1118] sort extensions by date and add an option to sort by other columns --- modules/ui_extensions.py | 44 +++++++++++++++++++++++++++++----------- style.css | 11 +++++++++- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index eec9586f..742e745e 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -162,15 +162,15 @@ def install_extension_from_url(dirname, url): shutil.rmtree(tmpdir, True) -def install_extension_from_index(url, hide_tags): +def install_extension_from_index(url, hide_tags, sort_column): ext_table, message = install_extension_from_url(None, url) - code, _ = refresh_available_extensions_from_data(hide_tags) + code, _ = refresh_available_extensions_from_data(hide_tags, sort_column) return code, ext_table, message -def refresh_available_extensions(url, hide_tags): +def refresh_available_extensions(url, hide_tags, sort_column): global available_extensions import urllib.request @@ -179,18 +179,28 @@ def refresh_available_extensions(url, hide_tags): available_extensions = json.loads(text) - code, tags = refresh_available_extensions_from_data(hide_tags) + code, tags = refresh_available_extensions_from_data(hide_tags, sort_column) return url, code, gr.CheckboxGroup.update(choices=tags), '' -def refresh_available_extensions_for_tags(hide_tags): - code, _ = refresh_available_extensions_from_data(hide_tags) +def refresh_available_extensions_for_tags(hide_tags, sort_column): + code, _ = refresh_available_extensions_from_data(hide_tags, sort_column) return code, '' -def refresh_available_extensions_from_data(hide_tags): +sort_ordering = [ + # (reverse, order_by_function) + (True, lambda x: x.get('added', 'z')), + (False, lambda x: x.get('added', 'z')), + (False, lambda x: x.get('name', 'z')), + (True, lambda x: x.get('name', 'z')), + (False, lambda x: 'z'), +] + + +def refresh_available_extensions_from_data(hide_tags, sort_column): extlist = available_extensions["extensions"] installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions} @@ -210,8 +220,11 @@ def refresh_available_extensions_from_data(hide_tags): """ - for ext in extlist: + sort_reverse, sort_function = sort_ordering[sort_column if 0 <= sort_column < len(sort_ordering) else 0] + + for ext in sorted(extlist, key=sort_function, reverse=sort_reverse): name = ext.get("name", "noname") + added = ext.get('added', 'unknown') url = ext.get("url", None) description = ext.get("description", "") extension_tags = ext.get("tags", []) @@ -233,7 +246,7 @@ def refresh_available_extensions_from_data(hide_tags): code += f""" {html.escape(name)}
{tags_text} - {html.escape(description)} + {html.escape(description)}

Added: {html.escape(added)}

{install_code} @@ -291,25 +304,32 @@ def create_ui(): with gr.Row(): hide_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Hide extensions with tags", choices=["script", "ads", "localization", "installed"]) + sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order", ], type="index") install_result = gr.HTML() available_extensions_table = gr.HTML() refresh_available_extensions_button.click( fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update()]), - inputs=[available_extensions_index, hide_tags], + inputs=[available_extensions_index, hide_tags, sort_column], outputs=[available_extensions_index, available_extensions_table, hide_tags, install_result], ) install_extension_button.click( fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]), - inputs=[extension_to_install, hide_tags], + inputs=[extension_to_install, hide_tags, sort_column], outputs=[available_extensions_table, extensions_table, install_result], ) hide_tags.change( fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), - inputs=[hide_tags], + inputs=[hide_tags, sort_column], + outputs=[available_extensions_table, install_result] + ) + + sort_column.change( + fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + inputs=[hide_tags, sort_column], outputs=[available_extensions_table, install_result] ) diff --git a/style.css b/style.css index ee74d79e..f1b23b53 100644 --- a/style.css +++ b/style.css @@ -555,7 +555,7 @@ img2maskimg, #img2maskimg > .h-60, #img2maskimg > .h-60 > div, #img2maskimg > .h /* Extensions */ -#tab_extensions table{ +#tab_extensions table``{ border-collapse: collapse; } @@ -581,6 +581,15 @@ img2maskimg, #img2maskimg > .h-60, #img2maskimg > .h-60 > div, #img2maskimg > .h font-size: 95%; } +#available_extensions .info{ + margin: 0; +} + +#available_extensions .date_added{ + opacity: 0.85; + font-size: 90%; +} + #image_buttons_txt2img button, #image_buttons_img2img button, #image_buttons_extras button{ min-width: auto; padding-left: 0.5em; From 65ed4421e609dda3112f236c13e4db14caa71364 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 6 Jan 2023 13:55:50 +0300 Subject: [PATCH 1035/1118] add callback for when the script is unloaded --- modules/script_callbacks.py | 18 +++++++++++++++++- webui.py | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index de69fd9f..608c5300 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -71,6 +71,7 @@ callback_map = dict( callbacks_before_component=[], callbacks_after_component=[], callbacks_image_grid=[], + callbacks_script_unloaded=[], ) @@ -171,6 +172,14 @@ def image_grid_callback(params: ImageGridLoopParams): report_exception(c, 'image_grid') +def script_unloaded_callback(): + for c in reversed(callback_map['callbacks_script_unloaded']): + try: + c.callback() + except Exception: + report_exception(c, 'script_unloaded') + + def add_callback(callbacks, fun): stack = [x for x in inspect.stack() if x.filename != __file__] filename = stack[0].filename if len(stack) > 0 else 'unknown file' @@ -202,7 +211,7 @@ def on_app_started(callback): def on_model_loaded(callback): """register a function to be called when the stable diffusion model is created; the model is - passed as an argument""" + passed as an argument; this function is also called when the script is reloaded. """ add_callback(callback_map['callbacks_model_loaded'], callback) @@ -279,3 +288,10 @@ def on_image_grid(callback): - params: ImageGridLoopParams - parameters to be used for grid creation. Can be modified. """ add_callback(callback_map['callbacks_image_grid'], callback) + + +def on_script_unloaded(callback): + """register a function to be called before the script is unloaded. Any hooks/hijacks/monkeying about that + the script did should be reverted here""" + + add_callback(callback_map['callbacks_script_unloaded'], callback) diff --git a/webui.py b/webui.py index ff6eb6eb..733a06b5 100644 --- a/webui.py +++ b/webui.py @@ -187,12 +187,14 @@ def webui(): sd_samplers.set_samplers() + modules.script_callbacks.script_unloaded_callback() extensions.list_extensions() localization.list_localizations(cmd_opts.localizations_dir) modelloader.forbid_loaded_nonbuiltin_upscalers() modules.scripts.reload_scripts() + modules.script_callbacks.model_loaded_callback(shared.sd_model) modelloader.load_upscalers() for module in [module for name, module in sys.modules.items() if name.startswith("modules.ui")]: From 848605fb654a55ee6947335d7df6e13366606fad Mon Sep 17 00:00:00 2001 From: brkirch Date: Fri, 6 Jan 2023 06:58:49 -0500 Subject: [PATCH 1036/1118] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1944d33..fea6cb35 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Licenses for borrowed code can be found in `Settings -> Licenses` screen, and al - Ideas for optimizations - https://github.com/basujindal/stable-diffusion - Cross Attention layer optimization - Doggettx - https://github.com/Doggettx/stable-diffusion, original idea for prompt editing. - Cross Attention layer optimization - InvokeAI, lstein - https://github.com/invoke-ai/InvokeAI (originally http://github.com/lstein/stable-diffusion) -- Sub-quadratic Cross Attention layer optimization - Alex Birch (https://github.com/Birch-san), Amin Rezaei (https://github.com/AminRezaei0x443) +- Sub-quadratic Cross Attention layer optimization - Alex Birch (https://github.com/Birch-san/diffusers/pull/1), Amin Rezaei (https://github.com/AminRezaei0x443/memory-efficient-attention) - Textual Inversion - Rinon Gal - https://github.com/rinongal/textual_inversion (we're not using his code, but we are using his ideas). - Idea for SD upscale - https://github.com/jquesnelle/txt2imghd - Noise generation for outpainting mk2 - https://github.com/parlance-zz/g-diffuser-bot From 5e6566324bba20554bcc04f3dda798e560397f38 Mon Sep 17 00:00:00 2001 From: brkirch Date: Fri, 6 Jan 2023 07:06:26 -0500 Subject: [PATCH 1037/1118] Always end version number with a digit --- webui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 733a06b5..8737e593 100644 --- a/webui.py +++ b/webui.py @@ -16,7 +16,7 @@ from modules.paths import script_path import torch # Truncate version number of nightly/local build of PyTorch to not cause exceptions with CodeFormer or Safetensors if ".dev" in torch.__version__ or "+git" in torch.__version__: - torch.__version__ = re.search(r'[\d.]+', torch.__version__).group(0) + torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) from modules import shared, devices, sd_samplers, upscaler, extensions, localization, ui_tempdir import modules.codeformer_model as codeformer From 3246a2d6b898da6a98fe9df4dc67944635a41bd3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 6 Jan 2023 16:03:43 +0300 Subject: [PATCH 1038/1118] remove restriction for saving dropdowns to ui-config.json --- modules/scripts.py | 1 - modules/ui.py | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/modules/scripts.py b/modules/scripts.py index 0c44f191..35164093 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -290,7 +290,6 @@ class ScriptRunner: script.group = group dropdown = gr.Dropdown(label="Script", elem_id="script_list", choices=["None"] + self.titles, value="None", type="index") - dropdown.save_to_config = True inputs[0] = dropdown for script in self.selectable_scripts: diff --git a/modules/ui.py b/modules/ui.py index 030f0685..b79d24ee 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -435,11 +435,9 @@ def create_toprow(is_img2img): with gr.Row(): with gr.Column(scale=1, elem_id="style_pos_col"): prompt_style = gr.Dropdown(label="Style 1", elem_id=f"{id_part}_style_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys()))) - prompt_style.save_to_config = True with gr.Column(scale=1, elem_id="style_neg_col"): prompt_style2 = gr.Dropdown(label="Style 2", elem_id=f"{id_part}_style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys()))) - prompt_style2.save_to_config = True return prompt, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button @@ -638,7 +636,6 @@ def create_sampler_and_steps_selection(choices, tabname): if opts.samplers_in_dropdown: with FormRow(elem_id=f"sampler_selection_{tabname}"): sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value=choices[0].name, type="index") - sampler_index.save_to_config = True steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=20) else: with FormGroup(elem_id=f"sampler_selection_{tabname}"): @@ -1794,7 +1791,7 @@ def create_ui(): if init_field is not None: init_field(saved_value) - if type(x) in [gr.Slider, gr.Radio, gr.Checkbox, gr.Textbox, gr.Number] and x.visible: + if type(x) in [gr.Slider, gr.Radio, gr.Checkbox, gr.Textbox, gr.Number, gr.Dropdown] and x.visible: apply_field(x, 'visible') if type(x) == gr.Slider: @@ -1815,11 +1812,8 @@ def create_ui(): if type(x) == gr.Number: apply_field(x, 'value') - # Since there are many dropdowns that shouldn't be saved, - # we only mark dropdowns that should be saved. - if type(x) == gr.Dropdown and getattr(x, 'save_to_config', False): + if type(x) == gr.Dropdown: apply_field(x, 'value', lambda val: val in x.choices, getattr(x, 'init_field', None)) - apply_field(x, 'visible') visit(txt2img_interface, loadsave, "txt2img") visit(img2img_interface, loadsave, "img2img") From 50194de93ffc9db763d9b08fcc9c3bde1aa86151 Mon Sep 17 00:00:00 2001 From: Kuma <36082288+KumiIT@users.noreply.github.com> Date: Fri, 6 Jan 2023 16:12:45 +0100 Subject: [PATCH 1039/1118] typo UI fixes #6391 --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 57e489d0..865c3c07 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -430,7 +430,7 @@ options_templates.update(options_section(('ui', "User interface"), { "samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group"), "dimensions_and_batch_together": OptionInfo(True, "Show Witdth/Height and Batch sliders in same row"), 'quicksettings': OptionInfo("sd_model_checkpoint", "Quicksettings list"), - 'ui_reorder': OptionInfo(", ".join(ui_reorder_categories), "txt2img/ing2img UI item order"), + 'ui_reorder': OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), 'localization': OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), })) From 3992ecbe6e46a465062508c677964534e7397f72 Mon Sep 17 00:00:00 2001 From: Mitchell Boot <47387831+Mitchell1711@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:02:46 +0100 Subject: [PATCH 1040/1118] Added UI elements Added a new row to hires fix that shows the new resolution after scaling --- modules/ui.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/ui.py b/modules/ui.py index b79d24ee..20f7d2a2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -255,6 +255,12 @@ def add_style(name: str, prompt: str, negative_prompt: str): return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(4)] +def calc_resolution_hires(x, y, scale): + #final res can only be a multiple of 8 + scaled_x = int(x * scale // 8) * 8 + scaled_y = int(y * scale // 8) * 8 + + return "

Upscaled Resolution: "+str(scaled_x)+"x"+str(scaled_y)+"

" def apply_styles(prompt, prompt_neg, style1_name, style2_name): prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, [style1_name, style2_name]) @@ -718,6 +724,12 @@ def create_ui(): hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") hr_resize_x = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") hr_resize_y = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") + + with FormRow(elem_id="txt2img_hires_fix_row3"): + hr_final_resolution = gr.HTML(value=calc_resolution_hires(width.value, height.value, hr_scale.value), elem_id="txtimg_hr_finalres") + hr_scale.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) + width.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) + height.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) elif category == "batch": if not opts.dimensions_and_batch_together: From 991368c8d54404d8e13d4c6e76a0f32644e65ad4 Mon Sep 17 00:00:00 2001 From: Mitchell Boot <47387831+Mitchell1711@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:24:29 +0100 Subject: [PATCH 1041/1118] remove camelcase --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 20f7d2a2..6fc8b7d7 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -260,7 +260,7 @@ def calc_resolution_hires(x, y, scale): scaled_x = int(x * scale // 8) * 8 scaled_y = int(y * scale // 8) * 8 - return "

Upscaled Resolution: "+str(scaled_x)+"x"+str(scaled_y)+"

" + return "

Upscaled resolution: "+str(scaled_x)+"x"+str(scaled_y)+"

" def apply_styles(prompt, prompt_neg, style1_name, style2_name): prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, [style1_name, style2_name]) From c18add68ef7d2de3617cbbaff864b0c74cfdf6c0 Mon Sep 17 00:00:00 2001 From: brkirch Date: Fri, 6 Jan 2023 16:42:47 -0500 Subject: [PATCH 1042/1118] Added license --- html/licenses.html | 29 ++++++++++++++++++++++++++++- modules/sd_hijack_optimizations.py | 1 + modules/sub_quadratic_attention.py | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/html/licenses.html b/html/licenses.html index 9eeaa072..570630eb 100644 --- a/html/licenses.html +++ b/html/licenses.html @@ -184,7 +184,7 @@ SOFTWARE.

SwinIR

-Code added by contirubtors, most likely copied from this repository. +Code added by contributors, most likely copied from this repository.
                                  Apache License
@@ -390,3 +390,30 @@ SOFTWARE.
    limitations under the License.
 
+

Memory Efficient Attention

+The sub-quadratic cross attention optimization uses modified code from the Memory Efficient Attention package that Alex Birch optimized for 3D tensors. This license is updated to reflect that. +
+MIT License
+
+Copyright (c) 2023 Alex Birch
+Copyright (c) 2023 Amin Rezaei
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+ diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index b416e9ac..cdc63ed7 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -216,6 +216,7 @@ def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): # Based on Birch-san's modified implementation of sub-quadratic attention from https://github.com/Birch-san/diffusers/pull/1 +# The sub_quad_attention_forward function is under the MIT License listed under Memory Efficient Attention in the Licenses section of the web UI interface def sub_quad_attention_forward(self, x, context=None, mask=None): assert mask is None, "attention-mask not currently implemented for SubQuadraticCrossAttnProcessor." diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index 95924d24..fea7aaac 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -1,7 +1,7 @@ # original source: # https://github.com/AminRezaei0x443/memory-efficient-attention/blob/1bc0d9e6ac5f82ea43a375135c4e1d3896ee1694/memory_efficient_attention/attention_torch.py # license: -# unspecified +# MIT License (see Memory Efficient Attention under the Licenses section in the web UI interface for the full license) # credit: # Amin Rezaei (original author) # Alex Birch (optimized algorithm for 3D tensors, at the expense of removing bias, masking and callbacks) From 82c1f10b144f733460feead0bdc37a861489dc57 Mon Sep 17 00:00:00 2001 From: Dean Hopkins Date: Fri, 6 Jan 2023 22:00:12 +0000 Subject: [PATCH 1043/1118] increase upscale api validation limit --- modules/api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/models.py b/modules/api/models.py index f77951fc..22b88c59 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -125,7 +125,7 @@ class ExtrasBaseRequest(BaseModel): gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.") codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.") codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.") - upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=4, description="By how much to upscale the image, only used when resize_mode=0.") + upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=8, description="By how much to upscale the image, only used when resize_mode=0.") upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.") upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.") upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the choosen size?") From 79e39fae6110c20a3ee6255e2841c877f65e8cbd Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 7 Jan 2023 01:45:28 +0300 Subject: [PATCH 1044/1118] CLIP hijack rework --- modules/sd_hijack.py | 6 +- modules/sd_hijack_clip.py | 328 +++++++++--------- modules/sd_hijack_clip_old.py | 81 +++++ .../textual_inversion/textual_inversion.py | 1 - modules/ui.py | 2 +- 5 files changed, 246 insertions(+), 172 deletions(-) create mode 100644 modules/sd_hijack_clip_old.py diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index fa2cd4bb..71cc145a 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -150,10 +150,10 @@ class StableDiffusionModelHijack: def clear_comments(self): self.comments = [] - def tokenize(self, text): - _, remade_batch_tokens, _, _, _, token_count = self.clip.process_text([text]) + def get_prompt_lengths(self, text): + _, token_count = self.clip.process_texts([text]) - return remade_batch_tokens[0], token_count, sd_hijack_clip.get_target_prompt_token_count(token_count) + return token_count, self.clip.get_target_prompt_token_count(token_count) class EmbeddingsWithFixes(torch.nn.Module): diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index ca92b142..ac3020d7 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -1,12 +1,28 @@ import math +from collections import namedtuple import torch from modules import prompt_parser, devices from modules.shared import opts -def get_target_prompt_token_count(token_count): - return math.ceil(max(token_count, 1) / 75) * 75 + +class PromptChunk: + """ + This object contains token ids, weight (multipliers:1.4) and textual inversion embedding info for a chunk of prompt. + If a prompt is short, it is represented by one PromptChunk, otherwise, multiple are necessary. + Each PromptChunk contains an exact amount of tokens - 77, which includes one for start and end token, + so just 75 tokens from prompt. + """ + + def __init__(self): + self.tokens = [] + self.multipliers = [] + self.fixes = [] + + +PromptChunkFix = namedtuple('PromptChunkFix', ['offset', 'embedding']) +"""This is a marker showing that textual inversion embedding's vectors have to placed at offset in the prompt chunk""" class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): @@ -14,17 +30,49 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): super().__init__() self.wrapped = wrapped self.hijack = hijack + self.chunk_length = 75 + + def empty_chunk(self): + """creates an empty PromptChunk and returns it""" + + chunk = PromptChunk() + chunk.tokens = [self.id_start] + [self.id_end] * (self.chunk_length + 1) + chunk.multipliers = [1.0] * (self.chunk_length + 2) + return chunk + + def get_target_prompt_token_count(self, token_count): + """returns the maximum number of tokens a prompt of a known length can have before it requires one more PromptChunk to be represented""" + + return math.ceil(max(token_count, 1) / self.chunk_length) * self.chunk_length def tokenize(self, texts): + """Converts a batch of texts into a batch of token ids""" + raise NotImplementedError def encode_with_transformers(self, tokens): + """ + converts a batch of token ids (in python lists) into a single tensor with numeric respresentation of those tokens; + All python lists with tokens are assumed to have same length, usually 77. + if input is a list with B elements and each element has T tokens, expected output shape is (B, T, C), where C depends on + model - can be 768 and 1024 + """ + raise NotImplementedError def encode_embedding_init_text(self, init_text, nvpt): + """Converts text into a tensor with this text's tokens' embeddings. Note that those are embeddings before they are passed through + transformers. nvpt is used as a maximum length in tokens. If text produces less teokens than nvpt, only this many is returned.""" + raise NotImplementedError - def tokenize_line(self, line, used_custom_terms, hijack_comments): + def tokenize_line(self, line): + """ + this transforms a single prompt into a list of PromptChunk objects - as many as needed to + represent the prompt. + Returns the list and the total number of tokens in the prompt. + """ + if opts.enable_emphasis: parsed = prompt_parser.parse_prompt_attention(line) else: @@ -32,205 +80,152 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): tokenized = self.tokenize([text for text, _ in parsed]) - fixes = [] - remade_tokens = [] - multipliers = [] + chunks = [] + chunk = PromptChunk() + token_count = 0 last_comma = -1 - for tokens, (text, weight) in zip(tokenized, parsed): - i = 0 - while i < len(tokens): - token = tokens[i] + def next_chunk(): + """puts current chunk into the list of results and produces the next one - empty""" + nonlocal token_count + nonlocal last_comma + nonlocal chunk - embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, i) + token_count += len(chunk.tokens) + to_add = self.chunk_length - len(chunk.tokens) + if to_add > 0: + chunk.tokens += [self.id_end] * to_add + chunk.multipliers += [1.0] * to_add + + chunk.tokens = [self.id_start] + chunk.tokens + [self.id_end] + chunk.multipliers = [1.0] + chunk.multipliers + [1.0] + + last_comma = -1 + chunks.append(chunk) + chunk = PromptChunk() + + for tokens, (text, weight) in zip(tokenized, parsed): + position = 0 + while position < len(tokens): + token = tokens[position] if token == self.comma_token: - last_comma = len(remade_tokens) - elif opts.comma_padding_backtrack != 0 and max(len(remade_tokens), 1) % 75 == 0 and last_comma != -1 and len(remade_tokens) - last_comma <= opts.comma_padding_backtrack: - last_comma += 1 - reloc_tokens = remade_tokens[last_comma:] - reloc_mults = multipliers[last_comma:] + last_comma = len(chunk.tokens) - remade_tokens = remade_tokens[:last_comma] - length = len(remade_tokens) + # this is when we are at the end of alloted 75 tokens for the current chunk, and the current token is not a comma. opts.comma_padding_backtrack + # is a setting that specifies that is there is a comma nearby, the text after comma should be moved out of this chunk and into the next. + elif opts.comma_padding_backtrack != 0 and len(chunk.tokens) == self.chunk_length and last_comma != -1 and len(chunk.tokens) - last_comma <= opts.comma_padding_backtrack: + break_location = last_comma + 1 - rem = int(math.ceil(length / 75)) * 75 - length - remade_tokens += [self.id_end] * rem + reloc_tokens - multipliers = multipliers[:last_comma] + [1.0] * rem + reloc_mults + reloc_tokens = chunk.tokens[break_location:] + reloc_mults = chunk.multipliers[break_location:] + chunk.tokens = chunk.tokens[:break_location] + chunk.multipliers = chunk.multipliers[:break_location] + + next_chunk() + chunk.tokens = reloc_tokens + chunk.multipliers = reloc_mults + + if len(chunk.tokens) == self.chunk_length: + next_chunk() + + embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, position) if embedding is None: - remade_tokens.append(token) - multipliers.append(weight) - i += 1 - else: - emb_len = int(embedding.vec.shape[0]) - iteration = len(remade_tokens) // 75 - if (len(remade_tokens) + emb_len) // 75 != iteration: - rem = (75 * (iteration + 1) - len(remade_tokens)) - remade_tokens += [self.id_end] * rem - multipliers += [1.0] * rem - iteration += 1 - fixes.append((iteration, (len(remade_tokens) % 75, embedding))) - remade_tokens += [0] * emb_len - multipliers += [weight] * emb_len - used_custom_terms.append((embedding.name, embedding.checksum())) - i += embedding_length_in_tokens + chunk.tokens.append(token) + chunk.multipliers.append(weight) + position += 1 + continue - token_count = len(remade_tokens) - prompt_target_length = get_target_prompt_token_count(token_count) - tokens_to_add = prompt_target_length - len(remade_tokens) + emb_len = int(embedding.vec.shape[0]) + if len(chunk.tokens) + emb_len > self.chunk_length: + next_chunk() - remade_tokens = remade_tokens + [self.id_end] * tokens_to_add - multipliers = multipliers + [1.0] * tokens_to_add + chunk.fixes.append(PromptChunkFix(len(chunk.tokens), embedding)) - return remade_tokens, fixes, multipliers, token_count + chunk.tokens += [0] * emb_len + chunk.multipliers += [weight] * emb_len + position += embedding_length_in_tokens + + if len(chunk.tokens) > 0: + next_chunk() + + return chunks, token_count + + def process_texts(self, texts): + """ + Accepts a list of texts and calls tokenize_line() on each, with cache. Returns the list of results and maximum + length, in tokens, of all texts. + """ - def process_text(self, texts): - used_custom_terms = [] - remade_batch_tokens = [] - hijack_comments = [] - hijack_fixes = [] token_count = 0 cache = {} - batch_multipliers = [] + batch_chunks = [] for line in texts: if line in cache: - remade_tokens, fixes, multipliers = cache[line] + chunks = cache[line] else: - remade_tokens, fixes, multipliers, current_token_count = self.tokenize_line(line, used_custom_terms, hijack_comments) + chunks, current_token_count = self.tokenize_line(line) token_count = max(current_token_count, token_count) - cache[line] = (remade_tokens, fixes, multipliers) + cache[line] = chunks - remade_batch_tokens.append(remade_tokens) - hijack_fixes.append(fixes) - batch_multipliers.append(multipliers) + batch_chunks.append(chunks) - return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count + return batch_chunks, token_count - def process_text_old(self, texts): - id_start = self.id_start - id_end = self.id_end - maxlen = self.wrapped.max_length # you get to stay at 77 - used_custom_terms = [] - remade_batch_tokens = [] - hijack_comments = [] - hijack_fixes = [] - token_count = 0 + def forward(self, texts): + """ + Accepts an array of texts; Passes texts through transformers network to create a tensor with numerical representation of those texts. + Returns a tensor with shape of (B, T, C), where B is length of the array; T is length, in tokens, of texts (including padding) - T will + be a multiple of 77; and C is dimensionality of each token - for SD1 it's 768, and for SD2 it's 1024. + An example shape returned by this function can be: (2, 77, 768). + Webui usually sends just one text at a time through this function - the only time when texts is an array with more than one elemenet + is when you do prompt editing: "a picture of a [cat:dog:0.4] eating ice cream" + """ - cache = {} - batch_tokens = self.tokenize(texts) - batch_multipliers = [] - for tokens in batch_tokens: - tuple_tokens = tuple(tokens) + if opts.use_old_emphasis_implementation: + import modules.sd_hijack_clip_old + return modules.sd_hijack_clip_old.forward_old(self, texts) - if tuple_tokens in cache: - remade_tokens, fixes, multipliers = cache[tuple_tokens] - else: - fixes = [] - remade_tokens = [] - multipliers = [] - mult = 1.0 + batch_chunks, token_count = self.process_texts(texts) - i = 0 - while i < len(tokens): - token = tokens[i] + used_embeddings = {} + chunk_count = max([len(x) for x in batch_chunks]) - embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, i) + zs = [] + for i in range(chunk_count): + batch_chunk = [chunks[i] if i < len(chunks) else self.empty_chunk() for chunks in batch_chunks] - mult_change = self.token_mults.get(token) if opts.enable_emphasis else None - if mult_change is not None: - mult *= mult_change - i += 1 - elif embedding is None: - remade_tokens.append(token) - multipliers.append(mult) - i += 1 - else: - emb_len = int(embedding.vec.shape[0]) - fixes.append((len(remade_tokens), embedding)) - remade_tokens += [0] * emb_len - multipliers += [mult] * emb_len - used_custom_terms.append((embedding.name, embedding.checksum())) - i += embedding_length_in_tokens + tokens = [x.tokens for x in batch_chunk] + multipliers = [x.multipliers for x in batch_chunk] + self.hijack.fixes = [x.fixes for x in batch_chunk] - if len(remade_tokens) > maxlen - 2: - vocab = {v: k for k, v in self.wrapped.tokenizer.get_vocab().items()} - ovf = remade_tokens[maxlen - 2:] - overflowing_words = [vocab.get(int(x), "") for x in ovf] - overflowing_text = self.wrapped.tokenizer.convert_tokens_to_string(''.join(overflowing_words)) - hijack_comments.append(f"Warning: too many input tokens; some ({len(overflowing_words)}) have been truncated:\n{overflowing_text}\n") + for fixes in self.hijack.fixes: + for position, embedding in fixes: + used_embeddings[embedding.name] = embedding - token_count = len(remade_tokens) - remade_tokens = remade_tokens + [id_end] * (maxlen - 2 - len(remade_tokens)) - remade_tokens = [id_start] + remade_tokens[0:maxlen - 2] + [id_end] - cache[tuple_tokens] = (remade_tokens, fixes, multipliers) + z = self.process_tokens(tokens, multipliers) + zs.append(z) - multipliers = multipliers + [1.0] * (maxlen - 2 - len(multipliers)) - multipliers = [1.0] + multipliers[0:maxlen - 2] + [1.0] + if len(used_embeddings) > 0: + embeddings_list = ", ".join([f'{name} [{embedding.checksum()}]' for name, embedding in used_embeddings.items()]) + self.hijack.comments.append(f"Used embeddings: {embeddings_list}") - remade_batch_tokens.append(remade_tokens) - hijack_fixes.append(fixes) - batch_multipliers.append(multipliers) - return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count - - def forward(self, text): - use_old = opts.use_old_emphasis_implementation - if use_old: - batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text_old(text) - else: - batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = self.process_text(text) - - self.hijack.comments += hijack_comments - - if len(used_custom_terms) > 0: - self.hijack.comments.append("Used embeddings: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms])) - - if use_old: - self.hijack.fixes = hijack_fixes - return self.process_tokens(remade_batch_tokens, batch_multipliers) - - z = None - i = 0 - while max(map(len, remade_batch_tokens)) != 0: - rem_tokens = [x[75:] for x in remade_batch_tokens] - rem_multipliers = [x[75:] for x in batch_multipliers] - - self.hijack.fixes = [] - for unfiltered in hijack_fixes: - fixes = [] - for fix in unfiltered: - if fix[0] == i: - fixes.append(fix[1]) - self.hijack.fixes.append(fixes) - - tokens = [] - multipliers = [] - for j in range(len(remade_batch_tokens)): - if len(remade_batch_tokens[j]) > 0: - tokens.append(remade_batch_tokens[j][:75]) - multipliers.append(batch_multipliers[j][:75]) - else: - tokens.append([self.id_end] * 75) - multipliers.append([1.0] * 75) - - z1 = self.process_tokens(tokens, multipliers) - z = z1 if z is None else torch.cat((z, z1), axis=-2) - - remade_batch_tokens = rem_tokens - batch_multipliers = rem_multipliers - i += 1 - - return z + return torch.hstack(zs) def process_tokens(self, remade_batch_tokens, batch_multipliers): - if not opts.use_old_emphasis_implementation: - remade_batch_tokens = [[self.id_start] + x[:75] + [self.id_end] for x in remade_batch_tokens] - batch_multipliers = [[1.0] + x[:75] + [1.0] for x in batch_multipliers] - + """ + sends one single prompt chunk to be encoded by transformers neural network. + remade_batch_tokens is a batch of tokens - a list, where every element is a list of tokens; usually + there are exactly 77 tokens in the list. batch_multipliers is the same but for multipliers instead of tokens. + Multipliers are used to give more or less weight to the outputs of transformers network. Each multiplier + corresponds to one token. + """ tokens = torch.asarray(remade_batch_tokens).to(devices.device) + # this is for SD2: SD1 uses the same token for padding and end of text, while SD2 uses different ones. if self.id_end != self.id_pad: for batch_pos in range(len(remade_batch_tokens)): index = remade_batch_tokens[batch_pos].index(self.id_end) @@ -239,8 +234,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): z = self.encode_with_transformers(tokens) # restoring original mean is likely not correct, but it seems to work well to prevent artifacts that happen otherwise - batch_multipliers_of_same_length = [x + [1.0] * (75 - len(x)) for x in batch_multipliers] - batch_multipliers = torch.asarray(batch_multipliers_of_same_length).to(devices.device) + batch_multipliers = torch.asarray(batch_multipliers).to(devices.device) original_mean = z.mean() z *= batch_multipliers.reshape(batch_multipliers.shape + (1,)).expand(z.shape) new_mean = z.mean() diff --git a/modules/sd_hijack_clip_old.py b/modules/sd_hijack_clip_old.py new file mode 100644 index 00000000..6d9fbbe6 --- /dev/null +++ b/modules/sd_hijack_clip_old.py @@ -0,0 +1,81 @@ +from modules import sd_hijack_clip +from modules import shared + + +def process_text_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, texts): + id_start = self.id_start + id_end = self.id_end + maxlen = self.wrapped.max_length # you get to stay at 77 + used_custom_terms = [] + remade_batch_tokens = [] + hijack_comments = [] + hijack_fixes = [] + token_count = 0 + + cache = {} + batch_tokens = self.tokenize(texts) + batch_multipliers = [] + for tokens in batch_tokens: + tuple_tokens = tuple(tokens) + + if tuple_tokens in cache: + remade_tokens, fixes, multipliers = cache[tuple_tokens] + else: + fixes = [] + remade_tokens = [] + multipliers = [] + mult = 1.0 + + i = 0 + while i < len(tokens): + token = tokens[i] + + embedding, embedding_length_in_tokens = self.hijack.embedding_db.find_embedding_at_position(tokens, i) + + mult_change = self.token_mults.get(token) if shared.opts.enable_emphasis else None + if mult_change is not None: + mult *= mult_change + i += 1 + elif embedding is None: + remade_tokens.append(token) + multipliers.append(mult) + i += 1 + else: + emb_len = int(embedding.vec.shape[0]) + fixes.append((len(remade_tokens), embedding)) + remade_tokens += [0] * emb_len + multipliers += [mult] * emb_len + used_custom_terms.append((embedding.name, embedding.checksum())) + i += embedding_length_in_tokens + + if len(remade_tokens) > maxlen - 2: + vocab = {v: k for k, v in self.wrapped.tokenizer.get_vocab().items()} + ovf = remade_tokens[maxlen - 2:] + overflowing_words = [vocab.get(int(x), "") for x in ovf] + overflowing_text = self.wrapped.tokenizer.convert_tokens_to_string(''.join(overflowing_words)) + hijack_comments.append(f"Warning: too many input tokens; some ({len(overflowing_words)}) have been truncated:\n{overflowing_text}\n") + + token_count = len(remade_tokens) + remade_tokens = remade_tokens + [id_end] * (maxlen - 2 - len(remade_tokens)) + remade_tokens = [id_start] + remade_tokens[0:maxlen - 2] + [id_end] + cache[tuple_tokens] = (remade_tokens, fixes, multipliers) + + multipliers = multipliers + [1.0] * (maxlen - 2 - len(multipliers)) + multipliers = [1.0] + multipliers[0:maxlen - 2] + [1.0] + + remade_batch_tokens.append(remade_tokens) + hijack_fixes.append(fixes) + batch_multipliers.append(multipliers) + return batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count + + +def forward_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, texts): + batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = process_text_old(self, texts) + + self.hijack.comments += hijack_comments + + if len(used_custom_terms) > 0: + self.hijack.comments.append("Used embeddings: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms])) + + self.hijack.fixes = hijack_fixes + return self.process_tokens(remade_batch_tokens, batch_multipliers) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index f9f5e8cd..45882ed6 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -79,7 +79,6 @@ class EmbeddingDatabase: self.word_embeddings[embedding.name] = embedding - # TODO changing between clip and open clip changes tokenization, which will cause embeddings to stop working ids = model.cond_stage_model.tokenize([embedding.name])[0] first_id = ids[0] diff --git a/modules/ui.py b/modules/ui.py index b79d24ee..5d2f5bad 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -368,7 +368,7 @@ def update_token_counter(text, steps): flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) prompts = [prompt_text for step, prompt_text in flat_prompts] - tokens, token_count, max_length = max([model_hijack.tokenize(prompt) for prompt in prompts], key=lambda args: args[1]) + token_count, max_length = max([model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) style_class = ' class="red"' if (token_count > max_length) else "" return f"{token_count}/{max_length}" From f94cfc563bbedd923d5e95563a5e8d93c8516ac3 Mon Sep 17 00:00:00 2001 From: Mitchell Boot <47387831+Mitchell1711@users.noreply.github.com> Date: Sat, 7 Jan 2023 01:15:22 +0100 Subject: [PATCH 1045/1118] Changed HTML to textbox instead Using HTML caused an issue where the row would expand for a frame when changing the sliders because of the loading animation. This solution also doesn't use any additional HTML padding --- modules/ui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 6fc8b7d7..6ea1b5d7 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -260,7 +260,7 @@ def calc_resolution_hires(x, y, scale): scaled_x = int(x * scale // 8) * 8 scaled_y = int(y * scale // 8) * 8 - return "

Upscaled resolution: "+str(scaled_x)+"x"+str(scaled_y)+"

" + return str(scaled_x)+"x"+str(scaled_y) def apply_styles(prompt, prompt_neg, style1_name, style2_name): prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, [style1_name, style2_name]) @@ -726,7 +726,10 @@ def create_ui(): hr_resize_y = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") with FormRow(elem_id="txt2img_hires_fix_row3"): - hr_final_resolution = gr.HTML(value=calc_resolution_hires(width.value, height.value, hr_scale.value), elem_id="txtimg_hr_finalres") + hr_final_resolution = gr.Textbox(value=calc_resolution_hires(width.value, height.value, hr_scale.value), + elem_id="txtimg_hr_finalres", + label="Upscaled resolution", + interactive=False) hr_scale.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) width.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) height.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) From 08066676a47b560235d4c085dd3cfcb470b80997 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 7 Jan 2023 07:22:07 +0300 Subject: [PATCH 1046/1118] make it not break on empty inputs; thank you tarded, we are --- modules/sd_hijack_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index ac3020d7..16aef76a 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -147,7 +147,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): chunk.multipliers += [weight] * emb_len position += embedding_length_in_tokens - if len(chunk.tokens) > 0: + if len(chunk.tokens) > 0 or len(chunks) == 0: next_chunk() return chunks, token_count From 1740c33547b62f692834c95914a2b295d51684c7 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 7 Jan 2023 07:48:44 +0300 Subject: [PATCH 1047/1118] more comments --- modules/sd_hijack_clip.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 16aef76a..5520c9b2 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -3,7 +3,7 @@ from collections import namedtuple import torch -from modules import prompt_parser, devices +from modules import prompt_parser, devices, sd_hijack from modules.shared import opts @@ -22,14 +22,24 @@ class PromptChunk: PromptChunkFix = namedtuple('PromptChunkFix', ['offset', 'embedding']) -"""This is a marker showing that textual inversion embedding's vectors have to placed at offset in the prompt chunk""" +"""An object of this type is a marker showing that textual inversion embedding's vectors have to placed at offset in the prompt +chunk. Thos objects are found in PromptChunk.fixes and, are placed into FrozenCLIPEmbedderWithCustomWordsBase.hijack.fixes, and finally +are applied by sd_hijack.EmbeddingsWithFixes's forward function.""" class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): + """A pytorch module that is a wrapper for FrozenCLIPEmbedder module. it enhances FrozenCLIPEmbedder, making it possible to + have unlimited prompt length and assign weights to tokens in prompt. + """ + def __init__(self, wrapped, hijack): super().__init__() + self.wrapped = wrapped - self.hijack = hijack + """Original FrozenCLIPEmbedder module; can also be FrozenOpenCLIPEmbedder or xlmr.BertSeriesModelWithTransformation, + depending on model.""" + + self.hijack: sd_hijack.StableDiffusionModelHijack = hijack self.chunk_length = 75 def empty_chunk(self): @@ -55,7 +65,8 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): converts a batch of token ids (in python lists) into a single tensor with numeric respresentation of those tokens; All python lists with tokens are assumed to have same length, usually 77. if input is a list with B elements and each element has T tokens, expected output shape is (B, T, C), where C depends on - model - can be 768 and 1024 + model - can be 768 and 1024. + Among other things, this call will read self.hijack.fixes, apply it to its inputs, and clear it (setting it to None). """ raise NotImplementedError @@ -113,7 +124,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): last_comma = len(chunk.tokens) # this is when we are at the end of alloted 75 tokens for the current chunk, and the current token is not a comma. opts.comma_padding_backtrack - # is a setting that specifies that is there is a comma nearby, the text after comma should be moved out of this chunk and into the next. + # is a setting that specifies that if there is a comma nearby, the text after the comma should be moved out of this chunk and into the next. elif opts.comma_padding_backtrack != 0 and len(chunk.tokens) == self.chunk_length and last_comma != -1 and len(chunk.tokens) - last_comma <= opts.comma_padding_backtrack: break_location = last_comma + 1 From de9738044571877450d1038e18f1ecce93d24af3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 7 Jan 2023 08:53:53 +0300 Subject: [PATCH 1048/1118] this breaks on default config because width, height, hr_scale are None at that point. --- modules/ui.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index f946382d..a18b9007 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -725,14 +725,8 @@ def create_ui(): hr_resize_x = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") hr_resize_y = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") - with FormRow(elem_id="txt2img_hires_fix_row3"): - hr_final_resolution = gr.Textbox(value=calc_resolution_hires(width.value, height.value, hr_scale.value), - elem_id="txtimg_hr_finalres", - label="Upscaled resolution", - interactive=False) - hr_scale.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) - width.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) - height.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) + with FormRow(elem_id="txt2img_hires_fix_row3"): + hr_final_resolution = gr.Textbox(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) elif category == "batch": if not opts.dimensions_and_batch_together: @@ -744,6 +738,10 @@ def create_ui(): with FormGroup(elem_id="txt2img_script_container"): custom_inputs = modules.scripts.scripts_txt2img.setup_ui() + hr_scale.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) + width.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) + height.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) + txt2img_gallery, generation_info, html_info, html_log = create_output_panel("txt2img", opts.outdir_txt2img_samples) parameters_copypaste.bind_buttons({"txt2img": txt2img_paste}, None, txt2img_prompt) From 1a5b86ad65fd738eadea1ad72f4abad3a4aabf17 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 7 Jan 2023 09:56:37 +0300 Subject: [PATCH 1049/1118] rework hires fix preview for #6437: movie it to where it takes less place, make it actually account for all relevant sliders and calculate dimensions correctly --- modules/processing.py | 1 - modules/ui.py | 40 +++++++++++++++++++++++++++------------- modules/ui_components.py | 8 ++++++++ style.css | 17 +++++++++++++++++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index a408d622..82157bc9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -711,7 +711,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.truncate_x = 0 self.truncate_y = 0 - def init(self, all_prompts, all_seeds, all_subseeds): if self.enable_hr: if self.hr_resize_x == 0 and self.hr_resize_y == 0: diff --git a/modules/ui.py b/modules/ui.py index a18b9007..6c765262 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -20,7 +20,7 @@ from PIL import Image, PngImagePlugin from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru -from modules.ui_components import FormRow, FormGroup, ToolButton +from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML from modules.paths import script_path from modules.shared import opts, cmd_opts, restricted_opts @@ -255,12 +255,20 @@ def add_style(name: str, prompt: str, negative_prompt: str): return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(4)] -def calc_resolution_hires(x, y, scale): - #final res can only be a multiple of 8 - scaled_x = int(x * scale // 8) * 8 - scaled_y = int(y * scale // 8) * 8 - - return str(scaled_x)+"x"+str(scaled_y) + +def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y): + from modules import processing, devices + + if not enable: + return "" + + p = processing.StableDiffusionProcessingTxt2Img(width=width, height=height, enable_hr=True, hr_scale=hr_scale, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y) + + with devices.autocast(): + p.init([""], [0], [0]) + + return f"resize to: {p.hr_upscale_to_x}x{p.hr_upscale_to_y}" + def apply_styles(prompt, prompt_neg, style1_name, style2_name): prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, [style1_name, style2_name]) @@ -712,6 +720,7 @@ def create_ui(): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="txt2img_restore_faces") tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling") enable_hr = gr.Checkbox(label='Hires. fix', value=False, elem_id="txt2img_enable_hr") + hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) elif category == "hires_fix": with FormGroup(visible=False, elem_id="txt2img_hires_fix") as hr_options: @@ -724,9 +733,6 @@ def create_ui(): hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") hr_resize_x = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") hr_resize_y = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") - - with FormRow(elem_id="txt2img_hires_fix_row3"): - hr_final_resolution = gr.Textbox(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) elif category == "batch": if not opts.dimensions_and_batch_together: @@ -738,9 +744,16 @@ def create_ui(): with FormGroup(elem_id="txt2img_script_container"): custom_inputs = modules.scripts.scripts_txt2img.setup_ui() - hr_scale.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) - width.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) - height.change(fn=calc_resolution_hires, inputs=[width, height, hr_scale], outputs=hr_final_resolution, show_progress=False) + hr_resolution_preview_inputs = [enable_hr, width, height, hr_scale, hr_resize_x, hr_resize_y] + hr_resolution_preview_args = dict( + fn=calc_resolution_hires, + inputs=hr_resolution_preview_inputs, + outputs=[hr_final_resolution], + show_progress=False + ) + + for input in hr_resolution_preview_inputs: + input.change(**hr_resolution_preview_args) txt2img_gallery, generation_info, html_info, html_log = create_output_panel("txt2img", opts.outdir_txt2img_samples) parameters_copypaste.bind_buttons({"txt2img": txt2img_paste}, None, txt2img_prompt) @@ -803,6 +816,7 @@ def create_ui(): fn=lambda x: gr_show(x), inputs=[enable_hr], outputs=[hr_options], + show_progress = False, ) txt2img_paste_fields = [ diff --git a/modules/ui_components.py b/modules/ui_components.py index 91eb0e3d..cac001dc 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -23,3 +23,11 @@ class FormGroup(gr.Group, gr.components.FormComponent): def get_block_name(self): return "group" + + +class FormHTML(gr.HTML, gr.components.FormComponent): + """Same as gr.HTML but fits inside gradio forms""" + + def get_block_name(self): + return "html" + diff --git a/style.css b/style.css index f1b23b53..76721756 100644 --- a/style.css +++ b/style.css @@ -642,6 +642,23 @@ footer { opacity: 0.85; } +#txtimg_hr_finalres{ + min-height: 0 !important; + padding: .625rem .75rem; + margin-left: -0.75em + +} + +#txtimg_hr_finalres .resolution{ + font-weight: bold; +} + +#txt2img_checkboxes > div > div{ + flex: 0; + white-space: nowrap; + min-width: auto; +} + /* The following handles localization for right-to-left (RTL) languages like Arabic. The rtl media type will only be activated by the logic in javascript/localization.js. If you change anything above, you need to make sure it is RTL compliant by just running From a36e2744e2b18a2582247bc5b95bfa0339dfa629 Mon Sep 17 00:00:00 2001 From: Taithrah Date: Sat, 7 Jan 2023 04:09:02 -0500 Subject: [PATCH 1050/1118] Update hints.js Small touch up to hints --- javascript/hints.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index dda66e09..73ab4a26 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -4,7 +4,7 @@ titles = { "Sampling steps": "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results", "Sampling method": "Which algorithm to use to produce the image", "GFPGAN": "Restore low quality faces using GFPGAN neural network", - "Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help", + "Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps higher than 30-40 does not help", "DDIM": "Denoising Diffusion Implicit Models - best at inpainting", "DPM adaptive": "Ignores step count - uses a number of steps determined by the CFG and resolution", @@ -12,8 +12,8 @@ titles = { "Batch size": "How many image to create in a single batch", "CFG Scale": "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results", "Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result", - "\u{1f3b2}\ufe0f": "Set seed to -1, which will cause a new random number to be used every time", - "\u267b\ufe0f": "Reuse seed from last generation, mostly useful if it was randomed", + "\u{1f3b2}\ufe0f": "Set seed to -1 will set a new random number every time.", + "\u267b\ufe0f": "Reuse seed from last generation, most useful if it was randomized.", "\u{1f3a8}": "Add a random artist to the prompt.", "\u2199\ufe0f": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", "\u{1f4c2}": "Open images output directory", @@ -74,7 +74,7 @@ titles = { "Style 1": "Style to apply; styles have components for both positive and negative prompts and apply to both", "Style 2": "Style to apply; styles have components for both positive and negative prompts and apply to both", "Apply style": "Insert selected styles into prompt fields", - "Create style": "Save current prompts as a style. If you add the token {prompt} to the text, the style use that as placeholder for your prompt when you use the style in the future.", + "Create style": "Save current prompts as a style. If you add the token {prompt} to the text, the style uses that as a placeholder for your prompt when you use the style in the future.", "Checkpoint name": "Loads weights from checkpoint before making images. You can either use hash or a part of filename (as seen in settings) for checkpoint name. Recommended to use with Y axis for less switching.", "Inpainting conditioning mask strength": "Only applies to inpainting models. Determines how strongly to mask off the original image for inpainting and img2img. 1.0 means fully masked, which is the default behaviour. 0.0 means a fully unmasked conditioning. Lower values will help preserve the overall composition of the image, but will struggle with large changes.", @@ -92,12 +92,12 @@ titles = { "Weighted sum": "Result = A * (1 - M) + B * M", "Add difference": "Result = A + (B - C) * M", - "Learning rate": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", + "Learning rate": "How fast should training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", "Clip skip": "Early stopping parameter for CLIP model; 1 is stop at last layer as usual, 2 is stop at penultimate layer, etc.", - "Approx NN": "Cheap neural network approximation. Very fast compared to VAE, but produces pictures with 4 times smaller horizontal/vertical resoluton and lower quality.", - "Approx cheap": "Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resoluton and extremely low quality.", + "Approx NN": "Cheap neural network approximation. Very fast compared to VAE, but produces pictures with 4 times smaller horizontal/vertical resolution and lower quality.", + "Approx cheap": "Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resolution and extremely low quality.", "Hires. fix": "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition", "Hires steps": "Number of sampling steps for upscaled picture. If 0, uses same as for original.", From 0fc1848e40dbd46c93753a2937403e1139ecd366 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sat, 7 Jan 2023 11:25:41 +0200 Subject: [PATCH 1051/1118] CI: Use native actions/setup-python caching --- .github/workflows/on_pull_request.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index b097d180..a168be5b 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -19,22 +19,19 @@ jobs: - name: Checkout Code uses: actions/checkout@v3 - name: Set up Python 3.10 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.10.6 - - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + cache: pip + cache-dependency-path: | + **/requirements*txt - name: Install PyLint run: | python -m pip install --upgrade pip pip install pylint # This lets PyLint check to see if it can resolve imports - name: Install dependencies - run : | + run: | export COMMANDLINE_ARGS="--skip-torch-cuda-test --exit" python launch.py - name: Analysing the code with pylint From a77873974b97618351791ea3015639be7d9f98d1 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sat, 7 Jan 2023 11:34:02 +0200 Subject: [PATCH 1052/1118] ... also for tests. --- .github/workflows/run_tests.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 49dc92bd..ecb9012a 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -14,11 +14,9 @@ jobs: uses: actions/setup-python@v4 with: python-version: 3.10.6 - - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: ${{ runner.os }}-pip- + cache: pip + cache-dependency-path: | + **/requirements*txt - name: Run tests run: python launch.py --tests basic_features --no-half --disable-opt-split-attention --use-cpu all --skip-torch-cuda-test - name: Upload main app stdout-stderr From fdfce4711076c2ebac1089bac8169d043eb7978f Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 7 Jan 2023 13:29:47 +0300 Subject: [PATCH 1053/1118] add "from" resolution for hires fix to be less confusing. --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 6c765262..99483130 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -267,7 +267,7 @@ def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resiz with devices.autocast(): p.init([""], [0], [0]) - return f"resize to: {p.hr_upscale_to_x}x{p.hr_upscale_to_y}" + return f"resize: from {width}x{height} to {p.hr_upscale_to_x}x{p.hr_upscale_to_y}" def apply_styles(prompt, prompt_neg, style1_name, style2_name): From 151233399c4b79934bdbb7c12a97eeb6499572fb Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 7 Jan 2023 13:30:06 +0300 Subject: [PATCH 1054/1118] new screenshot --- README.md | 9 +++------ screenshot.png | Bin 525075 -> 420577 bytes txt2img_Screenshot.png | Bin 337094 -> 0 bytes 3 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 txt2img_Screenshot.png diff --git a/README.md b/README.md index fea6cb35..d783fdf0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ # Stable Diffusion web UI A browser interface based on Gradio library for Stable Diffusion. -![](txt2img_Screenshot.png) - -Check the [custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Scripts) wiki page for extra scripts developed by users. +![](screenshot.png) ## Features [Detailed feature showcase with images](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features): @@ -97,9 +95,8 @@ Alternatively, use online services (like Google Colab): 1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH" 2. Install [git](https://git-scm.com/download/win). 3. Download the stable-diffusion-webui repository, for example by running `git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git`. -4. Place `model.ckpt` in the `models` directory (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it). -5. _*(Optional)*_ Place `GFPGANv1.4.pth` in the base directory, alongside `webui.py` (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it). -6. Run `webui-user.bat` from Windows Explorer as normal, non-administrator, user. +4. Place stable diffusion checkpoint (`model.ckpt`) in the `models/Stable-diffusion` directory (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it). +5. Run `webui-user.bat` from Windows Explorer as normal, non-administrator, user. ### Automatic Installation on Linux 1. Install the dependencies: diff --git a/screenshot.png b/screenshot.png index 86c3209fe3a3b92e5afa584e9e6dcd0b3dcf2ecf..47a1be4ec43e315f3e47139b10b0f9a8045904f3 100644 GIT binary patch literal 420577 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

`Q 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%0A6io>QrB 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

wa>Q!HR7!wpNdE9?fzp4e6pd4 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%0D{ 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 z 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∈!^3(>czVm2s*OFz>ECMA(lusN^+nW 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

d9 zM4L}1P3G}Qi38DOME~+~%Pub5&NC}zL1W~7aQHvP40?`d(T}jPuYNX@|MS{4EyK33 zw?I(~XP=*MT^{*4Zu$|+?iA(z;2!Gyujy}2x{~abY+f;ktDqAY^-S?Vzcm*=U<++I zzQIRGH!_hH)aE`rFZit+$j5fJhcUxz>fJeK=wK})1Tq=ohLJMD+%%$-6t+bqleA3K zdsS~_Zk>+v-yZ;^u`EnyqJhWV^W$^Y+%!pQZv;rIT)@B`l=>7WPij@MwtM7m@xODgT*J>sA zO^=t#ek$MYy^2h4;eM&*f?3>xs4{*yvA2}R*BD_*52ti z3!;twRwo+((QCs~6%RWFic0|jfqgJs3kBjJevpv;2)PV2G(R-!`TqD0mbytr@(3Jm z(L6>V`*YNJU#FGb?iB%dh&Ra@pE{w21i!0J#K1c&G)V%yh!D*YAn?@J2phEsx8`0g zg^T=zMyIhoVf4vA+zQ}O;QjM!tK%lLZt3^TFX?`a&BF{PW(IIB(4D9)w~)evUmd9} zkp9P2>HwB%JU7F^00VjsWS@X9(%_)tcq^PI0sDYn=4wY_wNdr_Q8HFIek+%LI{D^> z$~0nhId_1aBN(fSIY-X2snK_I+VZPcC#z;>Q1z2O^!U_bz15rC5I)77@0YyNy&ZQv zH|bGn8JrVxrg{61vfhOGCdb+!bE;<5eetdjZnP!IUYJS?*0K-yegi-MuD@%(ayN13 zFV^B=-POn0Fbu|ja_d3gKuqt@Qer*3C);&}AngJ+qDh^~_D+LeH>X zUIqcCmJ$X@au%%qYIU!4!Jvtgx{}kZjyb*tAoHV9##>LPf}cBQ5u^+R_1JK^4|Jgh z`|%&L1@U)STStdQU0syHVon1!%PvIvon}HHOF&VE=7NE~&!1R|dC35l8L3K*1s5ai zAzanyMP@K#z*a!#q!$8<@c9?_ZA<~CRyXk3nU2i;20r5M?e&qVg@UTeyAxEN-qCQd zg?C2PXbmIIpOB+8@1u-apVDq=nI`Bav8hS?olho`FMXFE*$bd4`geg#x4cshG`-)j z3GvT1mevV(Fv$7qR(94;R?tbnU=y6sTF9De+W$$X~@|7X#P+_LTD5 z6LK~)?Qth;D^K%v&id{W;mMmoG&iPS7+A1H7gP#sVi6&%KG9}}d~(Vm0!PNDOv`w_ z>k^SYeSFXKSX2+2ri-ZHt$V~(jvE7xe3`)K;NjD{ho7^*bdDL4O`#5g-z}5e$K-GzIw|~4in&aBpKQHZee2b z+3~B1CTHiBHs_+!vb;mKMb{!1!_yY(R;|`(BRg$aC^cPgb+V`4KA26AaL~*#;{wh@ zrYGKqC}LmpVU&$7nqvB(8H+Hn`e;I~%ne$-*^}wxa`$amX#SQyD*xB!EdTf&*$5;= z1XLUy3B^Nh=W5Wg?op?%u&8;wCm#l3naRN?p<2Pa_Rqf;MHqjdoL_x`VL zNs=-9-U))41Ac~WR=gCI%;5_@Y7U&@xFN`9RPX5=)i$7iOfLn`NVHnWmw50v5%2}8 zsAe)dQ(>KJ+|-~2XiD-JuQ%R+8wp}vosV|;WEKl)SZ43k)g>pc0mj5i zt^}>R7H^&8SdsDs>fd&8Xl(4bwNR2;+(AB$fBKY;iu#QWC&rU=|d^nnSop^x3 z8p#deL8Hg_c?SeMK__u?UPqQj+Jg;<*^90lYB-w;4>e3tliS*#j?h$x&|iWtPkCZa zq&7UCY=3F6db9n2Dm#O8$^IxAk>kdB=7@c7d0uwM*v!IneY1xe2{pH~sboCu6bXfz zn=FlQjzIY1H^xBZ#507D6gv~w;)o@o6$U!X{G{h6dw?vdO24IcaYea>GWI^hZuQ2W zm81phlLu%-LVu@=*NcquT|({1!mzGrpDgzKOog*-+#&V8v`k)QEWeM93;EFZi z70A*@k&1f?Zt(`ruTM-E#39cEDGsCOI{KN99iw>EWpo(Dtx5N9?awrKg=%SVm@*nE z5C~MFu}zni4ROjz5|+zRdRXO>`Q@N$ADOTHVa_YF{cgi-wHIf`X-byQNvjHw)OWtT zO`7cS@O8G#A~>!27jauPWJB>siT68Klip$f4Tf%bM%wmT4*rj7gPu^>% znVy6i{J{Yzp%E+9S=r64Mi0gWFbz{EEaR?2**x#?v*cu?ckEtMeb@h_R=YAdUbem= z^F)bWfe0D)3q>La4Q8|Wbhj!lo|MYVx7gR}Y_lf`yGDTjRI#VInKT9=<=foBJTu6hTSR!H6!5Pk(m5D?OA$RFSS zy~^so;?|ab_U&U`XiM5+gCiZeKD_vZmx=!CO++59^%_o9qAfcBbm@OrXjX9>-s}U# z<;Ok~`(I^S8NvQ+nE!01wA0#pEEmq+-At<=iy+_I^A;u&7!7)%!uYkDSSZbTG&=kB zegc1-G`RBqRnq10Ou>JxVqkyJ9)zoPcmrx$4nei~D94 z*?3twd()S4PvkplW?~EIQ+0asR>%L95I6V2&$SN^LVFQBQNsIZ=@F4qXaFa!D*zTr zb};5j(V$cpZD84#K5u(8uK-uaFHBa)upD#Vy>M!!wNp|VQ<&lMcI#}93KS*j58?rk znoZjPL8v(XTs;ul_>OpkT(gC+6Ng~}-e$lNuYv8n+SA4`LqBe1N~YbJc!*n-Zk_87 zt@cyhrzVW&jxNw4M#}VbL71hqCJ2{2OWP=fv+23u3U+OGcgHN?d zk831#xNuZH?V}J$Dr*izs!+ffeg8o-oUiqGi(bdKH>2-w9!fsDautU5sF8z;cJay< zl+K53EvNKttl4v3^$~9jYkbEW-{M77_|Rt@Bb^i5M~-tGPT;V^$;v#7XqiBrtR#xNj2wScYY;V32T*y z6dZl$J(BuJUY8s4kX#&P9=R1@M~%*f`!6{G=)1>7^07G4k0~Jj%~Erm)-r^?5egtwGC>jf&47>(9A{1HN$JwsdaG=_){p6;Rb#Xwu$)-MhN#kNIktf#((!~Bu^AF zx!5QLX~M1E(>vZuiDY29a9yZrVmAG}1~GZ#Y^Qe&=-7hEaIuzB|qK(OY`A0+%IZEa_L zE$_I#gD{gC&e%_pdQri-Tg^>r-oABaA4dc|T>nwk?vqsXG=}T)po(?Yd+F?rsq|yI zjy>N^1NC999COs?#x4#J(Rf%K?b$NNo!sjpjYcT0csMYv(;rEYl30SF%C!C~*l59K z?)eP=FVlEyecQ_8B}4m$6t6S+Y|3%`^FOLzMRvWa^Z>CzUW7g4Uno1mr$i8H-Vk+Hoch@bYW}4#yPenp=H)b@tih*OYI>Ff2=E*<}!R zQ653QdGRHVI-6h6pexvxWa-ziJSaSMANOKr|?!C!@6>306W?Y9MP|7)x?I3A&)G6SIDbOg@0IhUe8L z|D;IecRB-R!Zl(1Mvo;Lv183j8zn18FgZ*}J(j0vWshb(!6j}% z=5^@pO+Vbw!h#C?ljKRr60R8Khe;;9A&lmx=6Dsp1Pzz=3k8z3tr>joKOJOt#)$bE2~AlT~J`rv)73- zSTVEi`+lHHi9+E#Qo;tOlPhN29k(#{;luu3p_M+=L)w2tN_r{!Jfbx&-xJNPuHK3^ zGQXS$ITJw-hNxKNwsiJ+26OvvyhQrX3b$S^dRf~bVc&b@z_Jbtk%5rCcKBS`IYK}e7aEf$*Zl-jr!x`5E>tqzYzgf6ZVREAjO8U;8d;C z>GX+4!c=_)E?6Foo$(Q-@?+WUke?czvvAF^DBw>z3~ri8K%Q~NlKQ4N;&q>lF>&GR z<1?jO!L48R1VLPKoR|0GjtD;ei1FX9xyZJ6KmjD-U;o8n+b;OHn=m5T=UV3!Xc8&> zhhy<0*K8A}Y@xj4>;LFyZ^)7V(IXthGs8DPPB{d8FM)-g70=Q-DgfUn$IG*H{r7!~ z{pXTpvO*8`RC+fESD8|sfnAGBVW<+Ucg37o82s5%vdoxTun*1v@Nt6ZzUAj60X@Q?~-y z!u{=gGoKbvXAqEcb4%0tb&r9TF|*jF6}(m#FGogjCHl?Cp)uLCa{^N0a2gbtD~okf zw_3!59@hVkD>3_SmX(LU50i@;MBZsqzx*tc7;O?<1@$qKk7*g+GGa_GC0f*295T7# z-{*FVrrFRyislwEc_4H$z54A`*YTbyTw)CphB@A2c%W7*x!i$05|LIDt4(_5?}y~Y zGi2{@VepPF4P$5k_%dUcR1CGFjTTo*ag?Lfjo5j`U(efCR?kNT-?%vrgplIRzQODB zS@yL7Wf|~~ORApqrs=w9Zq!VBaA2w&QAjM_vUIOtJo zC%z!=Iv&HXbogRTVQl}=rcEf{jo{ZH!>MGA*%^rmTsSvBSdm_7Q_ff|1^4BiQf>Hc zdKG_v5LE-Mh4VHGWKZTq7w75{UG9xV&R*=kn$-v#7Ww|L19bvQh9k|SBM$*Bms9mH zfw-Jo0`N}5TV=`hq^z`Nv-r_Uz|I&kNfLdwI4a4W3 zFYD&-%KD3{KXCA2|EUr+98Z;L&K1wwgH`)L5LB$W)jp~sf+y)!^jIWOKe(gM}A6699*P4|v+Nclnjrs5yuxVfb@|S|-JmcZWh%Raw&0L5Jkt79rk7E(iK7Jew z(BX5dS%`U+W;DMmdtwQ$v*R4o`8y!t)|w56bC&?yMaX#7I!h#i>vU`)RD^Wk&FbrQ z<}IcQ6pzjV^4Iy7MDoUefi%`i`hpk3v9;@S{^9o&vcs!H4>&xQ?l5FMq_=Oek!zM( zQ)*JEK7ZyPjzDb2Kbv6?e^{txP!Hs~+T?K3=I?1~KEM{NcOY4DUmxgej%{sBU+fR+ zV#|sC?Xp7o*ONR<1Vmfti;S0!Xb-(Zd7UR>Ez8}f zyoz%O5-O2^A*d#CKWCobLjXwR01)I#(Ml=&1i%}k51*J?vwYcP1mHCpu!)Z>CBB~b?na+(lB4Gnb zlA&~E8Tpy)cDGqk79E2%mAy9)o^Y&5oc#9UOEHkro4$fWq@XIk;VQtGO9%w~gn%!^6@sT;b(SPOoM=MWJAUmQt>76VxeHUu_Z;+?!E(2YAn$Y{ zXKl78@8Y}LeVK#rZZ97IY-taNt*`1I>BfRm&h^ZBi zz~V8CDMbzBiw1hnUohPFNv}06Gs0QyYX+AN-1_j}@$g@mS7dXhobML6aZ#hE;Wrg&*rBJr zea&$kIO;@qB2j4JGA_1f@6_5;{7qRczhP6(vcM(@KnKiN(5b4_2;w~w9|l}mKa{@a z%R0deg8WbwX)K~XtW{u5g1eph8GPjA3>LYrqlC#8>5(iF=mc zlj-{Gk>u$`&}aw(b0WqfS!k4o8<2&(Fn(VD=dt1>z5KYI5DW zvn@&-H1BTz5=oBcfO_m%p~f)WaiS`jeJ5;vG4fUFrSmd{k9uytz4dLPbj%YCmE1SdzYDC&irjye>R)O=Oi4zgr^wQ9`nlHUBK-oV`=R?%!@1g5ZyIH@f z`;T3sYuV!8++GmsekdM(#>a=_85mIMK%k`xsC6zXv-uLGBKQZ_ zH1BPMc2vbd>ng2|TOYOcmAqQ6hX#mU3XVx%h0z&JBQVv7zj%*&c0Q=ra}@&TSgR?4 z|HOf~2}AQMf7Zv|2G=fPZp$$oKEBC6zVr@L_bSqsfPky>(H}y|AI;2l9wa;fU0mx_cOAfVe0_=$ z2659_=N}*Isw#8&U9uo5?wz-@7Vn90f&|Ad0lJYzBFii*DbRZSyx0KY`4X}WiX#ZD?hK9+Szd> zyrnM_3L5*SO@+a^6Ybe+eBK@`&xP<`RlE=YF=67>02Fq41&Neqlq%AZ4Ye@9#T*d6 zU@r(qE$m(;jc9)L$57sH{qFqaFM7z1+F9nZb@Cc*bV1F~?;T(P4_3QNvsb8k|CsyT z$tj}hRi|#1Fmt~B2Ac@CGK;U`5^l;D=Z7wHkLb1v8X)S7i`@m?Z!;pZ;F=;3ZE2Iv z+us03YAJ(*C$YR^^lVs_`A+A?iN>XO^lx9sF>-zYhzabq@W ze)Sid@5euhcr~UpHd(cKd-L-*)$YZxEm4F+#!3C6e)l)CFaiu@{6!NU8hReOS?;rr zbh9;gFS+ugvGdJ@ZHShV``|ruOJ&!kW4-?Caf8b4W^w%vk6(`|Snm7y35pbuqf`ZQ zO9)f=y7Z#lSouxvG*}S?=&EC`j?OTBC4 z#QsGW=#(I$uNe&7wf^WD)$*tZVBn1ga>9+DV=xtsaon{k(74yCS`d&gv)gLWg^z)d zu(ffS;_q9vnl$TU-P#zsIF2i-K|_Wluw-E{Wv&Wvs)6KR0Yjt#+qH2Wgk`z$NhYDx z^AJ;}{l7cfMZRRAEnehx=}#8xY0$qug$j@ddFHpO&kET7AF|Fo916E@`?Fvq24y#6 zNgJ|dAKM^N3faq^WGRe&*C3{KP@%;#wrp7{vSp9)t1xz1#}Z>33XLUtukPo$@8@~n z_b-3QanLc>b$yrf{G2Q`G1?sS=sf%PY03Hiw{Jeav;$!V-dlj3CM&8N72kpNM4Y zdP*dM=OzQiTB%6I?YA2NpcMI73LaV33$DoxcAC6tgz3w3AcQFQO73~Tbz$-w5Oq1Q z-IahM)y+JRUVNk2{rcUBFY>h0S99(@Ra43-XjG7aQUd>pWI-2*$(TS>&Rl&N9ooiO z2p}GnfxYI})NHWi%|%5`8a@|zr7cq-#3B~|(^TLiP^8Xd#r-ckxGe=7$0C*jNIn+; zs_Zb!o+*}mEVw5x2Fa8zLE$d%g;IdAB!)E*rG9BmBqf+JIZ+urD6};Bo-o$<&F5Pb zyKQ1k1TuB1;THeucvT#H9D;txFPGh$OI{EAyik9uy^EVy!d;c%`?NxT&ca6h5C9M% z>eVer8^a+E8$`GIZPz;?4l^&8hZ=Fkg28&^)YHb#Vn0*MBIu^cq~clp4R;@dMEo>>;rt z&!^{ovKoyz$M4APZ_mG4Tz1+Q$^d#p%h!2%rBc`>sb=Jz{gdK|yU&8%vw~C{B756c z=C=>FrV9QlnCjLh|MW~o;Ek`i-THG;OH{}NL`Oyoy=aNoe zUW~7{ekp>eqXPfiv9v>yl;#v~y+<)TQR^ixIS>kYedrYO9k=3Ayg;ll) z!d|6RV*U{_MC= z3OAO>w6dPmCV5x*Opp(5S=9OD_(1TM1yVe89=FPcFDAi-6kyI!F+Bj9VP|63mv5oO4jYn= z-Q`oER~vJ~Dp^!R)t|vwbh^i+NM=85ufN#C^8k19))Drhw_G@RrryQ}3&CTLg7yBy zakuRZ_X?}}Juwf0^Lo0G6Vo_(R zzmi=5e2Om?|L#>CF*n8---r;-(q`mdSf4>m2_S2;|Q3o7G+rQ^oe|t#RZ` z`Zl$B#w`u{V)Ko&3<;v)z$*xmQsdx-6XAuYNrb1&*r(0Rfj6+QBd>`fP_N)^Z1V0fFgx^Xu&8J zY3$@yR8o_`=2QX+tl4wkuU?+me;d;n!nmJTcGi$AGFoq{OLn5twqt(nS0sK|9XT(Z zR{lDAt^lS+yis5hl8g~nSP5wCcNm5zPm!Tr^{uZFu}{O5_Kkw$dA#_rbgmu{|)LqBFa zdMk@8zD+3ChRRI` z%L?_}GoxA1(VZ@qs5Re!K-!-0gE%6*$Tm4=WnOK!Fn4jYznk=qKP|tc?Bf%!DgVP- zp?~&QaFBc0Q7J4N_HgsV>WGtg7JZHj#_?O?RBWB}$}MK{^b9@@;r!46^wqasO8Mc8 zD5B5h63iWDQzOZ1#98ZnStq@kfm5PV|5{wdIq>F%R zx?*-FI`m^GN2ts}5bXAGDodZamX9n^-T>(2KMLT^fwSpf_)sFLR+Y)0bsS)n`>l$A z&D)SkUCD~KnhoF;ZNrQA;D(5{$6UC(Y}E5-9js_`&fsh%D4%Afv1Xqq>Iozhc&_oV z`#*nD(U#dmG{qvsY7k(-&J07FNaEX`0a;XJNH>=3yK;r{zYn=pE27tFoFL|P8)8e% zg8g1{Z%bI)7G-dSlM)r)w5g2n?KwMrP5vKcR8LWCWQgpFgg25##v>!;MnwUBHdgcd z!G+6KqaXgTz%DtjbHUNNqNTBj5U1XcWVt> z`n4)6Qev=5=11PK?l@1?Wg*#y(JH3?3E%3nG0dwU2Y};Iod( zSbd0`_Y~kcuZ^}Z_6?Q)N&VxlKXE@%=it*s7Tq!Edb5iR-Ms-o$mHi;$R3v+@_{msGk(Rl{@I}w5MRNU$U6re6x?g|~BKKo&BU{wN43E5I+cgSZ~Aev~*4OR}6 zJpPu-$AjJFfw%gG;XB{0fq3XBlB&H)(cf&|XZSVqw2Zie*_C5twqU&um;If+)oKq4 zHYlWK=Sn@Caj!%y$5W>LfI?R6k3k;2{V zUf7l+E~9GLmH<<`B;tR19OpgW!u{(hPNIy`9>egZ&9E-}ed@D%5iwg< zzvy@`?Sxf|oMv>v1#{~|D;{D##N80{xZSNgU_=~q-Vyj3;ppe^U1d>F`Zpgkt$&^^ z|F2lBKNkg8@X~O}LNi~u$>T#uoo#}gaN}vNtWl3#(0^b0Q-H<&-{K7?J?0&{{fOZx z%1nsXtb=a?EykZ`FSl*Rbj+tUo9^~y9-VQzC;Ui2{cN@92L+c)(+Q9Lrv9mV>vxLt zgq~yt>+qgiJm-h*Hr7<8V7JVQDkbaQ;An>m-vLY*B}V%LGYz-#NJQ5-Ro0EtvZW5A zymiz?6qN2>gMt3|_-R2_X9;mEKq$}C?)BLy%&ilN5dXqYUEKMl*Na}Yp;(PmB|+Sg z6_*B}G=0rg4}e0H2u&;oSKe%I^yuU!Y9DlFJmglovF^MyRV}yBe&=o2VJ`|*wr+jq zUEntN9nvv8+DIn|^57ZV5r|&>Bkrx(Pz9u|D{Jgz&l6CAYRb2_nI31U{nL=jTY2Nh zk!$jIpj|IHAQC>vyz=YR?8V~aZYeE^fK+jc5c5~(7z1|w^Q<)Nu5524<6|w!^^iW& z*x`f^=<`;9T~Cq;O%IolZ;0Lx-!-wY@%{#IoPxBGqcO;xui&YtkLe4m`sRfNELez! z5dDw`KjD4W`x(KjqXu>p2lXLqb9+8;dbRIRfY56N*N#5c#aG_Pn&_Jc9V)#-~f%?d9Go0~Pi)+ZKwJC3yfAX;)PK=i5tkoy^q6BMxGTX}Y+6iRe zEsCQ(o75)vc*q6>at`uoOAy6+PQFu&xmkjB+64&uZe>tj9{B=Og^^s$@4-4}4nMA6 zQ0ko4Wv^yaee>P5-LE7L9(4*Xd?5^ySMEUV$c@ZA)|u^DoPHgN^ma{Z%Lobf^LSYt zT3k$N7niZuNy;lZ!cS=7SevVOW@RFB8yv`=AU%gzancYDz|**Z3XsA@KNIV~8-lq8|-ow8@Bq=R3#OOAoDu&>nJ z;}ISXVmbQrX)|*Qj73B=U#vAX!@PRfkLk9n*fFkh#UIhM<%2hK7`sHV3K_zzRJB&@ zKcdl_;xcw`(}5{+VcSHPT#Xtqd0jR`&5I#If^nB20Rh-_6~p5seH09w_ak`Me3p?Y zP|zXO*x7>%pXxgUyj8ya6K5@dhD2zShXUBW)1GS(bje=@`#tOUq*-WKv(2ZbI@z3L zv60_Uj_5Tl;I>j-_;#oE`SNDZq6W z;a?|J0Dnkn$wy|7`832Oa0*WoMLlb+dt2ji0blc9ydPIkf065oAtltQv)>l@>z9M6 zRor34n7N;L)5{%$Eb4Jdf{UGYyr&5RH(Imu+e$0;lJTu`SfM7WD&?(Te+O&e%2^8d zM=;umF}MnHUi7>7)x)QPHcI`n6#tN-bbhA*y1dEQ2cPVT`u1L^Dqzrv^Kz#PhKi0x z2}3zHIU<>4u!IY`gpk1F4ruEVfQ%)eU6!Omd|Bs`QXSAInfXP6nLrpTF%QJ2GFlF@ zeg;54Kt5BW)NYT+3n_VUZ7uevg>PcJh_ahruWT0E`Cs)1^k|cHy_iha>en3WrVc@E zm||Y3cz$jF?6f?R45;soV^b5pZR5+Rw(fkn#_iY)9lSjxc7DBCS$V{zInM4QSHRMH1hXmasO^uC!0`!il@~9i1k$fA7%BXmu_hRz?6U0+ zC*_{|F%t;l^!l!(zgr5d+s=7d@APK=Fvw#v|(pMMKTmmMt5&-u#Masp8DJJ-Q6dbP$<#`uy6pm(5 zXFmzWL40s8rGk7uU5gWzVn)wUao!NMCx2x>rdB6Fir9h!2wm|pdRT{=pK4h7!jl1PFQ&7vDxk(q~k;uk)>jVI0e!Z4uY-*sofnE!t@0_h6~5EzbVkpFNX#`)gC9Yx^Pg3Yj67a*cU2MTQZOImc^tev zzAUWnuN1YrQ&8!@1)RiF(xCN67aVy225P6RSk1ympp@xdm?pdbz2n{~{nsuy@D444 z!TG>o%VIHblALW8*Atsu^1F9@)%-X!`XN|Fv;LAWVoPh@^z5e{=14lG_Mn+IlGt(9 zSqKv--=K;U+X`Pmhd0=%dhL1H0R_gPiHIq9U>k>W(fA2o+NYmfmL&g>M} z2*y3;p>RuGbO6qn?Oh;Qo4yrT_!JPQ{vswj!xaB2$B4x5wIulPU>wnRxIW^XE_tY{ z(tA%O*nQ<&J>gd^Lon8w!l|>8I}s65zuFkG5Xr`|?|eF{iY{GEU#oQG?@;ai;B6;i zNT^?E{Oykhd#G;{Uvjl*&Pmy8S898k|nJ6x2N2Yl& z7%_TV26rp5XL<%KwD1}zbdFy!S7=R?+J1`Kx%H1A-RS$UQsg!+9B@2}?)*8h@2rcc zrVlhmA-r~eei^8_aP)88pvJn_95+M9mr#2X5^>m__M4X%2Zv5uPsUG%Ez1b0Q5{UC zm_p!7Ar)j%wyht|~Xo&1(raa2Z?1?$P{khaGWq1j)GF1~pm#_?ukApwGv zR3S!mLl8u6Mrq8&8a1;-IQ8bcIyJMWZTiR%6KEptvY%Y@iT-L+-=*=QqsjqmvwQS9 zf|q>|M~Y;W2ztLDmqv->%zk2EZ05(%mSv$pc_sDQAMoXaK9NfDQVVG*2g({;Kmh>R@ z=V_J6A`8PMN@K>*jK_s8;EB!ts%WZXkf<}76t@HsG_t!(RYAO;*wFuVI2su~6~vDq zb024Loy%rLj45*^QZ?t52(e6zd=~cDKQL{kBdnYEsIWF9GuO-qF16`w!@`1DSuR2H zhu-LE)DQzF@hp}j0j(wykMcZ1K5s7)Sg1)|?+c)U1mW(>QdIfH+8bbPciDNhd;{uE3!L&B(4;NlGSHb_WVF*cehAgyd_s!#@ z&(Jy*{}u*{hcJEk7GEpYlm1CMd`ec|nVAHFk9VXWuMs-)YTWj=qp-2?LJ(gi2f6io zsJyD__uKA!%k~$dXhZ<7IBl{Q>nss{{L_}Y%59Qw1_aZ%b5kpdgA%i8YOSF#XX{Bs#-mo|Y_sVcrw**D^Go`U%^4^E72C?39|e=- zcoggSdmjJhmS&#>;MSn0e^U~6j#&<*4pYHc@wcL07ZX#v&bIUO*IeOgyPHb}T9ctq z>jLS2Nptyt*_cz18JR$DT+puD8X8;N6;d^;k}k`;nC76HW>a`BMYjBsm6T2Xl8to% zkd^f7b870LM?(S^82T^OU1f*oY=OeH>lNXd)q6oR>hK~=?Ncu_32ZRccY@v=r zFa)&?xoSqMedXeiJTh7GVP~UM=!&~OZ6TX!qIpBd`f?5(|Mri;da@J;1HpC?&UTho z!N{~r#h|6e+ZyE4rCZ^7p^JL{UPl-ZgSDT8K0N;ptHOpxm}Mk~VxCkQgMorSD|Q<0CSU)J2C?&03dN;Q#a9^J|`9B<5`UJ3@g5}FH|?HlJ_PY zzkKE6Kn`=(LU)FvtC$;BAEdE!s7{tjn_C3A1ugcyK%;OkNF$;3Ad&+}b$*=?FA#5lgQ$0n`+*7G*2=?aQHBrl~18E^!OCY041@ZBR^c{`| z0lT?ec#mc@G?ZypF#7a17B7lqVfu3-|Ean*lYqdbb?WT>&Zy}agCjRaY+r^>8Nrli zo-6=W^bD>Ig%Sr!R{81&1Qc6*u~km$5`48dAGjBIg0ccs*mwHXJJ|AUO#@(H*|tL zr=GzRcv)TEs^9y za&5nO?QLYd9HtvDk)-=~uZpsN+QtLC!$c;qw{tDigGgT_jvq`8{#ZP<*-Lg6{2eJj z0~b1bL!LuODG3a+$#mr0n4wP;(;4o(6-pz#DxwNq6}`h7zLRe~Q0yk{)xT>6rrr__ zy;T|wV+A^og%n<%G)X_#fAdoA3woG46`If60w3;qL2kIq~cNRn49yl z$T*-1p2vBESoC{9L^J~E1$51DfcEMmtnvp&{y}^DGrkO!+Ij2|S1WH-gDR(#J#Zy^ zA|kk#G3EREA9}i+e@}JPa`0%qyJ+d3U^t>NY$~IETytzY+78&l;LMW0d`a-qdoBc6 zqMGxwh*whxU~-E=PIEYVi%-5IPsp;^R}t)$H|Ngj>x%= zx+{v!9kUdR8%Q}$RqM8fx8^9jC2F(Q{p&jc3!cU5%qV-p>_bVV3;mYR*lGK4n@16U zX2KQbaKquxG(tq#|KOKXHkbPeXdWX(eDg1vDj$H?wLf2w5iGR7*s0T>jX>+=KL85y zc4PCRC;7CbIL_B?oEqr|zYBEwFPzFew~!u%bhuWAatQjK61dz|jl&8-L-XEvZCt!1 zi{cb`(8Gd1aWu8Pwl{uQah#$ziyl{oQa$>GvP9+l6atJbm`Cu&X~J9>&thTK66BibrMPS zSkB84Zd82-b{6fCAKA^#efB4^4g2mVGYKApfZIOWE?(&53)8u279Nax?55?(xU(X+ z3&A@MP~BiJVA&~Nu1;=fAxYqc+0|l&P-aK`u@Ka2NMK&A8qgU52?5|KxOf&HF+l>+ z(b8s-O|{4%Q1NeT9;joDxH;~#)Uv2TgB?Ml7*qJKn_qvQ78{z_Wb88KnuHo3m5NAftxzqN}!L=P{W z9NMW3WDmf8a1`${0`3NP?gx*3&x!jF%!BRulQf+L{xKoixjOMfUdO~=?Dugga1EgPP13iJ`@TSPrYP`BW|%!gk+5unc5KqWrGCHfN!FuD zXzNIvkH)s?2$<+6uxB$SBNAEI1+e>jTeBodSVz>}FaIU|{XIs0$keJKI6lT}O@Bl< zM75vwNW$i}t9J7KdFr`TDd(~Kby11eo5pp#>+7d6 z$Wu)5t|Xuz`tefT+sg9M_&CbLk9h2_{%EdSt~&rpYu+-a#5irtI&KHIRc~rx$@7;h zgU0wq-?}1JM;kN{fh+qioU5bn`zLH(c>+{Rq}=Ma1`y~vvO2%_z`8yxgg&lYA41Oo zg+_hE?FNkw161V!HI9=h82C#J4Ouk=Kwy9xL?qnz;X{30@^b|c5XdHdiW9?wI~O(3 zWfX(P3upc1!y!_EMcx83I3MzE&kY#(`|$w zJ1rZRU#ca)zMQc{&wsJ<6Pq15&Tl*s6|)z!#>{A(i-5wmTeVzFWI89u#?ykXyu^fM z%|<_AC{Oml&LrPNu32O`c29h%nJTz!(Hz5LMb~0u3~nFt&%|{@Rcku#NA$G5P`tS! zP*xSU=lrRY+kWkaeD~F<#+5htTdL(9;@9Ly`XDo z0~Kaht@~-4HayTU{b6w=8_xgOFxwA;#1`_&H5y$OR-IqIE2afoVD*5T4>a!Zu?VW# zhoBv7a#`vxl-1T@4oC5vBX4i{>^~lMl7oEmb}G4fIYAw!?UO%t>+p7Az#iI?zl^gv zad&CgLyV%|Y)IQSf{M})&Z>zK6CvROk!I^I%F5TdBxu5$tYqrwyC zDyyy`P&X=s|MhgBPBMeSeV~CGK-^)fmKP}8uZzwWy^;vTRXmj7>=ds%5I3rce_H$7Qm%Kx(PYhS!8GA8hjF_Jq zHEjkL1d$Sp=Z!7e&aaueeVoYCV@SM zWdnmnLXOnvbwq2Xc03j}eERy*w55i9>-ej|1lB~GJOT0v>HW#C(@DL~-9&mI^F6!nE~-@+?mb;;V% zSr^P!b4Qd0ZA>40Yq*t$C$5f7{GO^AmLHxd zaHYjfw|yHFD*TH}PDjXaEwM=uJEkG60?jG8&h zniM88di-GcGg`bzrMjWm* z`Dn55s7{1RI5^E-D~=wt3UnLvvMjyGTYPPYt$4>Sa||d`-D-_j3uzFe-a3 zG+5GKrR+rs8g~4Gn>)!6g6eopSU&Zs-MJZ@J7B%;hiFC=)E9s0x&KnarIb_caLzT+ znQg4u)RMXFwNt?nO6=((M#Amux-m@62xDhvefD8h5^y!JDP5||>VhkbQx@(Way`^6 z4yf0oJBb|7Z%>W6u8Qh&xif7(66lGAcc&&y1|0J^95@dBl5pjgWKi#4qx1RpUE-u z%J$aQPMMKY+dNH1F+<0e#(tuO_M%4X{-gB0^dj2vKUYLN3)*MwI8DBjyQF9O;KwDs zSj*Zw<_2`p;peSEe~?dRTjlE2M|`1InDiS0!z#~6$bXJSlX!IpcztAt&w_;zE>wQSw(?%wdpsI7?j)Z$ z-^J^Wz(QaptaCXgErG$omtOy4alAsRNx%@h zX7JVTL~Mq}p@kzD2UuZM@7GoI1KYLvZ|@^G^^ZW&P8&M{$`{!J61S;vA@%EbTRDdP z{rrOh*Pla>!I1(q%FxsXecySFyYVIj&8AKvZ@E&ZpIvfsBO(KWLOZkRNKipvkN-!| zLrr0`LLpAD7Flg~-V*CiF;snozverKe-&$&KFH6=g8k8=L z!jzCjq8`C3w}Z4}2T$8}H_t8>6kUDVvFZOM6%b79mWHYhHw?Z_=jq4}6M~1XI&M=Ka?~{HWW}X)UHr}DHm@H~PNsm%A?R#O#F>BtaH zzliMS2ob?WpOHyXSM6x6ZQ=T<&*|kS_b8CT+ucg9~H0z=goaaHT0>juRufd=^(*|5d( zJh~5LAV*k4eae=3BgaGcIoQp-b^>jx%~tJvzJsq*EM)R3OnYf?cGiVV&cH>UGwAkv z!x_4sO@ZUE*$4Dn_F6N z`nxPDH9<48Y*+%iJimy|!Ya%VSXy_5T#;ZZ08___L>5a0w~iNh7K{1eu1K6;KF{DVKW>W-`Lqouhz_s?v z-aP#^S>+(ND(YBORGI4IDR);L$~L{UaFMFqt74qc_ugCkb-BYGlPQ(Wz@BfF!$a}A zzg)u?YoSULsnv3%mNtHhbyJsS_-w6@(u$p3_rJ}spPwzJS?D%u@c&9yL0H=UEy{Jt zQ%^{_5Fa8$2)h^U3X{J_!Z*W>`o{7q>$IOSH>j^aR!JxD>fJwW;gZFSgF}D;`yL=KnzLO*H?qUgWegdKlhD#tV zP^HB4`vYE}4$=b@z+#P|#KJpGF#~U-+{52`9uBXZernlt;1KwwtGbs%bs%SHusnCU zyeZkIu840Uim|^rGHYrM;;RcI7$Qm@ROgV7qWyFKSkxeyqa7L}m?wY3e^4Z195R@~M1mXSa0EiEr!nxrsP_lL2`*EQznvSa z_WizT5%Fs!cCRQMx^XyWo^xhJxjTXZ#X{86HSZ1#HJRFG=en}D zo#BQME?^1Gz-J~B8hJ!rgDJ9R4~my49CVSFFl570_ys`z`l=cW``_SVHLIs@79uEG z{9^2mPX|b$S(8x=hw#19>LR8p{{+M2Tf``|b; z?&(0yj}3a%VESmK&*cnEuruw)FFP%;2Z>Hk@cx8o+t!R;={}q(+|4#X-oE}|d`b~_ zr{_7HegT{G;vkx5gzo~~X1rc;L^77=YWi89zT*k$^P|l>&V{p%68h&M=l3<+7I>Od zn$N(6MR8i_n$iRg(GDN}0YxYkQVE?4WlS8bL@$1Of2v+4sgW%Sy1*ov31O9Oq+Q3X zjrUpZt^^c)4RZ^v)!SjSth#7bH@zz?0HJ?B%;~Y3R6=D4Mr0$~Gl`mloSykTnI?wi!d~tEi3kes{C>Av)AJ!3h1uE`Ay=ttI z)zbLAQMEbw8Yn+Oa=0ZMFOg*l(%@<+AJx?%Y7k8NNc(gdFH`50Y7>WI5E{GfQ>{aZ z5v@Mna{f}7E*KLU#i3s7#UQ~+1pCm=4L~`ErLR{$6l4p?d;BM~G&@GFDu2>Aa3^bn z_LzUH?|sI}aO~Ty_nDiw|Kzu5hk?afqlj@AZ&IfU`0@W-Nt??>VpAE1_l#b_cO^*@ zJ-aBnXm?;|GLkdeSV-~a8>W2@o={d24hA(7329Y{uCKXa_Zx%S_($N6IjU54%ocTy zY4&5&R_{feoV$r#xodA|MaF})xTuvXgQ!y|&Y^m~a@PSuu4nt_l=Kf5|BhXE?J3zu znzMn#m@40AaX$j?>^dkk_rvgFalVr8Bx?8fhG~)SpUHv3#f`w_)oQbE{`~!iPNie- zX#PwLq-7HTb1=U~3@6FM6pR^>(I=#8K1WIhv7*gqxG+B%hCj>8%ruK`)a49NzGsBhmZb3G(^+ipL<OeFVW>|l6i>93`6X< zYfP#7s$_fM_ig6ZLq@G$ptOKk1f;QD{7d#P>RVi1QlyjczQ-?&4N-vMGbKeESwW-0 zRW!|bu8xV`{=!-5KIItf^#->=f@}@RjCqiMWke=MH5}ZMz0v5q=2%_MBjNNuTt?&i z`AGRE37kg(aBoqbqVObbdWGu7EW)A&g3_|zw;g9hY)4lzUfu6M_IOaqngyeM#Rh>V zSjYm%Dii^;N3YP$D!5^sex9^<>N-z~N&>@v*Zi2Fn|%vkiCcJtCxaDg17oBXpR?nj z5t99(`~&bYIpv)1QGE@TB~L`cY2pV42N@xsS-Ovo zf2CSFuzAmoLs1vF{q|N`w_1W`m%XvGa7g(yJzAi{b0z^PsHYEt_FD9rZd127g_vp- z@ENiY6mWUvHqz6ZxBA{2axTp~9<@J%3nC>jbweR=ECkZx#w%rJFl=3F9$!}En6d0Y zdd&$Xlq^uJm$&!8vAM<#4dk_ zU^46Jt0D5vkkFb@G(24}t={#t&4)7%k<;;kx-T}qU4^j*WZwFcehSlcNf7hrj|wW&siOV_uw_w4UGjJ!Y%?@@~wY z-9ogHZ&afRWB-)56NXq{-h9V6Q%iz9cQ1v6xlQ;lglpQyTQGO_#7Fm8d48-oY3 zljKyoUPoutoA;;snlbJ#FRp3Y{Kwq!`jSpL+s}#Gio{BF5-f)JN9qi1yXP*7GNQnC zRIP$91_l>Gt#6iY?yE?Nge->%nn9bit!td27EL;g+#lQ8G4j4|tQNzK%a0Pn;sd@# zhl+gV{VbX*(k8(1yYAq#Rf!t!6&ek1Rt+zD0AwurbPkU$FP>wLCGiBCzISfM9-p6G zKzZCASy!m5Y;^+?>{GpKNUijXDVXOEc%@AY9`a-#L+ByQli|nE_uL9{5PbR95qtqr z!+eq^Cmy8a#2JfRG(`Q1*xg7r>@6w)KPcs>3&K#c#q|mAH=soUspUx+QJnM{$4Xll zG7R}!A_}RYF@J~$f4CJA3rQ5Tw}5*V*ji-E)eN!RRmj_LxtztQYy0BVln6=n|$lyi@VWd7ROPM5PZ z_ce|`j(%&|&+8>1BI27=tX93ov32o^Ho&S885J6rkK%)Hp5j!uguqsosL`_P&Dx=| zd4sJAvsJV9e*479C|Z~5~(2CF4iku{_zr^3+2jYIOVE3u9^5Y4EA?Tyt?JcE~J(;aoqmZ%@O%V zq*?;!#LoGp654p3bx+k62j*&&*a~@=Prl9J)>lMG0z#w&g%@Cpv8!c9t|)}hwICK7Bi5#Q^ucl}LAd4v%aS+bVskz;6k2lyR9>`n00Mm9 zqbUvC1pVqJh3MEVh?v_&{RM?>h(K-UD(AX*S>n6EqF3)VQKD(7Jy21jAm(i-|ApsE zOLaoov$H4bYH!NOJ|em{W*mMsr5i*qY=Z{XXmKTxwT?@ceXZrfa|Y?lZ-*N7*((H^ zrN!53>%Gu#!zMVXbY_#<)^gX+*$XEJwDAju%q&>bj_4r+yq`azb6`2LGCwobxYXJ7 zwHe?faK3jx`}seQ&}S+XH$lcEb5qSap%v5D_#lXSHDEt!<1i{Sp3qn_&p=Ur=r@vF zo08fV7x1VgJhHRvO9i~=@oXp7(B=Q08kusVgcF+-6Zf1Fm(F|Zj9u)X^3>N_aM@=O z8hS0wv2*Q1d-&L|HRFUjo#qKo>dfOxx3OU*S8PalAC^4zt1tCX)Z!1D^Sv|P8XkL` zbdDBMuCmscr&8A+A4F9BHXL+)nVY@8Xtw{em2%sy{g3r*|MXwm*;go9H;VYjvJ?6k zEzu;x!oE(D0Q$Jo>vE3*nqy>L8%m||YRnH~Sr3tx>YP!fzn|F8e^{M);`HK+rum~` z-?dL0$9Io0`9Ae8x7%agjsCevMp1|;v~Zp_pe2+y9V7s6xmO@i&IARhx%^2nVSOfd z?IZr=C6u9zQ}q3^Li2c{4xI@zN763}eLKUh0+D}!GP{e^q(V-`2pfdiHzTkBeR|C2 z&AqW9IQLaq$4WI`>1(kUdE_oMX>5lCt|*ZEcHCr1qtz4V9qIz}4;?+py#zaR^}}7h zY!k@%;lup*-?tgZknJyQ9j8cSdKsFVUUpAXh)RHP_}w zx3PZ$E(l4-3HnjE??VZaht#)g$8X^+;++y>iM4F%N>RhD@r5?WNNrxywxd8`clx); zz=AyGt;$7*t*ZKiWtpH91bVCwji$ecyV_Oz7L;EZh;9q7-sc;A7bZRBin~kVQja4} z)B~PF7dd&{Cg68PP?80C{JBFzI~`C#x_5{y0+<l?ie|3kRX5;G%{p{hF1jSwj0~0qj0nFH@gZwL9`l> z)X-iOWX%L@ceZi%?lUI!fhmq0q-KUfc@~$wxt5Y~+(u8QRv3ZUczi_DuRe2 zlLEy{1zECSR+%Aw76RM8vYQ@|-p#x1SB6hK+@q7VkP>JE(~)hMJ7N-KrMny@uw7_g zdsgErQ{=r_i8nvQeG-MgK?4FxUoG+lM;N&T;5nH_8phftY^(=RE&F={(U8dTQ$DP5 zBG=Z(yduvz>p2@d-<9G>5EwVAQxyyF4rP`Fnv8=TUR-lb&djX-L)Z9+`W6A-vBZdp zTmCZCj{Wq^&p@paj1!=OC{Zum^SC%Df|_s?4iJb>irJ9JLZADDaG&QTAN9@O_Z&R! zwfot2_vfD#QX=Ni0OYsF0}T34`sE2Bj-0D?-b|Qi(gokj*Eid~PJsERgYqqiR;KS# z@s`qD$`G67Pn*%@{}#gky^K8XT-@^!EdE!8q^V1=b0@)*B)}`5)rKWIaRe5=YLi5& zM|Z|BH@S(+JZ4rT=k##7J|YOR;*Q}woW)tVL(eQ3m*`FTZb)qUeI7Bj4vhRIbdJ9D zQz3C$NaA!XyJ`Xg06m8K!-E4LfstrqClH`F)SHI$10cK4R+WL@{9o$$%1)=PcY)_w zR?;RjI%H(p@{NZdi!cr+KLY8*2gKy6{i~V`5naDgtca~g-aG4Fm`?TIlx9YP&53hWHbUrUF zMI6O*2_!bhtsZt~ez>3*->m=ZhdhA0IR!uXkSxSHzNCgiT^W^nF-3Zuziaa6|idUl)V^P_B+jF*n($@>cd%LY{RSkY86 z9$ok?CiXiX&87)dLKPt9bY{hw>XOC$JDNCYoLwS;2#zeSfX~22v)ERsqiFGMGm1-T zNQCX-#~X_!?7`y#L`PUQYY4KO@ zx|mE5?G?MDRrUq9EW_$NoHJTIIg@(p;TT-9j9#B6r2U(#z(H?a#Iww-B&Yo$POUOe zrPOKqnOS+zl95Aw;7te&VU(ykMatBgoD00>+q9Lc0g&Cp8fz8(uqF5wy$g0chJvW6 z@UCcB%&4Ba*|0hf?WBEsk9Zi4GS(Zl!(Wd-mT|cfBm^4PSm>X9` z7BMRWdPQc`ykPL{=0jplzwX@4fSYRnI1V&u0e@Y62`HJD)l^H%lPtnMzuRxh;Z`}C zIcl`c{>(0($~w*y?cVG+6@{uS3m~s*U*;V@TA1y3b?4{ZXBN2Y&$Jip6Zjy41zF)j z%asshma^P zf)sqe{D&V<;dBj}Pt9V=)?$=w=6@AlsP^*aG!C$0An}QXg<*5s_dwBu!YyG ztOGPlFb`{B&079W3^KC13rqepW2kEyI@-=2uin>j=LtkHbJ^7CcYyd-q~B?oW&x7H znCYS&Hi_g2vP!6%9tOT&+2$pcXhtvojnDWy7RtKtZ=X9F4hz^f*6bsVsHa|9mr+alh1Jv4%5YDU=6gpcCSwBjPig_VnV_@(lymA^vY0 z=iY&+5^0j_%-jW3=Xp|?J_K{z+v+1%7!xLsReecSY?T)_dzkM`9)M`~egSlr?{cNs zCXG#b7VdUigF*pPJ9OqNq~i3_jfz~>KvE3n)%nKO$2sg8=@6p|(cP6_IPc!;<8t%Y z2KLnGBeR9qs-%l9ZZ!T3EpU z_P2K_YgMIX6AwwljiMe4-467|F|6XvuQ{BCcLWgVHmF*q+eo%7r`59#DWGq~%v5O{ zvoVAPhf-Zg`p$-q)FW%}xdKB4*SF8S83T*h@n{iK2VOpfg&=kXN}39jXxL|O!b?G1 zDs=-=?m35mh{W-RlAOa-y%5wN=ElKNq-b&+r}nv!i6H(zFjPgZb=Rxy;XP&Fx@4a+ ztEXP||A($OkB9pG_y1=MGu9boOEZ>Kq-+(&*anFt*+L?u4aL}HYwV&1g;ZpOY}rbT zCA(~4vKKP;vCEQO->dWcz2E12&h2*o?Z3Kp&Fi|J&&T6_F9sRU3n1&6Rw>wSZbO116#~7|x0U*UX zj;Oo2^j^{Ckt+O@2v*Kw7*WMZgvQ>So132N&OF;SwEbzoG$q?aoQL_vIE^SPA>`at zcfGWCoYn#E2!wGZxKNkM{u8Iv+qFTiy%5L#_lHA};ezk)jQ_aT9qQyIBHsu4KJ&i= zUd;!p9MIE4@v4D~F%b3OgR-A)Y$K89u9}m=Z3;GZ5Ry2A`7dY@lO zGr6J^W}vivYgXLj)%9kEJqV9d)5R2FC0YYo_-^VECGaC~wd#w`HG`C^2i(0|_*Z$X zrn9R)ukudig>IB7k-v@^COq)7P$(S)0|NmQN}u9 z6t%{OT$*e$a6ygHveM7B9PT7ps;t~#?b`WOf>>;=0M`yE&3MoTn?8*(QqqVa)@UI& z#;`9bTN^Nfg(@i-hu#SAmGmMc0V#o#YcDSaVBnz57>@|$WeEuz@TO2JTQeEM>wsk4 zuLq1?x5yJ&|H;fqefRX-+Xv!;Tdfj~Kio_Ezgp;he!Y>^bz+_GZiMrZAml<%+rX0T zZSsE~{GOV)eO$+3TKvjgdxk_}*XQg-Ah1@hjLw zd!lqEz@Snqmyu+r!pg*ht=&;~Ny&^HNhC8E%@V_K$`){Gp8HAcUM-aS4Y3WEW`IPL zk26zZ7y}W~o{iN)rh&-kYeBW&*Z}N~n5L0OM(<5=Sf%oLzP*?rl)D{z z$o3Y@xN$1*xrt%3UF4OqDG9+q_iNW}Y6hj-riF*%l%{9dQo6pRHe=4d$4_B zo#GcW{OHd<+BQF6KHqfy-Rr>sVg>Kk@tWR3N}_Ycfp5k4{Jem*4iAKtFj3fBK_sKn zm$|6}l*8C`)z>DUCkwU>3;%6_+;IGvZ29SaD<>m0?ZIn>-s%U}=col3&-#AYz;+n%;>9foc0?m36k z(oZfd38X$ccsTHOG*Yng*9pjB$q>P+MfWbbL7(5Vcj>cPZ{CXYI@dlVil_#*J~9W2 z)Ft$Y$3BP0gAJ#_!$#YyQOOFd&QWabR<%?p=TR0V1Etwp;ppL9gHpM^oQup10}&Of z`WIKY>S8|54u5R?WpskzPfLQRwMlN-mPPO>6k3eA<$7Gxh6@`#K;dKZdKbAeDHXHf zN17Im@)Bk4A?ZQ2$_&5I*?>F=+jT8#9$`nSY#EjzAm=SEHDslniH(B@x@zyT&;W>F z`Zp^?hyJsLjUX@ykn0fiZV>u39L)h1IxJM9D^XAU1xA` zbn&4;eVYT5(owZf>Z(VANe@Hl7Y!a~w*SZK=2Y%Ks~bsKI^cNWMBo@^17GS&91(K8 zKahvo+g~~+H#BgPNk8<%c$4V+BFM@1FGU9?Lk5pT-<j}{uCJgAxMdd3+xO0G z#lGuGYUPBHwH!SF9Lk~m0>I?S3`o)~ii1a>JvB)NGUxn){)%5DAK#on@YpE=&j=pH zgdy^39|eDTj(PE9A$H}GE;6j$S^DeaMC+qTJ;O(xt90c+`1WMxVSlC*n>%(g1zjoM7oa<*dV{@p-dtD-u=jcH1RUr?;T<-F{^hz7B^3@+& zW5QHAcVvBg#0#A+Q@S-v9a8uRLZ(K+#zxV5KhduYry$7|F%3Mev!dMS6Pc zrXZcfv^|x%iyB&!F~;v7y?;R=Yf|I3(1iNk&H-?6y_o5cbm zD<~A$GpMLdbumH&vDXL%KOZdz%ncL@Z!CO!j;rYp1J#p+S2cslDeT#oT)Rd#-Do_Y z1Y+MY2i@m+UtpHWdNtpTsm%Opo}RhQwWp4zH9sD>OZ|EKr{D4uo%Ey3E%r~R?*{#6 z<stLkL7c3@Qq;0b9e7vT;sPfL|ia&{87R(AZVAJRuml6bheB7u}jnW z@Pt9tPa?b@=17?coNE=|mWSLYOE-!wd5Ypmf;`@el;$d$IxIcX-;|xoGtF#! zVSe)kBQ2C zvti$sn1XNb{R}On?i+JFf)-u-RF_rJ3OIp@92bqIdQ4vqJN*rqP{%-2#ML!J14&F% z5kVZw$z^n_)o6&FD19VGEmpQ7l>*x*bMw(=C-THHIF+0K>x?g^^u88)RB(vHiub8& z-0Dn4PvmlXF{j7gruslnB=P~h^$sT6AsX51Mzfvc3?>EWhV{LA^WfWK!Oaw%M_qWG z|K4@j82xi0H>}fWR-YgP#0BWMHS46pOHbwxs**qC|H-6CoanVE=qh>Y(%sAr#Sn;-#e`>ieg#9Y#}8FWjlSxw%(oL74OHd_hiKT_8oBlUCrqyNG_T zUBq7#u2Xwh{=O4Zr;lHA~&8YgT(cVfO=F9HOv)@d)w{_@>N@sJuo4rNE zgcb)gTZWhbX`)$XgDa_Urn3w4$;EyXZi|X~VqUW3F}tFZ{7i&=14j351{-|}NL-|1Qw2wR9StEN7y!mX6ZvQKKu!oeKR8IbO-l-urLY-c-`v@i%< zA%H%G!qR9IlPFTb2(iXy`y zIubPorBZ16mQD~J5f8`eJH*23y=Q=l_lppd>OW<(KLfL=CEhd09|u~KJCU=gkJ1*0 zyuRJ=3b^&(gSr0^xR8_pq<3Zw#K6o7xna&-Vv-Q0@ziihc-U}D{xL zQv1Q_p*1^eV**m_7`b)-N=cJIS=#BXlL6mvG&ckqx9tCU$s;rkxpV*Xf_T^AlU~Uc zUnf+4%Y~30u1&8Mt{?a83%8JSqk}Zp}4MaZ^NN?fG_cuMsg-(vDDU2Gi-{heyp3FqDcnyBjl|QxW8@a{lfjT}4+j zOVCX%s&tE-4W)kB$=VJ0BsY~y30HbwLvGcBT0F4fz4)^U34~F@Hh*#0w~0>y+jZ@3 zWNoZe$^F?g;j9heC+UW@WAf-lLjF)1t0-VzgFUGSAn(J#nftC4r1N#`mxQv4;V@N_RNt^vkX-+xYhQHrNXIp*$u-E>Gv9>V#SPrS6dR5Y3EVTDRHq>}G@jABF8l=b% zc$EXWVr;&OS#XevJFJ8tJdq6$Btq0_js5reW3-%}%2ANfQt zA2L>`Yb8R|Z^#7yA>bkGWIi|%I!7o92{F-4sS9f~GE@l$%zTj`bnc-mZ=XNIjh#2+ z_DV8P`gMz|0riz%0|k?0P#fTPu`x=&~UGP zgI&j4#|V1B=6tQZRr{(mqF#;~=kblAuvujTX7nK2Knzy(ZSXXNi9t0PBDf{k`e|Z* zX2;bwZQyyKgHZQ|f{uRD=!h_b%4DD{P=(}v=+DZzxfu>t*Jxkx_@;@IrueBj|rxJ|*}kP{wyRWz%7=f?PdRNd5;?mu*OH zc<=&_a~nZF1S)Tov!KeB)a)TRCxg6z+dbLt9U%shdpYw*WjPsTIHd2<&amHl^_G?? zCEh=zP(iwFFZW&G_gN~}C#9is*Px?6;A`#fL-SinU;NL7!!`&m{(yW?ViyvO^m0E> zLOO9Re39AJo~O5ea&p+n?MsF`POhMGelL1cg%4H`sijfTv8QOv7(vlVS>l-Jm%yD; zbXzj!#$NKhXk%A#ta958s(K zsjPE)GZGPOMKEbrINH7MGL$AlhRHr|TCo@EuMyB)%>X)m z&_LO{DLU@Y3jgNeaZ8|ip$Kfhdz#%|oKosMq^In?ME>mTS0xU`fop%G2)uw5s}zpp z(}{4vR)RMPtsxon>lMc8{V)+oha=-yRmRC7yUt&;iUD8Z%_t0oN5X~fiJ2qjya^-7 z=ic5Dj8k1m2M$om>3rmT{=;V8XLap5zweLJ!heft8-|qCC24S$b(XG8R4k0{E)A*) zo=sFk%fGrnaaR;LnJ?h8o*|u5NzG$bc@x$!5vMb|RJoivpTs3y0;=_-t$<@po zCpy)aRlFFIpNbsJW+|uC?RVcz>BxzdR{7%(7ny2LuxBVj=~5aYA*&LDUk)B@tN z;dUO05E!xHlf;QPFeQB`U3V_y5R(TXFW_+3NMet5xfwZvqIz>h%Ub9}xDd;V z-HI5CYW>@3esM=XE3X`^s${tzV@_qR*#+NI8OqPs&EyTgCeQmqe24Lhps-4ek!|XG zaPUfmK1wdA>N1~aygAv1hi~!*KlT%I%s&yKS~%>- zssvHA@Lj&vzG;gl*eWq#Sh2|hcUJaUvbo9?^xf2W+$hzb7AEl+9&tQrXt+EYV4CiS z?tF`du@bd|L?TWOUYT}VTU#4T0`1bI{|J7+`R~Gbk4&0(VGKec%|uYDdqer&&DWLL ztFs6bBgMcG;#T!*HNSYh7ZMr~DJCx^Q{4p;uw=%BKs)Ju`N8QIrvC=L1^$9n0U|xM z^&B;h4SR4_1kp$)hQy3#tU$CvNZiCH49|odDT~)|okMVqi$`8~rn(V~`*Tj;ei{|> zqJjf)>fzy_ON!xvTl4n|x74;TPWP012*(MD3D1k4`#m8Sx_)^yu(B?Mv@sf(2VT0p zSEuI)t3~!B=2ltueRF;2V}JJ2UDs=2s2({Tj2KhPVYkRZhq0e|@DrLAasH{VyGuK+ zG8#=P8GaOtRYv9v8O0-2_m~}jevrSseNzyzWrW8xbAT7o&iIO9OP!@Y0OHvp7?oA9 zpAslygHy#VwmOq!&bwh62&4FVJzBU6NAM)GLF_~4Bd(!I(G6wqvr)uxXtRIWpS{|? zVopmMX#`W&^Xz!^mOOMpcHOm5)dNUVKS@Gy)wdM8b~Z*Sev{X|3;lIoE3nW9i-DW| zZ$S2Uh1X1hb;);p^NHTIL4%=WuX0x+N!8EmEcI+&6f-K9t0J5$H@qsAe$40SP34*%bgz2UZSxa2_rCTRnH~vRmssRx zm}7q1&y}W`ZclBoAj8LA1Jy2YsfweK@%XC# z%ktNlpQI-;cRO9Q@rW)J$h&%I=w|9`tS9$0UNAGsnerxeVrRY}ecs8cy(_xpQuarv zwe_N8MXsYf{G7Sz`>##qW0rOl*Rdw69)?a1gUgp4?`5#F4VmPN~}Pj9k_c03w0fCJEDteu^Bl>Q?X z;;fefLcuUjpssBo{#< zn8!p8&vlXoyNWnc&aJ33$WIdu$Qf`YI$esFyy!XCpDx;VVi3&k?`~3xvO~{wM{=oS&zFwm53EBCO;DiFm?8=h#e44R zXK^*?fPdd*y1=_OR}aPjI3@^809L3BQ{zjV5s0i})=3Q=W)+onW?RHLh3pZze$cZ6 zN725iudcQsDv@p7HmALT9Dz4w0&;AUpUvtF>8X=?>60#jaH0Me{mRu4#XVo^8ECYM zc=zB~679m6huP8wNsxsmryrHLxv1l^;U5jK^LmRn9$b=*8ACZ*;SgJ&vY$t+t}iBX~4Ev(`ef>ZgxC zgK~nXf8!B(jX@0Ip?0oS&NIGRJ_+M|y{j@jjeOen4)uYY-KU@hk&^B|PCgM8hC8#W12;@GkS}%vE=>9L^I-RUxpObRrOb;8{x8mEW3PRWfWy?8z9i zeN?kx`MUj>ZJVmhz!o#x>j>IJ3Aw0T1Le9Ls@)L~l~+`N(2BV+8&)fH!6PN0vHmGUNE-BtK?N((*x|q(8 zO_m-aS+h$<|Jo*7_CKR>aH-#&k&&U+g|(RA0c?1$=Ws9y_#mz)?lD+~grEi3Ln4?t zzrY@U2&fP^MtE(NMGk_7OIVt5j_tZWy>ji&Q(>78o~e2Fm29+|$HFFDoA+Dq3V)0q z8{#-Zu{alk%aLfsq5Q8d@kLgOHovv4x zd$KxcmZFqEdo}c>hKKsZDNZK$>-rh~~UkSrHHetxgrHARY8)4AZ)zh&sl} zH`=sfA>kR!sn;DQPW&n710{<$oQ*4TmG%f~ozgF4OyE9UnuL`C^tMRj76%bb^$1C~e}3?Zw#r zTf({sk=BdPGfqC^#MZ0?v(3I`lgWsdsfg@3Z|x>$n%JgsTgFZxh=@yO?ADnN?VsoV z$+v6mR$LqXq2N)m6IXl$C;7#W*i6s%?Y0%Uc(3MQ`wx~!M0+`9IU^1;$bOZF#@flI zLs&)5XE2){^L(pZ=4z+^rKdFJmO%`2;3WFe%B!j)O|=Me^DL_>SYK(xxQ}ccqT4ln zg%*FE6{TwRTnD`9tCS=qW2?GZQ7{kx{0`1ve^)1_;vubfjOT8dBp8e54LQ%m+q0|A zF&wQ`6^(^%T-96|%dD+8PR+!fd)9s<%?W1Mz*DZM?{@mfnPFLNB+054n$4w$L9;8K z*S&mON(9l#)Oqm%o^%};6wr(}Yl~|>;xQ~_ddh8Vm z1DVocdeQ%RneLK-3S7N@nY{Tk$ld;2M|(~e*Ml+}s@yM)ogOH^@oN|3{(Xjj32mz| z3=s|Z4%?aedN3xr@<*oHOR3*SBkT7zI1vojaPOo-SW40akmng>V-?(PgwC^{gBTKy ztZO|L7ACSMIxBb5;6GUEcJ7e~r=_ncc7FObQ&;t^_|eX`c**^3Rs-uWhMs!^g#&^Z zxC)qO7a%WNhmlLlRlfEW`*uiLmft)M_=GRt_!Y>d_;o(?Fl5O{Jf%mtdR{u3+2>e7 z=;7Vxi_eo0+=HS-rW{~VlDJXDnBui_xHrv25lFVU5L+A`BDJU~(22(+Pd~QC7*wF^ z!3&dZ?3IPCaaM!F68e?AJ3Qc5)2$tPx?FFKjg8|#sg&y4*R$wjT9%lM+48q;w=4EO zv~Te*yhx~d^5`mwDBN8xLJZSv>kw4T1d!k!GIaS{vv7bOkl$p!JZtpYUQH^wCYDc% z&{cy^y*`3jn}G#KGDbazh9}QUlN-q4hm5Z|*bsH9 zICz6dW7WeWnT_8eJj@W9S;Y}S}-<3kX5SDHEk*SP`>A?eOmVy)`DXhwib-pX;cX~?*8eV z{a_SSs6T2_9yIjN(CcVoj6#o{8V6?dFS|^2Dk`gT&*nq21$^TxO_P-qf3g|?;`{g6 zv7YNtUy;vOnNsZB6(qy=X`eh4LHWx+V z=*dAtN}Zt?9D81rW(%;K;A6t*RulpX{N zeJnL!4y0I}+2Dr_;L25ylQ&Ohx!tDLhQLbdl7#Ov(Sn*o)Ji=AnC%ap(2oqgEpr+% zZ>rK2+8D^`679-W`m3wG+upqkW!`#sLTw7K4ngoAMN9�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

7KZi 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 Q`unCT3L5g|vKB%A3+a1QHvj+t literal 525075 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