diff options
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/img2imgalt.py | 34 | ||||
-rw-r--r-- | scripts/loopback.py | 103 | ||||
-rw-r--r-- | scripts/postprocessing_upscale.py | 34 | ||||
-rw-r--r-- | scripts/prompt_matrix.py | 47 | ||||
-rw-r--r-- | scripts/xyz_grid.py | 201 |
5 files changed, 293 insertions, 126 deletions
diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index cbdfc6b3..bb00fb3f 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -6,23 +6,21 @@ from tqdm import trange import modules.scripts as scripts
import gradio as gr
-from modules import processing, shared, sd_samplers, prompt_parser
-from modules.processing import Processed
-from modules.shared import opts, cmd_opts, state
+from modules import processing, shared, sd_samplers, sd_samplers_common
import torch
import k_diffusion as K
-from PIL import Image
-from torch import autocast
-from einops import rearrange, repeat
-
-
def find_noise_for_image(p, cond, uncond, cfg_scale, steps):
x = p.init_latent
s_in = x.new_ones([x.shape[0]])
- dnw = K.external.CompVisDenoiser(shared.sd_model)
+ 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
@@ -37,7 +35,7 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps): 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)]
+ 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)
@@ -50,7 +48,7 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps): x = x + d * dt
- sd_samplers.store_latent(x)
+ 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,
@@ -69,7 +67,12 @@ 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]])
- dnw = K.external.CompVisDenoiser(shared.sd_model)
+ 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
@@ -84,7 +87,7 @@ def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): 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)]
+ 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))
@@ -104,7 +107,7 @@ def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): dt = sigmas[i] - sigmas[i - 1]
x = x + d * dt
- sd_samplers.store_latent(x)
+ 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,
@@ -125,7 +128,7 @@ class Script(scripts.Script): def show(self, is_img2img):
return is_img2img
- def ui(self, is_img2img):
+ def ui(self, is_img2img):
info = gr.Markdown('''
* `CFG Scale` should be 2 or lower.
''')
@@ -213,4 +216,3 @@ class Script(scripts.Script): processed = processing.process_images(p)
return processed
-
diff --git a/scripts/loopback.py b/scripts/loopback.py index 1dab9476..9c388aa8 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -1,13 +1,10 @@ -import numpy as np
-from tqdm import trange
+import math
-import modules.scripts as scripts
import gradio as gr
-
-from modules import processing, shared, sd_samplers, images
+import modules.scripts as scripts
+from modules import deepbooru, images, processing, shared
from modules.processing import Processed
-from modules.sd_samplers import samplers
-from modules.shared import opts, cmd_opts, state
+from modules.shared import opts, state
class Script(scripts.Script):
@@ -19,37 +16,68 @@ class Script(scripts.Script): def ui(self, is_img2img):
loops = gr.Slider(minimum=1, maximum=32, step=1, label='Loops', value=4, elem_id=self.elem_id("loops"))
- denoising_strength_change_factor = gr.Slider(minimum=0.9, maximum=1.1, step=0.01, label='Denoising strength change factor', value=1, elem_id=self.elem_id("denoising_strength_change_factor"))
+ 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")
+ append_interrogation = gr.Dropdown(label="Append interrogated prompt at each iteration", choices=["None", "CLIP", "DeepBooru"], value="None")
- return [loops, denoising_strength_change_factor]
+ return [loops, final_denoising_strength, denoising_curve, append_interrogation]
- def run(self, p, loops, denoising_strength_change_factor):
+ def run(self, p, loops, final_denoising_strength, denoising_curve, append_interrogation):
processing.fix_seed(p)
batch_count = p.n_iter
p.extra_generation_params = {
- "Denoising strength change factor": denoising_strength_change_factor,
+ "Final denoising strength": final_denoising_strength,
+ "Denoising curve": denoising_curve
}
p.batch_size = 1
p.n_iter = 1
- output_images, info = None, None
+ info = None
initial_seed = None
initial_info = None
+ initial_denoising_strength = p.denoising_strength
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
initial_color_corrections = [processing.setup_color_correction(p.init_images[0])]
- for n in range(batch_count):
- history = []
+ def calculate_denoising_strength(loop):
+ strength = initial_denoising_strength
+
+ if loops == 1:
+ return strength
+
+ progress = loop / (loops - 1)
+ match denoising_curve:
+ case "Aggressive":
+ strength = math.sin((progress) * math.pi * 0.5)
+ case "Lazy":
+ strength = 1 - math.cos((progress) * math.pi * 0.5)
+
+ case _:
+ strength = progress
+
+ change = (final_denoising_strength - initial_denoising_strength) * strength
+ return initial_denoising_strength + change
+
+ history = []
+
+ for n in range(batch_count):
# Reset to original init image at the start of each batch
p.init_images = original_init_image
+ # Reset to original denoising strength
+ p.denoising_strength = initial_denoising_strength
+
+ last_image = None
+
for i in range(loops):
p.n_iter = 1
p.batch_size = 1
@@ -58,30 +86,57 @@ class Script(scripts.Script): if opts.img2img_color_correction:
p.color_corrections = initial_color_corrections
+ if append_interrogation != "None":
+ p.prompt = original_prompt + ", " if original_prompt != "" else ""
+ if append_interrogation == "CLIP":
+ p.prompt += shared.interrogator.interrogate(p.init_images[0])
+ elif append_interrogation == "DeepBooru":
+ p.prompt += deepbooru.model.tag(p.init_images[0])
+
state.job = f"Iteration {i + 1}/{loops}, batch {n + 1}/{batch_count}"
processed = processing.process_images(p)
+ # Generation cancelled.
+ if state.interrupted:
+ break
+
if initial_seed is None:
initial_seed = processed.seed
initial_info = processed.info
- init_img = processed.images[0]
-
- p.init_images = [init_img]
p.seed = processed.seed + 1
- p.denoising_strength = min(max(p.denoising_strength * denoising_strength_change_factor, 0.1), 1)
- history.append(processed.images[0])
+ p.denoising_strength = calculate_denoising_strength(i + 1)
+
+ if state.skipped:
+ break
+ last_image = processed.images[0]
+ p.init_images = [last_image]
+ p.inpainting_fill = 1 # Set "masked content" to "original" for next loop.
+
+ if batch_count == 1:
+ history.append(last_image)
+ all_images.append(last_image)
+
+ if batch_count > 1 and not state.skipped and not state.interrupted:
+ history.append(last_image)
+ all_images.append(last_image)
+
+ p.inpainting_fill = original_inpainting_fill
+
+ if state.interrupted:
+ break
+
+ if len(history) > 1:
grid = images.image_grid(history, rows=1)
if opts.grid_save:
images.save_image(grid, p.outpath_grids, "grid", initial_seed, p.prompt, opts.grid_format, info=info, short_filename=not opts.grid_extended_filename, grid=True, p=p)
- grids.append(grid)
- all_images += history
-
- if opts.return_grid:
- all_images = grids + all_images
+ if opts.return_grid:
+ grids.append(grid)
+
+ all_images = grids + all_images
processed = Processed(p, all_images, initial_seed, initial_info)
diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 8842bd91..11eab31a 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -17,22 +17,24 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): def ui(self):
selected_tab = gr.State(value=0)
- with gr.Tabs(elem_id="extras_resize_mode"):
- with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by:
- upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize")
-
- with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to:
- with FormRow():
- upscaling_resize_w = gr.Number(label="Width", value=512, precision=0, elem_id="extras_upscaling_resize_w")
- upscaling_resize_h = gr.Number(label="Height", value=512, precision=0, elem_id="extras_upscaling_resize_h")
- upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop")
-
- with FormRow():
- extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
-
- with FormRow():
- extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
- extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility")
+ with gr.Column():
+ with FormRow():
+ with gr.Tabs(elem_id="extras_resize_mode"):
+ with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by:
+ upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize")
+
+ with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to:
+ with FormRow():
+ upscaling_resize_w = gr.Number(label="Width", value=512, precision=0, elem_id="extras_upscaling_resize_w")
+ upscaling_resize_h = gr.Number(label="Height", value=512, precision=0, elem_id="extras_upscaling_resize_h")
+ upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop")
+
+ with FormRow():
+ extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
+
+ with FormRow():
+ extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
+ extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility")
tab_scale_by.select(fn=lambda: 0, inputs=[], outputs=[selected_tab])
tab_scale_to.select(fn=lambda: 1, inputs=[], outputs=[selected_tab])
diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index dd95e588..e9b11517 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -44,16 +44,34 @@ class Script(scripts.Script): def title(self):
return "Prompt matrix"
- 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"))
-
- return [put_at_start, different_seeds]
-
- def run(self, p, put_at_start, different_seeds):
+ def ui(self, is_img2img):
+ gr.HTML('<br />')
+ 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"))
+ 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"))
+
+ 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}")
+
+ 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 = p.prompt[0] if type(p.prompt) == list else p.prompt
+ delimiter = ", " if variations_delimiter == "comma" else " "
all_prompts = []
prompt_matrix_parts = original_prompt.split("|")
@@ -66,20 +84,23 @@ class Script(scripts.Script): else:
selected_prompts = [prompt_matrix_parts[0]] + selected_prompts
- all_prompts.append(", ".join(selected_prompts))
+ all_prompts.append(delimiter.join(selected_prompts))
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.")
- p.prompt = all_prompts
+ 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 = original_prompt
+ p.prompt_for_display = positive_prompt
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, p.width, p.height, prompt_matrix_parts)
+ 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/xyz_grid.py b/scripts/xyz_grid.py index 3df40483..3895a795 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -25,6 +25,8 @@ from modules.ui_components import ToolButton fill_values_symbol = "\U0001f4d2" # 📒
+AxisInfo = namedtuple('AxisInfo', ['axis', 'values'])
+
def apply_field(field):
def fun(p, x, xs):
@@ -126,6 +128,24 @@ def apply_styles(p: StableDiffusionProcessingTxt2Img, x: str, _): p.styles.extend(x.split(','))
+def apply_uni_pc_order(p, x, xs):
+ opts.data["uni_pc_order"] = min(x, p.steps - 1)
+
+
+def apply_face_restore(p, opt, x):
+ opt = opt.lower()
+ if opt == 'codeformer':
+ is_active = True
+ p.face_restoration_model = 'CodeFormer'
+ elif opt == 'gfpgan':
+ is_active = True
+ p.face_restoration_model = 'GFPGAN'
+ else:
+ is_active = opt in ('true', 'yes', 'y', '1')
+
+ p.restore_faces = is_active
+
+
def format_value_add_label(p, opt, x):
if type(x) == float:
x = round(x, 8)
@@ -186,6 +206,7 @@ axis_options = [ AxisOption("Steps", int, apply_field("steps")),
AxisOptionTxt2Img("Hires steps", int, apply_field("hr_second_pass_steps")),
AxisOption("CFG Scale", float, apply_field("cfg_scale")),
+ AxisOptionImg2Img("Image CFG Scale", float, apply_field("image_cfg_scale")),
AxisOption("Prompt S/R", str, apply_prompt, format_value=format_value),
AxisOption("Prompt order", str_permutations, apply_order, format_value=format_value_join_list),
AxisOptionTxt2Img("Sampler", str, apply_sampler, format_value=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]),
@@ -202,54 +223,57 @@ axis_options = [ AxisOptionImg2Img("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight")),
AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: list(sd_vae.vae_dict)),
AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)),
+ AxisOption("UniPC Order", int, apply_uni_pc_order, cost=0.5),
+ AxisOption("Face restore", str, apply_face_restore, format_value=format_value),
]
-def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend, include_lone_images, include_sub_grids, first_axes_processed, second_axes_processed):
+def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend, include_lone_images, include_sub_grids, first_axes_processed, second_axes_processed, margin_size):
hor_texts = [[images.GridAnnotation(x)] for x in x_labels]
ver_texts = [[images.GridAnnotation(y)] for y in y_labels]
title_texts = [[images.GridAnnotation(z)] for z in z_labels]
- # Temporary list of all the images that are generated to be populated into the grid.
- # Will be filled with empty images for any individual step that fails to process properly
- image_cache = [None] * (len(xs) * len(ys) * len(zs))
+ list_size = (len(xs) * len(ys) * len(zs))
processed_result = None
- cell_mode = "P"
- cell_size = (1, 1)
- state.job_count = len(xs) * len(ys) * len(zs) * p.n_iter
+ state.job_count = list_size * p.n_iter
def process_cell(x, y, z, ix, iy, iz):
- nonlocal image_cache, processed_result, cell_mode, cell_size
+ nonlocal processed_result
def index(ix, iy, iz):
return ix + iy * len(xs) + iz * len(xs) * len(ys)
- state.job = f"{index(ix, iy, iz) + 1} out of {len(xs) * len(ys) * len(zs)}"
-
- processed: Processed = cell(x, y, z)
-
- try:
- # this dereference will throw an exception if the image was not processed
- # (this happens in cases such as if the user stops the process from the UI)
- processed_image = processed.images[0]
-
- if processed_result is None:
- # Use our first valid processed result as a template container to hold our full results
- processed_result = copy(processed)
- cell_mode = processed_image.mode
- cell_size = processed_image.size
- processed_result.images = [Image.new(cell_mode, cell_size)]
+ state.job = f"{index(ix, iy, iz) + 1} out of {list_size}"
+
+ processed: Processed = cell(x, y, z, ix, iy, iz)
+
+ if processed_result is None:
+ # Use our first processed result object as a template container to hold our full results
+ processed_result = copy(processed)
+ processed_result.images = [None] * list_size
+ processed_result.all_prompts = [None] * list_size
+ processed_result.all_seeds = [None] * list_size
+ processed_result.infotexts = [None] * list_size
+ processed_result.index_of_first_image = 1
+
+ idx = index(ix, iy, iz)
+ if processed.images:
+ # Non-empty list indicates some degree of success.
+ processed_result.images[idx] = processed.images[0]
+ processed_result.all_prompts[idx] = processed.prompt
+ processed_result.all_seeds[idx] = processed.seed
+ processed_result.infotexts[idx] = processed.infotexts[0]
+ else:
+ cell_mode = "P"
+ cell_size = (processed_result.width, processed_result.height)
+ if processed_result.images[0] is not None:
+ cell_mode = processed_result.images[0].mode
+ #This corrects size in case of batches:
+ cell_size = processed_result.images[0].size
+ processed_result.images[idx] = Image.new(cell_mode, cell_size)
- image_cache[index(ix, iy, iz)] = processed_image
- if include_lone_images:
- processed_result.images.append(processed_image)
- processed_result.all_prompts.append(processed.prompt)
- processed_result.all_seeds.append(processed.seed)
- processed_result.infotexts.append(processed.infotexts[0])
- except:
- image_cache[index(ix, iy, iz)] = Image.new(cell_mode, cell_size)
if first_axes_processed == 'x':
for ix, x in enumerate(xs):
@@ -283,24 +307,35 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend process_cell(x, y, z, ix, iy, iz)
if not processed_result:
+ # Should never happen, I've only seen it on one of four open tabs and it needed to refresh.
+ print("Unexpected error: Processing could not begin, you may need to refresh the tab or restart the service.")
+ return Processed(p, [])
+ elif not any(processed_result.images):
print("Unexpected error: draw_xyz_grid failed to return even a single processed image")
return Processed(p, [])
- grids = [None] * len(zs)
- for i in range(len(zs)):
- start_index = i * len(xs) * len(ys)
+ z_count = len(zs)
+ sub_grids = [None] * z_count
+ for i in range(z_count):
+ start_index = (i * len(xs) * len(ys)) + i
end_index = start_index + len(xs) * len(ys)
- grid = images.image_grid(image_cache[start_index:end_index], rows=len(ys))
+ grid = images.image_grid(processed_result.images[start_index:end_index], rows=len(ys))
if draw_legend:
- grid = images.draw_grid_annotations(grid, cell_size[0], cell_size[1], hor_texts, ver_texts)
-
- grids[i] = grid
- if include_sub_grids and len(zs) > 1:
- processed_result.images.insert(i+1, grid)
-
- original_grid_size = grids[0].size
- grids = images.image_grid(grids, rows=1)
- processed_result.images[0] = images.draw_grid_annotations(grids, original_grid_size[0], original_grid_size[1], title_texts, [[images.GridAnnotation()]])
+ grid = images.draw_grid_annotations(grid, processed_result.images[start_index].size[0], processed_result.images[start_index].size[1], hor_texts, ver_texts, margin_size)
+ processed_result.images.insert(i, grid)
+ processed_result.all_prompts.insert(i, processed_result.all_prompts[start_index])
+ processed_result.all_seeds.insert(i, processed_result.all_seeds[start_index])
+ processed_result.infotexts.insert(i, processed_result.infotexts[start_index])
+
+ sub_grid_size = processed_result.images[0].size
+ z_grid = images.image_grid(processed_result.images[:z_count], rows=1)
+ if draw_legend:
+ z_grid = images.draw_grid_annotations(z_grid, sub_grid_size[0], sub_grid_size[1], title_texts, [[images.GridAnnotation()]])
+ processed_result.images.insert(0, z_grid)
+ #TODO: Deeper aspects of the program rely on grid info being misaligned between metadata arrays, which is not ideal.
+ #processed_result.all_prompts.insert(0, processed_result.all_prompts[0])
+ #processed_result.all_seeds.insert(0, processed_result.all_seeds[0])
+ processed_result.infotexts.insert(0, processed_result.infotexts[0])
return processed_result
@@ -309,9 +344,11 @@ class SharedSettingsStackHelper(object): def __enter__(self):
self.CLIP_stop_at_last_layers = opts.CLIP_stop_at_last_layers
self.vae = opts.sd_vae
+ self.uni_pc_order = opts.uni_pc_order
def __exit__(self, exc_type, exc_value, tb):
opts.data["sd_vae"] = self.vae
+ opts.data["uni_pc_order"] = self.uni_pc_order
modules.sd_models.reload_model_weights()
modules.sd_vae.reload_vae_weights()
@@ -350,10 +387,16 @@ class Script(scripts.Script): fill_z_button = ToolButton(value=fill_values_symbol, elem_id="xyz_grid_fill_z_tool_button", visible=False)
with gr.Row(variant="compact", elem_id="axis_options"):
- draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"))
- include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images"))
- include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids"))
- no_fixed_seeds = gr.Checkbox(label='Keep -1 for seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"))
+ with gr.Column():
+ draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"))
+ no_fixed_seeds = gr.Checkbox(label='Keep -1 for seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"))
+ with gr.Column():
+ include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images"))
+ include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids"))
+ 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"))
+
+ with gr.Row(variant="compact", elem_id="swap_axes"):
swap_xy_axes_button = gr.Button(value="Swap X/Y axes", elem_id="xy_grid_swap_axes_button")
swap_yz_axes_button = gr.Button(value="Swap Y/Z axes", elem_id="yz_grid_swap_axes_button")
swap_xz_axes_button = gr.Button(value="Swap X/Z axes", elem_id="xz_grid_swap_axes_button")
@@ -392,9 +435,9 @@ class Script(scripts.Script): (z_values, "Z Values"),
)
- return [x_type, x_values, y_type, y_values, z_type, z_values, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds]
+ return [x_type, x_values, y_type, y_values, z_type, z_values, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size]
- def run(self, p, x_type, x_values, y_type, y_values, z_type, z_values, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds):
+ def run(self, p, x_type, x_values, y_type, y_values, z_type, z_values, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size):
if not no_fixed_seeds:
modules.processing.fix_seed(p)
@@ -405,7 +448,7 @@ class Script(scripts.Script): if opt.label == 'Nothing':
return [0]
- valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals)))]
+ valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals))) if x]
if opt.type == int:
valslist_ext = []
@@ -471,6 +514,11 @@ class Script(scripts.Script): z_opt = self.current_axis_options[z_type]
zs = process_axis(z_opt, z_values)
+ # this could be moved to common code, but unlikely to be ever triggered anywhere else
+ Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes
+ grid_mp = round(len(xs) * len(ys) * len(zs) * p.width * p.height / 1000000)
+ assert grid_mp < opts.img_max_size_mp, f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)'
+
def fix_axis_seeds(axis_opt, axis_list):
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]
@@ -511,12 +559,14 @@ class Script(scripts.Script): print(f"X/Y/Z plot will create {len(xs) * len(ys) * len(zs) * image_cell_count} images on {len(zs)} {len(xs)}x{len(ys)} grid{plural_s}{cell_console_text}. (Total steps to process: {total_steps})")
shared.total_tqdm.updateTotal(total_steps)
- grid_infotext = [None]
+ state.xyz_plot_x = AxisInfo(x_opt, xs)
+ state.xyz_plot_y = AxisInfo(y_opt, ys)
+ state.xyz_plot_z = AxisInfo(z_opt, zs)
# If one of the axes is very slow to change between (like SD model
# checkpoint), then make sure it is in the outer iteration of the nested
# `for` loop.
- first_axes_processed = 'x'
+ first_axes_processed = 'z'
second_axes_processed = 'y'
if x_opt.cost > y_opt.cost and x_opt.cost > z_opt.cost:
first_axes_processed = 'x'
@@ -537,7 +587,9 @@ class Script(scripts.Script): else:
second_axes_processed = 'y'
- def cell(x, y, z):
+ grid_infotext = [None] * (1 + len(zs))
+
+ def cell(x, y, z, ix, iy, iz):
if shared.state.interrupted:
return Processed(p, [], p.seed, "")
@@ -549,7 +601,9 @@ class Script(scripts.Script): res = process_images(pc)
- if grid_infotext[0] is None:
+ # Sets subgrid infotexts
+ subgrid_index = 1 + iz
+ if grid_infotext[subgrid_index] is None and ix == 0 and iy == 0:
pc.extra_generation_params = copy(pc.extra_generation_params)
pc.extra_generation_params['Script'] = self.title()
@@ -565,6 +619,12 @@ class Script(scripts.Script): if y_opt.label in ["Seed", "Var. seed"] and not no_fixed_seeds:
pc.extra_generation_params["Fixed Y Values"] = ", ".join([str(y) for y in ys])
+ grid_infotext[subgrid_index] = processing.create_infotext(pc, pc.all_prompts, pc.all_seeds, pc.all_subseeds)
+
+ # Sets main grid infotext
+ if grid_infotext[0] is None and ix == 0 and iy == 0 and iz == 0:
+ pc.extra_generation_params = copy(pc.extra_generation_params)
+
if z_opt.label != 'Nothing':
pc.extra_generation_params["Z Type"] = z_opt.label
pc.extra_generation_params["Z Values"] = z_values
@@ -589,10 +649,37 @@ class Script(scripts.Script): include_lone_images=include_lone_images,
include_sub_grids=include_sub_grids,
first_axes_processed=first_axes_processed,
- second_axes_processed=second_axes_processed
+ second_axes_processed=second_axes_processed,
+ margin_size=margin_size
)
+ if not processed.images:
+ # It broke, no further handling needed.
+ return processed
+
+ z_count = len(zs)
+
+ # Set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids)
+ processed.infotexts[:1+z_count] = grid_infotext[:1+z_count]
+
+ if not include_lone_images:
+ # Don't need sub-images anymore, drop from list:
+ processed.images = processed.images[:z_count+1]
+
if opts.grid_save:
- images.save_image(processed.images[0], p.outpath_grids, "xyz_grid", info=grid_infotext[0], extension=opts.grid_format, prompt=p.prompt, seed=processed.seed, grid=True, p=p)
+ # Auto-save main and sub-grids:
+ grid_count = z_count + 1 if z_count > 1 else 1
+ for g in range(grid_count):
+ #TODO: See previous comment about intentional data misalignment.
+ adj_g = g-1 if g > 0 else g
+ images.save_image(processed.images[g], p.outpath_grids, "xyz_grid", info=processed.infotexts[g], extension=opts.grid_format, prompt=processed.all_prompts[adj_g], seed=processed.all_seeds[adj_g], grid=True, p=processed)
+
+ if not include_sub_grids:
+ # Done with sub-grids, drop all related information:
+ for sg in range(z_count):
+ del processed.images[1]
+ del processed.all_prompts[1]
+ del processed.all_seeds[1]
+ del processed.infotexts[1]
return processed
|