From b790bcd6379c1e98e3668b346e46a67e7f7ebc30 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 5 Oct 2023 15:10:01 -0400 Subject: [PATCH] xyz grid allow string/select switch --- CHANGELOG.md | 2 + javascript/style.css | 1 + modules/processing.py | 1 - scripts/xyz_grid.py | 96 +++++++++++++++++++++++++++---------------- 4 files changed, 64 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f58b36b..050ecd765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -149,6 +149,8 @@ Upgrades are still possible and supported, but above is recommended for best exp - **Startup** - all main CLI parameters can now be set as environment variable as well for example `--data-dir ` can be specified as `SD_DATADIR=` before starting SD.Next + - **XYZ Grid** + - more flexibility to use selection or strings - **Logging** - get browser session info in server log - allow custom log file destination diff --git a/javascript/style.css b/javascript/style.css index 3576dfb26..7ef1a734f 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -42,6 +42,7 @@ footer { display: none; } table { overflow-x: auto !important; overflow-y: auto !important; } td { border-bottom: none !important; padding: 0.1em 0.5em !important; } tr { border-bottom: none !important; padding: 0.1em 0.5em !important; } +.gradio-column { min-width: unset !important; } /* general styled components */ .gradio-button.tool { max-width: min-content; min-width: min-content !important; align-self: end; font-size: 1.4em; color: var(--body-text-color) !important; } diff --git a/modules/processing.py b/modules/processing.py index 6630685a2..5547808a2 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -708,7 +708,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.prompt] else: p.all_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_styles_to_prompt(p.prompt, p.styles)] - if type(p.negative_prompt) == list: p.all_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in p.negative_prompt] else: diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 627726e55..a61eb960a 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -181,6 +181,12 @@ def str_permutations(x): return x +def list_to_csv_string(data_list): + with StringIO() as o: + csv.writer(o).writerow(data_list) + return o.getvalue().strip() + + class AxisOption: def __init__(self, label, tipe, apply, fmt=format_value_add_label, confirm=None, cost=0.0, choices=None): self.label = label @@ -208,7 +214,7 @@ axis_options = [ AxisOption("Prompt S/R", str, apply_prompt, fmt=format_value), AxisOption("Model", str, apply_checkpoint, fmt=format_value, cost=1.0, choices=lambda: sorted(sd_models.checkpoints_list)), AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: ['None'] + list(sd_vae.vae_dict)), - AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)), + AxisOption("Styles", str, apply_styles, choices=lambda: [s.name for s in shared.prompt_styles.styles.values()]), AxisOptionTxt2Img("Sampler", str, apply_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), AxisOptionImg2Img("Sampler", str, apply_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers_for_img2img]), AxisOption("Seed", int, apply_field("seed")), @@ -371,9 +377,8 @@ class SharedSettingsStackHelper(object): re_range = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\(([+-]\d+)\s*\))?\s*") re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\(([+-]\d+(?:.\d*)?)\s*\))?\s*") -re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*\])?\s*") -re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*\])?\s*") - +re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*])?\s*") +re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*])?\s*") class Script(scripts.Script): def title(self): @@ -388,24 +393,25 @@ class Script(scripts.Script): x_values = gr.Textbox(label="X values", lines=1, elem_id=self.elem_id("x_values")) x_values_dropdown = gr.Dropdown(label="X values",visible=False,multiselect=True,interactive=True) fill_x_button = ToolButton(value=symbols.fill, elem_id="xyz_grid_fill_x_tool_button", visible=False) - with gr.Row(): y_type = gr.Dropdown(label="Y type", choices=[x.label for x in self.current_axis_options], value=self.current_axis_options[0].label, type="index", elem_id=self.elem_id("y_type")) y_values = gr.Textbox(label="Y values", lines=1, elem_id=self.elem_id("y_values")) y_values_dropdown = gr.Dropdown(label="Y values",visible=False,multiselect=True,interactive=True) fill_y_button = ToolButton(value=symbols.fill, elem_id="xyz_grid_fill_y_tool_button", visible=False) - with gr.Row(): z_type = gr.Dropdown(label="Z type", choices=[x.label for x in self.current_axis_options], value=self.current_axis_options[0].label, type="index", elem_id=self.elem_id("z_type")) z_values = gr.Textbox(label="Z values", lines=1, elem_id=self.elem_id("z_values")) z_values_dropdown = gr.Dropdown(label="Z values",visible=False,multiselect=True,interactive=True) fill_z_button = ToolButton(value=symbols.fill, 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")) - no_fixed_seeds = gr.Checkbox(label='Keep random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds")) - no_grid = gr.Checkbox(label='Skip grid', value=False, elem_id=self.elem_id("no_xyz_grid")) - 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(): + csv_mode = gr.Checkbox(label='Text inputs', value=False, elem_id=self.elem_id("csv_mode")) + draw_legend = gr.Checkbox(label='Legend', value=True, elem_id=self.elem_id("draw_legend")) + no_fixed_seeds = gr.Checkbox(label='Random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds")) + with gr.Column(): + no_grid = gr.Checkbox(label='Skip grid', value=False, elem_id=self.elem_id("no_xyz_grid")) + include_lone_images = gr.Checkbox(label='Sub-images', value=False, elem_id=self.elem_id("include_lone_images")) + include_sub_grids = gr.Checkbox(label='Sub-grids', value=False, elem_id=self.elem_id("include_sub_grids")) with gr.Row(variant="compact", elem_id="axis_options"): margin_size = gr.Slider(label="Grid margins", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) with gr.Row(variant="compact", elem_id="swap_axes"): @@ -423,28 +429,48 @@ class Script(scripts.Script): xz_swap_args = [x_type, x_values, x_values_dropdown, z_type, z_values, z_values_dropdown] swap_xz_axes_button.click(swap_axes, inputs=xz_swap_args, outputs=xz_swap_args) - def fill(x_type): - axis = self.current_axis_options[x_type] - return axis.choices() if axis.choices else gr.update() + def fill(axis_type, csv_mode): + axis = self.current_axis_options[axis_type] + if axis.choices: + if csv_mode: + return list_to_csv_string(axis.choices()), gr.update() + else: + return gr.update(), axis.choices() + else: + return gr.update(), gr.update() - fill_x_button.click(fn=fill, inputs=[x_type], outputs=[x_values_dropdown]) - fill_y_button.click(fn=fill, inputs=[y_type], outputs=[y_values_dropdown]) - fill_z_button.click(fn=fill, inputs=[z_type], outputs=[z_values_dropdown]) + fill_x_button.click(fn=fill, inputs=[x_type, csv_mode], outputs=[x_values_dropdown]) + fill_y_button.click(fn=fill, inputs=[y_type, csv_mode], outputs=[y_values_dropdown]) + fill_z_button.click(fn=fill, inputs=[z_type, csv_mode], outputs=[z_values_dropdown]) - def select_axis(axis_type,axis_values_dropdown): + def select_axis(axis_type, axis_values, axis_values_dropdown, csv_mode): choices = self.current_axis_options[axis_type].choices has_choices = choices is not None - current_values = axis_values_dropdown + current_values = axis_values + current_dropdown_values = axis_values_dropdown if has_choices: choices = choices() - if isinstance(current_values,str): - current_values = current_values.split(",") - current_values = list(filter(lambda x: x in choices, current_values)) - return gr.Button.update(visible=has_choices),gr.Textbox.update(visible=not has_choices),gr.update(choices=choices if has_choices else None,visible=has_choices,value=current_values) + if csv_mode: + current_dropdown_values = list(filter(lambda x: x in choices, current_dropdown_values)) + current_values = list_to_csv_string(current_dropdown_values) + else: + current_dropdown_values = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(axis_values)))] + current_dropdown_values = list(filter(lambda x: x in choices, current_dropdown_values)) - x_type.change(fn=select_axis, inputs=[x_type,x_values_dropdown], outputs=[fill_x_button,x_values,x_values_dropdown]) - y_type.change(fn=select_axis, inputs=[y_type,y_values_dropdown], outputs=[fill_y_button,y_values,y_values_dropdown]) - z_type.change(fn=select_axis, inputs=[z_type,z_values_dropdown], outputs=[fill_z_button,z_values,z_values_dropdown]) + return (gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or csv_mode, value=current_values), + gr.update(choices=choices if has_choices else None, visible=has_choices and not csv_mode, value=current_dropdown_values)) + + x_type.change(fn=select_axis, inputs=[x_type, x_values, x_values_dropdown, csv_mode], outputs=[fill_x_button, x_values, x_values_dropdown]) + y_type.change(fn=select_axis, inputs=[y_type, y_values, y_values_dropdown, csv_mode], outputs=[fill_y_button, y_values, y_values_dropdown]) + z_type.change(fn=select_axis, inputs=[z_type, z_values, z_values_dropdown, csv_mode], outputs=[fill_z_button, z_values, z_values_dropdown]) + + def change_choice_mode(csv_mode, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown): + _fill_x_button, _x_values, _x_values_dropdown = select_axis(x_type, x_values, x_values_dropdown, csv_mode) + _fill_y_button, _y_values, _y_values_dropdown = select_axis(y_type, y_values, y_values_dropdown, csv_mode) + _fill_z_button, _z_values, _z_values_dropdown = select_axis(z_type, z_values, z_values_dropdown, csv_mode) + return _fill_x_button, _x_values, _x_values_dropdown, _fill_y_button, _y_values, _y_values_dropdown, _fill_z_button, _z_values, _z_values_dropdown + + csv_mode.change(fn=change_choice_mode, inputs=[csv_mode, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown, fill_y_button, y_values, y_values_dropdown, fill_z_button, z_values, z_values_dropdown]) def get_dropdown_update_from_params(axis,params): val_key = f"{axis} Values" @@ -464,9 +490,9 @@ class Script(scripts.Script): (z_values_dropdown, lambda params:get_dropdown_update_from_params("Z",params)), ) - return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, no_grid] + return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, no_grid, include_lone_images, include_sub_grids, margin_size] - def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, no_grid): # pylint: disable=arguments-differ + def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, no_grid, include_lone_images, include_sub_grids, margin_size): # pylint: disable=W0221 shared.log.debug(f'xyzgrid: x_type={x_type}|x_values={x_values}|x_values_dropdown={x_values_dropdown}|y_type={y_type}|{y_values}={y_values}|{y_values_dropdown}={y_values_dropdown}|z_type={z_type}|z_values={z_values}|z_values_dropdown={z_values_dropdown}|draw_legend={draw_legend}|include_lone_images={include_lone_images}|include_sub_grids={include_sub_grids}|no_grid={no_grid}|margin_size={margin_size}') if not no_fixed_seeds: processing.fix_seed(p) @@ -475,7 +501,7 @@ class Script(scripts.Script): def process_axis(opt, vals, vals_dropdown): if opt.label == 'Nothing': return [0] - if opt.choices is not None: + if opt.choices is not None and not csv_mode: valslist = vals_dropdown else: valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals))) if x] @@ -524,16 +550,16 @@ class Script(scripts.Script): return valslist x_opt = self.current_axis_options[x_type] - if x_opt.choices is not None: - x_values = ",".join(x_values_dropdown) + if x_opt.choices is not None and not csv_mode: + x_values = list_to_csv_string(x_values_dropdown) xs = process_axis(x_opt, x_values, x_values_dropdown) y_opt = self.current_axis_options[y_type] - if y_opt.choices is not None: - y_values = ",".join(y_values_dropdown) + if y_opt.choices is not None and not csv_mode: + y_values = list_to_csv_string(y_values_dropdown) ys = process_axis(y_opt, y_values, y_values_dropdown) z_opt = self.current_axis_options[z_type] - if z_opt.choices is not None: - z_values = ",".join(z_values_dropdown) + if z_opt.choices is not None and not csv_mode: + z_values = list_to_csv_string(z_values_dropdown) zs = process_axis(z_opt, z_values, z_values_dropdown) Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes