diff --git a/modules/ui.py b/modules/ui.py index c6ded6786..4256cdac2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -266,7 +266,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): seed = gr.Number(label='Seed', value=-1) with gr.Group(): - custom_inputs = modules.scripts.setup_ui(is_img2img=False) + custom_inputs = modules.scripts.setup_ui(is_img2img=True) with gr.Column(variant='panel'): diff --git a/script.js b/script.js index 7ead367e8..e200e7139 100644 --- a/script.js +++ b/script.js @@ -36,7 +36,7 @@ titles = { "None": "Do not do anything special", "Prompt matrix": "Separate prompts into parts using vertical pipe character (|) and the script will create a picture for every combination of them (except for the first part, which will be present in all combinations)", - "X/Y Plot": "Create a grid where images will have different parameters. Use inputs below to specify which parameterswill be shared by columns and rows", + "X/Y plot": "Create a grid where images will have different parameters. Use inputs below to specify which parameterswill be shared by columns and rows", "Custom code": "Run python code. Advanced user only. Must run program with --allow-code for this to work", "Prompt S/R": "Separate a list of words with commas, and the first word will be used as a keyword: script will search for this word in the prompt, and replace it with others", diff --git a/scripts/custom_code.py b/scripts/custom_code.py new file mode 100644 index 000000000..b359050a9 --- /dev/null +++ b/scripts/custom_code.py @@ -0,0 +1,40 @@ +import modules.scripts as scripts +import gradio as gr + +from modules.processing import Processed +from modules.shared import opts, cmd_opts, state + + +class Script(scripts.Script): + def title(self): + return "Custom code" + + def enabled(self): + return cmd_opts.allow_code + + def ui(self, is_img2img): + code = gr.Textbox(label="Python code", visible=False, lines=1) + + return [code] + + def run(self, p, code): + if not cmd_opts.allow_code: + return + + display_result_data = [[], -1, ""] + + def display(imgs, s=display_result_data[1], i=display_result_data[2]): + display_result_data[0] = imgs + display_result_data[1] = s + display_result_data[2] = i + + from types import ModuleType + compiled = compile(code, '', 'exec') + module = ModuleType("testmodule") + module.__dict__.update(globals()) + module.p = p + module.display = display + exec(compiled, module.__dict__) + + return Processed(p, *display_result_data) + diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py new file mode 100644 index 000000000..7087bcdec --- /dev/null +++ b/scripts/prompt_matrix.py @@ -0,0 +1,82 @@ +import math +from collections import namedtuple +from copy import copy +import random + +import modules.scripts as scripts +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.sd_samplers + + +def draw_xy_grid(xs, ys, x_label, y_label, cell): + res = [] + + ver_texts = [[images.GridAnnotation(y_label(y))] for y in ys] + hor_texts = [[images.GridAnnotation(x_label(x))] for x in xs] + + first_pocessed = None + + for iy, y in enumerate(ys): + for ix, x in enumerate(xs): + state.job = f"{ix + iy * len(xs) + 1} out of {len(xs) * len(ys)}" + + processed = cell(x, y) + if first_pocessed is None: + first_pocessed = processed + + res.append(processed.images[0]) + + grid = images.image_grid(res, rows=len(ys)) + grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts) + + first_pocessed.images = [grid] + + return first_pocessed + + +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) + + return [put_at_start] + + def run(self, p, put_at_start): + seed = int(random.randrange(4294967294) if p.seed == -1 else p.seed) + + original_prompt = p.prompt[0] if type(p.prompt) == list else p.prompt + + all_prompts = [] + 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)] + + if put_at_start: + selected_prompts = selected_prompts + [prompt_matrix_parts[0]] + else: + selected_prompts = [prompt_matrix_parts[0]] + selected_prompts + + all_prompts.append(", ".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 + p.prompt_for_display = original_prompt + p.seed = len(all_prompts) * [seed] + 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) + processed.images.insert(0, grid) + + return processed diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py new file mode 100644 index 000000000..85fbe884d --- /dev/null +++ b/scripts/xy_grid.py @@ -0,0 +1,154 @@ +from collections import namedtuple +from copy import copy +import random + +import modules.scripts as scripts +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.sd_samplers + + +def apply_field(field): + def fun(p, x, xs): + setattr(p, field, x) + + return fun + + +def apply_prompt(p, x, xs): + p.prompt = p.prompt.replace(xs[0], x) + + +samplers_dict = {} +for i, sampler in enumerate(modules.sd_samplers.samplers): + samplers_dict[sampler.name.lower()] = i + for alias in sampler.aliases: + samplers_dict[alias.lower()] = i + + +def apply_sampler(p, x, xs): + sampler_index = samplers_dict.get(x.lower(), None) + print(x, sampler_index) + if sampler_index is None: + raise RuntimeError(f"Unknown sampler: {x}") + + p.sampler_index = sampler_index + + +def format_value_add_label(p, opt, x): + return f"{opt.label}: {x}" + + +def format_value(p, opt, x): + return x + + +AxisOption = namedtuple("AxisOption", ["label", "type", "apply", "format_value"]) +AxisOptionImg2Img = namedtuple("AxisOptionImg2Img", ["label", "type", "apply", "format_value"]) + + +axis_options = [ + AxisOption("Seed", int, apply_field("seed"), format_value_add_label), + AxisOption("Steps", int, apply_field("steps"), format_value_add_label), + 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_prompt, 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 +] + + +def draw_xy_grid(xs, ys, x_label, y_label, cell): + res = [] + + ver_texts = [[images.GridAnnotation(y_label(y))] for y in ys] + hor_texts = [[images.GridAnnotation(x_label(x))] for x in xs] + + first_pocessed = None + + for iy, y in enumerate(ys): + for ix, x in enumerate(xs): + state.job = f"{ix + iy * len(xs) + 1} out of {len(xs) * len(ys)}" + + processed = cell(x, y) + if first_pocessed is None: + first_pocessed = processed + + res.append(processed.images[0]) + + grid = images.image_grid(res, rows=len(ys)) + grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts) + + first_pocessed.images = [grid] + + return first_pocessed + + +class Script(scripts.Script): + def title(self): + return "X/Y plot" + + def ui(self, is_img2img): + current_axis_options = [x for x in axis_options if type(x) == AxisOption or type(x) == AxisOptionImg2Img and is_img2img] + + with gr.Row(): + x_type = gr.Dropdown(label="X type", choices=[x.label for x in current_axis_options], value=current_axis_options[0].label, visible=False, type="index", elem_id="x_type") + x_values = gr.Textbox(label="X values", visible=False, lines=1) + + with gr.Row(): + y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[1].label, visible=False, type="index", elem_id="y_type") + y_values = gr.Textbox(label="Y values", visible=False, lines=1) + + return [x_type, x_values, y_type, y_values] + + def run(self, p, x_type, x_values, y_type, y_values): + p.seed = int(random.randrange(4294967294) if p.seed == -1 else p.seed) + + def process_axis(opt, vals): + valslist = [x.strip() for x in vals.split(",")] + + if opt.type == int: + valslist_ext = [] + + for val in valslist: + if "-" in val: + s = val.split("-") + start = int(s[0]) + end = int(s[1])+1 + step = 1 if len(s) < 3 else int(s[2]) + valslist_ext += list(range(start, end, step)) + else: + valslist_ext.append(val) + + valslist = valslist_ext + + valslist = [opt.type(x) for x in valslist] + + return valslist + + x_opt = axis_options[x_type] + xs = process_axis(x_opt, x_values) + + y_opt = axis_options[y_type] + ys = process_axis(y_opt, y_values) + + def cell(x, y): + pc = copy(p) + x_opt.apply(pc, x, xs) + y_opt.apply(pc, y, ys) + + return process_images(pc) + + processed = draw_xy_grid( + 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), + cell=cell + ) + + images.save_image(processed.images[0], p.outpath_grids, "xy_grid", prompt=p.prompt, seed=processed.seed) + + return processed