From f6dd495eb385ce221ee699b1945c0e44fb20d679 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 9 Nov 2023 09:04:50 -0500 Subject: [PATCH] multiple fixes --- CHANGELOG.md | 6 +- html/locale_en.json | 2 +- modules/generation_parameters_copypaste.py | 30 +++--- modules/images.py | 13 ++- modules/img2img.py | 5 + modules/processing.py | 6 +- modules/shared_items.py | 12 ++- modules/styles.py | 29 +++++- modules/ui.py | 111 +++++++++++---------- modules/ui_common.py | 2 +- modules/ui_extra_networks.py | 2 +- modules/ui_extra_networks_checkpoints.py | 4 +- wiki | 2 +- 13 files changed, 129 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e88ec824..82d6297dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,15 @@ - **Extra networks** - Use multi-threading for 5x load speedup - **General**: - - Reworked parser when pasting previously generated images/prompts + - Reworked parser when pasting previously generated images/prompts + includes all `txt2img`, `img2img` and `override` params - **Diffusers** - Fix DPM SDE scheduler + - Add additional pipeline types for manual model loads when loading from `safetensors` - **Fixes** - Fix inpaint + - Fix manual grid image save + - Fix img2img init image save - More uniform models paths - Improve extension compatibility - Improve BF16 support diff --git a/html/locale_en.json b/html/locale_en.json index f0c84af87..65f239e10 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -61,7 +61,7 @@ {"id":"","label":"Skip","localized":"","hint":"Stop processing current job and continue processing"}, {"id":"","label":"Interrupt","localized":"","hint":"Interrupt current processing job"}, {"id":"","label":"Pause","localized":"","hint":"Pause processing"}, - {"id":"","label":"Restore","localized":"","hint":"Restore parameters from last known generated image"}, + {"id":"","label":"Restore","localized":"","hint":"Restore parameters from current prompt or last known generated image"}, {"id":"","label":"Clear","localized":"","hint":"Clear prompts"}, {"id":"","label":"Networks","localized":"","hint":"Open extra network interface"}, {"id":"","label":"Interrogate\nCLIP","localized":"","hint":"Run interrogate using CLIP model"}, diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 1ec23abc6..e742e32fc 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -210,14 +210,15 @@ def parse_generation_parameters(x: str): if len(remaining) == 0: return res remaining = x[7:] if x.startswith('Prompt: ') else x - prompt, remaining = remaining.split('Negative prompt: ', maxsplit=1) if 'Negative prompt: ' in remaining else (remaining, '') + remaining = x[11:] if x.startswith('parameters: ') else x + prompt, remaining = remaining.strip().split('Negative prompt: ', maxsplit=1) if 'Negative prompt: ' in remaining else (remaining, '') res["Prompt"] = prompt.strip() - negative, remaining = remaining.split('Steps: ', maxsplit=1) if 'Steps: ' in remaining else (remaining, None) + negative, remaining = remaining.strip().split('Steps: ', maxsplit=1) if 'Steps: ' in remaining else (remaining, None) res["Negative prompt"] = negative.strip() if remaining is None: return res remaining = f'Steps: {remaining}' - for k, v in re_param.findall(remaining): + for k, v in re_param.findall(remaining.strip()): try: if v[0] == '"' and v[-1] == '"': v = unquote(v) @@ -230,8 +231,7 @@ def parse_generation_parameters(x: str): except Exception: pass res["Full quality"] = res.get('VAE', None) != 'TAESD' - for k, v in res.items(): - debug(f"Parse prompt: '{k}'={v}") + debug(f"Parse prompt: {res}") return res @@ -239,7 +239,7 @@ settings_map = {} infotext_to_setting_name_mapping = [ - ('Backed', 'sd_backend'), + ('Backend', 'sd_backend'), ('Model hash', 'sd_model_checkpoint'), ('Refiner', 'sd_model_refiner'), ('VAE', 'sd_vae'), @@ -282,13 +282,6 @@ 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: @@ -310,25 +303,25 @@ def connect_paste(button, local_paste_fields, input_comp, override_settings_comp if os.path.exists(filename): with open(filename, "r", encoding="utf8") as file: prompt = file.read() - shared.log.debug(f'Paste prompt last: {prompt}') + shared.log.debug(f'Paste prompt: type="params" prompt="{prompt}"') else: prompt = '' else: - shared.log.debug(f'Paste prompt current: {prompt}') + shared.log.debug(f'Paste prompt: type="current" prompt="{prompt}"') params = parse_generation_parameters(prompt) script_callbacks.infotext_pasted_callback(prompt, params) res = [] + applied = {} for output, key in local_paste_fields: if callable(key): v = key(params) else: v = params.get(key, None) - if v is not None: - debug(f"Parse apply: '{key}'={v}") if v is None: res.append(gr.update()) elif isinstance(v, type_of_gr_update): res.append(v) + applied[key] = v else: try: valtype = type(output.value) @@ -337,8 +330,10 @@ def connect_paste(button, local_paste_fields, input_comp, override_settings_comp else: val = valtype(v) res.append(gr.update(value=val)) + applied[key] = val except Exception: res.append(gr.update()) + debug(f"Parse apply: {applied}") return res if override_settings_component is not None: @@ -359,6 +354,7 @@ def connect_paste(button, local_paste_fields, input_comp, override_settings_comp continue vals[param_name] = v vals_pairs = [f"{k}: {v}" for k, v in vals.items()] + shared.log.debug(f'Settings overrides: {vals_pairs}') return gr.Dropdown.update(value=vals_pairs, choices=vals_pairs, visible=len(vals_pairs) > 0) local_paste_fields = local_paste_fields + [(override_settings_component, paste_settings)] diff --git a/modules/images.py b/modules/images.py index 072604409..cbb60b5ff 100644 --- a/modules/images.py +++ b/modules/images.py @@ -547,7 +547,7 @@ save_thread = threading.Thread(target=atomically_save_image, daemon=True) save_thread.start() -def save_image(image, path, basename = '', seed=None, prompt=None, extension=shared.opts.samples_format, info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix="", save_to_dirs=None): # pylint: disable=unused-argument +def save_image(image, path, basename='', seed=None, prompt=None, extension=shared.opts.samples_format, info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix='', save_to_dirs=None): # pylint: disable=unused-argument if image is None: shared.log.warning('Image is none') return None, None @@ -556,27 +556,30 @@ def save_image(image, path, basename = '', seed=None, prompt=None, extension=sha if path is None or len(path) == 0: # set default path to avoid errors when functions are triggered manually or via api and param is not set path = shared.opts.outdir_save namegen = FilenameGenerator(p, seed, prompt, image, grid=grid) + suffix = suffix if suffix is not None else '' + basename = basename if basename is not None else '' if shared.opts.save_to_dirs: dirname = namegen.apply(shared.opts.directories_filename_pattern or "[prompt_words]") path = os.path.join(path, dirname) - file_decoration = '' if forced_filename is None: if shared.opts.samples_filename_pattern and len(shared.opts.samples_filename_pattern) > 0: file_decoration = shared.opts.samples_filename_pattern else: file_decoration = "[seq]-[prompt_words]" file_decoration = namegen.apply(file_decoration) - file_decoration += suffix + file_decoration += suffix if suffix is not None else '' filename = os.path.join(path, f"{file_decoration}.{extension}") if basename == '' else os.path.join(path, f"{basename}-{file_decoration}.{extension}") else: - filename = forced_filename + forced_filename += suffix if suffix is not None else '' + filename = os.path.join(path, f"{forced_filename}.{extension}") if basename == '' else os.path.join(path, f"{basename}-{forced_filename}.{extension}") pnginfo = existing_info or {} if info is not None: pnginfo[pnginfo_section_name] = info params = script_callbacks.ImageSaveParams(image, p, filename, pnginfo) params.filename = namegen.sanitize(filename) dirname = os.path.dirname(params.filename) - os.makedirs(dirname, exist_ok=True) + if dirname is not None and len(dirname) > 0: + os.makedirs(dirname, exist_ok=True) # sequence if shared.opts.save_images_add_number or '[seq]' in params.filename: if '[seq]' not in params.filename: diff --git a/modules/img2img.py b/modules/img2img.py index b05254434..0152cf9d5 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -211,6 +211,11 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s p.extra_generation_params['Resize mode'] = resize_mode if mask: p.extra_generation_params["Mask blur"] = mask_blur + p.extra_generation_params["Mask alpha"] = mask_alpha + p.extra_generation_params["Mask invert"] = inpainting_mask_invert + p.extra_generation_params["Mask content"] = inpainting_fill + p.extra_generation_params["Mask area"] = inpaint_full_res + p.extra_generation_params["Mask padding"] = inpaint_full_res_padding p.is_batch = mode == 5 if p.is_batch: process_batch(p, img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args) diff --git a/modules/processing.py b/modules/processing.py index d51905d01..5e7832c5f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -166,11 +166,6 @@ class StableDiffusionProcessing: self.disable_extra_networks = False self.token_merging_ratio = 0 self.token_merging_ratio_hr = 0 - if not seed_enable_extras: - self.subseed = -1 - self.subseed_strength = 0 - self.seed_resize_from_h = 0 - self.seed_resize_from_w = 0 self.scripts = None self.script_args = script_args or [] self.per_script_args = {} @@ -603,6 +598,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No args["Init image hash"] = getattr(p, 'init_img_hash', None) args["Mask weight"] = getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None args['Resize mode'] = getattr(p, 'resize_mode', None) + args['Resize scale'] = getattr(p, 'scale_by', None) args["Mask blur"] = p.mask_blur if getattr(p, 'mask', None) is not None and getattr(p, 'mask_blur', 0) > 0 else None args["Denoising strength"] = getattr(p, 'denoising_strength', None) if 'face' in p.ops: diff --git a/modules/shared_items.py b/modules/shared_items.py index 85fba87a3..49387e56e 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -27,7 +27,7 @@ def list_crossattention(): def get_pipelines(): import diffusers from installer import log - pipelines = { + pipelines = { # note: not all pipelines can be used manually as they require prior pipeline next to decoder pipeline 'Autodetect': None, 'Stable Diffusion': getattr(diffusers, 'StableDiffusionPipeline', None), 'Stable Diffusion Img2Img': getattr(diffusers, 'StableDiffusionImg2ImgPipeline', None), @@ -37,9 +37,15 @@ def get_pipelines(): 'Stable Diffusion XL Img2Img': getattr(diffusers, 'StableDiffusionXLImg2ImgPipeline', None), 'Stable Diffusion XL Inpaint': getattr(diffusers, 'StableDiffusionXLInpaintPipeline', None), 'Stable Diffusion XL Instruct': getattr(diffusers, 'StableDiffusionXLInstructPix2PixPipeline', None), + 'Latent Consistency Model': getattr(diffusers, 'LatentConsistencyModelPipeline', None), + 'PixArt Alpha': getattr(diffusers, 'PixArtAlphaPipeline', None), + 'UniDiffuser': getattr(diffusers, 'UniDiffuserPipeline', None), + 'Wuerstchen': getattr(diffusers, 'WuerstchenCombinedPipeline', None), + 'Kandinsky 2.1': getattr(diffusers, 'KandinskyPipeline', None), + 'Kandinsky 2.2': getattr(diffusers, 'KandinskyV22Pipeline', None), + 'DeepFloyd IF': getattr(diffusers, 'IFPipeline', None), 'Custom Diffusers Pipeline': getattr(diffusers, 'DiffusionPipeline', None), - # 'Test': getattr(diffusers, 'TestPipeline', None), - # 'Kandinsky V1', 'Kandinsky V2', 'DeepFloyd IF', 'Shap-E', 'Kandinsky V1 Img2Img', 'Kandinsky V2 Img2Img', 'DeepFloyd IF Img2Img', 'Shap-E Img2Img', + # Segmind SSD-1B, Segmind Tiny } for k, v in pipelines.items(): if k != 'Autodetect' and v is None: diff --git a/modules/styles.py b/modules/styles.py index 9b63a6d01..427e76ea9 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -136,18 +136,33 @@ class StyleDatabase: return found[0] if len(found) > 0 else self.no_style def get_style_prompts(self, styles): + if styles is None or not isinstance(styles, list): + log.error(f'Invalid styles: {styles}') + return [] return [self.find_style(x).prompt for x in styles] def get_negative_style_prompts(self, styles): + if styles is None or not isinstance(styles, list): + log.error(f'Invalid styles: {styles}') + return [] return [self.find_style(x).negative_prompt for x in styles] def apply_styles_to_prompt(self, prompt, styles): + if styles is None or not isinstance(styles, list): + log.error(f'Invalid styles: {styles}') + return prompt return apply_styles_to_prompt(prompt, [self.find_style(x).prompt for x in styles]) def apply_negative_styles_to_prompt(self, prompt, styles): + if styles is None or not isinstance(styles, list): + log.error(f'Invalid styles: {styles}') + return prompt return apply_styles_to_prompt(prompt, [self.find_style(x).negative_prompt for x in styles]) def apply_styles_to_extra(self, p): + if p.styles is None or not isinstance(p.styles, list): + log.error(f'Invalid styles: {p.styles}') + return for style in p.styles: s = self.find_style(style) apply_styles_to_extra(p, s) @@ -173,19 +188,25 @@ class StyleDatabase: log.error(f'Failed to save style: name={name} file={path} error={e}') count = len(list(self.styles)) if count > 0: - log.debug(f'Saved styles: {path} {count}') + log.debug(f'Saved styles: folder="{path}" items={count}') def load_csv(self, legacy_file): if not os.path.isfile(legacy_file): return with open(legacy_file, "r", encoding="utf-8-sig", newline='') as file: reader = csv.DictReader(file, skipinitialspace=True) + num = 0 for row in reader: try: - self.styles[row["name"]] = Style(row["name"], row["prompt"] if "prompt" in row else row["text"], row.get("negative_prompt", "")) + name = row["name"] + prompt = row["prompt"] if "prompt" in row else row["text"] + negative = row.get("negative_prompt", "") if "negative_prompt" in row else row.get("negative", "") + self.styles[name] = Style(name, desc=name, prompt=prompt, negative_prompt=negative, extra="") + log.debug(f'Migrated style: {self.styles[name].__dict__}') + num += 1 except Exception: - log.error(f'Styles error: file={legacy_file} row={row}') - log.debug(f'Load legacy styles: file={legacy_file} items={len(self.styles.keys())}') + log.error(f'Styles error: file="{legacy_file}" row={row}') + log.info(f'Load legacy styles: file="{legacy_file}" loaded={num} created={len(list(self.styles))}') """ def save_csv(self, path: str) -> None: diff --git a/modules/ui.py b/modules/ui.py index ec75ee3a3..ee490a87d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -511,44 +511,48 @@ def create_ui(startup_timer = None): txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) txt2img_paste_fields = [ + # prompt (txt2img_prompt, "Prompt"), (txt2img_negative_prompt, "Negative prompt"), - (txt2img_prompt_styles, "Styles"), - (steps, "Steps"), - (seed, "Seed"), - (sampler_index, "Sampler"), - (cfg_scale, "CFG scale"), + # main (width, "Size-1"), (height, "Size-2"), - (subseed, "Variation seed"), - (subseed_strength, "Variation strength"), - (clip_skip, "Clip skip"), - (latent_index, "Latent sampler"), - (latent_index, "Secondary sampler"), - (denoising_strength, "Denoising strength"), - (refiner_steps, "Refiner steps"), - (refiner_start, "Refiner start"), - (full_quality, "Full quality"), - (restore_faces, "Face restoration"), + # sampler + (sampler_index, "Sampler"), + (steps, "Steps"), + # batch (batch_count, "Batch-1"), (batch_size, "Batch-2"), - (seed_resize_from_w, "Seed resize from-1"), - (seed_resize_from_h, "Seed resize from-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_force, "Hires force"), - (hr_scale, "Hires upscale"), + (latent_index, "Latent 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"), - (diffusers_guidance_rescale, "CFG rescale"), - (image_cfg_scale, "Image CFG scale"), - (refiner_steps, "Refiner steps"), + # refiner (refiner_start, "Refiner start"), - (tiling, "Tiling"), - (refiner_negative, "Negative2"), + (refiner_steps, "Refiner steps"), (refiner_prompt, "Prompt2"), - # TODO restore params complete list + (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 ] parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) @@ -839,46 +843,45 @@ def create_ui(startup_timer = None): ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) img2img_paste_fields = [ + # prompt (img2img_prompt, "Prompt"), (img2img_negative_prompt, "Negative prompt"), - # (img2img_prompt_styles, "Styles"), - (steps, "Steps"), - (seed, "Seed"), + # sampler (sampler_index, "Sampler"), - (cfg_scale, "CFG scale"), + (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"), - (full_quality, "Full quality"), - (clip_skip, "Clip skip"), - (latent_index, "Latent sampler"), - (latent_index, "Secondary sampler"), + # 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"), - (batch_size, "Batch size"), - (batch_count, "Batch count"), + (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"), - (resize_mode, "Resize mode"), - (image_cfg_scale, "Image CFG scale"), - (diffusers_guidance_rescale, "CFG rescale"), - (tiling, "Tiling"), - (mask_blur, "Mask blur"), - # TODO scale_by add to paste fields - (scale_by, "UNKNOWN"), - # from txt2img - (hr_force, "Hires force"), - (hr_scale, "Hires upscale"), - (hr_upscaler, "Hires upscaler"), - (hr_second_pass_steps, "Hires steps"), - (hr_second_pass_steps, "Hires steps"), - (hr_resize_x, "Hires resize-1"), - (hr_resize_y, "Hires resize-2"), - (refiner_steps, "Refiner steps"), - (refiner_start, "Refiner start"), - (refiner_prompt, "Prompt2"), - (refiner_negative, "Negative2"), *modules.scripts.scripts_img2img.infotext_fields ] parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) diff --git a/modules/ui_common.py b/modules/ui_common.py index f27589a8b..9206c6c59 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -93,7 +93,7 @@ def save_files(js_data, images, html_info, index): self.index_of_first_image = getattr(self, 'index_of_first_image', 0) self.infotexts = getattr(self, 'infotexts', [html_info]) self.infotext = self.infotexts[0] if len(self.infotexts) > 0 else html_info - self.outpath_grids = None + self.outpath_grids = shared.opts.outdir_grids or shared.opts.outdir_txt2img_grids try: data = json.loads(js_data) except Exception: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 0584b4f71..ba84f9040 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -321,7 +321,7 @@ class ExtraNetworksPage: return 'html/card-no-preview.png' if shared.opts.diffusers_dir in path: path = os.path.relpath(path, shared.opts.diffusers_dir) - ref = os.path.join(paths.models_path, 'Reference') + ref = os.path.join('models', 'Reference') fn = os.path.join(ref, path.replace('models--', '').replace('\\', '/').split('/')[0]) files = listdir(ref) else: diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 5dae382b7..98aac49af 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -2,10 +2,10 @@ import os import html import json import concurrent -from modules import shared, ui_extra_networks, sd_models, paths +from modules import shared, ui_extra_networks, sd_models -reference_dir = os.path.join(paths.models_path, 'Reference') +reference_dir = os.path.join('models', 'Reference') class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): def __init__(self): diff --git a/wiki b/wiki index e999774e3..c0b5cb267 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit e999774e3096ceb89a264548fdfaaa76d891c0df +Subproject commit c0b5cb2672f7b0ac0add0a321f22bc0e6b738d78