diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d195bbc9..0aa20ca7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ And it also includes fixes for all reported issues so far - vae: enable taesd batch decode enable/disable with settings -> diffusers > vae slicing - updated core requirements + - major internal ui module refactoring + this may cause compatibility issues if an extension is doing a direct import from `ui.py` + in which case, report it so i can add a compatibility layer - **Compile** - new option: **fused projections** pretty much free 5% performance boost for compatible models @@ -56,6 +59,7 @@ And it also includes fixes for all reported issues so far - ipadapter: allow changing of model/image on-the-fly - ipadapter: fix fallback of cross-attention on unload - python: fix python 3.9 compatibility + - sdxl: fix positive prompt embeds - img2img: clip and blip interrogate - img2img: sampler selection offset - sampler: guard against invalid sampler index diff --git a/modules/img2img.py b/modules/img2img.py index 2b3e30889..a69df2895 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -114,14 +114,13 @@ def img2img(id_task: str, mode: int, init_img_inpaint, init_mask_inpaint, steps, - sampler_index, hr_sampler_index, + sampler_index, mask_blur, mask_alpha, inpainting_fill, full_quality, restore_faces, tiling, n_iter, batch_size, cfg_scale, image_cfg_scale, diffusers_guidance_rescale, - refiner_steps, refiner_start, clip_skip, denoising_strength, @@ -140,7 +139,7 @@ def img2img(id_task: str, mode: int, shared.log.warning('Model not loaded') return [], '', '', 'Error: model not loaded' - debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|hr_sampler_index={hr_sampler_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|resize_name={resize_name}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') + debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}||mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|resize_name={resize_name}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') if mode == 5: if img2img_batch_files is None or len(img2img_batch_files) == 0: @@ -150,8 +149,6 @@ def img2img(id_task: str, mode: int, if sampler_index is None: sampler_index = 0 - if hr_sampler_index is None: - hr_sampler_index = 0 override_settings = create_override_settings_dict(override_settings_texts) @@ -213,7 +210,6 @@ def img2img(id_task: str, mode: int, seed_resize_from_w=seed_resize_from_w, seed_enable_extras=True, sampler_name = processing.get_sampler_name(sampler_index, img=True), - hr_sampler_name = processing.get_sampler_name(hr_sampler_index, img=True), batch_size=batch_size, n_iter=n_iter, steps=steps, @@ -233,7 +229,6 @@ def img2img(id_task: str, mode: int, denoising_strength=denoising_strength, image_cfg_scale=image_cfg_scale, diffusers_guidance_rescale=diffusers_guidance_rescale, - refiner_steps=refiner_steps, refiner_start=refiner_start, inpaint_full_res=inpaint_full_res != 0, inpaint_full_res_padding=inpaint_full_res_padding, diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 521c710e2..beea36b95 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -225,8 +225,8 @@ def process_diffusers(p: StableDiffusionProcessing): if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and len(p.prompt_embeds) > 0 and p.prompt_embeds[0] is not None: args['prompt_embeds'] = p.prompt_embeds[0] - if 'XL' in model.__class__.__name__ and len(getattr(p, 'negative_pooleds', [])) > 0: - args['pooled_prompt_embeds'] = p.negative_pooleds[0] + if 'XL' in model.__class__.__name__ and len(getattr(p, 'positive_pooleds', [])) > 0: + args['pooled_prompt_embeds'] = p.positive_pooleds[0] else: args['prompt'] = prompts if 'negative_prompt' in possible: diff --git a/modules/shared.py b/modules/shared.py index 6008d0af5..8c1069c4f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -570,7 +570,6 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "postprocessing_sep_img2img": OptionInfo("

Img2Img & Inpainting

", "", gr.HTML), "img2img_color_correction": OptionInfo(False, "Apply color correction"), - # "img2img_apply_overlay": OptionInfo(False, "Apply result as overlay"), "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified", gr.Checkbox, { "visible": False }), "img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", ui_components.FormColorPicker, {}), "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), @@ -584,7 +583,6 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "postprocessing_sep_upscalers": OptionInfo("

Upscaling

", "", gr.HTML), "upscaler_unload": OptionInfo(False, "Unload upscaler after processing"), - # 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1, "visible": False}), "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers], "visible": False}, refresh=refresh_upscalers), "upscaler_tile_size": OptionInfo(192, "Upscaler tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "upscaler_tile_overlap": OptionInfo(8, "Upscaler tile overlap", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), diff --git a/modules/ui.py b/modules/ui.py index 363387bb7..4b4b40f9d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1,17 +1,12 @@ import os -import json import mimetypes -from functools import reduce - import gradio as gr import gradio.routes import gradio.utils -import numpy as np -from PIL import Image -from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call -from modules import timer, gr_hijack, shared, theme, sd_models, script_callbacks, modelloader, prompt_parser, ui_common, ui_loadsave, ui_symbols, generation_parameters_copypaste -from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML -from modules.paths import script_path, data_path +from modules.call_queue import wrap_gradio_call +from modules import timer, gr_hijack, shared, theme, sd_models, script_callbacks, modelloader, ui_common, ui_loadsave, ui_symbols, ui_javascript, generation_parameters_copypaste +from modules.ui_components import FormRow +from modules.paths import script_path, data_path # pylint: disable=unused-import from modules.dml import directml_override_opts import modules.scripts import modules.textual_inversion.ui @@ -35,10 +30,6 @@ folder_symbol = ui_symbols.folder extra_networks_symbol = ui_symbols.networks apply_style_symbol = ui_symbols.apply save_style_symbol = ui_symbols.save -txt2img_paste_fields = [] -img2img_paste_fields = [] -txt2img_args = [] -img2img_args = [] paste_function = None gr_hijack.init() @@ -72,308 +63,14 @@ def send_gradio_gallery_to_image(x): return generation_parameters_copypaste.image_from_url_text(x[0]) -def add_style(name: str, prompt: str, negative_prompt: str): - from modules import styles - if name is None: - return [gr_show() for x in range(4)] - style = styles.Style(name, prompt, negative_prompt) - shared.prompt_styles.styles[style.name] = style - shared.prompt_styles.save_styles(shared.opts.styles_dir) - return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(2)] - - -def calc_resolution_hires(width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler): - from modules import processing, devices - if hr_upscaler == "None": - return "Hires resize: None" - p = processing.StableDiffusionProcessingTxt2Img(width=width, height=height, enable_hr=True, hr_scale=hr_scale, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y) - p.init_hr() - with devices.autocast(): - p.init([""], [0], [0]) - return f"Hires resize: from {p.width}x{p.height} to {p.hr_resize_x or p.hr_upscale_to_x}x{p.hr_resize_y or p.hr_upscale_to_y}" - - -def resize_from_to_html(width, height, scale_by): - target_width = int(width * scale_by) - target_height = int(height * scale_by) - if not target_width or not target_height: - return "Hires resize: no image selected" - return f"Hires resize: from {width}x{height} to {target_width}x{target_height}" - - -def apply_styles(prompt, prompt_neg, styles): - prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles) - prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles) - return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value=[])] - - -def parse_style(styles): - return styles.split('|') - - -def process_interrogate(interrogation_function, mode, ii_input_files, ii_input_dir, ii_output_dir, *ii_singles): - mode = int(mode) - if mode in {0, 1, 3, 4}: - return [interrogation_function(ii_singles[mode]), None] - if mode == 2: - return [interrogation_function(ii_singles[mode]["image"]), None] - if mode == 5: - if len(ii_input_files) > 0: - images = [f.name for f in ii_input_files] - else: - if not os.path.isdir(ii_input_dir): - log.error(f"Interrogate: Input directory not found: {ii_input_dir}") - return [gr.update(), None] - images = shared.listfiles(ii_input_dir) - if ii_output_dir != "": - os.makedirs(ii_output_dir, exist_ok=True) - else: - ii_output_dir = ii_input_dir - for image in images: - img = Image.open(image) - filename = os.path.basename(image) - left, _ = os.path.splitext(filename) - print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a', encoding='utf-8')) # pylint: disable=consider-using-with - return [gr.update(), None] - - -def interrogate(image): - if image is None: - log.error("Interrogate: no image selected") - return gr.update() - prompt = shared.interrogator.interrogate(image.convert("RGB")) - return gr.update() if prompt is None else prompt - - -def interrogate_deepbooru(image): - from modules import deepbooru - prompt = deepbooru.model.tag(image) - return gr.update() if prompt is None else prompt - - -def create_batch_inputs(tab): - with gr.Accordion(open=False, label="Batch", elem_id=f"{tab}_batch", elem_classes=["small-accordion"]): - with FormRow(elem_id=f"{tab}_row_batch"): - batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id=f"{tab}_batch_count") - batch_size = gr.Slider(minimum=1, maximum=32, step=1, label='Batch size', value=1, elem_id=f"{tab}_batch_size") - batch_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_batch_switch_btn", label="Switch dims") - batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) - return batch_count, batch_size - - -def create_seed_inputs(tab, reuse_visible=True): - with gr.Accordion(open=False, label="Seed", elem_id=f"{tab}_seed_group", elem_classes=["small-accordion"]): - with FormRow(elem_id=f"{tab}_seed_row", variant="compact"): - seed = gr.Number(label='Initial seed', value=-1, elem_id=f"{tab}_seed", container=True) - random_seed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_seed", label='Random seed') - reuse_seed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_seed", label='Reuse seed', visible=reuse_visible) - with FormRow(elem_id=f"{tab}_subseed_row", variant="compact", visible=shared.backend==shared.Backend.ORIGINAL): - subseed = gr.Number(label='Variation', value=-1, elem_id=f"{tab}_subseed", container=True) - random_subseed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_subseed") - reuse_subseed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_subseed", visible=reuse_visible) - subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=f"{tab}_subseed_strength") - with FormRow(visible=False): - seed_resize_from_w = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from width", value=0, elem_id=f"{tab}_seed_resize_from_w") - seed_resize_from_h = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from height", value=0, elem_id=f"{tab}_seed_resize_from_h") - random_seed.click(fn=lambda: [-1, -1], show_progress=False, inputs=[], outputs=[seed, subseed]) - random_subseed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[subseed]) - return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w - - -def create_advanced_inputs(tab): - with gr.Accordion(open=False, label="Advanced", elem_id=f"{tab}_advanced", elem_classes=["small-accordion"]): - with gr.Group(): - with FormRow(): - cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0, elem_id=f"{tab}_cfg_scale") - clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=14, step=1, elem_id=f"{tab}_clip_skip", interactive=True) - with FormRow(): - image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Secondary CFG scale', value=6.0, elem_id=f"{tab}_image_cfg_scale") - diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id=f"{tab}_image_cfg_rescale", visible=shared.backend == shared.Backend.DIFFUSERS) - with gr.Group(): - with FormRow(): - full_quality = gr.Checkbox(label='Full quality', value=True, elem_id=f"{tab}_full_quality") - restore_faces = gr.Checkbox(label='Face restore', value=False, visible=len(shared.face_restorers) > 1, elem_id=f"{tab}_restore_faces") - tiling = gr.Checkbox(label='Tiling', value=False, elem_id=f"{tab}_tiling", visible=shared.backend == shared.Backend.ORIGINAL) - with gr.Group(visible=shared.backend == shared.Backend.DIFFUSERS): - with FormRow(): - hdr_clamp = gr.Checkbox(label='HDR clamp', value=False, elem_id=f"{tab}_hdr_clamp") - hdr_boundary = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=4.0, label='Range', elem_id=f"{tab}_hdr_boundary") - hdr_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.95, label='Threshold', elem_id=f"{tab}_hdr_threshold") - with FormRow(): - hdr_center = gr.Checkbox(label='HDR center', value=False, elem_id=f"{tab}_hdr_center") - hdr_channel_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1.0, label='Channel shift', elem_id=f"{tab}_hdr_channel_shift") - hdr_full_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1, label='Full shift', elem_id=f"{tab}_hdr_full_shift") - with FormRow(): - hdr_maximize = gr.Checkbox(label='HDR maximize', value=False, elem_id=f"{tab}_hdr_maximize") - hdr_max_center = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=0.6, label='Center', elem_id=f"{tab}_hdr_max_center") - hdr_max_boundry = gr.Slider(minimum=0.5, maximum=2.0, step=0.1, value=1.0, label='Range', elem_id=f"{tab}_hdr_max_boundry") - return cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry - - -def create_resize_inputs(tab, images, time_selector=False, scale_visible=True, mode=None): - dummy_component = gr.Number(visible=False, value=0) - with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id=f"{tab}_resize_group"): - with gr.Row(): - if mode is not None: - resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value=mode, visible=False) - else: - resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value='None') - resize_time = gr.Radio(label="Resize order", elem_id=f"{tab}_resize_order", choices=['Before', 'After'], value="Before", visible=time_selector) - with gr.Row(): - resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in shared.sd_upscalers], value=opts.upscaler_for_img2img) - create_refresh_button(resize_name, modelloader.load_upscalers, lambda: {"choices": modelloader.load_upscalers()}, 'refresh_upscalers') - - with FormRow(visible=True) as _resize_group: - with gr.Column(elem_id=f"{tab}_column_size"): - selected_scale_tab = gr.State(value=0) # pylint: disable=abstract-class-instantiated - with gr.Tabs(): - with gr.Tab(label="Resize to") as tab_scale_to: - with FormRow(): - with gr.Column(elem_id=f"{tab}_column_size"): - with FormRow(): - width = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512, elem_id=f"{tab}_width") - height = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512, elem_id=f"{tab}_height") - res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_res_switch_btn") - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) - detect_image_size_btn = ToolButton(value=ui_symbols.detect, elem_id=f"{tab}_detect_image_size_btn") - detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False) - - with gr.Tab(label="Resize by") as tab_scale_by: - scale_by = gr.Slider(minimum=0.05, maximum=8.0, step=0.05, label="Scale", value=1.0, elem_id=f"{tab}_scale") - if scale_visible: - with FormRow(): - scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id=f"{tab}_scale_resolution_preview") - gr.Slider(label="Unused", elem_id=f"{tab}_unused_scale_by_slider") - button_update_resize_to = gr.Button(visible=False, elem_id=f"{tab}_update_resize_to") - - on_change_args = dict(fn=resize_from_to_html, _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, scale_by], outputs=scale_by_html, show_progress=False) - scale_by.release(**on_change_args) - button_update_resize_to.click(**on_change_args) - - for component in images: - component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) - - tab_scale_to.select(fn=lambda: 0, inputs=[], outputs=[selected_scale_tab]) - tab_scale_by.select(fn=lambda: 1, inputs=[], outputs=[selected_scale_tab]) - # resize_mode.change(fn=lambda x: gr.update(visible=x != 0), inputs=[resize_mode], outputs=[_resize_group]) - return resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time +def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id): + return ui_common.create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id) def connect_clear_prompt(button): # pylint: disable=unused-argument pass -def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed): - """ Connects a 'reuse (sub)seed' button's click event so that it copies last used - (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength - was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" - def copy_seed(gen_info_string: str, index: int): - res = -1 - try: - gen_info = json.loads(gen_info_string) - log.debug(f'Reuse: info={gen_info}') - index -= gen_info.get('index_of_first_image', 0) - index = int(index) - - if is_subseed and gen_info.get('subseed_strength', 0) > 0: - all_subseeds = gen_info.get('all_subseeds', [-1]) - res = all_subseeds[index if 0 <= index < len(all_subseeds) else 0] - else: - all_seeds = gen_info.get('all_seeds', [-1]) - res = all_seeds[index if 0 <= index < len(all_seeds) else 0] - except json.decoder.JSONDecodeError: - if gen_info_string != '': - log.error(f"Error parsing JSON generation info: {gen_info_string}") - return [res, gr_show(False)] - - dummy_component = gr.Number(visible=False, value=0) - reuse_seed.click(fn=copy_seed, _js="(x, y) => [x, selected_gallery_index()]", show_progress=False, inputs=[generation_info, dummy_component], outputs=[seed, dummy_component]) - - -def update_token_counter(text, steps): - from modules import extra_networks, sd_hijack - try: - text, _ = extra_networks.parse_prompt(text) - _, prompt_flat_list, _ = prompt_parser.get_multicond_prompt_list([text]) - prompt_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt_flat_list, steps) - except Exception: - prompt_schedules = [[[steps, text]]] - - flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) - prompts = [prompt_text for step, prompt_text in flat_prompts] - if shared.backend == shared.Backend.ORIGINAL: - token_count, max_length = max([sd_hijack.model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) - elif shared.backend == shared.Backend.DIFFUSERS: - if shared.sd_model is not None and hasattr(shared.sd_model, 'tokenizer'): - tokenizer = shared.sd_model.tokenizer - if tokenizer is None: - token_count = 0 - max_length = 75 - else: - has_bos_token = tokenizer.bos_token_id is not None - has_eos_token = tokenizer.eos_token_id is not None - ids = [shared.sd_model.tokenizer(prompt) for prompt in prompts] - if len(ids) > 0 and hasattr(ids[0], 'input_ids'): - ids = [x.input_ids for x in ids] - token_count = max([len(x) for x in ids]) - int(has_bos_token) - int(has_eos_token) - max_length = tokenizer.model_max_length - int(has_bos_token) - int(has_eos_token) - else: - token_count = 0 - max_length = 75 - return f"{token_count}/{max_length}" - - -def create_toprow(is_img2img: bool = False, id_part: str = None): - if id_part is None: - id_part = "img2img" if is_img2img else "txt2img" - with gr.Row(elem_id=f"{id_part}_toprow", variant="compact"): - with gr.Column(elem_id=f"{id_part}_prompt_container", scale=6): - with gr.Row(): - with gr.Column(scale=80): - with gr.Row(): - prompt = gr.Textbox(elem_id=f"{id_part}_prompt", label="Prompt", show_label=False, lines=3, placeholder="Prompt", elem_classes=["prompt"]) - with gr.Row(): - with gr.Column(scale=80): - with gr.Row(): - negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", label="Negative prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"]) - button_interrogate = None - button_deepbooru = None - if is_img2img: - with gr.Column(scale=1, elem_classes="interrogate-col"): - button_interrogate = gr.Button('Interrogate\nCLIP', elem_id=f"{id_part}_interrogate") - button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id=f"{id_part}_deepbooru") - with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"): - with gr.Row(elem_id=f"{id_part}_generate_box"): - submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary') - with gr.Row(elem_id=f"{id_part}_generate_line2"): - interrupt = gr.Button('Stop', elem_id=f"{id_part}_interrupt") - interrupt.click(fn=lambda: shared.state.interrupt(), _js="requestInterrupt", inputs=[], outputs=[]) - skip = gr.Button('Skip', elem_id=f"{id_part}_skip") - skip.click(fn=lambda: shared.state.skip(), inputs=[], outputs=[]) - pause = gr.Button('Pause', elem_id=f"{id_part}_pause") - pause.click(fn=lambda: shared.state.pause(), _js='checkPaused', inputs=[], outputs=[]) - with gr.Row(elem_id=f"{id_part}_tools"): - button_paste = gr.Button(value='Restore', variant='secondary', elem_id=f"{id_part}_paste") # symbols.paste - button_clear = gr.Button(value='Clear', variant='secondary', elem_id=f"{id_part}_clear_prompt_btn") # symbols.clear - button_extra = gr.Button(value='Networks', variant='secondary', elem_id=f"{id_part}_extra_networks_btn") # symbols.networks - button_clear.click(fn=lambda *x: ['', ''], inputs=[prompt, negative_prompt], outputs=[prompt, negative_prompt], show_progress=False) - with gr.Row(elem_id=f"{id_part}_counters"): - token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_token_counter", elem_classes=["token-counter"]) - token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") - negative_token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"]) - negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") - with gr.Row(elem_id=f"{id_part}_styles_row"): - styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in shared.prompt_styles.styles.values()], value=[], multiselect=True) - _styles_btn_refresh = create_refresh_button(styles, shared.prompt_styles.reload, lambda: {"choices": list(shared.prompt_styles.styles)}, f"{id_part}_styles_refresh") - # styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) - # styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) - styles_btn_select = gr.Button('Select', elem_id=f"{id_part}_styles_select", visible=False) - styles_btn_select.click(_js="applyStyles", fn=parse_style, inputs=[styles], outputs=[styles]) - styles_btn_apply = ToolButton(ui_symbols.apply, elem_id=f"{id_part}_extra_apply", visible=False) - styles_btn_apply.click(fn=apply_styles, inputs=[prompt, negative_prompt, styles], outputs=[prompt, negative_prompt, styles]) - return prompt, styles, negative_prompt, submit, button_interrogate, button_deepbooru, button_paste, button_extra, token_counter, token_button, negative_token_counter, negative_token_button - - def setup_progressbar(*args, **kwargs): # pylint: disable=unused-argument pass @@ -404,91 +101,6 @@ def apply_setting(key, value): return getattr(opts, key) -def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id): - return ui_common.create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id) - - -def create_sampler_and_steps_selection(choices, tabname): - def set_sampler_original_options(sampler_options, sampler_algo): - opts.data['schedulers_brownian_noise'] = 'brownian noise' in sampler_options - opts.data['schedulers_discard_penultimate'] = 'discard penultimate sigma' in sampler_options - opts.data['schedulers_sigma'] = sampler_algo - opts.save(shared.config_filename, silent=True) - - def set_sampler_diffuser_options(sampler_options): - opts.data['schedulers_use_karras'] = 'karras' in sampler_options - opts.data['schedulers_use_thresholding'] = 'dynamic thresholding' in sampler_options - opts.data['schedulers_use_loworder'] = 'low order' in sampler_options - opts.data['schedulers_rescale_betas'] = 'rescale beta' in sampler_options - opts.save(shared.config_filename, silent=True) - - with FormRow(elem_classes=['flex-break']): - sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value='Default', type="index") - steps = gr.Slider(minimum=1, maximum=99, step=1, label="Sampling steps", elem_id=f"{tabname}_steps", value=20) - if shared.backend == shared.Backend.ORIGINAL: - with FormRow(elem_classes=['flex-break']): - choices = ['brownian noise', 'discard penultimate sigma'] - values = [] - values += ['brownian noise'] if opts.data.get('schedulers_brownian_noise', False) else [] - values += ['discard penultimate sigma'] if opts.data.get('schedulers_discard_penultimate', True) else [] - sampler_options = gr.CheckboxGroup(label='Sampler options', choices=choices, value=values, type='value') - with FormRow(elem_classes=['flex-break']): - opts.data['schedulers_sigma'] = opts.data.get('schedulers_sigma', 'default') - sampler_algo = gr.Radio(label='Sigma algorithm', choices=['default', 'karras', 'exponential', 'polyexponential'], value=opts.data['schedulers_sigma'], type='value') - sampler_options.change(fn=set_sampler_original_options, inputs=[sampler_options, sampler_algo], outputs=[]) - sampler_algo.change(fn=set_sampler_original_options, inputs=[sampler_options, sampler_algo], outputs=[]) - else: - with FormRow(elem_classes=['flex-break']): - choices = ['karras', 'dynamic threshold', 'low order', 'rescale beta'] - values = [] - values += ['karras'] if opts.data.get('schedulers_use_karras', True) else [] - values += ['dynamic threshold'] if opts.data.get('schedulers_use_thresholding', False) else [] - values += ['low order'] if opts.data.get('schedulers_use_loworder', True) else [] - values += ['rescale beta'] if opts.data.get('schedulers_rescale_betas', False) else [] - sampler_options = gr.CheckboxGroup(label='Sampler options', choices=choices, value=values, type='value') - sampler_options.change(fn=set_sampler_diffuser_options, inputs=[sampler_options], outputs=[]) - return steps, sampler_index - - -def create_sampler_inputs(tab): - from modules import sd_samplers - with gr.Accordion(open=False, label="Sampler", elem_id=f"{tab}_sampler", elem_classes=["small-accordion"]): - with FormRow(elem_id=f"{tab}_row_sampler"): - sd_samplers.set_samplers() - steps, sampler_index = create_sampler_and_steps_selection(sd_samplers.samplers, tab) - return steps, sampler_index - - -def create_hires_inputs(tab): - with gr.Accordion(open=False, label="Second pass", elem_id=f"{tab}_second_pass", elem_classes=["small-accordion"]): - with FormGroup(): - with FormRow(elem_id=f"{tab}_hires_row1"): - enable_hr = gr.Checkbox(label='Enable second pass', value=False, elem_id=f"{tab}_enable_hr") - with FormRow(elem_id=f"{tab}_hires_row2"): - hr_sampler_index = gr.Dropdown(label='Secondary sampler', elem_id=f"{tab}_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") - denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.5, elem_id=f"{tab}_denoising_strength") - with FormRow(elem_id=f"{tab}_hires_finalres", variant="compact"): - hr_final_resolution = FormHTML(value="", elem_id=f"{tab}_hr_finalres", label="Upscaled resolution", interactive=False) - with FormRow(elem_id=f"{tab}_hires_fix_row1", variant="compact"): - hr_upscaler = gr.Dropdown(label="Upscaler", elem_id=f"{tab}_hr_upscaler", choices=[*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]], value=shared.latent_upscale_default_mode) - hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id=f"{tab}_hr_force") - with FormRow(elem_id=f"{tab}_hires_fix_row2", variant="compact"): - hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id=f"{tab}_steps_alt", value=20) - hr_scale = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Upscale by", value=2.0, elem_id=f"{tab}_hr_scale") - with FormRow(elem_id=f"{tab}_hires_fix_row3", variant="compact"): - hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id=f"{tab}_hr_resize_x") - hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id=f"{tab}_hr_resize_y") - with FormGroup(visible=shared.backend == shared.Backend.DIFFUSERS): - with FormRow(elem_id=f"{tab}_refiner_row1", variant="compact"): - refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id=f"{tab}_refiner_start") - refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id=f"{tab}_refiner_steps", value=5) - with FormRow(elem_id=f"{tab}_refiner_row3", variant="compact"): - refiner_prompt = gr.Textbox(value='', label='Secondary prompt', elem_id=f"{tab}_refiner_prompt") - with FormRow(elem_id="txt2img_refiner_row4", variant="compact"): - refiner_negative = gr.Textbox(value='', label='Secondary negative prompt', elem_id=f"{tab}_refiner_neg_prompt") - return enable_hr, hr_sampler_index, denoising_strength, hr_final_resolution, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative - - def get_value_for_setting(key): value = getattr(opts, key) info = opts.data_labels[key] @@ -501,417 +113,29 @@ def ordered_ui_categories(): return ['dimensions', 'sampler', 'seed', 'denoising', 'cfg', 'checkboxes', 'accordions', 'override_settings', 'scripts'] # a1111 compatibility item, not implemented -def create_override_inputs(tab): # pylint: disable=unused-argument - with FormRow(elem_id=f"{tab}_override_settings_row"): - override_settings = gr.Dropdown([], value=None, label="Override settings", visible=False, elem_id=f"{tab}_override_settings", multiselect=True) - override_settings.change(fn=lambda x: gr.Dropdown.update(visible=len(x) > 0), inputs=[override_settings], outputs=[override_settings]) - return override_settings - - def create_ui(startup_timer = None): if startup_timer is None: timer.startup = timer.Timer() - reload_javascript() + ui_javascript.reload_javascript() generation_parameters_copypaste.reset() - import modules.txt2img # pylint: disable=redefined-outer-name - modules.scripts.scripts_current = modules.scripts.scripts_txt2img - modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False) with gr.Blocks(analytics_enabled=False) as txt2img_interface: - txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, txt2img_submit, _interrogate, _deepbooru, txt2img_paste, txt2img_extra_networks_button, txt2img_token_counter, txt2img_token_button, txt2img_negative_token_counter, txt2img_negative_token_button = create_toprow(is_img2img=False, id_part="txt2img") + from modules import ui_txt2img + ui_txt2img.create_ui() + timer.startup.record("ui-txt2img") - txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False) - txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) - - with FormRow(variant='compact', elem_id="txt2img_extra_networks", visible=False) as extra_networks_ui: - from modules import ui_extra_networks - extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, txt2img_extra_networks_button, 'txt2img', skip_indexing=opts.extra_network_skip_indexing) - timer.startup.record('ui-extra-networks') - - with gr.Row(elem_id="txt2img_interface", equal_height=False): - with gr.Column(variant='compact', elem_id="txt2img_settings"): - - with FormRow(): - width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=512, elem_id="txt2img_width") - height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=512, elem_id="txt2img_height") - res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id="txt2img_res_switch_btn", label="Switch dims") - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) - - with FormGroup(elem_classes="settings-accordion"): - - steps, sampler_index = create_sampler_inputs('txt2img') - batch_count, batch_size = create_batch_inputs('txt2img') - seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs('txt2img') - cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = create_advanced_inputs('txt2img') - enable_hr, hr_sampler_index, denoising_strength, hr_final_resolution, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative = create_hires_inputs('txt2img') - override_settings = create_override_inputs('txt2img') - - txt2img_script_inputs = modules.scripts.scripts_txt2img.setup_ui() - - hr_resolution_preview_inputs = [width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler] - for preview_input in hr_resolution_preview_inputs: - preview_input.change( - fn=calc_resolution_hires, - _js="onCalcResolutionHires", - inputs=hr_resolution_preview_inputs, - outputs=[hr_final_resolution], - show_progress=False, - ) - - txt2img_gallery, txt2img_generation_info, txt2img_html_info, _txt2img_html_info_formatted, txt2img_html_log = ui_common.create_output_panel("txt2img") - connect_reuse_seed(seed, reuse_seed, txt2img_generation_info, is_subseed=False) - connect_reuse_seed(subseed, reuse_subseed, txt2img_generation_info, is_subseed=True) - - global txt2img_args # pylint: disable=global-statement - dummy_component = gr.Textbox(visible=False, value='dummy') - txt2img_args = [ - dummy_component, - txt2img_prompt, txt2img_negative_prompt, txt2img_prompt_styles, - steps, sampler_index, hr_sampler_index, - full_quality, restore_faces, tiling, - batch_count, batch_size, - cfg_scale, image_cfg_scale, diffusers_guidance_rescale, - clip_skip, - seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, - height, width, - enable_hr, denoising_strength, - hr_scale, hr_upscaler, hr_force, hr_second_pass_steps, hr_resize_x, hr_resize_y, - refiner_steps, refiner_start, refiner_prompt, refiner_negative, - hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, - override_settings, - ] - txt2img_dict = dict( - fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']), - _js="submit_txt2img", - inputs=txt2img_args + txt2img_script_inputs, - outputs=[ - txt2img_gallery, - txt2img_generation_info, - txt2img_html_info, - txt2img_html_log, - ], - show_progress=False, - ) - txt2img_prompt.submit(**txt2img_dict) - txt2img_submit.click(**txt2img_dict) - - global txt2img_paste_fields # pylint: disable=global-statement - txt2img_paste_fields = [ - # prompt - (txt2img_prompt, "Prompt"), - (txt2img_negative_prompt, "Negative prompt"), - # main - (width, "Size-1"), - (height, "Size-2"), - # sampler - (sampler_index, "Sampler"), - (steps, "Steps"), - # batch - (batch_count, "Batch-1"), - (batch_size, "Batch-2"), - # seed - (seed, "Seed"), - (subseed, "Variation seed"), - (subseed_strength, "Variation strength"), - # advanced - (cfg_scale, "CFG scale"), - (clip_skip, "Clip skip"), - (image_cfg_scale, "Image CFG scale"), - (diffusers_guidance_rescale, "CFG rescale"), - (full_quality, "Full quality"), - (restore_faces, "Face restoration"), - (tiling, "Tiling"), - # second pass - (enable_hr, "Second pass"), - (hr_sampler_index, "Hires sampler"), - (denoising_strength, "Denoising strength"), - (hr_upscaler, "Hires upscaler"), - (hr_force, "Hires force"), - (hr_second_pass_steps, "Hires steps"), - (hr_scale, "Hires upscale"), - (hr_resize_x, "Hires resize-1"), - (hr_resize_y, "Hires resize-2"), - # refiner - (refiner_start, "Refiner start"), - (refiner_steps, "Refiner steps"), - (refiner_prompt, "Prompt2"), - (refiner_negative, "Negative2"), - # hidden - (seed_resize_from_w, "Seed resize from-1"), - (seed_resize_from_h, "Seed resize from-2"), - *modules.scripts.scripts_txt2img.infotext_fields - ] - generation_parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) - txt2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None) - generation_parameters_copypaste.register_paste_params_button(txt2img_bindings) - - txt2img_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[txt2img_token_counter]) - txt2img_negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[txt2img_negative_token_counter]) - - ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) - - timer.startup.record("ui-txt2img") - - import modules.img2img # pylint: disable=redefined-outer-name - modules.scripts.scripts_current = modules.scripts.scripts_img2img - modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True) with gr.Blocks(analytics_enabled=False) as img2img_interface: - img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_paste, img2img_extra_networks_button, img2img_token_counter, img2img_token_button, img2img_negative_token_counter, img2img_negative_token_button = create_toprow(is_img2img=True, id_part="img2img") - img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="binary", visible=False) - - with FormRow(variant='compact', elem_id="img2img_extra_networks", visible=False) as extra_networks_ui: - from modules import ui_extra_networks - extra_networks_ui_img2img = ui_extra_networks.create_ui(extra_networks_ui, img2img_extra_networks_button, 'img2img', skip_indexing=opts.extra_network_skip_indexing) - - with FormRow(elem_id="img2img_interface", equal_height=False): - with gr.Column(variant='compact', elem_id="img2img_settings"): - copy_image_buttons = [] - copy_image_destinations = {} - - def copy_image(img): - return img['image'] if isinstance(img, dict) and 'image' in img else img - - def add_copy_image_controls(tab_name, elem): - with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"): - for title, name in zip(['➠ Image', '➠ Sketch', '➠ Inpaint', '➠ Inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']): - if name == tab_name: - gr.Button(title, interactive=False) - copy_image_destinations[name] = elem - continue - button = gr.Button(title) - copy_image_buttons.append((button, name, elem)) - - with gr.Tabs(elem_id="mode_img2img"): - img2img_selected_tab = gr.State(0) # pylint: disable=abstract-class-instantiated - with gr.TabItem('Image', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img: - init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=512) - add_copy_image_controls('img2img', init_img) - - with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch: - sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) - add_copy_image_controls('sketch', sketch) - - with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint: - init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA", height=512) - add_copy_image_controls('inpaint', init_img_with_mask) - - with gr.TabItem('Inpaint sketch', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color: - inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) - inpaint_color_sketch_orig = gr.State(None) # pylint: disable=abstract-class-instantiated - add_copy_image_controls('inpaint_sketch', inpaint_color_sketch) - - def update_orig(image, state): - if image is not None: - same_size = state is not None and state.size == image.size - has_exact_match = np.any(np.all(np.array(image) == np.array(state), axis=-1)) - edited = same_size and has_exact_match - return image if not edited or state is None else state - return state - - inpaint_color_sketch.change(update_orig, [inpaint_color_sketch, inpaint_color_sketch_orig], inpaint_color_sketch_orig) - - with gr.TabItem('Inpaint upload', id='inpaint_upload', elem_id="img2img_inpaint_upload_tab") as tab_inpaint_upload: - init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", elem_id="img_inpaint_base") - init_mask_inpaint = gr.Image(label="Mask", source="upload", interactive=True, type="pil", elem_id="img_inpaint_mask") - - with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch: - hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' - gr.HTML( - "

