From c608674fb0fd5d2564f6d8026c9d24ce2492c9d8 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 11 Feb 2025 11:33:37 -0500 Subject: [PATCH] styles support parsed and upparsed save and apply Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 12 +++-- extensions-builtin/sdnext-modernui | 2 +- modules/control/processors.py | 2 +- modules/control/unit.py | 2 +- modules/control/units/t2iadapter.py | 2 +- modules/dml/hijack/realesrgan_model.py | 2 +- modules/lama.py | 2 +- modules/merging/merge.py | 2 +- modules/merging/merge_rebasin.py | 2 +- modules/processing_class.py | 12 +++-- modules/processing_info.py | 13 +++++ modules/sd_hijack_hypertile.py | 2 +- modules/sd_vae_stablecascade.py | 2 +- modules/shared.py | 5 +- modules/styles.py | 15 ++++-- modules/ui_extra_networks.py | 73 +++++++++++++++++--------- modules/ui_sections.py | 4 +- wiki | 2 +- 18 files changed, 103 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b04910250..f8ee5be0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2025-02-10 +## Update for 2025-02-11 - **User Interface** - **Hints** @@ -25,12 +25,16 @@ - enable upscaler compile by default - fix shape mismatch errors on too many resolution changes - **Other** - - asymmetric tiling + - **Asymmetric tiling** allows for configurable image tiling for x/y axis separately enable in *scripts -> asymmetric tiling* *note*: traditional symmetric tiling is achieved by setting circular mode for both x and y - - persist *models -> hugginface -> token* - - persist *models -> civitai -> token* + - **Styles** + ability to save and/or restore prompts before or after parsing of wildcards + set in *settings -> networks -> styles* + - **Access tokens** + persist *models -> hugginface -> token* + persist *models -> civitai -> token* - global switch to lancosz method for all interal resize ops and bicubic for interpolation ops - **Fixes** - update torch nightly urls diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index 8aa153315..6d00d3fed 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit 8aa153315a61e96598ace47ede2cf7bfd95ff065 +Subproject commit 6d00d3fedf685f33b8b13f6477dd4af15bcc959e diff --git a/modules/control/processors.py b/modules/control/processors.py index 38b5f2062..1eae679e9 100644 --- a/modules/control/processors.py +++ b/modules/control/processors.py @@ -2,7 +2,7 @@ import os import time import numpy as np from PIL import Image -from modules.shared import log +from installer import log from modules.errors import display from modules import devices, images diff --git a/modules/control/unit.py b/modules/control/unit.py index eeb729740..bc0a9e5c8 100644 --- a/modules/control/unit.py +++ b/modules/control/unit.py @@ -1,7 +1,7 @@ from typing import Union from PIL import Image import gradio as gr -from modules.shared import log +from installer import log from modules.control import processors from modules.control.units import controlnet from modules.control.units import xs diff --git a/modules/control/units/t2iadapter.py b/modules/control/units/t2iadapter.py index 66abdc75e..2c39a54b5 100644 --- a/modules/control/units/t2iadapter.py +++ b/modules/control/units/t2iadapter.py @@ -3,7 +3,7 @@ import time from typing import Union import threading from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, MultiAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline # pylint: disable=unused-import -from modules.shared import log +from installer import log from modules import errors, sd_models from modules.control.units import detect diff --git a/modules/dml/hijack/realesrgan_model.py b/modules/dml/hijack/realesrgan_model.py index a8011beb5..b951765d0 100644 --- a/modules/dml/hijack/realesrgan_model.py +++ b/modules/dml/hijack/realesrgan_model.py @@ -1,7 +1,7 @@ import math import torch from modules.postprocess.realesrgan_model_arch import RealESRGANer -from modules.shared import log +from installer import log # DML Solution: Some of contents of output tensor turn to 0 after Extended Slices. Move it to cpu. diff --git a/modules/lama.py b/modules/lama.py index fcbf53581..24d1f8fa3 100644 --- a/modules/lama.py +++ b/modules/lama.py @@ -6,7 +6,7 @@ import numpy as np from torch.hub import download_url_to_file, get_dir from PIL import Image from modules import devices -from modules.shared import log +from installer import log LAMA_MODEL_URL = "https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt" diff --git a/modules/merging/merge.py b/modules/merging/merge.py index d6430033e..4f2e1a00b 100644 --- a/modules/merging/merge.py +++ b/modules/merging/merge.py @@ -7,7 +7,7 @@ import torch from tensordict import TensorDict import modules.memstats import modules.devices as devices -from modules.shared import log, console +from installer import log, console from modules.sd_models import read_state_dict from modules.merging import merge_methods from modules.merging.merge_utils import WeightClass diff --git a/modules/merging/merge_rebasin.py b/modules/merging/merge_rebasin.py index d5b6e4fdc..1e6780c2a 100644 --- a/modules/merging/merge_rebasin.py +++ b/modules/merging/merge_rebasin.py @@ -4,7 +4,7 @@ from random import shuffle from typing import NamedTuple import torch from scipy.optimize import linear_sum_assignment -from modules.shared import log +from installer import log SPECIAL_KEYS = [ diff --git a/modules/processing_class.py b/modules/processing_class.py index a01108c18..b3fa3e313 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -54,11 +54,11 @@ class StableDiffusionProcessing: do_not_reload_embeddings: bool = False, restore_faces: bool = False, # detailer - detailer_enabled: bool = False, - detailer_prompt: str = '', - detailer_negative: str = '', - detailer_steps: int = 10, - detailer_strength: float = 0.3, + detailer_enabled: bool = False, + detailer_prompt: str = '', + detailer_negative: str = '', + detailer_steps: int = 10, + detailer_strength: float = 0.3, # hdr corrections hdr_mode: int = 0, hdr_brightness: float = 0, @@ -241,6 +241,8 @@ class StableDiffusionProcessing: self.inpaint_full_res = inpaint_full_res != 0 if isinstance(inpaint_full_res, int) else self.inpaint_full_res # null items initialized later + self.prompts = None + self.negative_prompts = None self.all_prompts = None self.all_negative_prompts = None self.all_seeds = None diff --git a/modules/processing_info.py b/modules/processing_info.py index b5037fbf4..7d07a04dd 100644 --- a/modules/processing_info.py +++ b/modules/processing_info.py @@ -4,6 +4,8 @@ from modules import shared, sd_samplers_common, sd_vae, generation_parameters_co from modules.processing_class import StableDiffusionProcessing +args = {} # maintain history +infotext = '' # maintain history debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None if not shared.native: from modules import sd_hijack @@ -11,7 +13,12 @@ else: sd_hijack = None +def get_last_args(): + return args, infotext + + def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=None, all_subseeds=None, comments=None, iteration=0, position_in_batch=0, index=None, all_negative_prompts=None, grid=None): + global args, infotext # pylint: disable=global-statement if p is None: shared.log.warning('Processing info: no data') return '' @@ -179,6 +186,12 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No del args[k] debug(f'Infotext: args={args}') params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in args.items()]) + + if hasattr(p, 'original_prompt'): + args['Original prompt'] = p.original_prompt + if hasattr(p, 'original_negative'): + args['Original negative'] = p.original_negative + negative_prompt_text = f"\nNegative prompt: {all_negative_prompts[index] if all_negative_prompts[index] else ''}" infotext = f"{all_prompts[index]}{negative_prompt_text}\n{params_text}".strip() debug(f'Infotext: "{infotext}"') diff --git a/modules/sd_hijack_hypertile.py b/modules/sd_hijack_hypertile.py index 62f06cd62..c27bf8f39 100644 --- a/modules/sd_hijack_hypertile.py +++ b/modules/sd_hijack_hypertile.py @@ -10,7 +10,7 @@ import math import torch import torch.nn as nn from einops import rearrange -from modules.shared import log +from installer import log # global variables to keep track of changing image size in multiple passes diff --git a/modules/sd_vae_stablecascade.py b/modules/sd_vae_stablecascade.py index df9260e2d..198606e7d 100644 --- a/modules/sd_vae_stablecascade.py +++ b/modules/sd_vae_stablecascade.py @@ -55,7 +55,7 @@ def download_model(model_path): model_url = 'https://huggingface.co/stabilityai/stable-cascade/resolve/main/previewer.safetensors?download=true' if not os.path.exists(model_path): import torch - from modules.shared import log + from installer import log os.makedirs(os.path.dirname(model_path), exist_ok=True) log.info(f'Downloading Stable Cascade previewer: {model_path}') torch.hub.download_url_to_file(model_url, model_path) diff --git a/modules/shared.py b/modules/shared.py index 3b08cfc07..d3bb30dc7 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -106,7 +106,8 @@ def readfile(filename, silent=False, lock=False): fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access log.debug(f'Read: file="{filename}" json={len(data)} bytes={os.path.getsize(filename)} time={t1-t0:.3f} fn={fn}') except FileNotFoundError as err: - log.debug(f'Reading failed: {filename} {err}') + if not silent: + log.debug(f'Reading failed: {filename} {err}') except Exception as err: if not silent: log.error(f'Reading failed: {filename} {err}') @@ -980,6 +981,8 @@ options_templates.update(options_section(('extra_networks', "Networks"), { "extra_networks_styles_sep": OptionInfo("

Styles

", "", gr.HTML), "extra_networks_styles": OptionInfo(True, "Show reference styles"), + "extra_networks_save_unparsed": OptionInfo(True, "Save unparsed prompt"), + "extra_networks_apply_unparsed": OptionInfo(True, "Restore unparsed prompt"), "extra_networks_embed_sep": OptionInfo("

Embeddings

", "", gr.HTML), "diffusers_enable_embed": OptionInfo(True, "Enable embeddings support", gr.Checkbox, {"visible": native}), diff --git a/modules/styles.py b/modules/styles.py index 0c05d21a7..8fc1b543d 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -148,6 +148,11 @@ def apply_styles_to_extra(p, style: Style): reference_style = get_reference_style() extra = infotext.parse(reference_style) if shared.opts.extra_network_reference_values else {} + if not hasattr(p, 'original_prompt'): + p.original_prompt = p.prompt + if not hasattr(p, 'original_negative'): + p.original_negative = p.negative_prompt + style_extra = apply_wildcards_to_prompt(style.extra, [style.wildcards], silent=True) extra.update(infotext.parse(style_extra)) extra.pop('Prompt', None) @@ -304,24 +309,26 @@ class StyleDatabase: parsed_negative.append(prompt) return parsed_positive, parsed_negative - def apply_styles_to_prompt(self, prompt, styles): + def apply_styles_to_prompt(self, prompt, styles, wildcards:bool=True): if styles is None: return prompt if not isinstance(styles, list): shared.log.error(f'Styles invalid: {styles}') return prompt prompt = apply_styles_to_prompt(prompt, [self.find_style(x).prompt for x in styles]) - prompt = apply_wildcards_to_prompt(prompt, [self.find_style(x).wildcards for x in styles]) + if wildcards: + prompt = apply_wildcards_to_prompt(prompt, [self.find_style(x).wildcards for x in styles]) return prompt - def apply_negative_styles_to_prompt(self, prompt, styles): + def apply_negative_styles_to_prompt(self, prompt, styles, wildcards:bool=True): if styles is None: return prompt if not isinstance(styles, list): shared.log.error(f'Styles invalid: {styles}') return prompt prompt = apply_styles_to_prompt(prompt, [self.find_style(x).negative_prompt for x in styles]) - prompt = apply_wildcards_to_prompt(prompt, [self.find_style(x).wildcards for x in styles]) + if wildcards: + prompt = apply_wildcards_to_prompt(prompt, [self.find_style(x).wildcards for x in styles]) return prompt def apply_styles_to_extra(self, p): diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 7f4e37d08..f6784a381 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -785,6 +785,16 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): if 'modelVersions' in fullinfo: # sanitize massive objects fullinfo['modelVersions'] = [] info = fullinfo + """ + if prompt is not None: + item.prompt = prompt + if negative is not None: + item.negative = negative + if description is not None: + item.description = description + if wildcards is not None: + item.wildcards = wildcards + """ meta = page.metadata.get(item.name, {}) or {} if type(meta) is str: try: @@ -844,7 +854,6 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): Description{item.description} Preview Embedded{item.preview.startswith('data:')} ''' - # desc = f'Name: {os.path.basename(item.name)}\nDescription: {item.description}\nPrompt: {item.prompt}\nNegative: {item.negative}\nExtra: {item.extra}\n' if item.name.startswith('Diffusers'): url = item.name.replace('Diffusers/', '') url = f'https://huggingface.co/models/{url}' if url is not None else 'N/A' @@ -868,6 +877,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): {note} ''' + is_style = (page is not None) and (page.title == 'Style') return [ text, # gr.html img, # gr.image @@ -875,13 +885,13 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): info, # gr.json meta, # gr.json description, # gr.textbox - prompt, # gr.textbox - negative, # gr.textbox - parameters, # gr.textbox - wildcards, # gr.textbox + gr.update(value=prompt, visible=is_style), # gr.textbox + gr.update(value=negative, visible=is_style), # gr.textbox + gr.update(value=parameters, visible=is_style), # gr.textbox + gr.update(value=wildcards, visible=is_style), # gr.textbox gr.update(visible=valid), # details ui visible - gr.update(visible=page is not None and page.title != 'Style'), # details ui tabs visible - gr.update(visible=page is not None and page.title == 'Style'), # details ui text visible + gr.update(visible=not is_style), # details ui tabs visible + gr.update(visible=is_style), # details ui text visible ] def ui_refresh_click(title): @@ -920,39 +930,50 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): return ui_refresh_click(title) def ui_save_click(): - filename = os.path.join(paths.data_path, "params.txt") - if os.path.exists(filename): - with open(filename, "r", encoding="utf8") as file: - prompt = file.read() + if shared.opts.extra_networks_save_unparsed: + from modules.processing_info import get_last_args + params, text = get_last_args() else: - prompt = '' - params = infotext.parse(prompt) - res = show_details(text=None, img=None, desc=None, info=None, meta=None, parameters=None, description=None, prompt=None, negative=None, wildcards=None, params=params) + filename = os.path.join(paths.data_path, "params.txt") + if os.path.exists(filename): + with open(filename, "r", encoding="utf8") as file: + text = file.read() + else: + text = '' + params = infotext.parse(text) + prompt = params.get('Original prompt', None) or params.get('Prompt', '') + negative = params.get('Original negative', None) or params.get('Negative prompt', '') + res = show_details(text=None, img=None, desc=None, info=None, meta=None, parameters=None, description=None, prompt=prompt, negative=negative, wildcards=None, params=params) return res def ui_quicksave_click(name): - if name is None or len(name) < 1: - shared.log.warning("Network quick save style: no name provided") - return - fn = os.path.join(paths.data_path, "params.txt") - if os.path.exists(fn): - with open(fn, "r", encoding="utf8") as file: - prompt = file.read() + if shared.opts.extra_networks_save_unparsed: + from modules.processing_info import get_last_args + params, text = get_last_args() else: - prompt = '' - params = infotext.parse(prompt) + if name is None or len(name) < 1: + shared.log.warning("Network quick save style: no name provided") + return + fn = os.path.join(paths.data_path, "params.txt") + if os.path.exists(fn): + with open(fn, "r", encoding="utf8") as file: + text = file.read() + else: + text = '' + params = infotext.parse(text) fn = os.path.join(shared.opts.styles_dir, os.path.splitext(name)[0] + '.json') - prompt = params.get('Prompt', '') + prompt = params.get('Original prompt', None) or params.get('Prompt', '') + negative = params.get('Original negative', None) or params.get('Negative prompt', '') item = { "name": name, "description": '', "prompt": prompt, - "negative": params.get('Negative prompt', ''), + "negative": negative, "extra": '', } shared.writefile(item, fn, silent=True) if len(prompt) > 0: - shared.log.debug(f"Network quick save style: item={name} filename='{fn}'") + shared.log.debug(f"Network quick save style: item={name} filename='{fn}' unparsed={shared.opts.extra_networks_unparsed}") else: shared.log.warning(f"Network quick save model: item={name} filename='{fn}' prompt is empty") diff --git a/modules/ui_sections.py b/modules/ui_sections.py index 4f7ac1500..d83a8a60b 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -6,8 +6,8 @@ from modules.interrogate import interrogate 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) + prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles, wildcards=not shared.opts.extra_networks_apply_unparsed) + prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles, wildcards=not shared.opts.extra_networks_apply_unparsed) return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value=[])] diff --git a/wiki b/wiki index 7a515665c..69cee7888 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 7a515665c2edd44a5f314beec5a904689278a555 +Subproject commit 69cee788891ef65aaf918da59e0a36aae522f4e0