From e573e59b702caecde6b349c1a27c8bd6eaa987c5 Mon Sep 17 00:00:00 2001 From: Haoming Date: Wed, 30 Jul 2025 17:14:15 +0800 Subject: [PATCH] scripts --- scripts/img2imgalt.py | 218 -------------------- scripts/loopback.py | 28 ++- scripts/outpainting_mk_2.py | 295 --------------------------- scripts/poor_mans_outpainting.py | 146 ------------- scripts/postprocessing_codeformer.py | 38 ---- scripts/postprocessing_focal_crop.py | 56 ----- scripts/postprocessing_gfpgan.py | 34 --- scripts/prompt_matrix.py | 50 +++-- scripts/prompts_from_file.py | 97 +++------ scripts/sd_upscale.py | 123 ++++++++--- 10 files changed, 163 insertions(+), 922 deletions(-) delete mode 100644 scripts/img2imgalt.py delete mode 100644 scripts/outpainting_mk_2.py delete mode 100644 scripts/poor_mans_outpainting.py delete mode 100644 scripts/postprocessing_codeformer.py delete mode 100644 scripts/postprocessing_focal_crop.py delete mode 100644 scripts/postprocessing_gfpgan.py diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py deleted file mode 100644 index 1e833fa8..00000000 --- a/scripts/img2imgalt.py +++ /dev/null @@ -1,218 +0,0 @@ -from collections import namedtuple - -import numpy as np -from tqdm import trange - -import modules.scripts as scripts -import gradio as gr - -from modules import processing, shared, sd_samplers, sd_samplers_common - -import torch -import k_diffusion as K - -def find_noise_for_image(p, cond, uncond, cfg_scale, steps): - x = p.init_latent - - s_in = x.new_ones([x.shape[0]]) - if shared.sd_model.parameterization == "v": - dnw = K.external.CompVisVDenoiser(shared.sd_model) - skip = 1 - else: - dnw = K.external.CompVisDenoiser(shared.sd_model) - skip = 0 - sigmas = dnw.get_sigmas(steps).flip(0) - - shared.state.sampling_steps = steps - - for i in trange(1, len(sigmas)): - shared.state.sampling_step += 1 - - x_in = torch.cat([x] * 2) - 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)[skip:]] - t = dnw.sigma_to_t(sigma_in) - - eps = shared.sd_model.apply_model(x_in * c_in, t, cond=cond_in) - denoised_uncond, denoised_cond = (x_in + eps * c_out).chunk(2) - - denoised = denoised_uncond + (denoised_cond - denoised_uncond) * cfg_scale - - d = (x - denoised) / sigmas[i] - dt = sigmas[i] - sigmas[i - 1] - - x = x + d * dt - - sd_samplers_common.store_latent(x) - - # This shouldn't be necessary, but solved some VRAM issues - del x_in, sigma_in, cond_in, c_out, c_in, t, - del eps, denoised_uncond, denoised_cond, denoised, d, dt - - shared.state.nextjob() - - return x / x.std() - - -Cached = namedtuple("Cached", ["noise", "cfg_scale", "steps", "latent", "original_prompt", "original_negative_prompt", "sigma_adjustment"]) - - -# Based on changes suggested by briansemrau in https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/736 -def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): - x = p.init_latent - - s_in = x.new_ones([x.shape[0]]) - if shared.sd_model.parameterization == "v": - dnw = K.external.CompVisVDenoiser(shared.sd_model) - skip = 1 - else: - dnw = K.external.CompVisDenoiser(shared.sd_model) - skip = 0 - sigmas = dnw.get_sigmas(steps).flip(0) - - shared.state.sampling_steps = steps - - for i in trange(1, len(sigmas)): - shared.state.sampling_step += 1 - - x_in = torch.cat([x] * 2) - 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)[skip:]] - - if i == 1: - t = dnw.sigma_to_t(torch.cat([sigmas[i] * s_in] * 2)) - else: - t = dnw.sigma_to_t(sigma_in) - - eps = shared.sd_model.apply_model(x_in * c_in, t, cond=cond_in) - denoised_uncond, denoised_cond = (x_in + eps * c_out).chunk(2) - - denoised = denoised_uncond + (denoised_cond - denoised_uncond) * cfg_scale - - if i == 1: - d = (x - denoised) / (2 * sigmas[i]) - else: - d = (x - denoised) / sigmas[i - 1] - - dt = sigmas[i] - sigmas[i - 1] - x = x + d * dt - - sd_samplers_common.store_latent(x) - - # This shouldn't be necessary, but solved some VRAM issues - del x_in, sigma_in, cond_in, c_out, c_in, t, - del eps, denoised_uncond, denoised_cond, denoised, d, dt - - shared.state.nextjob() - - return x / sigmas[-1] - - -class Script(scripts.Script): - def __init__(self): - self.cache = None - - def title(self): - return "img2img alternative test" - - def show(self, is_img2img): - return is_img2img - - def ui(self, is_img2img): - info = gr.Markdown(''' - * `CFG Scale` should be 2 or lower. - ''') - - override_sampler = gr.Checkbox(label="Override `Sampling method` to Euler?(this method is built for it)", value=True, elem_id=self.elem_id("override_sampler")) - - override_prompt = gr.Checkbox(label="Override `prompt` to the same value as `original prompt`?(and `negative prompt`)", value=True, elem_id=self.elem_id("override_prompt")) - original_prompt = gr.Textbox(label="Original prompt", lines=1, elem_id=self.elem_id("original_prompt")) - original_negative_prompt = gr.Textbox(label="Original negative prompt", lines=1, elem_id=self.elem_id("original_negative_prompt")) - - override_steps = gr.Checkbox(label="Override `Sampling Steps` to the same value as `Decode steps`?", value=True, elem_id=self.elem_id("override_steps")) - st = gr.Slider(label="Decode steps", minimum=1, maximum=150, step=1, value=50, elem_id=self.elem_id("st")) - - override_strength = gr.Checkbox(label="Override `Denoising strength` to 1?", value=True, elem_id=self.elem_id("override_strength")) - - cfg = gr.Slider(label="Decode CFG scale", minimum=0.0, maximum=15.0, step=0.1, value=1.0, elem_id=self.elem_id("cfg")) - randomness = gr.Slider(label="Randomness", minimum=0.0, maximum=1.0, step=0.01, value=0.0, elem_id=self.elem_id("randomness")) - sigma_adjustment = gr.Checkbox(label="Sigma adjustment for finding noise for image", value=False, elem_id=self.elem_id("sigma_adjustment")) - - return [ - info, - override_sampler, - override_prompt, original_prompt, original_negative_prompt, - override_steps, st, - override_strength, - cfg, randomness, sigma_adjustment, - ] - - def run(self, p, _, override_sampler, override_prompt, original_prompt, original_negative_prompt, override_steps, st, override_strength, cfg, randomness, sigma_adjustment): - # Override - if override_sampler: - p.sampler_name = "Euler" - if override_prompt: - p.prompt = original_prompt - p.negative_prompt = original_negative_prompt - if override_steps: - p.steps = st - if override_strength: - p.denoising_strength = 1.0 - - def sample_extra(conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): - 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 \ - and self.cache.original_negative_prompt == original_negative_prompt \ - and self.cache.sigma_adjustment == sigma_adjustment - same_everything = same_params and self.cache.latent.shape == lat.shape and np.abs(self.cache.latent-lat).sum() < 100 - - if same_everything: - rec_noise = self.cache.noise - 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 * [original_negative_prompt]) - if sigma_adjustment: - rec_noise = find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg, st) - else: - rec_noise = find_noise_for_image(p, cond, uncond, cfg, st) - self.cache = Cached(rec_noise, cfg, st, lat, original_prompt, original_negative_prompt, sigma_adjustment) - - rand_noise = processing.create_random_tensors(p.init_latent.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w, p=p) - - combined_noise = ((1 - randomness) * rec_noise + randomness * rand_noise) / ((randomness**2 + (1-randomness)**2) ** 0.5) - - sampler = sd_samplers.create_sampler(p.sampler_name, p.sd_model) - - sigmas = sampler.model_wrap.get_sigmas(p.steps) - - noise_dt = combined_noise - (p.init_latent / sigmas[0]) - - p.seed = p.seed + 1 - - return sampler.sample_img2img(p, p.init_latent, noise_dt, conditioning, unconditional_conditioning, image_conditioning=p.image_conditioning) - - p.sample = sample_extra - - p.extra_generation_params["Decode prompt"] = original_prompt - p.extra_generation_params["Decode negative prompt"] = original_negative_prompt - p.extra_generation_params["Decode CFG scale"] = cfg - p.extra_generation_params["Decode steps"] = st - p.extra_generation_params["Randomness"] = randomness - p.extra_generation_params["Sigma Adjustment"] = sigma_adjustment - - processed = processing.process_images(p) - - return processed diff --git a/scripts/loopback.py b/scripts/loopback.py index 5ffe2c98..1ca94db9 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -1,13 +1,12 @@ import math import gradio as gr -import modules.scripts as scripts -from modules import images, processing, shared +from modules import images, processing, scripts from modules.processing import Processed from modules.shared import opts, state -class Script(scripts.Script): +class Loopback(scripts.Script): def title(self): return "Loopback" @@ -15,20 +14,21 @@ class Script(scripts.Script): return is_img2img def ui(self, is_img2img): - loops = gr.Slider(minimum=1, maximum=32, step=1, label='Loops', value=4, elem_id=self.elem_id("loops")) - final_denoising_strength = gr.Slider(minimum=0, maximum=1, step=0.01, label='Final denoising strength', value=0.5, elem_id=self.elem_id("final_denoising_strength")) - denoising_curve = gr.Dropdown(label="Denoising strength curve", choices=["Aggressive", "Linear", "Lazy"], value="Linear") + with gr.Row(): + loops = gr.Slider(minimum=1, maximum=8, step=1, label="Loops", value=2, elem_id=self.elem_id("loops")) + final_denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label="Final Denoising Strength", value=0.5, elem_id=self.elem_id("final_denoising_strength")) + denoising_curve = gr.Dropdown(label="Denoising Strength Curve", choices=("Aggressive", "Linear", "Lazy"), value="Linear", elem_id=self.elem_id("denoising_strength_curve")) return [loops, final_denoising_strength, denoising_curve] - def run(self, p, loops, final_denoising_strength, denoising_curve): + def run(self, p, loops: int, final_denoising_strength: float, denoising_curve: str): processing.fix_seed(p) - batch_count = p.n_iter p.extra_generation_params = { - "Final denoising strength": final_denoising_strength, - "Denoising curve": denoising_curve + "Final Denoising Strength": final_denoising_strength, + "Denoising Strength Curve": denoising_curve, } + batch_count = p.n_iter p.batch_size = 1 p.n_iter = 1 @@ -40,7 +40,6 @@ class Script(scripts.Script): grids = [] all_images = [] original_init_image = p.init_images - original_prompt = p.prompt original_inpainting_fill = p.inpainting_fill state.job_count = loops * batch_count @@ -86,7 +85,6 @@ class Script(scripts.Script): processed = processing.process_images(p) - # Generation cancelled. if state.interrupted or state.stopping_generation: break @@ -102,7 +100,7 @@ class Script(scripts.Script): last_image = processed.images[0] p.init_images = [last_image] - p.inpainting_fill = 1 # Set "masked content" to "original" for next loop. + p.inpainting_fill = 1 if batch_count == 1: history.append(last_image) @@ -127,6 +125,4 @@ class Script(scripts.Script): all_images = grids + all_images - processed = Processed(p, all_images, initial_seed, initial_info) - - return processed + return Processed(p, all_images, initial_seed, initial_info) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py deleted file mode 100644 index 5df9dff9..00000000 --- a/scripts/outpainting_mk_2.py +++ /dev/null @@ -1,295 +0,0 @@ -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 -from modules.processing import Processed, process_images -from modules.shared import opts, state - - -# this function is taken from https://github.com/parlance-zz/g-diffuser-bot -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[:] * (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 - - # create a generator with a static seed to make outpainting deterministic / only follow global seed - rng = np.random.default_rng(0) - - noise_window = _get_gaussian_window(width, height, mode=1) # start with simple gaussian noise - noise_rgb = rng.random((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 tying 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, elem_id=self.elem_id("pixels")) - mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=8, elem_id=self.elem_id("mask_blur")) - direction = gr.CheckboxGroup(label="Outpainting direction", choices=['left', 'right', 'up', 'down'], value=['left', 'right', 'up', 'down'], elem_id=self.elem_id("direction")) - noise_q = gr.Slider(label="Fall-off exponent (lower=higher detail)", minimum=0.0, maximum=4.0, step=0.01, value=1.0, elem_id=self.elem_id("noise_q")) - color_variation = gr.Slider(label="Color variation", minimum=0.0, maximum=1.0, step=0.01, value=0.05, elem_id=self.elem_id("color_variation")) - - 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.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 - - if left > 0 or right > 0: - mask_blur_x = mask_blur - else: - mask_blur_x = 0 - - if up > 0 or down > 0: - mask_blur_y = mask_blur - else: - mask_blur_y = 0 - - p.mask_blur_x = mask_blur_x*4 - p.mask_blur_y = mask_blur_y*4 - - 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 - - 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 - - images_to_process = [] - output_images = [] - 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[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_x if is_left else 0, - expand_pixels + mask_blur_y if is_top else 0, - mask.width - expand_pixels - mask_blur_x if is_right else res_w, - mask.height - expand_pixels - mask_blur_y 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) - 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 - 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 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 = output_images[n].crop(crop_region) - images_to_process.append(image_to_process) - - p.init_images = images_to_process - - latent_mask = Image.new("RGB", (p.width, p.height), "white") - draw = ImageDraw.Draw(latent_mask) - draw.rectangle(( - expand_pixels + mask_blur_x * 2 if is_left else 0, - expand_pixels + mask_blur_y * 2 if is_top else 0, - mask.width - expand_pixels - mask_blur_x * 2 if is_right else res_w, - mask.height - expand_pixels - mask_blur_y * 2 if is_bottom else res_h, - ), fill="black") - p.latent_mask = latent_mask - - proc = process_images(p) - - if initial_seed_and_info[0] is None: - initial_seed_and_info[0] = proc.seed - initial_seed_and_info[1] = proc.info - - 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 output_images - - batch_count = p.n_iter - batch_size = p.batch_size - p.n_iter = 1 - 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): - imgs = [init_img] * batch_size - state.job = f"Batch {i + 1} out of {batch_count}" - - if left > 0: - imgs = expand(imgs, batch_size, left, is_left=True) - if right > 0: - imgs = expand(imgs, batch_size, right, is_right=True) - if up > 0: - imgs = expand(imgs, batch_size, up, is_top=True) - if down > 0: - imgs = expand(imgs, batch_size, down, is_bottom=True) - - all_processed_images += imgs - - all_images = all_processed_images - - 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]) - - if opts.samples_save: - for img in all_processed_images: - images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.samples_format, info=res.info, p=p) - - 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 diff --git a/scripts/poor_mans_outpainting.py b/scripts/poor_mans_outpainting.py deleted file mode 100644 index ea0632b6..00000000 --- a/scripts/poor_mans_outpainting.py +++ /dev/null @@ -1,146 +0,0 @@ -import math - -import modules.scripts as scripts -import gradio as gr -from PIL import Image, ImageDraw - -from modules import images, devices -from modules.processing import Processed, process_images -from modules.shared import opts, state - - -class Script(scripts.Script): - def title(self): - return "Poor man's outpainting" - - def show(self, is_img2img): - return is_img2img - - def ui(self, is_img2img): - if not is_img2img: - return None - - pixels = gr.Slider(label="Pixels to expand", minimum=8, maximum=256, step=8, value=128, elem_id=self.elem_id("pixels")) - mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id=self.elem_id("mask_blur")) - inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='fill', type="index", elem_id=self.elem_id("inpainting_fill")) - direction = gr.CheckboxGroup(label="Outpainting direction", choices=['left', 'right', 'up', 'down'], value=['left', 'right', 'up', 'down'], elem_id=self.elem_id("direction")) - - return [pixels, mask_blur, inpainting_fill, direction] - - def run(self, p, pixels, mask_blur, inpainting_fill, direction): - initial_seed = None - initial_info = None - - p.mask_blur = mask_blur * 2 - p.inpainting_fill = inpainting_fill - p.inpaint_full_res = False - - 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 - - img = Image.new("RGB", (target_w, target_h)) - img.paste(init_img, (left, up)) - - mask = Image.new("L", (img.width, img.height), "white") - draw = ImageDraw.Draw(mask) - draw.rectangle(( - left + (mask_blur * 2 if left > 0 else 0), - up + (mask_blur * 2 if up > 0 else 0), - mask.width - right - (mask_blur * 2 if right > 0 else 0), - mask.height - down - (mask_blur * 2 if down > 0 else 0) - ), fill="black") - - latent_mask = Image.new("L", (img.width, img.height), "white") - latent_draw = ImageDraw.Draw(latent_mask) - latent_draw.rectangle(( - left + (mask_blur//2 if left > 0 else 0), - up + (mask_blur//2 if up > 0 else 0), - mask.width - right - (mask_blur//2 if right > 0 else 0), - mask.height - down - (mask_blur//2 if down > 0 else 0) - ), fill="black") - - devices.torch_gc() - - grid = images.split_grid(img, tile_w=p.width, tile_h=p.height, overlap=pixels) - grid_mask = images.split_grid(mask, tile_w=p.width, tile_h=p.height, overlap=pixels) - grid_latent_mask = images.split_grid(latent_mask, tile_w=p.width, tile_h=p.height, overlap=pixels) - - p.n_iter = 1 - p.batch_size = 1 - p.do_not_save_grid = True - p.do_not_save_samples = True - - work = [] - work_mask = [] - work_latent_mask = [] - work_results = [] - - for (y, h, row), (_, _, row_mask), (_, _, row_latent_mask) in zip(grid.tiles, grid_mask.tiles, grid_latent_mask.tiles): - for tiledata, tiledata_mask, tiledata_latent_mask in zip(row, row_mask, row_latent_mask): - x, w = tiledata[0:2] - - if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down: - continue - - work.append(tiledata[2]) - work_mask.append(tiledata_mask[2]) - work_latent_mask.append(tiledata_latent_mask[2]) - - batch_count = len(work) - print(f"Poor man's outpainting will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)}.") - - state.job_count = batch_count - - for i in range(batch_count): - p.init_images = [work[i]] - p.image_mask = work_mask[i] - p.latent_mask = work_latent_mask[i] - - state.job = f"Batch {i + 1} out of {batch_count}" - processed = process_images(p) - - if initial_seed is None: - initial_seed = processed.seed - initial_info = processed.info - - p.seed = processed.seed + 1 - work_results += processed.images - - - image_index = 0 - for y, h, row in grid.tiles: - for tiledata in row: - x, w = tiledata[0:2] - - if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down: - continue - - tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height)) - image_index += 1 - - combined_image = images.combine_grid(grid) - - if opts.samples_save: - images.save_image(combined_image, p.outpath_samples, "", initial_seed, p.prompt, opts.samples_format, info=initial_info, p=p) - - processed = Processed(p, [combined_image], initial_seed, initial_info) - - return processed - diff --git a/scripts/postprocessing_codeformer.py b/scripts/postprocessing_codeformer.py deleted file mode 100644 index 1851d7ae..00000000 --- a/scripts/postprocessing_codeformer.py +++ /dev/null @@ -1,38 +0,0 @@ -from PIL import Image -import numpy as np - -from modules import scripts_postprocessing, codeformer_model, ui_components -import gradio as gr - - -class ScriptPostprocessingCodeFormer(scripts_postprocessing.ScriptPostprocessing): - name = "CodeFormer" - order = 3000 - - def ui(self): - with ui_components.InputAccordion(False, label="CodeFormer") as enable: - with gr.Row(): - codeformer_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Visibility", value=1.0, elem_id="extras_codeformer_visibility") - codeformer_weight = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Weight (0 = maximum effect, 1 = minimum effect)", value=0, elem_id="extras_codeformer_weight") - - return { - "enable": enable, - "codeformer_visibility": codeformer_visibility, - "codeformer_weight": codeformer_weight, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, codeformer_visibility, codeformer_weight): - if codeformer_visibility == 0 or not enable: - return - - source_img = pp.image.convert("RGB") - - restored_img = codeformer_model.codeformer.restore(np.array(source_img, dtype=np.uint8), w=codeformer_weight) - res = Image.fromarray(restored_img) - - if codeformer_visibility < 1.0: - res = Image.blend(source_img, res, codeformer_visibility) - - pp.image = res - pp.info["CodeFormer visibility"] = round(codeformer_visibility, 3) - pp.info["CodeFormer weight"] = round(codeformer_weight, 3) diff --git a/scripts/postprocessing_focal_crop.py b/scripts/postprocessing_focal_crop.py deleted file mode 100644 index 90060c43..00000000 --- a/scripts/postprocessing_focal_crop.py +++ /dev/null @@ -1,56 +0,0 @@ - -from modules import scripts_postprocessing, ui_components, errors -import gradio as gr - -from modules.textual_inversion import autocrop - - -class ScriptPostprocessingFocalCrop(scripts_postprocessing.ScriptPostprocessing): - name = "Auto focal point crop" - order = 4010 - - def ui(self): - with ui_components.InputAccordion(False, label="Auto focal point crop") as enable: - face_weight = gr.Slider(label='Focal point face weight', value=0.9, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_face_weight") - entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_entropy_weight") - edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_edges_weight") - debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug") - - return { - "enable": enable, - "face_weight": face_weight, - "entropy_weight": entropy_weight, - "edges_weight": edges_weight, - "debug": debug, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, face_weight, entropy_weight, edges_weight, debug): - if not enable: - return - - if not pp.shared.target_width or not pp.shared.target_height: - return - - pp.image = pp.image.convert('RGB') - - dnn_model_path = None - try: - dnn_model_path = autocrop.download_and_cache_models() - except Exception: - errors.report("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", exc_info=True) - - autocrop_settings = autocrop.Settings( - crop_width=pp.shared.target_width, - crop_height=pp.shared.target_height, - face_points_weight=face_weight, - entropy_points_weight=entropy_weight, - corner_points_weight=edges_weight, - annotate_image=debug, - dnn_model_path=dnn_model_path, - ) - - result, *others = autocrop.crop_image(pp.image, autocrop_settings) - - pp.image = result - pp.extra_images = [pp.create_copy(x, nametags=["focal-crop-debug"], disable_processing=True) for x in others] - diff --git a/scripts/postprocessing_gfpgan.py b/scripts/postprocessing_gfpgan.py deleted file mode 100644 index b1a52028..00000000 --- a/scripts/postprocessing_gfpgan.py +++ /dev/null @@ -1,34 +0,0 @@ -from PIL import Image -import numpy as np - -from modules import scripts_postprocessing, gfpgan_model, ui_components -import gradio as gr - - -class ScriptPostprocessingGfpGan(scripts_postprocessing.ScriptPostprocessing): - name = "GFPGAN" - order = 2000 - - def ui(self): - with ui_components.InputAccordion(False, label="GFPGAN") as enable: - gfpgan_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Visibility", value=1.0, elem_id="extras_gfpgan_visibility") - - return { - "enable": enable, - "gfpgan_visibility": gfpgan_visibility, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, gfpgan_visibility): - if gfpgan_visibility == 0 or not enable: - return - - source_img = pp.image.convert("RGB") - - restored_img = gfpgan_model.gfpgan_fix_faces(np.array(source_img, dtype=np.uint8)) - res = Image.fromarray(restored_img) - - if gfpgan_visibility < 1.0: - res = Image.blend(source_img, res, gfpgan_visibility) - - pp.image = res - pp.info["GFPGAN visibility"] = round(gfpgan_visibility, 3) diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index 88324fe6..6de21d3c 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -1,12 +1,10 @@ import math -import modules.scripts as scripts import gradio as gr - +import modules.scripts as scripts from modules import images -from modules.processing import process_images +from modules.processing import fix_seed, process_images from modules.shared import opts, state -import modules.sd_samplers def draw_xy_grid(xs, ys, x_label, y_label, cell): @@ -33,40 +31,35 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell): grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts) first_processed.images = [grid] - return first_processed -class Script(scripts.Script): +class PromptMatrix(scripts.Script): def title(self): - return "Prompt matrix" + return "Prompt Matrix" def ui(self, is_img2img): - gr.HTML('
') + gr.HTML("
") with gr.Row(): with gr.Column(): - 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")) + put_at_start = gr.Checkbox(value=False, label="Put the variable parts at the start of prompt", elem_id=self.elem_id("put_at_start")) + different_seeds = gr.Checkbox(value=False, label="Use different seeds for each image", elem_id=self.elem_id("different_seeds")) + margin_size = gr.Slider(value=0, label="Grid Margins (px)", minimum=0, maximum=256, step=2, elem_id=self.elem_id("margin_size")) with gr.Column(): - prompt_type = gr.Radio(["positive", "negative"], label="Select prompt", elem_id=self.elem_id("prompt_type"), value="positive") - variations_delimiter = gr.Radio(["comma", "space"], label="Select joining char", elem_id=self.elem_id("variations_delimiter"), value="comma") - with gr.Column(): - margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) + prompt_type = gr.Radio(value="positive", label="Prompt", choices=("positive", "negative"), elem_id=self.elem_id("prompt_type")) + variations_delimiter = gr.Radio(value="comma", label="Joining Char.", choices=("comma", "space"), elem_id=self.elem_id("variations_delimiter")) return [put_at_start, different_seeds, prompt_type, variations_delimiter, margin_size] - def run(self, p, put_at_start, different_seeds, prompt_type, variations_delimiter, margin_size): - modules.processing.fix_seed(p) - # Raise error if promp type is not positive or negative - if prompt_type not in ["positive", "negative"]: - raise ValueError(f"Unknown prompt type {prompt_type}") - # Raise error if variations delimiter is not comma or space - if variations_delimiter not in ["comma", "space"]: - raise ValueError(f"Unknown variations delimiter {variations_delimiter}") + def run(self, p, put_at_start: bool, different_seeds: bool, prompt_type: str, variations_delimiter: str, margin_size: int): + fix_seed(p) + + assert prompt_type in ("positive", "negative") + assert variations_delimiter in ("comma", "space") prompt = p.prompt if prompt_type == "positive" else p.negative_prompt - original_prompt = prompt[0] if type(prompt) == list else prompt - positive_prompt = p.prompt[0] if type(p.prompt) == list else p.prompt + original_prompt = prompt[0] if isinstance(prompt, list) else prompt + positive_prompt = p.prompt[0] if isinstance(p.prompt, list) else p.prompt delimiter = ", " if variations_delimiter == "comma" else " " @@ -74,7 +67,7 @@ class Script(scripts.Script): prompt_matrix_parts = original_prompt.split("|") combination_count = 2 ** (len(prompt_matrix_parts) - 1) for combination_num in range(combination_count): - selected_prompts = [text.strip().strip(',') for n, text in enumerate(prompt_matrix_parts[1:]) if combination_num & (1 << n)] + selected_prompts = [text.strip().strip(",") for n, text in enumerate(prompt_matrix_parts[1:]) if combination_num & (1 << n)] if put_at_start: selected_prompts = selected_prompts + [prompt_matrix_parts[0]] @@ -86,18 +79,21 @@ class Script(scripts.Script): p.n_iter = math.ceil(len(all_prompts) / p.batch_size) p.do_not_save_grid = True - print(f"Prompt matrix will create {len(all_prompts)} images using a total of {p.n_iter} batches.") + print(f"PromptMatrix: creating {len(all_prompts)} images in {p.n_iter} batches") if prompt_type == "positive": p.prompt = all_prompts else: p.negative_prompt = all_prompts - p.seed = [p.seed + (i if different_seeds else 0) for i in range(len(all_prompts))] + p.prompt_for_display = positive_prompt + p.seed = [p.seed + (i if different_seeds else 0) for i in range(len(all_prompts))] + processed = process_images(p) grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) grid = images.draw_prompt_matrix(grid, processed.images[0].width, processed.images[0].height, prompt_matrix_parts, margin_size) + processed.images.insert(0, grid) processed.index_of_first_image = 1 processed.infotexts.insert(0, processed.infotexts[0]) diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 6129caab..4c3f015f 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -1,19 +1,16 @@ import copy -import random import shlex -import modules.scripts as scripts import gradio as gr - -from modules import sd_samplers, errors, sd_models -from modules.processing import Processed, process_images +import modules.scripts as scripts +from modules import errors, sd_models, sd_samplers +from modules.processing import Processed, fix_seed, process_images from modules.shared import state -from modules.images import image_grid, save_image -from modules.shared import opts + def process_model_tag(tag): info = sd_models.get_closet_checkpoint_match(tag) - assert info is not None, f'Unknown checkpoint: {tag}' + assert info is not None, f"Unknown checkpoint: {tag}" return info.name @@ -57,7 +54,7 @@ prompt_tags = { "restore_faces": process_boolean_tag, "tiling": process_boolean_tag, "do_not_save_samples": process_boolean_tag, - "do_not_save_grid": process_boolean_tag + "do_not_save_grid": process_boolean_tag, } @@ -70,7 +67,7 @@ def cmdargs(line): arg = args[pos] assert arg.startswith("--"), f'must start with "--": {arg}' - assert pos+1 < len(args), f'missing argument for command line option {arg}' + assert pos + 1 < len(args), f"missing argument for command line option {arg}" tag = arg[2:] @@ -85,11 +82,10 @@ def cmdargs(line): res[tag] = prompt continue - func = prompt_tags.get(tag, None) - assert func, f'unknown commandline option: {arg}' + assert func, f"unknown commandline option: {arg}" - val = args[pos+1] + val = args[pos + 1] if tag == "sampler_name": val = sd_samplers.samplers_map.get(val.lower(), None) @@ -102,30 +98,30 @@ def cmdargs(line): def load_prompt_file(file): if file is None: - return None, gr.update() + return None, gr.skip() else: - lines = [x.strip() for x in file.decode('utf8', errors='ignore').split("\n")] - return None, "\n".join(lines) + lines = [x.strip() for x in file.decode("utf8", errors="ignore").split("\n")] + return None, gr.update(value="\n".join(lines), lines=7) -class Script(scripts.Script): +class PromptsFromTexts(scripts.Script): def title(self): - return "Prompts from file or textbox" + return "Prompts from File or Textbox" 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")) - prompt_position = gr.Radio(["start", "end"], label="Insert prompts at the", elem_id=self.elem_id("prompt_position"), value="start") - make_combined = gr.Checkbox(label="Make a combined image containing all outputs (if more than one)", value=False) + checkbox_iterate = gr.Checkbox(value=False, label="Iterate seed every line", elem_id=self.elem_id("checkbox_iterate")) + checkbox_iterate_batch = gr.Checkbox(value=False, label="Use same random seed for all lines", elem_id=self.elem_id("checkbox_iterate_batch")) + prompt_position = gr.Radio(label="Insert prompts at the", choices=("start", "end"), value="start", elem_id=self.elem_id("prompt_position")) prompt_txt = gr.Textbox(label="List of prompt inputs", lines=2, elem_id=self.elem_id("prompt_txt")) - file = gr.File(label="Upload prompt inputs", type='binary', elem_id=self.elem_id("file")) + file = gr.File(label="Upload prompt inputs", type="binary", elem_id=self.elem_id("file")) - file.upload(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt], show_progress=False) + prompt_txt.change(lambda tb: gr.update(lines=7) if ("\n" in tb) else gr.update(lines=2), inputs=[prompt_txt], outputs=[prompt_txt], show_progress=False) + file.change(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt], show_progress=False) - return [checkbox_iterate, checkbox_iterate_batch, prompt_position, prompt_txt, make_combined] + return [checkbox_iterate, checkbox_iterate_batch, prompt_position, prompt_txt] - def run(self, p, checkbox_iterate, checkbox_iterate_batch, prompt_position, prompt_txt: str, make_combined): + def run(self, p, checkbox_iterate: bool, checkbox_iterate_batch: bool, prompt_position: str, prompt_txt: str): lines = [x for x in (x.strip() for x in prompt_txt.splitlines()) if x] p.do_not_save_grid = True @@ -138,7 +134,7 @@ class Script(scripts.Script): try: args = cmdargs(line) except Exception: - errors.report(f"Error parsing line {line} as commandline", exc_info=True) + errors.report(f'Error parsing line "{line}"', exc_info=True) args = {"prompt": line} else: args = {"prompt": line} @@ -147,9 +143,9 @@ class Script(scripts.Script): jobs.append(args) - print(f"Will process {len(lines)} lines in {job_count} jobs.") + print(f"Processing {len(lines)} lines in {job_count} jobs") if (checkbox_iterate or checkbox_iterate_batch) and p.seed == -1: - p.seed = int(random.randrange(4294967294)) + fix_seed(p) state.job_count = job_count @@ -162,60 +158,29 @@ class Script(scripts.Script): copy_p = copy.copy(p) for k, v in args.items(): if k == "sd_model": - copy_p.override_settings['sd_model_checkpoint'] = v + copy_p.override_settings["sd_model_checkpoint"] = v else: setattr(copy_p, k, v) if args.get("prompt") and p.prompt: if prompt_position == "start": - copy_p.prompt = args.get("prompt") + " " + p.prompt + copy_p.prompt = f'{args.get("prompt")} {p.prompt}' else: - copy_p.prompt = p.prompt + " " + args.get("prompt") + copy_p.prompt = f'{p.prompt} {args.get("prompt")}' if args.get("negative_prompt") and p.negative_prompt: if prompt_position == "start": - copy_p.negative_prompt = args.get("negative_prompt") + " " + p.negative_prompt + copy_p.negative_prompt = f'{args.get("negative_prompt")} {p.negative_prompt}' else: - copy_p.negative_prompt = p.negative_prompt + " " + args.get("negative_prompt") + copy_p.negative_prompt = f'{p.negative_prompt} {args.get("negative_prompt")}' proc = process_images(copy_p) images += proc.images if checkbox_iterate: p.seed = p.seed + (p.batch_size * p.n_iter) + all_prompts += proc.all_prompts infotexts += proc.infotexts - if make_combined and len(images) > 1: - combined_image = image_grid(images, batch_size=1, rows=None).convert("RGB") - full_infotext = "\n".join(infotexts) - - is_img2img = getattr(p, "init_images", None) is not None - - if opts.grid_save: # use grid specific Settings - save_image( - combined_image, - opts.outdir_grids or (opts.outdir_img2img_grids if is_img2img else opts.outdir_txt2img_grids), - "", - -1, - prompt_txt, - opts.grid_format, - full_infotext, - grid=True - ) - else: # use normal output Settings - save_image( - combined_image, - opts.outdir_samples or (opts.outdir_img2img_samples if is_img2img else opts.outdir_txt2img_samples), - "", - -1, - prompt_txt, - opts.samples_format, - full_infotext - ) - - images.insert(0, combined_image) - all_prompts.insert(0, prompt_txt) - infotexts.insert(0, full_infotext) - return Processed(p, images, p.seed, "", all_prompts=all_prompts, infotexts=infotexts) diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 64e34cd9..4b5abbb4 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -1,37 +1,76 @@ import math +import re -import modules.scripts as scripts import gradio as gr -from PIL import Image - -from modules import processing, shared, images, devices +import modules.scripts as scripts +from modules import devices, images, processing, shared from modules.processing import Processed from modules.shared import opts, state +from PIL import Image -class Script(scripts.Script): +class SDUpscale(scripts.Script): def title(self): - return "SD upscale" + return "SD Upscale" def show(self, is_img2img): return is_img2img def ui(self, is_img2img): - info = gr.HTML("

