From b64994b973ad8f4268bf785f25f92b66c8dced40 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 16 Sep 2022 19:24:48 +0300 Subject: added original negative prompt to img2img alt --- scripts/img2imgalt.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'scripts') diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index dbda3255..7f1f53a7 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -59,7 +59,7 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps): return x / x.std() -Cached = namedtuple("Cached", ["noise", "cfg_scale", "steps", "latent", "original_prompt"]) +Cached = namedtuple("Cached", ["noise", "cfg_scale", "steps", "latent", "original_prompt", "original_negative_prompt"]) class Script(scripts.Script): @@ -74,19 +74,20 @@ class Script(scripts.Script): def ui(self, is_img2img): original_prompt = gr.Textbox(label="Original prompt", lines=1) + original_negative_prompt = gr.Textbox(label="Original negative prompt", lines=1) cfg = gr.Slider(label="Decode CFG scale", minimum=0.0, maximum=15.0, step=0.1, value=1.0) st = gr.Slider(label="Decode steps", minimum=1, maximum=150, step=1, value=50) randomness = gr.Slider(label="randomness", minimum=0.0, maximum=1.0, step=0.01, value=0.0) - return [original_prompt, cfg, st, randomness] + return [original_prompt, original_negative_prompt, cfg, st, randomness] - def run(self, p, original_prompt, cfg, st, randomness): + def run(self, p, original_prompt, original_negative_prompt, cfg, st, randomness): p.batch_size = 1 p.batch_count = 1 def sample_extra(x, conditioning, unconditional_conditioning): lat = (p.init_latent.cpu().numpy() * 10).astype(int) - same_params = self.cache is not None and self.cache.cfg_scale == cfg and self.cache.steps == st and self.cache.original_prompt == original_prompt + same_params = self.cache is not None and self.cache.cfg_scale == cfg and self.cache.steps == st and self.cache.original_prompt == original_prompt and self.cache.original_negative_prompt == original_negative_prompt same_everything = same_params and self.cache.latent.shape == lat.shape and np.abs(self.cache.latent-lat).sum() < 100 if same_everything: @@ -94,9 +95,9 @@ class Script(scripts.Script): else: shared.state.job_count += 1 cond = p.sd_model.get_learned_conditioning(p.batch_size * [original_prompt]) - uncond = p.sd_model.get_learned_conditioning(p.batch_size * [""]) + uncond = p.sd_model.get_learned_conditioning(p.batch_size * [original_negative_prompt]) rec_noise = find_noise_for_image(p, cond, uncond, cfg, st) - self.cache = Cached(rec_noise, cfg, st, lat, original_prompt) + self.cache = Cached(rec_noise, cfg, st, lat, original_prompt, original_negative_prompt) rand_noise = processing.create_random_tensors(p.init_latent.shape[1:], [p.seed + x + 1 for x in range(p.init_latent.shape[0])]) -- cgit v1.2.3 From 2ee9fc8eb84d5e1864dbabd8a8c6b279a6ae21ac Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Fri, 16 Sep 2022 22:18:30 +0300 Subject: new outpainting script --- scripts/outpainting_mk_2.py | 290 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 scripts/outpainting_mk_2.py (limited to 'scripts') diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py new file mode 100644 index 00000000..a42c1aed --- /dev/null +++ b/scripts/outpainting_mk_2.py @@ -0,0 +1,290 @@ +import math + +import numpy as np +import skimage + +import modules.scripts as scripts +import gradio as gr +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 + + +def expand(x, dir, amount, power=0.75): + is_left = dir == 3 + is_right = dir == 1 + is_up = dir == 0 + is_down = dir == 2 + + if is_left or is_right: + noise = np.zeros((x.shape[0], amount, 3), dtype=float) + indexes = np.random.random((x.shape[0], amount)) ** power * (1 - np.arange(amount) / amount) + if is_right: + indexes = 1 - indexes + indexes = (indexes * (x.shape[1] - 1)).astype(int) + + for row in range(x.shape[0]): + if is_left: + noise[row] = x[row][indexes[row]] + else: + noise[row] = np.flip(x[row][indexes[row]], axis=0) + + x = np.concatenate([noise, x] if is_left else [x, noise], axis=1) + return x + + if is_up or is_down: + noise = np.zeros((amount, x.shape[1], 3), dtype=float) + indexes = np.random.random((x.shape[1], amount)) ** power * (1 - np.arange(amount) / amount) + if is_down: + indexes = 1 - indexes + indexes = (indexes * x.shape[0] - 1).astype(int) + + for row in range(x.shape[1]): + if is_up: + noise[:, row] = x[:, row][indexes[row]] + else: + noise[:, row] = np.flip(x[:, row][indexes[row]], axis=0) + + x = np.concatenate([noise, x] if is_up else [x, noise], axis=0) + return x + + +def get_matched_noise(_np_src_image, np_mask_rgb, noise_q=1, color_variation=0.05): + # helper fft routines that keep ortho normalization and auto-shift before and after fft + def _fft2(data): + if data.ndim > 2: # has channels + out_fft = np.zeros((data.shape[0], data.shape[1], data.shape[2]), dtype=np.complex128) + for c in range(data.shape[2]): + c_data = data[:, :, c] + out_fft[:, :, c] = np.fft.fft2(np.fft.fftshift(c_data), norm="ortho") + out_fft[:, :, c] = np.fft.ifftshift(out_fft[:, :, c]) + else: # one channel + out_fft = np.zeros((data.shape[0], data.shape[1]), dtype=np.complex128) + out_fft[:, :] = np.fft.fft2(np.fft.fftshift(data), norm="ortho") + out_fft[:, :] = np.fft.ifftshift(out_fft[:, :]) + + return out_fft + + def _ifft2(data): + if data.ndim > 2: # has channels + out_ifft = np.zeros((data.shape[0], data.shape[1], data.shape[2]), dtype=np.complex128) + for c in range(data.shape[2]): + c_data = data[:, :, c] + out_ifft[:, :, c] = np.fft.ifft2(np.fft.fftshift(c_data), norm="ortho") + out_ifft[:, :, c] = np.fft.ifftshift(out_ifft[:, :, c]) + else: # one channel + out_ifft = np.zeros((data.shape[0], data.shape[1]), dtype=np.complex128) + out_ifft[:, :] = np.fft.ifft2(np.fft.fftshift(data), norm="ortho") + out_ifft[:, :] = np.fft.ifftshift(out_ifft[:, :]) + + return out_ifft + + def _get_gaussian_window(width, height, std=3.14, mode=0): + window_scale_x = float(width / min(width, height)) + window_scale_y = float(height / min(width, height)) + + window = np.zeros((width, height)) + x = (np.arange(width) / width * 2. - 1.) * window_scale_x + for y in range(height): + fy = (y / height * 2. - 1.) * window_scale_y + if mode == 0: + window[:, y] = np.exp(-(x ** 2 + fy ** 2) * std) + else: + window[:, y] = (1 / ((x ** 2 + 1.) * (fy ** 2 + 1.))) ** (std / 3.14) # hey wait a minute that's not gaussian + + return window + + def _get_masked_window_rgb(np_mask_grey, hardness=1.): + np_mask_rgb = np.zeros((np_mask_grey.shape[0], np_mask_grey.shape[1], 3)) + if hardness != 1.: + hardened = np_mask_grey[:] ** hardness + else: + hardened = np_mask_grey[:] + for c in range(3): + np_mask_rgb[:, :, c] = hardened[:] + return np_mask_rgb + + width = _np_src_image.shape[0] + height = _np_src_image.shape[1] + num_channels = _np_src_image.shape[2] + + np_src_image = _np_src_image[:] * (1. - np_mask_rgb) + np_mask_grey = (np.sum(np_mask_rgb, axis=2) / 3.) + img_mask = np_mask_grey > 1e-6 + ref_mask = np_mask_grey < 1e-3 + + windowed_image = _np_src_image * (1. - _get_masked_window_rgb(np_mask_grey)) + windowed_image /= np.max(windowed_image) + windowed_image += np.average(_np_src_image) * np_mask_rgb # / (1.-np.average(np_mask_rgb)) # rather than leave the masked area black, we get better results from fft by filling the average unmasked color + + src_fft = _fft2(windowed_image) # get feature statistics from masked src img + src_dist = np.absolute(src_fft) + src_phase = src_fft / src_dist + + noise_window = _get_gaussian_window(width, height, mode=1) # start with simple gaussian noise + noise_rgb = np.random.random_sample((width, height, num_channels)) + noise_grey = (np.sum(noise_rgb, axis=2) / 3.) + noise_rgb *= color_variation # the colorfulness of the starting noise is blended to greyscale with a parameter + for c in range(num_channels): + noise_rgb[:, :, c] += (1. - color_variation) * noise_grey + + noise_fft = _fft2(noise_rgb) + for c in range(num_channels): + noise_fft[:, :, c] *= noise_window + noise_rgb = np.real(_ifft2(noise_fft)) + shaped_noise_fft = _fft2(noise_rgb) + shaped_noise_fft[:, :, :] = np.absolute(shaped_noise_fft[:, :, :]) ** 2 * (src_dist ** noise_q) * src_phase # perform the actual shaping + + brightness_variation = 0. # color_variation # todo: temporarily tieing brightness variation to color variation for now + contrast_adjusted_np_src = _np_src_image[:] * (brightness_variation + 1.) - brightness_variation * 2. + + # scikit-image is used for histogram matching, very convenient! + shaped_noise = np.real(_ifft2(shaped_noise_fft)) + shaped_noise -= np.min(shaped_noise) + shaped_noise /= np.max(shaped_noise) + shaped_noise[img_mask, :] = skimage.exposure.match_histograms(shaped_noise[img_mask, :] ** 1., contrast_adjusted_np_src[ref_mask, :], channel_axis=1) + shaped_noise = _np_src_image[:] * (1. - np_mask_rgb) + shaped_noise * np_mask_rgb + + matched_noise = shaped_noise[:] + + return np.clip(matched_noise, 0., 1.) + + + +class Script(scripts.Script): + def title(self): + return "Outpainting mk2" + + def show(self, is_img2img): + return is_img2img + + def ui(self, is_img2img): + if not is_img2img: + return None + + info = gr.HTML("