Upload images or process images in a directory" + - "
Add inpaint batch mask directory to enable inpaint batch processing" - f"{hidden}

" - ) - img2img_batch_files = gr.Files(label="Batch Process", interactive=True, elem_id="img2img_image_batch") - img2img_batch_input_dir = gr.Textbox(label="Inpaint batch input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") - img2img_batch_output_dir = gr.Textbox(label="Inpaint batch output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") - img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") - - img2img_tabs = [tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch] - for i, tab in enumerate(img2img_tabs): - tab.select(fn=lambda tabnum=i: tabnum, inputs=[], outputs=[img2img_selected_tab]) - - for button, name, elem in copy_image_buttons: - button.click(fn=copy_image, inputs=[elem], outputs=[copy_image_destinations[name]]) - button.click(fn=lambda: None, _js=f"switch_to_{name.replace(' ', '_')}", inputs=[], outputs=[]) - - with FormGroup(elem_classes="settings-accordion"): - - steps, sampler_index = create_sampler_inputs('img2img') - resize_mode, resize_name, width, height, scale_by, selected_scale_tab, _resize_time = create_resize_inputs('img2img', [init_img, sketch]) - batch_count, batch_size = create_batch_inputs('img2img') - seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs('img2img') - - with gr.Accordion(open=False, label="Denoise", elem_classes=["small-accordion"], elem_id="img2img_denoise_group"): - with FormRow(): - denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="img2img_denoising_strength") - refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start") - - cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = create_advanced_inputs('img2img') - - with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls: - with FormRow(): - mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id="img2img_mask_blur") - mask_alpha = gr.Slider(label="Mask transparency", visible=False, elem_id="img2img_mask_alpha") - with FormRow(): - with gr.Column(): - inpainting_mask_invert = gr.Radio(label='Mask mode', choices=['Inpaint masked', 'Inpaint not masked'], value='Inpaint masked', type="index", elem_id="img2img_mask_mode") - with gr.Column(): - inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'noise', 'nothing'], value='original', type="index", elem_id="img2img_inpainting_fill") - with FormRow(): - with gr.Column(): - inpaint_full_res = gr.Radio(label="Inpaint area", choices=["Whole picture", "Only masked"], type="index", value="Whole picture", elem_id="img2img_inpaint_full_res") - with gr.Column(): - inpaint_full_res_padding = gr.Slider(label='Masked padding', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding") - - def select_img2img_tab(tab): - return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3) - - for i, elem in enumerate(img2img_tabs): - elem.select(fn=lambda tab=i: select_img2img_tab(tab), inputs=[], outputs=[inpaint_controls, mask_alpha]) # pylint: disable=cell-var-from-loop - - override_settings = create_override_inputs('img2img') - - with FormGroup(elem_id="img2img_script_container"): - img2img_script_inputs = modules.scripts.scripts_img2img.setup_ui() - - img2img_gallery, img2img_generation_info, img2img_html_info, _img2img_html_info_formatted, img2img_html_log = ui_common.create_output_panel("img2img") - - connect_reuse_seed(seed, reuse_seed, img2img_generation_info, is_subseed=False) - connect_reuse_seed(subseed, reuse_subseed, img2img_generation_info, is_subseed=True) - - img2img_prompt_img.change(fn=modules.images.image_data, inputs=[img2img_prompt_img], outputs=[img2img_prompt, img2img_prompt_img]) - dummy_component1 = gr.Textbox(visible=False, value='dummy') - dummy_component2 = gr.Number(visible=False, value=0) - global img2img_args # pylint: disable=global-statement - img2img_args = [ - dummy_component1, dummy_component2, - img2img_prompt, img2img_negative_prompt, img2img_prompt_styles, - init_img, - sketch, - init_img_with_mask, - inpaint_color_sketch, - inpaint_color_sketch_orig, - init_img_inpaint, - init_mask_inpaint, - steps, - sampler_index, hr_sampler_index, - mask_blur, mask_alpha, - inpainting_fill, - full_quality, restore_faces, tiling, - batch_count, batch_size, - cfg_scale, image_cfg_scale, - diffusers_guidance_rescale, - refiner_steps, - refiner_start, - clip_skip, - denoising_strength, - seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, - selected_scale_tab, - height, width, - scale_by, - resize_mode, resize_name, - inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, - img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, - hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, - override_settings, - ] - img2img_dict = dict( - fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']), - _js="submit_img2img", - inputs= img2img_args + img2img_script_inputs, - outputs=[ - img2img_gallery, - img2img_generation_info, - img2img_html_info, - img2img_html_log, - ], - show_progress=False, - ) - img2img_prompt.submit(**img2img_dict) - submit.click(**img2img_dict) - dummy_component = gr.Textbox(visible=False, value='dummy') - - interrogate_args = dict( - _js="get_img2img_tab_index", - inputs=[ - dummy_component, - img2img_batch_files, - img2img_batch_input_dir, - img2img_batch_output_dir, - init_img, - sketch, - init_img_with_mask, - inpaint_color_sketch, - init_img_inpaint, - ], - outputs=[img2img_prompt, dummy_component], - ) - img2img_interrogate.click(fn=lambda *args: process_interrogate(interrogate, *args), **interrogate_args) - img2img_deepbooru.click(fn=lambda *args: process_interrogate(interrogate_deepbooru, *args), **interrogate_args) - - img2img_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_prompt, steps], outputs=[img2img_token_counter]) - img2img_negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[img2img_negative_token_counter]) - - ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) - global img2img_paste_fields # pylint: disable=global-statement - img2img_paste_fields = [ - # prompt - (img2img_prompt, "Prompt"), - (img2img_negative_prompt, "Negative prompt"), - # sampler - (sampler_index, "Sampler"), - (steps, "Steps"), - # resize - (resize_mode, "Resize mode"), - (width, "Size-1"), - (height, "Size-2"), - (scale_by, "Resize scale"), - # batch - (batch_count, "Batch-1"), - (batch_size, "Batch-2"), - # seed - (seed, "Seed"), - (subseed, "Variation seed"), - (subseed_strength, "Variation strength"), - # denoise - (denoising_strength, "Denoising strength"), - (refiner_start, "Refiner start"), - # advanced - (cfg_scale, "CFG scale"), - (image_cfg_scale, "Image CFG scale"), - (clip_skip, "Clip skip"), - (diffusers_guidance_rescale, "CFG rescale"), - (full_quality, "Full quality"), - (restore_faces, "Face restoration"), - (tiling, "Tiling"), - # inpaint - (mask_blur, "Mask blur"), - (mask_alpha, "Mask alpha"), - (inpainting_mask_invert, "Mask invert"), - (inpainting_fill, "Masked content"), - (inpaint_full_res, "Mask area"), - (inpaint_full_res_padding, "Masked padding"), - # hidden - (seed_resize_from_w, "Seed resize from-1"), - (seed_resize_from_h, "Seed resize from-2"), - *modules.scripts.scripts_img2img.infotext_fields - ] - generation_parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) - generation_parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) - img2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None) - generation_parameters_copypaste.register_paste_params_button(img2img_bindings) - - timer.startup.record("ui-img2img") + from modules import ui_img2img + ui_img2img.create_ui() + timer.startup.record("ui-img2img") modules.scripts.scripts_current = None - if shared.backend == shared.Backend.DIFFUSERS: - with gr.Blocks(analytics_enabled=False) as control_interface: + with gr.Blocks(analytics_enabled=False) as control_interface: + if shared.backend == shared.Backend.DIFFUSERS: from modules import ui_control ui_control.create_ui() timer.startup.record("ui-control") - else: - control_interface = None with gr.Blocks(analytics_enabled=False) as extras_interface: from modules import ui_postprocessing @@ -920,7 +144,7 @@ def create_ui(startup_timer = None): with gr.Blocks(analytics_enabled=False) as train_interface: from modules import ui_train - ui_train.create_ui([txt2img_prompt, txt2img_negative_prompt, steps, sampler_index, cfg_scale, seed, width, height]) + ui_train.create_ui() timer.startup.record("ui-train") with gr.Blocks(analytics_enabled=False) as models_interface: @@ -997,7 +221,6 @@ def create_ui(startup_timer = None): components = [] component_dict = {} shared.settings_components = component_dict - dummy_component1 = gr.Label(visible=False) script_callbacks.ui_settings_callback() opts.reorder() @@ -1071,6 +294,7 @@ def create_ui(startup_timer = None): tab_item_keys = [] current_tab = None current_row = None + dummy_component = gr.Textbox(visible=False, value='dummy') with gr.Tabs(elem_id="settings"): for i, (k, item) in enumerate(opts.data_labels.items()): section_must_be_skipped = item.section[0] is None @@ -1153,7 +377,7 @@ def create_ui(startup_timer = None): for _interface, label, _ifid in interfaces: shared.tab_names.append(label) - with gr.Blocks(theme=theme.gradio_theme, analytics_enabled=False, title="SD.Next") as demo: + with gr.Blocks(theme=theme.gradio_theme, analytics_enabled=False, title="SD.Next") as ui_app: with gr.Row(elem_id="quicksettings", variant="compact"): for _i, k, _item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])): component = create_setting_component(k, is_quicksettings=True) @@ -1203,6 +427,7 @@ def create_ui(startup_timer = None): show_progress=info.refresh is not None, ) + dummy_component = gr.Textbox(visible=False, value='dummy') button_set_checkpoint = gr.Button('Change model', elem_id='change_checkpoint', visible=False) button_set_checkpoint.click( fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'), @@ -1246,7 +471,7 @@ def create_ui(startup_timer = None): def get_settings_values(): return [get_value_for_setting(key) for key in component_keys] - demo.load( + ui_app.load( fn=get_settings_values, inputs=[], outputs=[component_dict[k] for k in component_keys if component_dict[k] is not None], @@ -1255,99 +480,5 @@ def create_ui(startup_timer = None): timer.startup.record("ui-defaults") loadsave.dump_defaults() - demo.ui_loadsave = loadsave - return demo - - -def webpath(fn): - if fn.startswith(script_path): - web_path = os.path.relpath(fn, script_path).replace('\\', '/') - else: - web_path = os.path.abspath(fn) - return f'file={web_path}?{os.path.getmtime(fn)}' - - -def html_head(): - head = '' - main = ['script.js'] - for js in main: - script_js = os.path.join(script_path, "javascript", js) - head += f'\n' - added = [] - for script in modules.scripts.list_scripts("javascript", ".js"): - if script.filename in main: - continue - head += f'\n' - added.append(script.path) - for script in modules.scripts.list_scripts("javascript", ".mjs"): - head += f'\n' - added.append(script.path) - added = [a.replace(script_path, '').replace('\\', '/') for a in added] - # log.debug(f'Adding JS scripts: {added}') - return head - - -def html_body(): - body = '' - inline = '' - if opts.theme_style != 'Auto': - inline += f"set_theme('{opts.theme_style.lower()}');" - body += f'\n' - return body - - -def html_css(is_builtin: bool): - added = [] - - def stylesheet(fn): - added.append(fn) - return f'' - - css = 'sdnext.css' if is_builtin else 'base.css' - head = stylesheet(os.path.join(script_path, 'javascript', css)) - for cssfile in modules.scripts.list_files_with_name("style.css"): - if not os.path.isfile(cssfile): - continue - head += stylesheet(cssfile) - if opts.gradio_theme in theme.list_builtin_themes(): - head += stylesheet(os.path.join(script_path, "javascript", f"{opts.gradio_theme}.css")) - if os.path.exists(os.path.join(data_path, "user.css")): - head += stylesheet(os.path.join(data_path, "user.css")) - added = [a.replace(script_path, '').replace('\\', '/') for a in added] - # log.debug(f'Adding CSS stylesheets: {added}') - return head - - -def reload_javascript(): - is_builtin = theme.reload_gradio_theme() - head = html_head() - css = html_css(is_builtin) - body = html_body() - - def template_response(*args, **kwargs): - res = shared.GradioTemplateResponseOriginal(*args, **kwargs) - res.body = res.body.replace(b'', f'{head}'.encode("utf8")) - res.body = res.body.replace(b'', f'{css}{body}'.encode("utf8")) - res.init_headers() - return res - - gradio.routes.templates.TemplateResponse = template_response - - -def setup_ui_api(app): - from pydantic import BaseModel, Field # pylint: disable=no-name-in-module - from typing import List - - class QuicksettingsHint(BaseModel): # pylint: disable=too-few-public-methods - name: str = Field(title="Name of the quicksettings field") - label: str = Field(title="Label of the quicksettings field") - - def quicksettings_hint(): - return [QuicksettingsHint(name=k, label=v.label) for k, v in opts.data_labels.items()] - - app.add_api_route("/internal/quicksettings-hint", quicksettings_hint, methods=["GET"], response_model=List[QuicksettingsHint]) - app.add_api_route("/internal/ping", lambda: {}, methods=["GET"]) - - -if not hasattr(shared, 'GradioTemplateResponseOriginal'): - shared.GradioTemplateResponseOriginal = gradio.routes.templates.TemplateResponse + ui_app.ui_loadsave = loadsave + return ui_app diff --git a/modules/ui_common.py b/modules/ui_common.py index 5c35e3f4c..a5bfa2e62 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -4,9 +4,11 @@ import os import shutil import platform import subprocess +from functools import reduce import gradio as gr -from modules import call_queue, shared +from modules import call_queue, shared, prompt_parser from modules.generation_parameters_copypaste import image_from_url_text, parse_generation_parameters +from modules.ui_components import FormRow, ToolButton import modules.ui_symbols as symbols import modules.images import modules.script_callbacks @@ -17,6 +19,10 @@ debug = shared.log.trace if os.environ.get('SD_PASTE_DEBUG', None) is not None e debug('Trace: PASTE') +def gr_show(visible=True): + return {"visible": visible, "__type__": "update"} + + def update_generation_info(generation_info, html_info, img_index): try: generation_info = json.loads(generation_info) @@ -259,7 +265,6 @@ def create_output_panel(tabname, preview=True): def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id, visible: bool = True): - def refresh(): refresh_method() args = refreshed_args() if callable(refreshed_args) else refreshed_args @@ -267,21 +272,86 @@ def create_refresh_button(refresh_component, refresh_method, refreshed_args, ele setattr(refresh_component, k, v) return gr.update(**(args or {})) - from modules.ui_components import ToolButton refresh_button = ToolButton(value=symbols.refresh, elem_id=elem_id, visible=visible) refresh_button.click(fn=refresh, inputs=[], outputs=[refresh_component]) return refresh_button -def create_browse_button(browse_component, elem_id): +def create_browse_button(browse_component, elem_id): def browse(folder): # import subprocess if folder is not None: return gr.update(value = folder) return gr.update() - from modules.ui_components import ToolButton browse_button = ToolButton(value=symbols.folder, elem_id=elem_id) browse_button.click(fn=browse, _js="async () => await browseFolder()", inputs=[browse_component], outputs=[browse_component]) # browse_button.click(fn=browse, inputs=[browse_component], outputs=[browse_component]) return browse_button + + +def create_override_inputs(tab): # pylint: disable=unused-argument + with FormRow(elem_id=f"{tab}_override_settings_row"): + override_settings = gr.Dropdown([], value=None, label="Override settings", visible=False, elem_id=f"{tab}_override_settings", multiselect=True) + override_settings.change(fn=lambda x: gr.Dropdown.update(visible=len(x) > 0), inputs=[override_settings], outputs=[override_settings]) + return override_settings + + +def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed): + """ Connects a 'reuse (sub)seed' button's click event so that it copies last used + (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength + was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" + def copy_seed(gen_info_string: str, index: int): + res = -1 + try: + gen_info = json.loads(gen_info_string) + shared.log.debug(f'Reuse: info={gen_info}') + index -= gen_info.get('index_of_first_image', 0) + index = int(index) + + if is_subseed and gen_info.get('subseed_strength', 0) > 0: + all_subseeds = gen_info.get('all_subseeds', [-1]) + res = all_subseeds[index if 0 <= index < len(all_subseeds) else 0] + else: + all_seeds = gen_info.get('all_seeds', [-1]) + res = all_seeds[index if 0 <= index < len(all_seeds) else 0] + except json.decoder.JSONDecodeError: + if gen_info_string != '': + shared.log.error(f"Error parsing JSON generation info: {gen_info_string}") + return [res, gr_show(False)] + + dummy_component = gr.Number(visible=False, value=0) + reuse_seed.click(fn=copy_seed, _js="(x, y) => [x, selected_gallery_index()]", show_progress=False, inputs=[generation_info, dummy_component], outputs=[seed, dummy_component]) + + +def update_token_counter(text, steps): + from modules import extra_networks, sd_hijack + try: + text, _ = extra_networks.parse_prompt(text) + _, prompt_flat_list, _ = prompt_parser.get_multicond_prompt_list([text]) + prompt_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt_flat_list, steps) + except Exception: + prompt_schedules = [[[steps, text]]] + + flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) + prompts = [prompt_text for step, prompt_text in flat_prompts] + if shared.backend == shared.Backend.ORIGINAL: + token_count, max_length = max([sd_hijack.model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) + elif shared.backend == shared.Backend.DIFFUSERS: + if shared.sd_model is not None and hasattr(shared.sd_model, 'tokenizer'): + tokenizer = shared.sd_model.tokenizer + if tokenizer is None: + token_count = 0 + max_length = 75 + else: + has_bos_token = tokenizer.bos_token_id is not None + has_eos_token = tokenizer.eos_token_id is not None + ids = [shared.sd_model.tokenizer(prompt) for prompt in prompts] + if len(ids) > 0 and hasattr(ids[0], 'input_ids'): + ids = [x.input_ids for x in ids] + token_count = max([len(x) for x in ids]) - int(has_bos_token) - int(has_eos_token) + max_length = tokenizer.model_max_length - int(has_bos_token) - int(has_eos_token) + else: + token_count = 0 + max_length = 75 + return f"{token_count}/{max_length}" diff --git a/modules/ui_control.py b/modules/ui_control.py index ea9de4336..a992c033f 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -12,7 +12,7 @@ from modules.control.units import lite # vislearn ControlNet-XS from modules.control.units import t2iadapter # TencentARC T2I-Adapter from modules.control.units import reference # reference pipeline from modules.control.units import ipadapter # reference pipeline -from modules import errors, shared, progress, sd_samplers, ui, ui_components, ui_symbols, ui_common, generation_parameters_copypaste, call_queue +from modules import errors, shared, progress, sd_samplers, ui_components, ui_symbols, ui_common, ui_sections, generation_parameters_copypaste, call_queue from modules.ui_components import FormRow, FormGroup @@ -312,7 +312,7 @@ def create_ui(_blocks: gr.Blocks=None): return [(control_ui, 'Control', 'control')] with gr.Blocks(analytics_enabled = False) as control_ui: - prompt, styles, negative, btn_generate, _btn_interrogate, _btn_deepbooru, btn_paste, btn_extra, prompt_counter, btn_prompt_counter, negative_counter, btn_negative_counter = ui.create_toprow(is_img2img=False, id_part='control') + prompt, styles, negative, btn_generate, _btn_interrogate, _btn_deepbooru, btn_paste, btn_extra, prompt_counter, btn_prompt_counter, negative_counter, btn_negative_counter = ui_sections.create_toprow(is_img2img=False, id_part='control') with FormGroup(elem_id="control_interface", equal_height=False): with gr.Row(elem_id='control_settings'): @@ -329,15 +329,15 @@ def create_ui(_blocks: gr.Blocks=None): mask_blur = gr.Slider(minimum=0, maximum=100, step=1, label='Blur', value=8, elem_id="control_mask_blur") mask_overlap = gr.Slider(minimum=0, maximum=100, step=1, label='Overlap', value=8, elem_id="control_mask_overlap") - resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time = ui.create_resize_inputs('control', [], time_selector=True, scale_visible=False, mode='Fixed') + resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time = ui_sections.create_resize_inputs('control', [], time_selector=True, scale_visible=False, mode='Fixed') with gr.Accordion(open=False, label="Sampler", elem_id="control_sampler", elem_classes=["small-accordion"]): sd_samplers.set_samplers() - steps, sampler_index = ui.create_sampler_and_steps_selection(sd_samplers.samplers, "control") + steps, sampler_index = ui_sections.create_sampler_and_steps_selection(sd_samplers.samplers, "control") - batch_count, batch_size = ui.create_batch_inputs('control') - seed, _reuse_seed, subseed, _reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui.create_seed_inputs('control', reuse_visible=False) - cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = ui.create_advanced_inputs('control') + batch_count, batch_size = ui_sections.create_batch_inputs('control') + seed, _reuse_seed, subseed, _reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui_sections.create_seed_inputs('control', reuse_visible=False) + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = ui_sections.create_advanced_inputs('control') with gr.Accordion(open=False, label="Video", elem_id="control_video", elem_classes=["small-accordion"]): with gr.Row(): @@ -351,7 +351,7 @@ def create_ui(_blocks: gr.Blocks=None): video_interpolate = gr.Slider(label='Interpolate frames', minimum=0, maximum=24, step=1, value=0, visible=False) video_type.change(fn=video_type_change, inputs=[video_type], outputs=[video_duration, video_loop, video_pad, video_interpolate]) - override_settings = ui.create_override_inputs('control') + override_settings = ui_common.create_override_inputs('control') with FormRow(variant='compact', elem_id="control_extra_networks", visible=False) as extra_networks_ui: from modules import timer, ui_extra_networks @@ -680,8 +680,8 @@ def create_ui(_blocks: gr.Blocks=None): show_preview.change(fn=lambda x: gr.update(visible=x), inputs=[show_preview], outputs=[column_preview]) show_ip.change(fn=lambda x: gr.update(visible=x), inputs=[show_ip], outputs=[column_ip]) input_type.change(fn=lambda x: gr.update(visible=x == 2), inputs=[input_type], outputs=[column_init]) - btn_prompt_counter.click(fn=call_queue.wrap_queued_call(ui.update_token_counter), inputs=[prompt, steps], outputs=[prompt_counter]) - btn_negative_counter.click(fn=call_queue.wrap_queued_call(ui.update_token_counter), inputs=[negative, steps], outputs=[negative_counter]) + btn_prompt_counter.click(fn=call_queue.wrap_queued_call(ui_common.update_token_counter), inputs=[prompt, steps], outputs=[prompt_counter]) + btn_negative_counter.click(fn=call_queue.wrap_queued_call(ui_common.update_token_counter), inputs=[negative, steps], outputs=[negative_counter]) select_fields = [input_mode, input_image, init_image, input_type, input_resize, input_inpaint, input_video, input_batch, input_folder, mask_blur, mask_overlap] select_output = [output_tabs, result_txt] diff --git a/modules/ui_img2img.py b/modules/ui_img2img.py new file mode 100644 index 000000000..9fdc26d34 --- /dev/null +++ b/modules/ui_img2img.py @@ -0,0 +1,295 @@ +import os +from PIL import Image +import gradio as gr +import numpy as np +from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call +from modules import shared, ui_common, ui_sections, generation_parameters_copypaste +from modules.ui_components import FormRow, FormGroup + + +def process_interrogate(interrogation_function, mode, ii_input_files, ii_input_dir, ii_output_dir, *ii_singles): + mode = int(mode) + if mode in {0, 1, 3, 4}: + return [interrogation_function(ii_singles[mode]), None] + if mode == 2: + return [interrogation_function(ii_singles[mode]["image"]), None] + if mode == 5: + if len(ii_input_files) > 0: + images = [f.name for f in ii_input_files] + else: + if not os.path.isdir(ii_input_dir): + shared.log.error(f"Interrogate: Input directory not found: {ii_input_dir}") + return [gr.update(), None] + images = shared.listfiles(ii_input_dir) + if ii_output_dir != "": + os.makedirs(ii_output_dir, exist_ok=True) + else: + ii_output_dir = ii_input_dir + for image in images: + img = Image.open(image) + filename = os.path.basename(image) + left, _ = os.path.splitext(filename) + print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a', encoding='utf-8')) # pylint: disable=consider-using-with + return [gr.update(), None] + + +def interrogate(image): + if image is None: + shared.log.error("Interrogate: no image selected") + return gr.update() + prompt = shared.interrogator.interrogate(image.convert("RGB")) + return gr.update() if prompt is None else prompt + + +def interrogate_deepbooru(image): + from modules import deepbooru + prompt = deepbooru.model.tag(image) + return gr.update() if prompt is None else prompt + + +def create_ui(): + import modules.img2img # pylint: disable=redefined-outer-name + modules.scripts.scripts_current = modules.scripts.scripts_img2img + modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True) + with gr.Blocks(analytics_enabled=False) as _img2img_interface: + img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_paste, img2img_extra_networks_button, img2img_token_counter, img2img_token_button, img2img_negative_token_counter, img2img_negative_token_button = ui_sections.create_toprow(is_img2img=True, id_part="img2img") + img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="binary", visible=False) + + with FormRow(variant='compact', elem_id="img2img_extra_networks", visible=False) as extra_networks_ui: + from modules import ui_extra_networks + extra_networks_ui_img2img = ui_extra_networks.create_ui(extra_networks_ui, img2img_extra_networks_button, 'img2img', skip_indexing=shared.opts.extra_network_skip_indexing) + + with FormRow(elem_id="img2img_interface", equal_height=False): + with gr.Column(variant='compact', elem_id="img2img_settings"): + copy_image_buttons = [] + copy_image_destinations = {} + + def copy_image(img): + return img['image'] if isinstance(img, dict) and 'image' in img else img + + def add_copy_image_controls(tab_name, elem): + with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"): + for title, name in zip(['➠ Image', '➠ Sketch', '➠ Inpaint', '➠ Inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']): + if name == tab_name: + gr.Button(title, interactive=False) + copy_image_destinations[name] = elem + continue + button = gr.Button(title) + copy_image_buttons.append((button, name, elem)) + + with gr.Tabs(elem_id="mode_img2img"): + img2img_selected_tab = gr.State(0) # pylint: disable=abstract-class-instantiated + with gr.TabItem('Image', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img: + init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=512) + add_copy_image_controls('img2img', init_img) + + with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch: + sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) + add_copy_image_controls('sketch', sketch) + + with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint: + init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA", height=512) + add_copy_image_controls('inpaint', init_img_with_mask) + + with gr.TabItem('Inpaint sketch', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color: + inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512) + inpaint_color_sketch_orig = gr.State(None) # pylint: disable=abstract-class-instantiated + add_copy_image_controls('inpaint_sketch', inpaint_color_sketch) + + def update_orig(image, state): + if image is not None: + same_size = state is not None and state.size == image.size + has_exact_match = np.any(np.all(np.array(image) == np.array(state), axis=-1)) + edited = same_size and has_exact_match + return image if not edited or state is None else state + return state + + inpaint_color_sketch.change(update_orig, [inpaint_color_sketch, inpaint_color_sketch_orig], inpaint_color_sketch_orig) + + with gr.TabItem('Inpaint upload', id='inpaint_upload', elem_id="img2img_inpaint_upload_tab") as tab_inpaint_upload: + init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", elem_id="img_inpaint_base") + init_mask_inpaint = gr.Image(label="Mask", source="upload", interactive=True, type="pil", elem_id="img_inpaint_mask") + + with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch: + hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' + gr.HTML( + "