Will upscale the image by the selected scale factor; use width and height sliders to set tile size

") - overlap = gr.Slider(minimum=0, maximum=256, step=16, label='Tile overlap', value=64, elem_id=self.elem_id("overlap")) - scale_factor = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label='Scale Factor', value=2.0, elem_id=self.elem_id("scale_factor")) - upscaler_index = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index", elem_id=self.elem_id("upscaler_index")) + gr.HTML( + """

Upscale the image by the selected Scale Factor; + use the Width and Height to set the tile size

""" + ) - return [info, overlap, upscaler_index, scale_factor] + with gr.Row(): + upscaler_index = gr.Dropdown( + label="Upscaler", + choices=[x.name for x in shared.sd_upscalers], + value=shared.sd_upscalers[0].name, + type="index", + elem_id=self.elem_id("upscaler_index"), + ) + scale_factor = gr.Slider( + label="Scale Factor", + value=2.0, + minimum=1.0, + maximum=8.0, + step=0.05, + elem_id=self.elem_id("scale_factor"), + ) - def run(self, p, _, overlap, upscaler_index, scale_factor): + with gr.Row(): + overlap = gr.Slider( + label="Tile Overlap", + value=64, + minimum=0, + maximum=256, + step=16, + elem_id=self.elem_id("overlap"), + ) + override = gr.Checkbox( + label="Save to Extras folder instead", + value=False, + elem_id=self.elem_id("override"), + ) + + return [overlap, upscaler_index, scale_factor, override] + + def run(self, p, overlap: int, upscaler_index: str | int, scale_factor: float, override: bool): 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] + upscaler = next( + (x for x in shared.sd_upscalers if x.name == upscaler_index), + None, + ) + assert upscaler is not None + else: + assert isinstance(upscaler_index, int) + upscaler = shared.sd_upscalers[upscaler_index] - p.extra_generation_params["SD upscale overlap"] = overlap - p.extra_generation_params["SD upscale upscaler"] = upscaler.name + processing.fix_seed(p) + + p.extra_generation_params["SD Upscale - Overlap"] = overlap + p.extra_generation_params["SD Upscale - Upscaler"] = upscaler.name initial_info = None seed = p.seed @@ -56,14 +95,21 @@ class Script(scripts.Script): work = [] - for _y, _h, row in grid.tiles: + for _, _, row in grid.tiles: for tiledata in row: work.append(tiledata[2]) batch_count = math.ceil(len(work) / batch_size) state.job_count = batch_count * upscale_count - print(f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.") + print( + f""" +[SD Upscale] +- Processing {len(grid.tiles[0][2])}x{len(grid.tiles)} tiles +- totaling {len(work)} images at a batch size of {batch_size} +- resulting in {state.job_count} iterations + """ + ) result_images = [] for n in range(upscale_count): @@ -73,7 +119,7 @@ class Script(scripts.Script): work_results = [] for i in range(batch_count): p.batch_size = batch_size - p.init_images = work[i * batch_size:(i + 1) * batch_size] + p.init_images = work[i * batch_size : (i + 1) * batch_size] state.job = f"Batch {i + 1 + n * batch_count} out of {state.job_count}" processed = processing.process_images(p) @@ -85,7 +131,7 @@ class Script(scripts.Script): work_results += processed.images image_index = 0 - for _y, _h, row in grid.tiles: + for _, _, row in grid.tiles: for tiledata in row: tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height)) image_index += 1 @@ -94,10 +140,35 @@ class Script(scripts.Script): result_images.append(combined_image) if opts.samples_save: - images.save_image(combined_image, p.outpath_samples, "", start_seed, p.prompt, opts.samples_format, info=initial_info, p=p) + if override: + images.save_image( + combined_image, + path=opts.outdir_samples or opts.outdir_extras_samples, + basename="", + extension=opts.samples_format, + info=initial_info, + short_filename=True, + no_prompt=True, + grid=False, + pnginfo_section_name="extras", + existing_info=None, + forced_filename=None, + suffix="", + ) + else: + images.save_image( + combined_image, + p.outpath_samples, + "", + start_seed, + p.prompt, + opts.samples_format, + info=initial_info, + p=p, + ) - processed = Processed(p, result_images, seed, initial_info) + new_w, new_h = img.size + pattern = r"Size: (\d+)x(\d+)" + initial_info = re.sub(pattern, f"Size: {new_w}x{new_h}", initial_info) - p.n_iter = upscale_count - - return processed + return Processed(p, result_images, seed, initial_info)