Recommended settings: Sampling Steps: 80-100, Sampler: Euler a, Denoising strength: 0.8

") + + pixels = gr.Slider(label="Pixels to expand", minimum=8, maximum=256, step=8, value=128) + mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=8, visible=False) + direction = gr.CheckboxGroup(label="Outpainting direction", choices=['left', 'right', 'up', 'down'], value=['left', 'right', 'up', 'down']) + noise_q = gr.Slider(label="Fall-off exponent (lower=higher detail)", minimum=0.0, maximum=4.0, step=0.01, value=1.0) + color_variation = gr.Slider(label="Color variation", minimum=0.0, maximum=1.0, step=0.01, value=0.05) + + return [info, pixels, mask_blur, direction, noise_q, color_variation] + + def run(self, p, _, pixels, mask_blur, direction, noise_q, color_variation): + initial_seed_and_info = [None, None] + + process_width = p.width + process_height = p.height + + p.mask_blur = mask_blur*4 + p.inpaint_full_res = False + p.inpainting_fill = 1 + p.do_not_save_samples = True + p.do_not_save_grid = True + + left = pixels if "left" in direction else 0 + right = pixels if "right" in direction else 0 + up = pixels if "up" in direction else 0 + down = pixels if "down" in direction else 0 + + init_img = p.init_images[0] + target_w = math.ceil((init_img.width + left + right) / 64) * 64 + target_h = math.ceil((init_img.height + up + down) / 64) * 64 + + if left > 0: + left = left * (target_w - init_img.width) // (left + right) + if right > 0: + right = target_w - init_img.width - left + + if up > 0: + up = up * (target_h - init_img.height) // (up + down) + + 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, 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 + + img = Image.new("RGB", (init.width + pixels_horiz, init.height + pixels_vert)) + img.paste(init, (pixels_horiz if is_left else 0, pixels_vert if is_top else 0)) + mask = Image.new("RGB", (init.width + pixels_horiz, init.height + pixels_vert), "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 mask.width, + mask.height - expand_pixels - mask_blur if is_bottom else mask.height, + ), 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") + + 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 + + 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, + ) + + image_to_process = out.crop(crop_region) + mask = mask.crop(crop_region) + + 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 + + latent_mask = Image.new("RGB", (p.width, p.height), "white") + draw = ImageDraw.Draw(latent_mask) + draw.rectangle(( + expand_pixels + mask_blur * 2 if is_left else 0, + expand_pixels + mask_blur * 2 if is_top else 0, + mask.width - expand_pixels - mask_blur * 2 if is_right else mask.width, + mask.height - expand_pixels - mask_blur * 2 if is_bottom else mask.height, + ), fill="black") + 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)) + return out + + img = init_image + + 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) + + res = Processed(p, [img], 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) + + return res + -- cgit v1.2.3 From 304222ef94d1c3c60fab466a96c448868f391bce Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 17 Sep 2022 13:49:36 +0300 Subject: X/Y plot support for switching checkpoints. --- scripts/xy_grid.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'scripts') diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index eccfda87..680dd702 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -10,7 +10,9 @@ import gradio as gr from modules import images from modules.processing import process_images, Processed from modules.shared import opts, cmd_opts, state +import modules.shared as shared import modules.sd_samplers +import modules.sd_models import re @@ -41,6 +43,15 @@ def apply_sampler(p, x, xs): p.sampler_index = sampler_index +def apply_checkpoint(p, x, xs): + applicable = [info for info in modules.sd_models.checkpoints_list.values() if x in info.title] + assert len(applicable) > 0, f'Checkpoint {x} for found' + + info = applicable[0] + + modules.sd_models.reload_model_weights(shared.sd_model, info) + + def format_value_add_label(p, opt, x): if type(x) == float: x = round(x, 8) @@ -74,6 +85,7 @@ axis_options = [ AxisOption("CFG Scale", float, apply_field("cfg_scale"), format_value_add_label), AxisOption("Prompt S/R", str, apply_prompt, format_value), AxisOption("Sampler", str, apply_sampler, format_value), + AxisOption("Checkpoint name", str, apply_checkpoint, format_value), AxisOptionImg2Img("Denoising", float, apply_field("denoising_strength"), format_value_add_label), # as it is now all AxisOptionImg2Img items must go after AxisOption ones ] @@ -215,4 +227,7 @@ class Script(scripts.Script): if opts.grid_save: images.save_image(processed.images[0], p.outpath_grids, "xy_grid", prompt=p.prompt, seed=processed.seed, grid=True, p=p) + # restore checkpoint in case it was changed by axes + modules.sd_models.reload_model_weights(shared.sd_model) + return processed -- cgit v1.2.3 From 140f89315380dbcc541f6e18e3d355a06ea3e2f0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 17 Sep 2022 14:55:40 +0300 Subject: process all values for x/y plot right away to error out if any are bad before any processing begins --- scripts/xy_grid.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'scripts') diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index 680dd702..6a157722 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -90,11 +90,11 @@ axis_options = [ ] -def draw_xy_grid(p, xs, ys, x_label, y_label, cell, draw_legend): +def draw_xy_grid(p, xs, ys, x_labels, y_labels, cell, draw_legend): res = [] - ver_texts = [[images.GridAnnotation(y_label(y))] for y in ys] - hor_texts = [[images.GridAnnotation(x_label(x))] for x in xs] + ver_texts = [[images.GridAnnotation(y)] for y in y_labels] + hor_texts = [[images.GridAnnotation(x)] for x in x_labels] first_pocessed = None @@ -218,8 +218,8 @@ class Script(scripts.Script): p, xs=xs, ys=ys, - x_label=lambda x: x_opt.format_value(p, x_opt, x), - y_label=lambda y: y_opt.format_value(p, y_opt, y), + x_labels=[x_opt.format_value(p, x_opt, x) for x in xs], + y_labels=[y_opt.format_value(p, y_opt, y) for y in ys], cell=cell, draw_legend=draw_legend ) -- cgit v1.2.3 From ba295b32688629cf575d67f1750a7838b008858b Mon Sep 17 00:00:00 2001 From: Tony Beeman Date: Sat, 17 Sep 2022 01:34:33 -0700 Subject: * Fix process_images where the number of images is not a multiple of (batch_size * n_iter), which would cause us to throw an exception. * Add a textbox option to Prompts from file (ease of use and it makes it much easier to use on a mobile device) * Fix the fact that Prompts from file was sometimes passing an empty batch. --- scripts/prompts_from_file.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'scripts') diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index d9b01c81..513d9a1c 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -13,28 +13,42 @@ from modules.shared import opts, cmd_opts, state class Script(scripts.Script): def title(self): - return "Prompts from file" + return "Prompts from file or textbox" def ui(self, is_img2img): + # This checkbox would look nicer as two tabs, but there are two problems: + # 1) There is a bug in Gradio 3.3 that prevents visibility from working on Tabs + # 2) Even with Gradio 3.3.1, returning a control (like Tabs) that can't be used as input + # causes a AttributeError: 'Tabs' object has no attribute 'preprocess' assert, + # due to the way Script assumes all controls returned can be used as inputs. + # Therefore, there's no good way to use grouping components right now, + # so we will use a checkbox! :) + checkbox_txt = gr.Checkbox(label="Show Textbox", value=False) file = gr.File(label="File with inputs", type='bytes') - - return [file] - - def run(self, p, data: bytes): - lines = [x.strip() for x in data.decode('utf8', errors='ignore').split("\n")] + prompt_txt = gr.TextArea(label="Prompts") + checkbox_txt.change(fn=lambda x: [gr.File.update(visible = not x), gr.TextArea.update(visible = x)], inputs=[checkbox_txt], outputs=[file, prompt_txt]) + return [checkbox_txt, file, prompt_txt] + + def run(self, p, checkbox_txt, data: bytes, prompt_txt: str): + if (checkbox_txt): + lines = [x.strip() for x in prompt_txt.splitlines()] + else: + lines = [x.strip() for x in data.decode('utf8', errors='ignore').split("\n")] lines = [x for x in lines if len(x) > 0] - batch_count = math.ceil(len(lines) / p.batch_size) - print(f"Will process {len(lines) * p.n_iter} images in {batch_count * p.n_iter} batches.") + img_count = len(lines) * p.n_iter + batch_count = math.ceil(img_count / p.batch_size) + loop_count = math.ceil(batch_count / p.n_iter) + print(f"Will process {img_count} images in {batch_count} batches.") p.do_not_save_grid = True state.job_count = batch_count images = [] - for batch_no in range(batch_count): - state.job = f"{batch_no + 1} out of {batch_count * p.n_iter}" - p.prompt = lines[batch_no*p.batch_size:(batch_no+1)*p.batch_size] * p.n_iter + for loop_no in range(loop_count): + state.job = f"{loop_no + 1} out of {loop_count}" + p.prompt = lines[loop_no*p.batch_size:(loop_no+1)*p.batch_size] * p.n_iter proc = process_images(p) images += proc.images -- cgit v1.2.3