Upload images or process images in a directory" + + "
Add inpaint batch mask directory to enable inpaint batch processing" + f"{hidden}

" + ) + img2img_batch_files = gr.Files(label="Batch Process", interactive=True, elem_id="img2img_image_batch") + img2img_batch_input_dir = gr.Textbox(label="Inpaint batch input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") + img2img_batch_output_dir = gr.Textbox(label="Inpaint batch output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") + img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") + + img2img_tabs = [tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch] + for i, tab in enumerate(img2img_tabs): + tab.select(fn=lambda tabnum=i: tabnum, inputs=[], outputs=[img2img_selected_tab]) + + for button, name, elem in copy_image_buttons: + button.click(fn=copy_image, inputs=[elem], outputs=[copy_image_destinations[name]]) + button.click(fn=lambda: None, _js=f"switch_to_{name.replace(' ', '_')}", inputs=[], outputs=[]) + + with FormGroup(elem_classes="settings-accordion"): + + steps, sampler_index = ui_sections.create_sampler_inputs('img2img') + resize_mode, resize_name, width, height, scale_by, selected_scale_tab, _resize_time = ui_sections.create_resize_inputs('img2img', [init_img, sketch]) + batch_count, batch_size = ui_sections.create_batch_inputs('img2img') + seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui_sections.create_seed_inputs('img2img') + + with gr.Accordion(open=False, label="Denoise", elem_classes=["small-accordion"], elem_id="img2img_denoise_group"): + with FormRow(): + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="img2img_denoising_strength") + refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start") + + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = ui_sections.create_advanced_inputs('img2img') + + with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls: + with FormRow(): + mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id="img2img_mask_blur") + mask_alpha = gr.Slider(label="Mask transparency", visible=False, elem_id="img2img_mask_alpha") + with FormRow(): + with gr.Column(): + inpainting_mask_invert = gr.Radio(label='Mask mode', choices=['Inpaint masked', 'Inpaint not masked'], value='Inpaint masked', type="index", elem_id="img2img_mask_mode") + with gr.Column(): + inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'noise', 'nothing'], value='original', type="index", elem_id="img2img_inpainting_fill") + with FormRow(): + with gr.Column(): + inpaint_full_res = gr.Radio(label="Inpaint area", choices=["Whole picture", "Only masked"], type="index", value="Whole picture", elem_id="img2img_inpaint_full_res") + with gr.Column(): + inpaint_full_res_padding = gr.Slider(label='Masked padding', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding") + + def select_img2img_tab(tab): + return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3) + + for i, elem in enumerate(img2img_tabs): + elem.select(fn=lambda tab=i: select_img2img_tab(tab), inputs=[], outputs=[inpaint_controls, mask_alpha]) # pylint: disable=cell-var-from-loop + + override_settings = ui_common.create_override_inputs('img2img') + + with FormGroup(elem_id="img2img_script_container"): + img2img_script_inputs = modules.scripts.scripts_img2img.setup_ui() + + img2img_gallery, img2img_generation_info, img2img_html_info, _img2img_html_info_formatted, img2img_html_log = ui_common.create_output_panel("img2img") + + ui_common.connect_reuse_seed(seed, reuse_seed, img2img_generation_info, is_subseed=False) + ui_common.connect_reuse_seed(subseed, reuse_subseed, img2img_generation_info, is_subseed=True) + + img2img_prompt_img.change(fn=modules.images.image_data, inputs=[img2img_prompt_img], outputs=[img2img_prompt, img2img_prompt_img]) + dummy_component1 = gr.Textbox(visible=False, value='dummy') + dummy_component2 = gr.Number(visible=False, value=0) + img2img_args = [ + dummy_component1, dummy_component2, + img2img_prompt, img2img_negative_prompt, img2img_prompt_styles, + init_img, + sketch, + init_img_with_mask, + inpaint_color_sketch, + inpaint_color_sketch_orig, + init_img_inpaint, + init_mask_inpaint, + steps, + sampler_index, + mask_blur, mask_alpha, + inpainting_fill, + full_quality, restore_faces, tiling, + batch_count, batch_size, + cfg_scale, image_cfg_scale, + diffusers_guidance_rescale, + refiner_start, + clip_skip, + denoising_strength, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + selected_scale_tab, + height, width, + scale_by, + resize_mode, resize_name, + inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, + img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, + hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, + override_settings, + ] + img2img_dict = dict( + fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']), + _js="submit_img2img", + inputs= img2img_args + img2img_script_inputs, + outputs=[ + img2img_gallery, + img2img_generation_info, + img2img_html_info, + img2img_html_log, + ], + show_progress=False, + ) + img2img_prompt.submit(**img2img_dict) + submit.click(**img2img_dict) + dummy_component = gr.Textbox(visible=False, value='dummy') + + interrogate_args = dict( + _js="get_img2img_tab_index", + inputs=[ + dummy_component, + img2img_batch_files, + img2img_batch_input_dir, + img2img_batch_output_dir, + init_img, + sketch, + init_img_with_mask, + inpaint_color_sketch, + init_img_inpaint, + ], + outputs=[img2img_prompt, dummy_component], + ) + img2img_interrogate.click(fn=lambda *args: process_interrogate(interrogate, *args), **interrogate_args) + img2img_deepbooru.click(fn=lambda *args: process_interrogate(interrogate_deepbooru, *args), **interrogate_args) + + img2img_token_button.click(fn=wrap_queued_call(ui_common.update_token_counter), inputs=[img2img_prompt, steps], outputs=[img2img_token_counter]) + img2img_negative_token_button.click(fn=wrap_queued_call(ui_common.update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[img2img_negative_token_counter]) + + ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) + img2img_paste_fields = [ + # prompt + (img2img_prompt, "Prompt"), + (img2img_negative_prompt, "Negative prompt"), + # sampler + (sampler_index, "Sampler"), + (steps, "Steps"), + # resize + (resize_mode, "Resize mode"), + (width, "Size-1"), + (height, "Size-2"), + (scale_by, "Resize scale"), + # batch + (batch_count, "Batch-1"), + (batch_size, "Batch-2"), + # seed + (seed, "Seed"), + (subseed, "Variation seed"), + (subseed_strength, "Variation strength"), + # denoise + (denoising_strength, "Denoising strength"), + (refiner_start, "Refiner start"), + # advanced + (cfg_scale, "CFG scale"), + (image_cfg_scale, "Image CFG scale"), + (clip_skip, "Clip skip"), + (diffusers_guidance_rescale, "CFG rescale"), + (full_quality, "Full quality"), + (restore_faces, "Face restoration"), + (tiling, "Tiling"), + # inpaint + (mask_blur, "Mask blur"), + (mask_alpha, "Mask alpha"), + (inpainting_mask_invert, "Mask invert"), + (inpainting_fill, "Masked content"), + (inpaint_full_res, "Mask area"), + (inpaint_full_res_padding, "Masked padding"), + # hidden + (seed_resize_from_w, "Seed resize from-1"), + (seed_resize_from_h, "Seed resize from-2"), + *modules.scripts.scripts_img2img.infotext_fields + ] + generation_parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) + generation_parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) + img2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None) + generation_parameters_copypaste.register_paste_params_button(img2img_bindings) diff --git a/modules/ui_javascript.py b/modules/ui_javascript.py new file mode 100644 index 000000000..4c4fd75dc --- /dev/null +++ b/modules/ui_javascript.py @@ -0,0 +1,99 @@ +import os +import gradio.routes +import gradio.utils +from modules import shared, theme +from modules.paths import script_path, data_path +import modules.scripts + +def webpath(fn): + if fn.startswith(script_path): + web_path = os.path.relpath(fn, script_path).replace('\\', '/') + else: + web_path = os.path.abspath(fn) + return f'file={web_path}?{os.path.getmtime(fn)}' + + +def html_head(): + head = '' + main = ['script.js'] + for js in main: + script_js = os.path.join(script_path, "javascript", js) + head += f'\n' + added = [] + for script in modules.scripts.list_scripts("javascript", ".js"): + if script.filename in main: + continue + head += f'\n' + added.append(script.path) + for script in modules.scripts.list_scripts("javascript", ".mjs"): + head += f'\n' + added.append(script.path) + added = [a.replace(script_path, '').replace('\\', '/') for a in added] + # log.debug(f'Adding JS scripts: {added}') + return head + + +def html_body(): + body = '' + inline = '' + if shared.opts.theme_style != 'Auto': + inline += f"set_theme('{shared.opts.theme_style.lower()}');" + body += f'\n' + return body + + +def html_css(is_builtin: bool): + added = [] + + def stylesheet(fn): + added.append(fn) + return f'' + + css = 'sdnext.css' if is_builtin else 'base.css' + head = stylesheet(os.path.join(script_path, 'javascript', css)) + for cssfile in modules.scripts.list_files_with_name("style.css"): + if not os.path.isfile(cssfile): + continue + head += stylesheet(cssfile) + if shared.opts.gradio_theme in theme.list_builtin_themes(): + head += stylesheet(os.path.join(script_path, "javascript", f"{shared.opts.gradio_theme}.css")) + if os.path.exists(os.path.join(data_path, "user.css")): + head += stylesheet(os.path.join(data_path, "user.css")) + added = [a.replace(script_path, '').replace('\\', '/') for a in added] + # log.debug(f'Adding CSS stylesheets: {added}') + return head + + +def reload_javascript(): + is_builtin = theme.reload_gradio_theme() + head = html_head() + css = html_css(is_builtin) + body = html_body() + + def template_response(*args, **kwargs): + res = shared.GradioTemplateResponseOriginal(*args, **kwargs) + res.body = res.body.replace(b'', f'{head}'.encode("utf8")) + res.body = res.body.replace(b'', f'{css}{body}'.encode("utf8")) + res.init_headers() + return res + + gradio.routes.templates.TemplateResponse = template_response + + +def setup_ui_api(app): + from pydantic import BaseModel, Field # pylint: disable=no-name-in-module + from typing import List + + class QuicksettingsHint(BaseModel): # pylint: disable=too-few-public-methods + name: str = Field(title="Name of the quicksettings field") + label: str = Field(title="Label of the quicksettings field") + + def quicksettings_hint(): + return [QuicksettingsHint(name=k, label=v.label) for k, v in shared.opts.data_labels.items()] + + app.add_api_route("/internal/quicksettings-hint", quicksettings_hint, methods=["GET"], response_model=List[QuicksettingsHint]) + app.add_api_route("/internal/ping", lambda: {}, methods=["GET"]) + + +if not hasattr(shared, 'GradioTemplateResponseOriginal'): + shared.GradioTemplateResponseOriginal = gradio.routes.templates.TemplateResponse diff --git a/modules/ui_sections.py b/modules/ui_sections.py new file mode 100644 index 000000000..1ac21828b --- /dev/null +++ b/modules/ui_sections.py @@ -0,0 +1,258 @@ +import gradio as gr +from modules import shared, modelloader, ui_symbols, ui_common, sd_samplers +from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML + + +def create_toprow(is_img2img: bool = False, id_part: str = None): + def apply_styles(prompt, prompt_neg, styles): + prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles) + prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles) + return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value=[])] + + + def parse_style(styles): + return styles.split('|') + + if id_part is None: + id_part = "img2img" if is_img2img else "txt2img" + with gr.Row(elem_id=f"{id_part}_toprow", variant="compact"): + with gr.Column(elem_id=f"{id_part}_prompt_container", scale=6): + with gr.Row(): + with gr.Column(scale=80): + with gr.Row(): + prompt = gr.Textbox(elem_id=f"{id_part}_prompt", label="Prompt", show_label=False, lines=3, placeholder="Prompt", elem_classes=["prompt"]) + with gr.Row(): + with gr.Column(scale=80): + with gr.Row(): + negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", label="Negative prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"]) + button_interrogate = None + button_deepbooru = None + if is_img2img: + with gr.Column(scale=1, elem_classes="interrogate-col"): + button_interrogate = gr.Button('Interrogate\nCLIP', elem_id=f"{id_part}_interrogate") + button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id=f"{id_part}_deepbooru") + with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"): + with gr.Row(elem_id=f"{id_part}_generate_box"): + submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary') + with gr.Row(elem_id=f"{id_part}_generate_line2"): + interrupt = gr.Button('Stop', elem_id=f"{id_part}_interrupt") + interrupt.click(fn=lambda: shared.state.interrupt(), _js="requestInterrupt", inputs=[], outputs=[]) + skip = gr.Button('Skip', elem_id=f"{id_part}_skip") + skip.click(fn=lambda: shared.state.skip(), inputs=[], outputs=[]) + pause = gr.Button('Pause', elem_id=f"{id_part}_pause") + pause.click(fn=lambda: shared.state.pause(), _js='checkPaused', inputs=[], outputs=[]) + with gr.Row(elem_id=f"{id_part}_tools"): + button_paste = gr.Button(value='Restore', variant='secondary', elem_id=f"{id_part}_paste") # symbols.paste + button_clear = gr.Button(value='Clear', variant='secondary', elem_id=f"{id_part}_clear_prompt_btn") # symbols.clear + button_extra = gr.Button(value='Networks', variant='secondary', elem_id=f"{id_part}_extra_networks_btn") # symbols.networks + button_clear.click(fn=lambda *x: ['', ''], inputs=[prompt, negative_prompt], outputs=[prompt, negative_prompt], show_progress=False) + with gr.Row(elem_id=f"{id_part}_counters"): + token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_token_counter", elem_classes=["token-counter"]) + token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button") + negative_token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"]) + negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") + with gr.Row(elem_id=f"{id_part}_styles_row"): + styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in shared.prompt_styles.styles.values()], value=[], multiselect=True) + _styles_btn_refresh = ui_common.create_refresh_button(styles, shared.prompt_styles.reload, lambda: {"choices": list(shared.prompt_styles.styles)}, f"{id_part}_styles_refresh") + # styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) + # styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) + styles_btn_select = gr.Button('Select', elem_id=f"{id_part}_styles_select", visible=False) + styles_btn_select.click(_js="applyStyles", fn=parse_style, inputs=[styles], outputs=[styles]) + styles_btn_apply = ToolButton(ui_symbols.apply, elem_id=f"{id_part}_extra_apply", visible=False) + styles_btn_apply.click(fn=apply_styles, inputs=[prompt, negative_prompt, styles], outputs=[prompt, negative_prompt, styles]) + return prompt, styles, negative_prompt, submit, button_interrogate, button_deepbooru, button_paste, button_extra, token_counter, token_button, negative_token_counter, negative_token_button + + +def create_sampler_inputs(tab, accordion=True): + with gr.Accordion(open=False, label="Sampler", elem_id=f"{tab}_sampler", elem_classes=["small-accordion"]) if accordion else gr.Group(): + with FormRow(elem_id=f"{tab}_row_sampler"): + sd_samplers.set_samplers() + steps, sampler_index = create_sampler_and_steps_selection(sd_samplers.samplers, tab) + return steps, sampler_index + + +def create_batch_inputs(tab): + with gr.Accordion(open=False, label="Batch", elem_id=f"{tab}_batch", elem_classes=["small-accordion"]): + with FormRow(elem_id=f"{tab}_row_batch"): + batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id=f"{tab}_batch_count") + batch_size = gr.Slider(minimum=1, maximum=32, step=1, label='Batch size', value=1, elem_id=f"{tab}_batch_size") + batch_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_batch_switch_btn", label="Switch dims") + batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) + return batch_count, batch_size + + +def create_seed_inputs(tab, reuse_visible=True): + with gr.Accordion(open=False, label="Seed", elem_id=f"{tab}_seed_group", elem_classes=["small-accordion"]): + with FormRow(elem_id=f"{tab}_seed_row", variant="compact"): + seed = gr.Number(label='Initial seed', value=-1, elem_id=f"{tab}_seed", container=True) + random_seed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_seed", label='Random seed') + reuse_seed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_seed", label='Reuse seed', visible=reuse_visible) + with FormRow(elem_id=f"{tab}_subseed_row", variant="compact", visible=shared.backend==shared.Backend.ORIGINAL): + subseed = gr.Number(label='Variation', value=-1, elem_id=f"{tab}_subseed", container=True) + random_subseed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_subseed") + reuse_subseed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_subseed", visible=reuse_visible) + subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=f"{tab}_subseed_strength") + with FormRow(visible=False): + seed_resize_from_w = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from width", value=0, elem_id=f"{tab}_seed_resize_from_w") + seed_resize_from_h = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from height", value=0, elem_id=f"{tab}_seed_resize_from_h") + random_seed.click(fn=lambda: [-1, -1], show_progress=False, inputs=[], outputs=[seed, subseed]) + random_subseed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[subseed]) + return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w + + +def create_advanced_inputs(tab): + with gr.Accordion(open=False, label="Advanced", elem_id=f"{tab}_advanced", elem_classes=["small-accordion"]): + with gr.Group(): + with FormRow(): + cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0, elem_id=f"{tab}_cfg_scale") + clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=14, step=1, elem_id=f"{tab}_clip_skip", interactive=True) + with FormRow(): + image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Secondary CFG scale', value=6.0, elem_id=f"{tab}_image_cfg_scale") + diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id=f"{tab}_image_cfg_rescale", visible=shared.backend == shared.Backend.DIFFUSERS) + with gr.Group(): + with FormRow(): + full_quality = gr.Checkbox(label='Full quality', value=True, elem_id=f"{tab}_full_quality") + restore_faces = gr.Checkbox(label='Face restore', value=False, visible=len(shared.face_restorers) > 1, elem_id=f"{tab}_restore_faces") + tiling = gr.Checkbox(label='Tiling', value=False, elem_id=f"{tab}_tiling", visible=shared.backend == shared.Backend.ORIGINAL) + with gr.Group(visible=shared.backend == shared.Backend.DIFFUSERS): + with FormRow(): + hdr_clamp = gr.Checkbox(label='HDR clamp', value=False, elem_id=f"{tab}_hdr_clamp") + hdr_boundary = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=4.0, label='Range', elem_id=f"{tab}_hdr_boundary") + hdr_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.95, label='Threshold', elem_id=f"{tab}_hdr_threshold") + with FormRow(): + hdr_center = gr.Checkbox(label='HDR center', value=False, elem_id=f"{tab}_hdr_center") + hdr_channel_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1.0, label='Channel shift', elem_id=f"{tab}_hdr_channel_shift") + hdr_full_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1, label='Full shift', elem_id=f"{tab}_hdr_full_shift") + with FormRow(): + hdr_maximize = gr.Checkbox(label='HDR maximize', value=False, elem_id=f"{tab}_hdr_maximize") + hdr_max_center = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=0.6, label='Center', elem_id=f"{tab}_hdr_max_center") + hdr_max_boundry = gr.Slider(minimum=0.5, maximum=2.0, step=0.1, value=1.0, label='Range', elem_id=f"{tab}_hdr_max_boundry") + return cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry + + +def create_sampler_and_steps_selection(choices, tabname): + def set_sampler_original_options(sampler_options, sampler_algo): + shared.opts.data['schedulers_brownian_noise'] = 'brownian noise' in sampler_options + shared.opts.data['schedulers_discard_penultimate'] = 'discard penultimate sigma' in sampler_options + shared.opts.data['schedulers_sigma'] = sampler_algo + shared.opts.save(shared.config_filename, silent=True) + + def set_sampler_diffuser_options(sampler_options): + shared.opts.data['schedulers_use_karras'] = 'karras' in sampler_options + shared.opts.data['schedulers_use_thresholding'] = 'dynamic thresholding' in sampler_options + shared.opts.data['schedulers_use_loworder'] = 'low order' in sampler_options + shared.opts.data['schedulers_rescale_betas'] = 'rescale beta' in sampler_options + shared.opts.save(shared.config_filename, silent=True) + + with FormRow(elem_classes=['flex-break']): + sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value='Default', type="index") + steps = gr.Slider(minimum=1, maximum=99, step=1, label="Sampling steps", elem_id=f"{tabname}_steps", value=20) + if shared.backend == shared.Backend.ORIGINAL: + with FormRow(elem_classes=['flex-break']): + choices = ['brownian noise', 'discard penultimate sigma'] + values = [] + values += ['brownian noise'] if shared.opts.data.get('schedulers_brownian_noise', False) else [] + values += ['discard penultimate sigma'] if shared.opts.data.get('schedulers_discard_penultimate', True) else [] + sampler_options = gr.CheckboxGroup(label='Sampler options', choices=choices, value=values, type='value') + with FormRow(elem_classes=['flex-break']): + shared.opts.data['schedulers_sigma'] = shared.opts.data.get('schedulers_sigma', 'default') + sampler_algo = gr.Radio(label='Sigma algorithm', choices=['default', 'karras', 'exponential', 'polyexponential'], value=shared.opts.data['schedulers_sigma'], type='value') + sampler_options.change(fn=set_sampler_original_options, inputs=[sampler_options, sampler_algo], outputs=[]) + sampler_algo.change(fn=set_sampler_original_options, inputs=[sampler_options, sampler_algo], outputs=[]) + else: + with FormRow(elem_classes=['flex-break']): + choices = ['karras', 'dynamic threshold', 'low order', 'rescale beta'] + values = [] + values += ['karras'] if shared.opts.data.get('schedulers_use_karras', True) else [] + values += ['dynamic threshold'] if shared.opts.data.get('schedulers_use_thresholding', False) else [] + values += ['low order'] if shared.opts.data.get('schedulers_use_loworder', True) else [] + values += ['rescale beta'] if shared.opts.data.get('schedulers_rescale_betas', False) else [] + sampler_options = gr.CheckboxGroup(label='Sampler options', choices=choices, value=values, type='value') + sampler_options.change(fn=set_sampler_diffuser_options, inputs=[sampler_options], outputs=[]) + return steps, sampler_index + + +def create_hires_inputs(tab): + with gr.Accordion(open=False, label="Second pass", elem_id=f"{tab}_second_pass", elem_classes=["small-accordion"]): + with FormGroup(): + with FormRow(elem_id=f"{tab}_hires_row1"): + enable_hr = gr.Checkbox(label='Enable second pass', value=False, elem_id=f"{tab}_enable_hr") + with FormRow(elem_id=f"{tab}_hires_row2"): + hr_sampler_index = gr.Dropdown(label='Secondary sampler', elem_id=f"{tab}_sampling_alt", choices=[x.name for x in sd_samplers.samplers], value='Default', type="index") + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.5, elem_id=f"{tab}_denoising_strength") + with FormRow(elem_id=f"{tab}_hires_finalres", variant="compact"): + hr_final_resolution = FormHTML(value="", elem_id=f"{tab}_hr_finalres", label="Upscaled resolution", interactive=False) + with FormRow(elem_id=f"{tab}_hires_fix_row1", variant="compact"): + hr_upscaler = gr.Dropdown(label="Upscaler", elem_id=f"{tab}_hr_upscaler", choices=[*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]], value=shared.latent_upscale_default_mode) + hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id=f"{tab}_hr_force") + with FormRow(elem_id=f"{tab}_hires_fix_row2", variant="compact"): + hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id=f"{tab}_steps_alt", value=20) + hr_scale = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Upscale by", value=2.0, elem_id=f"{tab}_hr_scale") + with FormRow(elem_id=f"{tab}_hires_fix_row3", variant="compact"): + hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id=f"{tab}_hr_resize_x") + hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id=f"{tab}_hr_resize_y") + with FormGroup(visible=shared.backend == shared.Backend.DIFFUSERS): + with FormRow(elem_id=f"{tab}_refiner_row1", variant="compact"): + refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id=f"{tab}_refiner_start") + refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id=f"{tab}_refiner_steps", value=5) + with FormRow(elem_id=f"{tab}_refiner_row3", variant="compact"): + refiner_prompt = gr.Textbox(value='', label='Secondary prompt', elem_id=f"{tab}_refiner_prompt") + with FormRow(elem_id="txt2img_refiner_row4", variant="compact"): + refiner_negative = gr.Textbox(value='', label='Secondary negative prompt', elem_id=f"{tab}_refiner_neg_prompt") + return enable_hr, hr_sampler_index, denoising_strength, hr_final_resolution, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative + + +def create_resize_inputs(tab, images, time_selector=False, scale_visible=True, mode=None): + def resize_from_to_html(width, height, scale_by): + target_width = int(width * scale_by) + target_height = int(height * scale_by) + if not target_width or not target_height: + return "Hires resize: no image selected" + return f"Hires resize: from {width}x{height} to {target_width}x{target_height}" + + dummy_component = gr.Number(visible=False, value=0) + with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id=f"{tab}_resize_group"): + with gr.Row(): + if mode is not None: + resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value=mode, visible=False) + else: + resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value='None') + resize_time = gr.Radio(label="Resize order", elem_id=f"{tab}_resize_order", choices=['Before', 'After'], value="Before", visible=time_selector) + with gr.Row(): + resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in shared.sd_upscalers], value=shared.opts.upscaler_for_img2img) + ui_common.create_refresh_button(resize_name, modelloader.load_upscalers, lambda: {"choices": modelloader.load_upscalers()}, 'refresh_upscalers') + + with FormRow(visible=True) as _resize_group: + with gr.Column(elem_id=f"{tab}_column_size"): + selected_scale_tab = gr.State(value=0) # pylint: disable=abstract-class-instantiated + with gr.Tabs(): + with gr.Tab(label="Resize to") as tab_scale_to: + with FormRow(): + with gr.Column(elem_id=f"{tab}_column_size"): + with FormRow(): + width = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512, elem_id=f"{tab}_width") + height = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512, elem_id=f"{tab}_height") + res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_res_switch_btn") + res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) + detect_image_size_btn = ToolButton(value=ui_symbols.detect, elem_id=f"{tab}_detect_image_size_btn") + detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False) + + with gr.Tab(label="Resize by") as tab_scale_by: + scale_by = gr.Slider(minimum=0.05, maximum=8.0, step=0.05, label="Scale", value=1.0, elem_id=f"{tab}_scale") + if scale_visible: + with FormRow(): + scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id=f"{tab}_scale_resolution_preview") + gr.Slider(label="Unused", elem_id=f"{tab}_unused_scale_by_slider") + button_update_resize_to = gr.Button(visible=False, elem_id=f"{tab}_update_resize_to") + + on_change_args = dict(fn=resize_from_to_html, _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, scale_by], outputs=scale_by_html, show_progress=False) + scale_by.release(**on_change_args) + button_update_resize_to.click(**on_change_args) + + for component in images: + component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) + + tab_scale_to.select(fn=lambda: 0, inputs=[], outputs=[selected_scale_tab]) + tab_scale_by.select(fn=lambda: 1, inputs=[], outputs=[selected_scale_tab]) + # resize_mode.change(fn=lambda x: gr.update(visible=x != 0), inputs=[resize_mode], outputs=[_resize_group]) + return resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time diff --git a/modules/ui_train.py b/modules/ui_train.py index 97437dfc9..4676ab6fb 100644 --- a/modules/ui_train.py +++ b/modules/ui_train.py @@ -3,12 +3,13 @@ import gradio as gr from modules import sd_hijack, script_callbacks, shared from modules.ui_components import FormRow from modules.ui_common import create_refresh_button +from modules.ui_sections import create_sampler_inputs from modules.call_queue import wrap_gradio_gpu_call from modules.textual_inversion import textual_inversion import modules.errors -def create_ui(txt2img_preview_params): +def create_ui(): dummy_component = gr.Label(visible=False) with gr.Row(elem_id="train_tab"): @@ -41,8 +42,24 @@ def create_ui(txt2img_preview_params): return gr_show(False), gr_show(True), gr_show(False) elif tab == 'hn': return gr_show(False), gr_show(False), gr_show(True) - else: + elif tab == 'pr': return gr_show(True), gr_show(False), gr_show(False) + else: + return gr_show(False), gr_show(False), gr_show(False) + + ### preview tab + + with gr.Tab(label="Preview settings", id="train_preview_tab") as tab_preview: + tab_preview.select(fn=lambda: train_tab_change('pr'), inputs=[], outputs=[action_pp, action_ti, action_hn]) + prompt = gr.Textbox(label="Prompt", value="", placeholder="Prompt to be used for previews", lines=2) + negative = gr.Textbox(label="Negative prompt", value="", placeholder="Negative prompt to be used for previews", lines=2) + steps, sampler_index = create_sampler_inputs('train', accordion=False) + cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0) + seed = gr.Number(label='Initial seed', value=-1) + with gr.Row(): + width = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512) + height = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512) + txt2img_preview_params = [prompt, negative, steps, sampler_index, cfg_scale, seed, width, height] ### preprocess tab diff --git a/modules/ui_txt2img.py b/modules/ui_txt2img.py new file mode 100644 index 000000000..094659037 --- /dev/null +++ b/modules/ui_txt2img.py @@ -0,0 +1,150 @@ +import gradio as gr +from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call +from modules import timer, shared, ui_common, ui_symbols, ui_sections, generation_parameters_copypaste +from modules.ui_components import FormRow, FormGroup, ToolButton + + +def calc_resolution_hires(width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler): + from modules import processing, devices + if hr_upscaler == "None": + return "Hires resize: None" + p = processing.StableDiffusionProcessingTxt2Img(width=width, height=height, enable_hr=True, hr_scale=hr_scale, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y) + p.init_hr() + with devices.autocast(): + p.init([""], [0], [0]) + return f"Hires resize: from {p.width}x{p.height} to {p.hr_resize_x or p.hr_upscale_to_x}x{p.hr_resize_y or p.hr_upscale_to_y}" + + +def create_ui(): + import modules.txt2img # pylint: disable=redefined-outer-name + modules.scripts.scripts_current = modules.scripts.scripts_txt2img + modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False) + with gr.Blocks(analytics_enabled=False) as _txt2img_interface: + txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, txt2img_submit, _interrogate, _deepbooru, txt2img_paste, txt2img_extra_networks_button, txt2img_token_counter, txt2img_token_button, txt2img_negative_token_counter, txt2img_negative_token_button = ui_sections.create_toprow(is_img2img=False, id_part="txt2img") + + txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False) + txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) + + with FormRow(variant='compact', elem_id="txt2img_extra_networks", visible=False) as extra_networks_ui: + from modules import ui_extra_networks + extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, txt2img_extra_networks_button, 'txt2img', skip_indexing=shared.opts.extra_network_skip_indexing) + timer.startup.record('ui-extra-networks') + + with gr.Row(elem_id="txt2img_interface", equal_height=False): + with gr.Column(variant='compact', elem_id="txt2img_settings"): + + with FormRow(): + width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=512, elem_id="txt2img_width") + height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=512, elem_id="txt2img_height") + res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id="txt2img_res_switch_btn", label="Switch dims") + res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) + + with FormGroup(elem_classes="settings-accordion"): + + steps, sampler_index = ui_sections.create_sampler_inputs('txt2img') + batch_count, batch_size = ui_sections.create_batch_inputs('txt2img') + seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui_sections.create_seed_inputs('txt2img') + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = ui_sections.create_advanced_inputs('txt2img') + enable_hr, hr_sampler_index, denoising_strength, hr_final_resolution, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative = ui_sections.create_hires_inputs('txt2img') + override_settings = ui_common.create_override_inputs('txt2img') + + txt2img_script_inputs = modules.scripts.scripts_txt2img.setup_ui() + + hr_resolution_preview_inputs = [width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler] + for preview_input in hr_resolution_preview_inputs: + preview_input.change( + fn=calc_resolution_hires, + _js="onCalcResolutionHires", + inputs=hr_resolution_preview_inputs, + outputs=[hr_final_resolution], + show_progress=False, + ) + + txt2img_gallery, txt2img_generation_info, txt2img_html_info, _txt2img_html_info_formatted, txt2img_html_log = ui_common.create_output_panel("txt2img") + ui_common.connect_reuse_seed(seed, reuse_seed, txt2img_generation_info, is_subseed=False) + ui_common.connect_reuse_seed(subseed, reuse_subseed, txt2img_generation_info, is_subseed=True) + + dummy_component = gr.Textbox(visible=False, value='dummy') + txt2img_args = [ + dummy_component, + txt2img_prompt, txt2img_negative_prompt, txt2img_prompt_styles, + steps, sampler_index, hr_sampler_index, + full_quality, restore_faces, tiling, + batch_count, batch_size, + cfg_scale, image_cfg_scale, diffusers_guidance_rescale, + clip_skip, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + height, width, + enable_hr, denoising_strength, + hr_scale, hr_upscaler, hr_force, hr_second_pass_steps, hr_resize_x, hr_resize_y, + refiner_steps, refiner_start, refiner_prompt, refiner_negative, + hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, + override_settings, + ] + txt2img_dict = dict( + fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']), + _js="submit_txt2img", + inputs=txt2img_args + txt2img_script_inputs, + outputs=[ + txt2img_gallery, + txt2img_generation_info, + txt2img_html_info, + txt2img_html_log, + ], + show_progress=False, + ) + txt2img_prompt.submit(**txt2img_dict) + txt2img_submit.click(**txt2img_dict) + txt2img_paste_fields = [ + # prompt + (txt2img_prompt, "Prompt"), + (txt2img_negative_prompt, "Negative prompt"), + # main + (width, "Size-1"), + (height, "Size-2"), + # sampler + (sampler_index, "Sampler"), + (steps, "Steps"), + # batch + (batch_count, "Batch-1"), + (batch_size, "Batch-2"), + # seed + (seed, "Seed"), + (subseed, "Variation seed"), + (subseed_strength, "Variation strength"), + # advanced + (cfg_scale, "CFG scale"), + (clip_skip, "Clip skip"), + (image_cfg_scale, "Image CFG scale"), + (diffusers_guidance_rescale, "CFG rescale"), + (full_quality, "Full quality"), + (restore_faces, "Face restoration"), + (tiling, "Tiling"), + # second pass + (enable_hr, "Second pass"), + (hr_sampler_index, "Hires sampler"), + (denoising_strength, "Denoising strength"), + (hr_upscaler, "Hires upscaler"), + (hr_force, "Hires force"), + (hr_second_pass_steps, "Hires steps"), + (hr_scale, "Hires upscale"), + (hr_resize_x, "Hires resize-1"), + (hr_resize_y, "Hires resize-2"), + # refiner + (refiner_start, "Refiner start"), + (refiner_steps, "Refiner steps"), + (refiner_prompt, "Prompt2"), + (refiner_negative, "Negative2"), + # hidden + (seed_resize_from_w, "Seed resize from-1"), + (seed_resize_from_h, "Seed resize from-2"), + *modules.scripts.scripts_txt2img.infotext_fields + ] + generation_parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) + txt2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None) + generation_parameters_copypaste.register_paste_params_button(txt2img_bindings) + + txt2img_token_button.click(fn=wrap_queued_call(ui_common.update_token_counter), inputs=[txt2img_prompt, steps], outputs=[txt2img_token_counter]) + txt2img_negative_token_button.click(fn=wrap_queued_call(ui_common.update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[txt2img_negative_token_counter]) + + ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) diff --git a/wiki b/wiki index 87ee8d4c1..671e644b4 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 87ee8d4c1c2116b116bcceea5de59d806a40a929 +Subproject commit 671e644b4a2f1fe9342a23fe7d4b44d79dae88e8