diff --git a/TODO.md b/TODO.md index 174c4b337..d2939f9fa 100644 --- a/TODO.md +++ b/TODO.md @@ -63,3 +63,5 @@ Tech that can be integrated as part of the core workflow... - ability to view/add/edit model description shown in extra networks cards - add option to specify fallback sampler if primary sampler is not compatible with desired operation +- make clip skip a local parameter +- remove obsolete items from settings diff --git a/javascript/ui.js b/javascript/ui.js index 774422b93..52a9341ce 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -338,6 +338,28 @@ function reconnect_ui() { const atEnd = () => showSubmitButtons('txt2img', true) requestProgress(task_id, el1, el2, atEnd, null, true) } + + sd_model = gradioApp().getElementById("setting_sd_model_checkpoint") + let loadingStarted = 0; + let loadingMonitor = 0; + const sd_model_callback = () => { + loading = sd_model.querySelector(".eta-bar") + if (!loading) { + loadingStarted = 0 + clearInterval(loadingMonitor) + } else { + if (loadingStarted === 0) { + loadingStarted = Date.now(); + loadingMonitor = setInterval(() => { + elapsed = Date.now() - loadingStarted; + console.log('Loading', elapsed) + if (elapsed > 3000 && loading) loading.style.display = 'none'; + }, 5000); + } + } + }; + const sd_model_observer = new MutationObserver(sd_model_callback); + sd_model_observer.observe(sd_model, { attributes: true, childList: true, subtree: true }); } -var start_check = setInterval(reconnect_ui, 50) +var start_check = setInterval(reconnect_ui, 50); diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index bc58011f2..964432d72 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -11,7 +11,7 @@ from modules import shared, ui_tempdir, script_callbacks re_param_code = r'\s*([\w ]+):\s*("(?:\\"[^,]|\\"|\\|[^\"])+"|[^,]*)(?:,|$)' re_param = re.compile(re_param_code) re_imagesize = re.compile(r"^(\d+)x(\d+)$") -re_hypernet_hash = re.compile("\(([0-9a-f]+)\)$") +re_hypernet_hash = re.compile("\(([0-9a-f]+)\)$") # pylint: disable=anomalous-backslash-in-string type_of_gr_update = type(gr.update()) paste_fields = {} @@ -102,7 +102,6 @@ def bind_buttons(buttons, send_image, send_generate_info): for tabname, button in buttons.items(): source_text_component = send_generate_info if isinstance(send_generate_info, gr.components.Component) else None source_tabname = send_generate_info if isinstance(send_generate_info, str) else None - register_paste_params_button(ParamBinding(paste_button=button, tabname=tabname, source_text_component=source_text_component, source_image_component=send_image, source_tabname=source_tabname)) @@ -116,7 +115,6 @@ def connect_paste_params_buttons(): destination_image_component = paste_fields[binding.tabname]["init_img"] fields = paste_fields[binding.tabname]["fields"] override_settings_component = binding.override_settings_component or paste_fields[binding.tabname]["override_settings_component"] - destination_width_component = next(iter([field for field, name in fields if name == "Size-1"] if fields else []), None) destination_height_component = next(iter([field for field, name in fields if name == "Size-2"] if fields else []), None) @@ -127,17 +125,14 @@ def connect_paste_params_buttons(): else: func = send_image_and_dimensions if destination_width_component else lambda x: x jsfunc = None - binding.paste_button.click( fn=func, _js=jsfunc, inputs=[binding.source_image_component], outputs=[destination_image_component, destination_width_component, destination_height_component] if destination_width_component else [destination_image_component], ) - if binding.source_text_component is not None and fields is not None: connect_paste(binding.paste_button, fields, binding.source_text_component, override_settings_component, binding.tabname) - if binding.source_tabname is not None and fields is not None: paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration'] + (["Seed"] if shared.opts.send_seed else []) + binding.paste_field_names binding.paste_button.click( @@ -145,7 +140,6 @@ def connect_paste_params_buttons(): inputs=[field for field, name in paste_fields[binding.source_tabname]["fields"] if name in paste_field_names], outputs=[field for field, name in fields if name in paste_field_names], ) - binding.paste_button.click( fn=None, _js=f"switch_to_{binding.tabname}", @@ -159,14 +153,12 @@ def send_image_and_dimensions(x): img = x else: img = image_from_url_text(x) - if shared.opts.send_size and isinstance(img, Image.Image): w = img.width h = img.height else: w = gr.update() h = gr.update() - return img, w, h @@ -238,33 +230,25 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model returns a dict with field values """ - res = {} - prompt = "" negative_prompt = "" - done_with_prompt = False - *lines, lastline = x.strip().split("\n") if len(re_param.findall(lastline)) < 3: lines.append(lastline) lastline = '' - for _i, line in enumerate(lines): line = line.strip() if line.startswith("Negative prompt:"): done_with_prompt = True line = line[16:].strip() - if done_with_prompt: negative_prompt += ("" if negative_prompt == "" else "\n") + line else: prompt += ("" if prompt == "" else "\n") + line - res["Prompt"] = prompt res["Negative prompt"] = negative_prompt - for k, v in re_param.findall(lastline): v = v[1:-1] if v[0] == '"' and v[-1] == '"' else v m = re_imagesize.match(v) @@ -273,31 +257,24 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model res[k+"-2"] = m.group(2) else: res[k] = v - # Missing CLIP skip means it was set to 1 (the default) if "Clip skip" not in res: res["Clip skip"] = "1" - hypernet = res.get("Hypernet", None) if hypernet is not None: res["Prompt"] += f"""""" - if "Hires resize-1" not in res: res["Hires resize-1"] = 0 res["Hires resize-2"] = 0 - # Infer additional override settings for token merging token_merging_ratio = res.get("Token merging ratio", None) token_merging_ratio_hr = res.get("Token merging ratio hr", None) - if token_merging_ratio is not None or token_merging_ratio_hr is not None: res["Token merging"] = 'True' - if token_merging_ratio is None: res["Token merging hr only"] = 'True' else: res["Token merging hr only"] = 'False' - if res.get("Token merging random", None) is None: res["Token merging random"] = 'False' if res.get("Token merging merge attention", None) is None: @@ -312,14 +289,12 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model res["Token merging stride y"] = '2' restore_old_hires_fix_params(res) - return res settings_map = {} - infotext_to_setting_name_mapping = [ ('Clip skip', 'CLIP_stop_at_last_layers', ), ('Conditional mask weight', 'inpainting_mask_weight'), @@ -349,34 +324,29 @@ infotext_to_setting_name_mapping = [ def create_override_settings_dict(text_pairs): """creates processing's override_settings parameters from gradio's multiselect - Example input: ['Clip skip: 2', 'Model hash: e6e99610c4', 'ENSD: 31337'] Example output: {'CLIP_stop_at_last_layers': 2, 'sd_model_checkpoint': 'e6e99610c4', 'eta_noise_seed_delta': 31337} """ - res = {} params = {} for pair in text_pairs: k, v = pair.split(":", maxsplit=1) - params[k] = v.strip() - for param_name, setting_name in infotext_to_setting_name_mapping: value = params.get(param_name, None) - if value is None: continue - res[setting_name] = shared.opts.cast_value(setting_name, value) - return res -def connect_paste(button, paste_fields, input_comp, override_settings_component, tabname): +def connect_paste(button, paste_fields, input_comp, override_settings_component, tabname): # pylint: disable=redefined-outer-name def paste_func(prompt): + if 'Negative prompt' not in prompt and 'Steps' not in prompt: + prompt = None if not prompt and not shared.cmd_opts.hide_ui_dir_config: filename = os.path.join(data_path, "params.txt") if os.path.exists(filename): @@ -384,17 +354,14 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component, prompt = file.read() else: prompt = '' - params = parse_generation_parameters(prompt) script_callbacks.infotext_pasted_callback(prompt, params) res = [] - for output, key in paste_fields: if callable(key): v = key(params) else: v = params.get(key, None) - if v is None: res.append(gr.update()) elif isinstance(v, type_of_gr_update): @@ -402,42 +369,31 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component, else: try: valtype = type(output.value) - if valtype == bool and v == "False": val = False else: val = valtype(v) - res.append(gr.update(value=val)) except Exception: res.append(gr.update()) - return res if override_settings_component is not None: def paste_settings(params): vals = {} - for param_name, setting_name in infotext_to_setting_name_mapping: v = params.get(param_name, None) if v is None: continue - if setting_name == "sd_model_checkpoint" and shared.opts.disable_weights_auto_swap: continue - v = shared.opts.cast_value(setting_name, v) current_value = getattr(shared.opts, setting_name, None) - if v == current_value: continue - vals[param_name] = v - vals_pairs = [f"{k}: {v}" for k, v in vals.items()] - return gr.Dropdown.update(value=vals_pairs, choices=vals_pairs, visible=len(vals_pairs) > 0) - paste_fields = paste_fields + [(override_settings_component, paste_settings)] button.click( diff --git a/modules/processing.py b/modules/processing.py index 1d49da237..597ea5bee 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -111,7 +111,7 @@ class StableDiffusionProcessing: """ The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing """ - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, width: int = 512, height: int = 512, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 20, cfg_scale: float = 6.0, width: int = 512, height: int = 512, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument if sampler_index is not None: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) @@ -165,6 +165,7 @@ class StableDiffusionProcessing: self.all_negative_prompts = None self.all_seeds = None self.all_subseeds = None + self.clip_skip = opts.CLIP_stop_at_last_layers self.iteration = 0 @property @@ -302,8 +303,7 @@ class Processed: self.index_of_first_image = index_of_first_image self.styles = p.styles self.job_timestamp = state.job_timestamp - self.clip_skip = opts.CLIP_stop_at_last_layers - + self.clip_skip = p.clip_skip self.eta = p.eta self.ddim_discretize = p.ddim_discretize self.s_churn = p.s_churn @@ -457,11 +457,9 @@ def fix_seed(p): p.subseed = get_fixed_seed(p.subseed) -def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0): # pylint: disable=unused-argument +def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0): # pylint: disable=unused-argument index = position_in_batch + iteration * p.batch_size - clip_skip = getattr(p, 'clip_skip', opts.CLIP_stop_at_last_layers) - generation_params = { "Steps": p.steps, "Sampler": p.sampler_name, @@ -478,7 +476,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Seed resize from": (None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"), "Denoising strength": getattr(p, 'denoising_strength', None), "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None, - "Clip skip": None if clip_skip <= 1 else clip_skip, + "Clip skip": p.clip_skip, "ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta, "Token merging ratio": None if not (opts.token_merging or cmd_opts.token_merging) or opts.token_merging_hr_only else opts.token_merging_ratio, "Token merging ratio hr": None if not (opts.token_merging or cmd_opts.token_merging) else opts.token_merging_ratio_hr, diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index c994a0b67..b979ed6f9 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -205,7 +205,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): is when you do prompt editing: "a picture of a [cat:dog:0.4] eating ice cream" """ - batch_chunks, token_count = self.process_texts(texts) + batch_chunks, _token_count = self.process_texts(texts) used_embeddings = {} chunk_count = max([len(x) for x in batch_chunks]) @@ -219,7 +219,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): self.hijack.fixes = [x.fixes for x in batch_chunk] for fixes in self.hijack.fixes: - for position, embedding in fixes: + for _position, embedding in fixes: used_embeddings[embedding.name] = embedding z = self.process_tokens(tokens, multipliers) diff --git a/modules/shared.py b/modules/shared.py index 466ec6605..adccb4812 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -3,10 +3,8 @@ import sys import time import json import datetime - import gradio as gr import tqdm - import modules.interrogate import modules.memmon import modules.styles @@ -21,7 +19,6 @@ demo: gr.Blocks = None log = setup_log parser = cmd_args.parser url = 'https://github.com/vladmandic/automatic' - if os.environ.get('IGNORE_CMD_ARGS_ERRORS', None) is None: cmd_opts = parser.parse_args() else: @@ -53,10 +50,7 @@ ui_reorder_categories = [ ] cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or cmd_opts.server_name) and not cmd_opts.enable_insecure - -devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = \ - (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer']) - +devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer']) device = devices.device sd_upscalers = [] sd_model = None @@ -97,7 +91,6 @@ class State: def nextjob(self): if opts.live_previews_enable and opts.show_progress_every_n_steps == -1: self.do_set_current_image() - self.job_no += 1 self.sampling_step = 0 self.current_image_sampling_step = 0 @@ -129,13 +122,11 @@ class State: self.interrupted = False self.textinfo = None self.time_start = time.time() - devices.torch_gc() def end(self): self.job = "" self.job_count = 0 - devices.torch_gc() def set_current_image(self): @@ -162,9 +153,7 @@ class State: state = State() state.server_start = time.time() - interrogator = modules.interrogate.InterrogateModels("interrogate") - face_restorers = [] class OptionInfo: @@ -181,7 +170,6 @@ class OptionInfo: def options_section(section_identifier, options_dict): for _k, v in options_dict.items(): v.section = section_identifier - return options_dict @@ -227,26 +215,25 @@ def refresh_themes(): hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} tab_names = [] - options_templates = {} default_checkpoint = list_checkpoint_tiles()[0] if len(list_checkpoint_tiles()) > 0 else "model.ckpt" options_templates.update(options_section(('sd', "Stable Diffusion"), { "sd_model_checkpoint": OptionInfo(default_checkpoint, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), - "sd_checkpoint_cache": OptionInfo(0, "Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae_checkpoint_cache": OptionInfo(0, "VAE Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae": OptionInfo("Automatic", "SD VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), - "sd_vae_as_default": OptionInfo(True, "Ignore selected VAE for stable diffusion checkpoints that have their own .vae.pt next to them"), + "sd_checkpoint_cache": OptionInfo(0, "Model checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "sd_vae_checkpoint_cache": OptionInfo(0, "VAE checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "sd_vae": OptionInfo("Automatic", "Select VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), + "sd_vae_as_default": OptionInfo(True, "Ignore selected VAE for stable diffusion checkpoints that have their own .vae.pt next to them", gr.Checkbox, {"visible": False}), "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for img2img", gr.Slider, {"minimum": 0.5, "maximum": 1.5, "step": 0.01}), "img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."), - "img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising)."), + "img2img_fix_steps": OptionInfo(False, "For image processing do exactly the amount of steps as specified."), "img2img_background_color": OptionInfo("#ffffff", "With img2img, fill image's transparent parts with this color.", ui_components.FormColorPicker, {}), "enable_quantization": OptionInfo(True, "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds."), - "enable_emphasis": OptionInfo(True, "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention"), - "enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"), + "enable_emphasis": OptionInfo(True, "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention", gr.Checkbox, {"visible": False}), + "enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image", gr.Checkbox, {"visible": False}), "comma_padding_backtrack": OptionInfo(20, "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1 }), - "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}), + "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1, "visible": False}), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "cross_attention_optimization": OptionInfo("Scaled-Dot-Product", "Cross-attention optimization method", gr.Radio, lambda: {"choices": shared_items.list_crossattention() }), "cross_attention_options": OptionInfo([], "Cross-attention advanced options", gr.CheckboxGroup, lambda: {"choices": ['xFormers enable flash Attention', 'SDP disable memory attention']}), @@ -254,6 +241,8 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { "sub_quad_kv_chunk_size": OptionInfo(512, "Sub-quadratic cross-attentionkv chunk size for the sub-quadratic cross-attention layer optimization to use", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8}), "sub_quad_chunk_threshold": OptionInfo(80, "Sub-quadratic cross-attention percentage of VRAM chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), "always_batch_cond_uncond": OptionInfo(False, "Disables cond/uncond batching that is enabled to save memory with --medvram or --lowvram"), + "multiple_tqdm": OptionInfo(False, "Add a second progress bar to the console that shows progress for an entire job.", gr.Checkbox, {"visible": False}), + "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console.", gr.Checkbox, {"visible": False}), })) options_templates.update(options_section(('system-paths', "System Paths"), { @@ -322,6 +311,7 @@ options_templates.update(options_section(('saving-paths', "Image Paths"), { })) options_templates.update(options_section(('cuda', "CUDA Settings"), { + "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation. Set to 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}), "precision": OptionInfo("Autocast", "Precision type", gr.Radio, lambda: {"choices": ["Autocast", "Full"]}), "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" else "FP16", "Device precision type", gr.Radio, lambda: {"choices": ["FP32", "FP16", "BF16"]}), "no_half": OptionInfo(False, "Use full precision for model (--no-half)"), @@ -341,7 +331,7 @@ options_templates.update(options_section(('upscaling', "Upscaling"), { "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which Real-ESRGAN models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), - "upscaler_for_img2img": OptionInfo("SwinIR_4x", "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), + "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), "use_old_hires_fix_width_height": OptionInfo(False, "For hires fix, use width/height sliders to set final resolution rather than first pass (disables Upscale by, Resize width/height to)."), "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers."), })) @@ -352,12 +342,6 @@ options_templates.update(options_section(('face-restoration', "Face restoration" "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), })) -options_templates.update(options_section(('system', "System"), { - "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation. Set to 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}), - "multiple_tqdm": OptionInfo(False, "Add a second progress bar to the console that shows progress for an entire job."), - "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console."), -})) - options_templates.update(options_section(('training', "Training"), { "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."), "pin_memory": OptionInfo(True, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."), @@ -404,13 +388,13 @@ options_templates.update(options_section(('ui', "User interface"), { "do_not_show_images": OptionInfo(False, "Do not show any images in results for web"), "add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"), "add_model_name_to_info": OptionInfo(True, "Add model name to generation information"), - "disable_weights_auto_swap": OptionInfo(True, "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint."), + "disable_weights_auto_swap": OptionInfo(True, "Do not change the selected model when reading generation parameters."), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), "font": OptionInfo("", "Font for image grids that have text"), - "js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"), - "js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"), - "show_progress_in_title": OptionInfo(False, "Show generation progress in window title."), + "js_modal_lightbox": OptionInfo(True, "Enable full page image viewer", gr.Checkbox, {"visible": False}), + "js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer", gr.Checkbox, {"visible": False}), + "show_progress_in_title": OptionInfo(False, "Show generation progress in window title.", gr.Checkbox, {"visible": False}), "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), @@ -486,17 +470,14 @@ class Options: def __setattr__(self, key, value): if self.data is not None: if key in self.data or key in self.data_labels: - assert not cmd_opts.freeze_settings, "changing settings is disabled" - - info = opts.data_labels.get(key, None) - comp_args = info.component_args if info else None - if isinstance(comp_args, dict) and comp_args.get('visible', True) is False: - raise RuntimeError(f"not possible to set {key} because it is restricted") - + if cmd_opts.freeze_settings: + print(f'Settings are frozen: {key}') + return if cmd_opts.hide_ui_dir_config and key in restricted_opts: - raise RuntimeError(f"not possible to set {key} because it is restricted") - - self.data[key] = value + print(f'Settings key is restricted: {key}') + return + else: + self.data[key] = value return return super(Options, self).__setattr__(key, value) @@ -505,24 +486,19 @@ class Options: if self.data is not None: if item in self.data: return self.data[item] - if item in self.data_labels: return self.data_labels[item].default - return super(Options, self).__getattribute__(item) def set(self, key, value): """sets an option and calls its onchange callback, returning True if the option changed and False otherwise""" - oldval = self.data.get(key, None) if oldval == value: return False - try: setattr(self, key, value) except RuntimeError: return False - if self.data_labels[key].onchange is not None: try: self.data_labels[key].onchange() @@ -530,37 +506,30 @@ class Options: errors.display(e, f"changing setting {key} to {value}") setattr(self, key, oldval) return False - return True def get_default(self, key): """returns the default value for the key""" - data_label = self.data_labels.get(key) if data_label is None: return None - return data_label.default def save(self, filename): assert not cmd_opts.freeze_settings, "saving settings is disabled" - with open(filename, "w", encoding="utf8") as file: json.dump(self.data, file, indent=4) def same_type(self, x, y): if x is None or y is None: return True - type_x = self.typemap.get(type(x), type(x)) type_y = self.typemap.get(type(y), type(y)) - return type_x == type_y def load(self, filename): with open(filename, "r", encoding="utf8") as file: self.data = json.load(file) - bad_settings = 0 for k, v in self.data.items(): info = self.data_labels.get(k, None) @@ -574,7 +543,6 @@ class Options: def onchange(self, key, func, call=True): item = self.data_labels.get(key) item.onchange = func - if call: func() @@ -587,13 +555,11 @@ class Options: def reorder(self): """reorder settings so that all items related to section always go together""" - section_ids = {} settings_items = self.data_labels.items() for k, item in settings_items: if item.section not in section_ids: section_ids[item.section] = len(section_ids) - self.data_labels = {k: v for k, v in sorted(settings_items, key=lambda x: section_ids[x[1].section])} def cast_value(self, key, value): @@ -619,27 +585,20 @@ class Options: return value - opts = Options() - batch_cond_uncond = opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram) parallel_processing_allowed = not cmd_opts.lowvram and not cmd_opts.medvram xformers_available = False config_filename = cmd_opts.ui_settings_file - os.makedirs(opts.hypernetwork_dir, exist_ok=True) hypernetworks = {} loaded_hypernetworks = [] - if os.path.exists(config_filename): opts.load(config_filename) - cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) - settings_components = None """assinged from ui.py, a mapping on setting names to gradio components repsponsible for those settings""" - latent_upscale_default_mode = "Latent" latent_upscale_modes = { "Latent": {"mode": "bilinear", "antialias": False}, @@ -649,11 +608,10 @@ latent_upscale_modes = { "Latent (nearest)": {"mode": "nearest", "antialias": False}, "Latent (nearest-exact)": {"mode": "nearest-exact", "antialias": False}, } - progress_print_out = sys.stdout - gradio_theme = gr.themes.Base() + def reload_gradio_theme(theme_name=None): global gradio_theme # pylint: disable=global-statement if not theme_name: @@ -714,7 +672,6 @@ class TotalTQDM: total_tqdm = TotalTQDM() - mem_mon = modules.memmon.MemUsageMonitor("MemMon", device, opts) mem_mon.start() diff --git a/modules/txt2img.py b/modules/txt2img.py index 70204293b..2fcb4c49d 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -7,8 +7,8 @@ import modules.shared as shared from modules.ui import plaintext_to_html -def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, override_settings_texts, *args): - override_settings = create_override_settings_dict(override_settings_texts) # pylint: disable=unused-argument +def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, override_settings_texts, *args): # pylint: disable=unused-argument + override_settings = create_override_settings_dict(override_settings_texts) p = StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, diff --git a/modules/ui.py b/modules/ui.py index 811e9261a..c811cd3eb 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -81,34 +81,25 @@ def visit(x, func, path=""): def add_style(name: str, prompt: str, negative_prompt: str): if name is None: return [gr_show() for x in range(4)] - style = modules.styles.PromptStyle(name, prompt, negative_prompt) shared.prompt_styles.styles[style.name] = style - # Save all loaded prompt styles: this allows us to update the storage format in the future more easily, because we - # reserialize all styles every time we save them 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(enable, width, height, hr_scale, hr_resize_x, hr_resize_y): from modules import processing, devices - if not enable: return "" - 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) - with devices.autocast(): p.init([""], [0], [0]) - return f"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 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=[])] @@ -125,7 +116,6 @@ def process_interrogate(interrogation_function, mode, ii_input_dir, ii_output_di 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) @@ -144,44 +134,35 @@ def interrogate_deepbooru(image): prompt = deepbooru.model.tag(image) return gr.update() if prompt is None else prompt + def change_clip_skip(val): shared.opts.CLIP_stop_at_last_layers = val + def create_seed_inputs(target_interface): with FormRow(elem_id=target_interface + '_seed_row', variant="compact"): seed = gr.Number(label='Seed', value=-1, elem_id=target_interface + '_seed') seed.style(container=False) random_seed = ToolButton(random_symbol, elem_id=target_interface + '_random_seed') reuse_seed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_seed') - seed_checkbox = gr.Checkbox(label='Extra', elem_id=target_interface + '_subseed_show', value=False, visible=False) # Ghost checkbox, so it still gets sent. For compatibility with extensions that call txt2img or img2img manually - with FormRow(visible=True, elem_id=target_interface + '_subseed_row'): subseed = gr.Number(label='Variation seed', value=-1, elem_id=target_interface + '_subseed') subseed.style(container=False) random_subseed = ToolButton(random_symbol, elem_id=target_interface + '_random_subseed') reuse_subseed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_subseed') subseed_strength = gr.Slider(label='Strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=target_interface + '_subseed_strength') - with FormRow(visible=False): seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from width", value=0, elem_id=target_interface + '_seed_resize_from_w') seed_resize_from_h = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from height", value=0, elem_id=target_interface + '_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, seed_checkbox - def connect_clear_prompt(button): """Given clear button, prompt, and token_counter objects, setup clear prompt button click event""" - button.click( - _js="clear_prompt", - fn=None, - inputs=[], - outputs=[], - ) + button.click(_js="clear_prompt", fn=None, inputs=[], outputs=[]) def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed): @@ -190,7 +171,6 @@ def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" def copy_seed(gen_info_string: str, index): res = -1 - try: gen_info = json.loads(gen_info_string) index -= gen_info.get('index_of_first_image', 0) @@ -201,35 +181,24 @@ def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: 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 != '': print("Error parsing JSON generation info:", file=sys.stderr) print(gen_info_string, file=sys.stderr) - return [res, gr_show(False)] - 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] - ) + 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): 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: # a parsing error can happen here during typing, and we don't want to bother the user with # messages related to it in console prompt_schedules = [[[steps, text]]] - flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) prompts = [prompt_text for step, prompt_text in flat_prompts] token_count, max_length = max([model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) @@ -238,67 +207,43 @@ def update_token_counter(text, steps): def create_toprow(is_img2img): 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(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)") - with gr.Row(): with gr.Column(scale=80): with gr.Row(): negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)") - 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="interrogate") button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id="deepbooru") - with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"): with gr.Row(elem_id=f"{id_part}_generate_box", elem_classes="generate-box"): interrupt = gr.Button('Stop', elem_id=f"{id_part}_interrupt", elem_classes="generate-box-interrupt") skip = gr.Button('Skip', elem_id=f"{id_part}_skip", elem_classes="generate-box-skip") submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary') - - skip.click( - fn=lambda: shared.state.skip(), - inputs=[], - outputs=[], - ) - - interrupt.click( - fn=lambda: shared.state.interrupt(), - inputs=[], - outputs=[], - ) - + skip.click(fn=lambda: shared.state.skip(), inputs=[], outputs=[]) + interrupt.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[]) with gr.Row(elem_id=f"{id_part}_tools"): paste = ToolButton(value=paste_symbol, elem_id="paste") clear_prompt_button = ToolButton(value=clear_prompt_symbol, elem_id=f"{id_part}_clear_prompt") extra_networks_button = ToolButton(value=extra_networks_symbol, elem_id=f"{id_part}_extra_networks") prompt_style_apply = ToolButton(value=apply_style_symbol, elem_id=f"{id_part}_style_apply") save_style = ToolButton(value=save_style_symbol, elem_id=f"{id_part}_style_create") - 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") - - clear_prompt_button.click( - fn=lambda *x: x, - _js="confirm_clear_prompt", - inputs=[prompt, negative_prompt], - outputs=[prompt, negative_prompt], - ) - + clear_prompt_button.click(fn=lambda *x: x, _js="confirm_clear_prompt", inputs=[prompt, negative_prompt], outputs=[prompt, negative_prompt]) with gr.Row(elem_id=f"{id_part}_styles_row"): prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in shared.prompt_styles.styles.items()], value=[], multiselect=True) create_refresh_button(prompt_styles, shared.prompt_styles.reload, lambda: {"choices": [k for k, v in shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles") - return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button @@ -309,32 +254,25 @@ def setup_progressbar(*args, **kwargs): # pylint: disable=unused-argument def apply_setting(key, value): if value is None: return gr.update() - if shared.cmd_opts.freeze_settings: return gr.update() - # dont allow model to be swapped when model hash exists in prompt if key == "sd_model_checkpoint" and opts.disable_weights_auto_swap: return gr.update() - if key == "sd_model_checkpoint": ckpt_info = sd_models.get_closet_checkpoint_match(value) - if ckpt_info is not None: value = ckpt_info.title else: return gr.update() - comp_args = opts.data_labels[key].component_args if comp_args and isinstance(comp_args, dict) and comp_args.get('visible') is False: return - valtype = type(opts.data_labels[key].default) oldval = opts.data.get(key, None) opts.data[key] = valtype(value) if valtype != type(None) else value if oldval != value and opts.data_labels[key].onchange is not None: opts.data_labels[key].onchange() - opts.save(shared.config_filename) return getattr(opts, key) @@ -343,18 +281,12 @@ def create_refresh_button(refresh_component, refresh_method, refreshed_args, ele def refresh(): refresh_method() args = refreshed_args() if callable(refreshed_args) else refreshed_args - for k, v in args.items(): setattr(refresh_component, k, v) - return gr.update(**(args or {})) refresh_button = ToolButton(value=refresh_symbol, elem_id=elem_id) - refresh_button.click( - fn=refresh, - inputs=[], - outputs=[refresh_component] - ) + refresh_button.click(fn=refresh, inputs=[], outputs=[refresh_component]) return refresh_button @@ -366,117 +298,89 @@ def create_sampler_and_steps_selection(choices, tabname): with FormRow(elem_id=f"sampler_selection_{tabname}"): sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value="UniPC" if tabname == 'txt2img' else "Euler a", type="index") steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=10 if tabname == 'txt2img' else 20) - return steps, sampler_index def ordered_ui_categories(): user_order = {x.strip(): i * 2 + 1 for i, x in enumerate(shared.opts.ui_reorder.split(","))} - for i, category in sorted(enumerate(shared.ui_reorder_categories), key=lambda x: user_order.get(x[1], x[0] * 2 + 0)): yield category def get_value_for_setting(key): value = getattr(opts, key) - info = opts.data_labels[key] args = info.component_args() if callable(info.component_args) else info.component_args or {} args = {k: v for k, v in args.items() if k not in {'precision'}} - return gr.update(value=value, **args) def create_override_settings_dropdown(tabname, row): # pylint: disable=unused-argument dropdown = gr.Dropdown([], label="Override settings", visible=False, elem_id=f"{tabname}_override_settings", multiselect=True) - - dropdown.change( - fn=lambda x: gr.Dropdown.update(visible=len(x) > 0), - inputs=[dropdown], - outputs=[dropdown], - ) - + dropdown.change(fn=lambda x: gr.Dropdown.update(visible=len(x) > 0), inputs=[dropdown], outputs=[dropdown]) return dropdown def create_ui(): import modules.img2img # pylint: disable=redefined-outer-name import modules.txt2img # pylint: disable=redefined-outer-name - reload_javascript() - parameters_copypaste.reset() - 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, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button = create_toprow(is_img2img=False) - dummy_component = gr.Label(visible=False) txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False) - 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, extra_networks_button, 'txt2img') - with gr.Row().style(equal_height=False): with gr.Column(variant='compact', elem_id="txt2img_settings"): for category in ordered_ui_categories(): if category == "sampler": steps, sampler_index = create_sampler_and_steps_selection(samplers, "txt2img") - elif category == "dimensions": with FormRow(): with gr.Column(elem_id="txt2img_column_size", scale=4): with FormRow(elem_id="txt2img_row_dimension"): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height") - with gr.Column(elem_id="txt2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") - with gr.Column(elem_id="txt2img_column_batch"): with FormRow(elem_id="txt2img_row_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count") batch_size = gr.Slider(minimum=1, maximum=32, step=1, label='Batch size', value=1, elem_id="txt2img_batch_size") - elif category == "cfg": with FormRow(): cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="txt2img_cfg_scale") clip_skip = gr.Slider(label='CLIP Skip', value=1, minimum=1, maximum=4, step=1, elem_id='txt2img_clip_skip', interactive=True) clip_skip.change(fn=change_clip_skip, show_progress=False, inputs=clip_skip) - elif category == "seed": seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('txt2img') - elif category == "checkboxes": with FormRow(elem_classes="checkboxes-row", variant="compact"): restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="txt2img_restore_faces") tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling") enable_hr = gr.Checkbox(label='Hires fix', value=False, elem_id="txt2img_enable_hr") hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) - elif category == "hires_fix": with FormGroup(visible=False, elem_id="txt2img_hires_fix") as hr_options: with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]], value=shared.latent_upscale_default_mode) hr_second_pass_steps = gr.Slider(minimum=0, maximum=150, step=1, label='Hires steps', value=0, elem_id="txt2img_hires_steps") denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.7, elem_id="txt2img_denoising_strength") - with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"): hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") hr_resize_x = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") hr_resize_y = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") - elif category == "override_settings": with FormRow(elem_id="txt2img_override_settings_row") as row: override_settings = create_override_settings_dropdown('txt2img', row) - elif category == "scripts": with FormGroup(elem_id="txt2img_script_container"): custom_inputs = modules.scripts.scripts_txt2img.setup_ui() - hr_resolution_preview_inputs = [enable_hr, width, height, hr_scale, hr_resize_x, hr_resize_y] for preview_input in hr_resolution_preview_inputs: preview_input.change( @@ -494,7 +398,6 @@ def create_ui(): ) txt2img_gallery, generation_info, html_info, html_log = create_output_panel("txt2img", opts.outdir_txt2img_samples) - connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) @@ -1297,9 +1200,7 @@ def create_ui(): info = opts.data_labels[key] t = type(info.default) - args = info.component_args() if callable(info.component_args) else info.component_args - if info.component is not None: comp = info.component elif t == str: @@ -1310,9 +1211,7 @@ def create_ui(): comp = gr.Checkbox else: raise ValueError(f'bad options item type: {str(t)} for key {key}') - elem_id = "setting_"+key - if info.refresh is not None: if is_quicksettings: res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) @@ -1323,7 +1222,6 @@ def create_ui(): create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key) else: res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) - return res components = [] diff --git a/script.js b/script.js index 03afe8445..9b0eebe03 100644 --- a/script.js +++ b/script.js @@ -1,7 +1,6 @@ function gradioApp() { const elems = document.getElementsByTagName('gradio-app') const elem = elems.length == 0 ? document : elems[0] - if (elem !== document) elem.getElementById = function(id){ return document.getElementById(id) } return elem.shadowRoot ? elem.shadowRoot : elem } @@ -34,12 +33,10 @@ function onOptionsChanged(callback){ } function runCallback(x, m){ - try { - x(m) - } catch (e) { - (console.error || console.log).call(console, e.message, e); - } + try { x(m) + } catch (e) { (console.error || console.log).call(console, e.message, e); } } + function executeCallbacks(queue, m) { queue.forEach(function(x){runCallback(x, m)}) } @@ -52,7 +49,6 @@ document.addEventListener("DOMContentLoaded", function() { executedOnLoaded = true; executeCallbacks(uiLoadedCallbacks); } - executeCallbacks(uiUpdateCallbacks, m); const newTab = get_uiCurrentTab(); if ( newTab && ( newTab !== uiCurrentTab ) ) { @@ -75,9 +71,7 @@ document.addEventListener('keydown', function(e) { } if (handled) { button = get_uiCurrentTabContent().querySelector('button[id$=_generate]'); - if (button) { - button.click(); - } + if (button) button.click(); e.preventDefault(); } }) @@ -87,18 +81,11 @@ document.addEventListener('keydown', function(e) { */ function uiElementIsVisible(el) { let isVisible = !el.closest('.\\!hidden'); - if ( ! isVisible ) { - return false; - } - + if (!isVisible) return false; while( isVisible = el.closest('.tabitem')?.style.display !== 'none' ) { - if ( ! isVisible ) { - return false; - } else if ( el.parentElement ) { - el = el.parentElement - } else { - break; - } + if ( ! isVisible ) return false; + else if ( el.parentElement ) el = el.parentElement + else break; } return isVisible; } diff --git a/webui.bat b/webui.bat index 209d972bd..6a6edcf10 100755 --- a/webui.bat +++ b/webui.bat @@ -2,10 +2,7 @@ if not defined PYTHON (set PYTHON=python) if not defined VENV_DIR (set "VENV_DIR=%~dp0%venv") - - set ERROR_REPORTING=FALSE - mkdir tmp 2>NUL %PYTHON% -c "" >tmp/stdout.txt 2>tmp/stderr.txt @@ -38,14 +35,14 @@ goto :show_stdout_stderr :activate_venv set PYTHON="%VENV_DIR%\Scripts\Python.exe" -echo venv %PYTHON% +echo Using VENV: %VENV_DIR% :skip_venv if [%ACCELERATE%] == ["True"] goto :accelerate goto :launch :accelerate -echo Checking for accelerate +echo Checking for accelerate: %ACCELERATE% set ACCELERATE="%VENV_DIR%\Scripts\accelerate.exe" if EXIST %ACCELERATE% goto :accelerate_launch @@ -56,7 +53,7 @@ exit /b :accelerate_launch echo Accelerating -%ACCELERATE% launch --num_cpu_threads_per_process=6 launch.py +%ACCELERATE% launch --num_cpu_threads_per_process=6 launch.py %* pause exit /b diff --git a/webui.sh b/webui.sh index ebbe586fb..75725a712 100755 --- a/webui.sh +++ b/webui.sh @@ -4,34 +4,16 @@ # change the variables in webui-user.sh instead # ################################################# -# If run from macOS, load defaults from webui-macos-env.sh -if [[ "$OSTYPE" == "darwin"* ]]; then - if [[ -f webui-macos-env.sh ]] - then - source ./webui-macos-env.sh - fi -fi +can_run_as_root=0 +export ERROR_REPORTING=FALSE +export PIP_IGNORE_INSTALLED=0 # Read variables from webui-user.sh -# shellcheck source=/dev/null if [[ -f webui-user.sh ]] then source ./webui-user.sh fi -# Set defaults -# Install directory without trailing slash -if [[ -z "${install_dir}" ]] -then - install_dir="${HOME}" -fi - -# Name of the subdirectory (defaults to stable-diffusion-webui) -if [[ -z "${clone_dir}" ]] -then - clone_dir="stable-diffusion-webui" -fi - # python3 executable if [[ -z "${python_cmd}" ]] then @@ -44,19 +26,11 @@ then export GIT="git" fi -# python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv) if [[ -z "${venv_dir}" ]] then venv_dir="venv" fi -if [[ -z "${LAUNCH_SCRIPT}" ]] -then - LAUNCH_SCRIPT="launch.py" -fi - -# this script cannot be run as root by default -can_run_as_root=0 # read any command line flags to the webui.sh script while getopts "f" flag > /dev/null 2>&1 @@ -67,102 +41,48 @@ do esac done -# Disable sentry logging -export ERROR_REPORTING=FALSE - -# Do not reinstall existing pip packages on Debian/Ubuntu -export PIP_IGNORE_INSTALLED=0 - -# Pretty print -delimiter="################################################################" - -printf "\n%s\n" "${delimiter}" -printf "\e[1m\e[32mInstall script for stable-diffusion + Web UI\n" -printf "\e[1m\e[34mTested on Debian 11 (Bullseye)\e[0m" -printf "\n%s\n" "${delimiter}" - # Do not run as root if [[ $(id -u) -eq 0 && can_run_as_root -eq 0 ]] then - printf "\n%s\n" "${delimiter}" - printf "\e[1m\e[31mERROR: This script must not be launched as root, aborting...\e[0m" - printf "\n%s\n" "${delimiter}" + echo "Cannot run as root" exit 1 -else - printf "\n%s\n" "${delimiter}" - printf "Running on \e[1m\e[32m%s\e[0m user" "$(whoami)" - printf "\n%s\n" "${delimiter}" -fi - -if [[ -d .git ]] -then - printf "\n%s\n" "${delimiter}" - printf "Repo already cloned, using it as install directory" - printf "\n%s\n" "${delimiter}" - install_dir="${PWD}/../" - clone_dir="${PWD##*/}" fi for preq in "${GIT}" "${python_cmd}" do if ! hash "${preq}" &>/dev/null then - printf "\n%s\n" "${delimiter}" - printf "\e[1m\e[31mERROR: %s is not installed, aborting...\e[0m" "${preq}" - printf "\n%s\n" "${delimiter}" + printf "Error: %s is not installed, aborting...\n" "${preq}" exit 1 fi done if ! "${python_cmd}" -c "import venv" &>/dev/null then - printf "\n%s\n" "${delimiter}" - printf "\e[1m\e[31mERROR: python3-venv is not installed, aborting...\e[0m" - printf "\n%s\n" "${delimiter}" + echo "Error: python3-venv is not installed" exit 1 fi -cd "${install_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/, aborting...\e[0m" "${install_dir}"; exit 1; } -if [[ -d "${clone_dir}" ]] -then - cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } -else - printf "\n%s\n" "${delimiter}" - printf "Clone stable-diffusion-webui" - printf "\n%s\n" "${delimiter}" - "${GIT}" clone https://github.com/vladmandic/automatic.git "${clone_dir}" - cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } -fi - -printf "\n%s\n" "${delimiter}" -printf "Create and activate python venv" -printf "\n%s\n" "${delimiter}" -cd "${install_dir}"/"${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } +echo "Create and activate python venv" if [[ ! -d "${venv_dir}" ]] then "${python_cmd}" -m venv "${venv_dir}" first_launch=1 fi -# shellcheck source=/dev/null + if [[ -f "${venv_dir}"/bin/activate ]] then source "${venv_dir}"/bin/activate else - printf "\n%s\n" "${delimiter}" - printf "\e[1m\e[31mERROR: Cannot activate python venv, aborting...\e[0m" - printf "\n%s\n" "${delimiter}" + echo "Error: Cannot activate python venv" exit 1 fi if [[ ! -z "${ACCELERATE}" ]] && [ ${ACCELERATE}="True" ] && [ -x "$(command -v accelerate)" ] then - printf "\n%s\n" "${delimiter}" - printf "Accelerating launch.py..." - printf "\n%s\n" "${delimiter}" - exec accelerate launch --num_cpu_threads_per_process=6 "${LAUNCH_SCRIPT}" "$@" + echo "Accelerating launch.py..." + exec accelerate launch --num_cpu_threads_per_process=6 launch.py "$@" else - printf "\n%s\n" "${delimiter}" - printf "Launching launch.py..." - printf "\n%s\n" "${delimiter}" - exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@" + echo "Launching launch.py..." + exec "${python_cmd}" launch.py "$@" fi