mirror of
https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
synced 2025-01-04 05:45:05 +08:00
Merge branch 'dev' into test-fp8
This commit is contained in:
commit
110485d5bb
@ -64,11 +64,14 @@ class ExtraOptionsSection(scripts.Script):
|
|||||||
p.override_settings[name] = value
|
p.override_settings[name] = value
|
||||||
|
|
||||||
|
|
||||||
shared.options_templates.update(shared.options_section(('ui', "User interface"), {
|
shared.options_templates.update(shared.options_section(('settings_in_ui', "Settings in UI", "ui"), {
|
||||||
"extra_options_txt2img": shared.OptionInfo([], "Options in main UI - txt2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in txt2img interfaces").needs_reload_ui(),
|
"settings_in_ui": shared.OptionHTML("""
|
||||||
"extra_options_img2img": shared.OptionInfo([], "Options in main UI - img2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in img2img interfaces").needs_reload_ui(),
|
This page allows you to add some settings to the main interface of txt2img and img2img tabs.
|
||||||
"extra_options_cols": shared.OptionInfo(1, "Options in main UI - number of columns", gr.Number, {"precision": 0}).needs_reload_ui(),
|
"""),
|
||||||
"extra_options_accordion": shared.OptionInfo(False, "Options in main UI - place into an accordion").needs_reload_ui()
|
"extra_options_txt2img": shared.OptionInfo([], "Settings for txt2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in txt2img interfaces").needs_reload_ui(),
|
||||||
|
"extra_options_img2img": shared.OptionInfo([], "Settings for img2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in img2img interfaces").needs_reload_ui(),
|
||||||
|
"extra_options_cols": shared.OptionInfo(1, "Number of columns for added settings", gr.Number, {"precision": 0}).needs_reload_ui(),
|
||||||
|
"extra_options_accordion": shared.OptionInfo(False, "Place added settings into an accordion").needs_reload_ui()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ Original author: @tfernd Github: https://github.com/tfernd/HyperTile
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import functools
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
@ -189,6 +188,19 @@ DEPTH_LAYERS_XL = {
|
|||||||
|
|
||||||
RNG_INSTANCE = random.Random()
|
RNG_INSTANCE = random.Random()
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def get_divisors(value: int, min_value: int, /, max_options: int = 1) -> list[int]:
|
||||||
|
"""
|
||||||
|
Returns divisors of value that
|
||||||
|
x * min_value <= value
|
||||||
|
in big -> small order, amount of divisors is limited by max_options
|
||||||
|
"""
|
||||||
|
max_options = max(1, max_options) # at least 1 option should be returned
|
||||||
|
min_value = min(min_value, value)
|
||||||
|
divisors = [i for i in range(min_value, value + 1) if value % i == 0] # divisors in small -> big order
|
||||||
|
ns = [value // i for i in divisors[:max_options]] # has at least 1 element # big -> small order
|
||||||
|
return ns
|
||||||
|
|
||||||
|
|
||||||
def random_divisor(value: int, min_value: int, /, max_options: int = 1) -> int:
|
def random_divisor(value: int, min_value: int, /, max_options: int = 1) -> int:
|
||||||
"""
|
"""
|
||||||
@ -196,13 +208,7 @@ def random_divisor(value: int, min_value: int, /, max_options: int = 1) -> int:
|
|||||||
x * min_value <= value
|
x * min_value <= value
|
||||||
if max_options is 1, the behavior is deterministic
|
if max_options is 1, the behavior is deterministic
|
||||||
"""
|
"""
|
||||||
min_value = min(min_value, value)
|
ns = get_divisors(value, min_value, max_options=max_options) # get cached divisors
|
||||||
|
|
||||||
# All big divisors of value (inclusive)
|
|
||||||
divisors = [i for i in range(min_value, value + 1) if value % i == 0] # divisors in small -> big order
|
|
||||||
|
|
||||||
ns = [value // i for i in divisors[:max_options]] # has at least 1 element # big -> small order
|
|
||||||
|
|
||||||
idx = RNG_INSTANCE.randint(0, len(ns) - 1)
|
idx = RNG_INSTANCE.randint(0, len(ns) - 1)
|
||||||
|
|
||||||
return ns[idx]
|
return ns[idx]
|
||||||
@ -212,7 +218,7 @@ def set_hypertile_seed(seed: int) -> None:
|
|||||||
RNG_INSTANCE.seed(seed)
|
RNG_INSTANCE.seed(seed)
|
||||||
|
|
||||||
|
|
||||||
@functools.cache
|
@cache
|
||||||
def largest_tile_size_available(width: int, height: int) -> int:
|
def largest_tile_size_available(width: int, height: int) -> int:
|
||||||
"""
|
"""
|
||||||
Calculates the largest tile size available for a given width and height
|
Calculates the largest tile size available for a given width and height
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import hypertile
|
import hypertile
|
||||||
from modules import scripts, script_callbacks, shared
|
from modules import scripts, script_callbacks, shared
|
||||||
|
from scripts.hypertile_xyz import add_axis_options
|
||||||
|
|
||||||
|
|
||||||
class ScriptHypertile(scripts.Script):
|
class ScriptHypertile(scripts.Script):
|
||||||
@ -17,7 +18,10 @@ class ScriptHypertile(scripts.Script):
|
|||||||
configure_hypertile(p.width, p.height, enable_unet=shared.opts.hypertile_enable_unet)
|
configure_hypertile(p.width, p.height, enable_unet=shared.opts.hypertile_enable_unet)
|
||||||
|
|
||||||
def before_hr(self, p, *args):
|
def before_hr(self, p, *args):
|
||||||
configure_hypertile(p.hr_upscale_to_x, p.hr_upscale_to_y, enable_unet=shared.opts.hypertile_enable_unet_secondpass or shared.opts.hypertile_enable_unet)
|
# exclusive hypertile seed for the second pass
|
||||||
|
if not shared.opts.hypertile_enable_unet:
|
||||||
|
hypertile.set_hypertile_seed(p.all_seeds[0])
|
||||||
|
configure_hypertile(p.hr_upscale_to_x, p.hr_upscale_to_y, enable_unet=shared.opts.hypertile_enable_unet_secondpass)
|
||||||
|
|
||||||
|
|
||||||
def configure_hypertile(width, height, enable_unet=True):
|
def configure_hypertile(width, height, enable_unet=True):
|
||||||
@ -57,12 +61,12 @@ def on_ui_settings():
|
|||||||
"hypertile_enable_unet_secondpass": shared.OptionInfo(False, "Enable Hypertile U-Net for hires fix second pass"),
|
"hypertile_enable_unet_secondpass": shared.OptionInfo(False, "Enable Hypertile U-Net for hires fix second pass"),
|
||||||
"hypertile_max_depth_unet": shared.OptionInfo(3, "Hypertile U-Net max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}),
|
"hypertile_max_depth_unet": shared.OptionInfo(3, "Hypertile U-Net max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}),
|
||||||
"hypertile_max_tile_unet": shared.OptionInfo(256, "Hypertile U-net max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
"hypertile_max_tile_unet": shared.OptionInfo(256, "Hypertile U-net max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
||||||
"hypertile_swap_size_unet": shared.OptionInfo(3, "Hypertile U-net swap size", gr.Slider, {"minimum": 0, "maximum": 6, "step": 1}),
|
"hypertile_swap_size_unet": shared.OptionInfo(3, "Hypertile U-net swap size", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}),
|
||||||
|
|
||||||
"hypertile_enable_vae": shared.OptionInfo(False, "Enable Hypertile VAE").info("minimal change in the generated picture"),
|
"hypertile_enable_vae": shared.OptionInfo(False, "Enable Hypertile VAE").info("minimal change in the generated picture"),
|
||||||
"hypertile_max_depth_vae": shared.OptionInfo(3, "Hypertile VAE max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}),
|
"hypertile_max_depth_vae": shared.OptionInfo(3, "Hypertile VAE max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}),
|
||||||
"hypertile_max_tile_vae": shared.OptionInfo(128, "Hypertile VAE max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
"hypertile_max_tile_vae": shared.OptionInfo(128, "Hypertile VAE max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
||||||
"hypertile_swap_size_vae": shared.OptionInfo(3, "Hypertile VAE swap size ", gr.Slider, {"minimum": 0, "maximum": 6, "step": 1}),
|
"hypertile_swap_size_vae": shared.OptionInfo(3, "Hypertile VAE swap size ", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}),
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, opt in options.items():
|
for name, opt in options.items():
|
||||||
@ -71,3 +75,4 @@ def on_ui_settings():
|
|||||||
|
|
||||||
|
|
||||||
script_callbacks.on_ui_settings(on_ui_settings)
|
script_callbacks.on_ui_settings(on_ui_settings)
|
||||||
|
script_callbacks.on_before_ui(add_axis_options)
|
||||||
|
51
extensions-builtin/hypertile/scripts/hypertile_xyz.py
Normal file
51
extensions-builtin/hypertile/scripts/hypertile_xyz.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from modules import scripts
|
||||||
|
from modules.shared import opts
|
||||||
|
|
||||||
|
xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module
|
||||||
|
|
||||||
|
def int_applier(value_name:str, min_range:int = -1, max_range:int = -1):
|
||||||
|
"""
|
||||||
|
Returns a function that applies the given value to the given value_name in opts.data.
|
||||||
|
"""
|
||||||
|
def validate(value_name:str, value:str):
|
||||||
|
value = int(value)
|
||||||
|
# validate value
|
||||||
|
if not min_range == -1:
|
||||||
|
assert value >= min_range, f"Value {value} for {value_name} must be greater than or equal to {min_range}"
|
||||||
|
if not max_range == -1:
|
||||||
|
assert value <= max_range, f"Value {value} for {value_name} must be less than or equal to {max_range}"
|
||||||
|
def apply_int(p, x, xs):
|
||||||
|
validate(value_name, x)
|
||||||
|
opts.data[value_name] = int(x)
|
||||||
|
return apply_int
|
||||||
|
|
||||||
|
def bool_applier(value_name:str):
|
||||||
|
"""
|
||||||
|
Returns a function that applies the given value to the given value_name in opts.data.
|
||||||
|
"""
|
||||||
|
def validate(value_name:str, value:str):
|
||||||
|
assert value.lower() in ["true", "false"], f"Value {value} for {value_name} must be either true or false"
|
||||||
|
def apply_bool(p, x, xs):
|
||||||
|
validate(value_name, x)
|
||||||
|
value_boolean = x.lower() == "true"
|
||||||
|
opts.data[value_name] = value_boolean
|
||||||
|
return apply_bool
|
||||||
|
|
||||||
|
def add_axis_options():
|
||||||
|
extra_axis_options = [
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet First pass Enabled", str, bool_applier("hypertile_enable_unet"), choices=xyz_grid.boolean_choice(reverse=True)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Second pass Enabled", str, bool_applier("hypertile_enable_unet_secondpass"), choices=xyz_grid.boolean_choice(reverse=True)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Max Depth", int, int_applier("hypertile_max_depth_unet", 0, 3), choices=lambda: [str(x) for x in range(4)]),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Max Tile Size", int, int_applier("hypertile_max_tile_unet", 0, 512)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] Unet Swap Size", int, int_applier("hypertile_swap_size_unet", 0, 64)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Enabled", str, bool_applier("hypertile_enable_vae"), choices=xyz_grid.boolean_choice(reverse=True)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Max Depth", int, int_applier("hypertile_max_depth_vae", 0, 3), choices=lambda: [str(x) for x in range(4)]),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Max Tile Size", int, int_applier("hypertile_max_tile_vae", 0, 512)),
|
||||||
|
xyz_grid.AxisOption("[Hypertile] VAE Swap Size", int, int_applier("hypertile_swap_size_vae", 0, 64)),
|
||||||
|
]
|
||||||
|
set_a = {opt.label for opt in xyz_grid.axis_options}
|
||||||
|
set_b = {opt.label for opt in extra_axis_options}
|
||||||
|
if set_a.intersection(set_b):
|
||||||
|
return
|
||||||
|
|
||||||
|
xyz_grid.axis_options.extend(extra_axis_options)
|
@ -130,6 +130,10 @@ function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePromp
|
|||||||
} else {
|
} else {
|
||||||
promptContainer.insertBefore(prompt, promptContainer.firstChild);
|
promptContainer.insertBefore(prompt, promptContainer.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (elem) {
|
||||||
|
elem.classList.toggle('extra-page-prompts-active', showNegativePrompt || showPrompt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -388,3 +392,9 @@ function extraNetworksRefreshSingleCard(page, tabname, name) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener("keydown", function(event) {
|
||||||
|
if (event.key == "Escape") {
|
||||||
|
closePopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -44,3 +44,28 @@ onUiLoaded(function() {
|
|||||||
|
|
||||||
buttonShowAllPages.addEventListener("click", settingsShowAllTabs);
|
buttonShowAllPages.addEventListener("click", settingsShowAllTabs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
onOptionsChanged(function() {
|
||||||
|
if (gradioApp().querySelector('#settings .settings-category')) return;
|
||||||
|
|
||||||
|
var sectionMap = {};
|
||||||
|
gradioApp().querySelectorAll('#settings > div > button').forEach(function(x) {
|
||||||
|
sectionMap[x.textContent.trim()] = x;
|
||||||
|
});
|
||||||
|
|
||||||
|
opts._categories.forEach(function(x) {
|
||||||
|
var section = x[0];
|
||||||
|
var category = x[1];
|
||||||
|
|
||||||
|
var span = document.createElement('SPAN');
|
||||||
|
span.textContent = category;
|
||||||
|
span.className = 'settings-category';
|
||||||
|
|
||||||
|
var sectionElem = sectionMap[section];
|
||||||
|
if (!sectionElem) return;
|
||||||
|
|
||||||
|
sectionElem.parentElement.insertBefore(span, sectionElem);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ def dump_cache():
|
|||||||
with cache_lock:
|
with cache_lock:
|
||||||
cache_filename_tmp = cache_filename + "-"
|
cache_filename_tmp = cache_filename + "-"
|
||||||
with open(cache_filename_tmp, "w", encoding="utf8") as file:
|
with open(cache_filename_tmp, "w", encoding="utf8") as file:
|
||||||
json.dump(cache_data, file, indent=4)
|
json.dump(cache_data, file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
os.replace(cache_filename_tmp, cache_filename)
|
os.replace(cache_filename_tmp, cache_filename)
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ def get_optimal_device():
|
|||||||
|
|
||||||
|
|
||||||
def get_device_for(task):
|
def get_device_for(task):
|
||||||
if task in shared.cmd_opts.use_cpu:
|
if task in shared.cmd_opts.use_cpu or "all" in shared.cmd_opts.use_cpu:
|
||||||
return cpu
|
return cpu
|
||||||
|
|
||||||
return get_optimal_device()
|
return get_optimal_device()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
@ -15,9 +16,6 @@ re_imagesize = re.compile(r"^(\d+)x(\d+)$")
|
|||||||
re_hypernet_hash = re.compile("\(([0-9a-f]+)\)$")
|
re_hypernet_hash = re.compile("\(([0-9a-f]+)\)$")
|
||||||
type_of_gr_update = type(gr.update())
|
type_of_gr_update = type(gr.update())
|
||||||
|
|
||||||
paste_fields = {}
|
|
||||||
registered_param_bindings = []
|
|
||||||
|
|
||||||
|
|
||||||
class ParamBinding:
|
class ParamBinding:
|
||||||
def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None):
|
def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None):
|
||||||
@ -30,6 +28,10 @@ class ParamBinding:
|
|||||||
self.paste_field_names = paste_field_names or []
|
self.paste_field_names = paste_field_names or []
|
||||||
|
|
||||||
|
|
||||||
|
paste_fields: dict[str, dict] = {}
|
||||||
|
registered_param_bindings: list[ParamBinding] = []
|
||||||
|
|
||||||
|
|
||||||
def reset():
|
def reset():
|
||||||
paste_fields.clear()
|
paste_fields.clear()
|
||||||
registered_param_bindings.clear()
|
registered_param_bindings.clear()
|
||||||
@ -113,7 +115,6 @@ def register_paste_params_button(binding: ParamBinding):
|
|||||||
|
|
||||||
|
|
||||||
def connect_paste_params_buttons():
|
def connect_paste_params_buttons():
|
||||||
binding: ParamBinding
|
|
||||||
for binding in registered_param_bindings:
|
for binding in registered_param_bindings:
|
||||||
destination_image_component = paste_fields[binding.tabname]["init_img"]
|
destination_image_component = paste_fields[binding.tabname]["init_img"]
|
||||||
fields = paste_fields[binding.tabname]["fields"]
|
fields = paste_fields[binding.tabname]["fields"]
|
||||||
@ -313,6 +314,9 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
|
|||||||
if "VAE Decoder" not in res:
|
if "VAE Decoder" not in res:
|
||||||
res["VAE Decoder"] = "Full"
|
res["VAE Decoder"] = "Full"
|
||||||
|
|
||||||
|
skip = set(shared.opts.infotext_skip_pasting)
|
||||||
|
res = {k: v for k, v in res.items() if k not in skip}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@ -443,3 +447,4 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component,
|
|||||||
outputs=[],
|
outputs=[],
|
||||||
show_progress=False,
|
show_progress=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,10 +47,20 @@ def Block_get_config(self):
|
|||||||
|
|
||||||
|
|
||||||
def BlockContext_init(self, *args, **kwargs):
|
def BlockContext_init(self, *args, **kwargs):
|
||||||
|
if scripts.scripts_current is not None:
|
||||||
|
scripts.scripts_current.before_component(self, **kwargs)
|
||||||
|
|
||||||
|
scripts.script_callbacks.before_component_callback(self, **kwargs)
|
||||||
|
|
||||||
res = original_BlockContext_init(self, *args, **kwargs)
|
res = original_BlockContext_init(self, *args, **kwargs)
|
||||||
|
|
||||||
add_classes_to_gradio_component(self)
|
add_classes_to_gradio_component(self)
|
||||||
|
|
||||||
|
scripts.script_callbacks.after_component_callback(self, **kwargs)
|
||||||
|
|
||||||
|
if scripts.scripts_current is not None:
|
||||||
|
scripts.scripts_current.after_component(self, **kwargs)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
from torch import Tensor
|
||||||
import platform
|
import platform
|
||||||
from modules.sd_hijack_utils import CondFunc
|
from modules.sd_hijack_utils import CondFunc
|
||||||
from packaging import version
|
from packaging import version
|
||||||
@ -51,6 +52,17 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs):
|
|||||||
return cumsum_func(input, *args, **kwargs)
|
return cumsum_func(input, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# MPS workaround for https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14046
|
||||||
|
def interpolate_with_fp32_fallback(orig_func, *args, **kwargs) -> Tensor:
|
||||||
|
try:
|
||||||
|
return orig_func(*args, **kwargs)
|
||||||
|
except RuntimeError as e:
|
||||||
|
if "not implemented for" in str(e) and "Half" in str(e):
|
||||||
|
input_tensor = args[0]
|
||||||
|
return orig_func(input_tensor.to(torch.float32), *args[1:], **kwargs).to(input_tensor.dtype)
|
||||||
|
else:
|
||||||
|
print(f"An unexpected RuntimeError occurred: {str(e)}")
|
||||||
|
|
||||||
if has_mps:
|
if has_mps:
|
||||||
if platform.mac_ver()[0].startswith("13.2."):
|
if platform.mac_ver()[0].startswith("13.2."):
|
||||||
# MPS workaround for https://github.com/pytorch/pytorch/issues/95188, thanks to danieldk (https://github.com/explosion/curated-transformers/pull/124)
|
# MPS workaround for https://github.com/pytorch/pytorch/issues/95188, thanks to danieldk (https://github.com/explosion/curated-transformers/pull/124)
|
||||||
@ -77,6 +89,9 @@ if has_mps:
|
|||||||
# MPS workaround for https://github.com/pytorch/pytorch/issues/96113
|
# MPS workaround for https://github.com/pytorch/pytorch/issues/96113
|
||||||
CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps')
|
CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps')
|
||||||
|
|
||||||
|
# MPS workaround for https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14046
|
||||||
|
CondFunc('torch.nn.functional.interpolate', interpolate_with_fp32_fallback, None)
|
||||||
|
|
||||||
# MPS workaround for https://github.com/pytorch/pytorch/issues/92311
|
# MPS workaround for https://github.com/pytorch/pytorch/issues/92311
|
||||||
if platform.processor() == 'i386':
|
if platform.processor() == 'i386':
|
||||||
for funcName in ['torch.argmax', 'torch.Tensor.argmax']:
|
for funcName in ['torch.argmax', 'torch.Tensor.argmax']:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
|
|
||||||
@ -8,13 +9,14 @@ from modules.shared_cmd_options import cmd_opts
|
|||||||
|
|
||||||
|
|
||||||
class OptionInfo:
|
class OptionInfo:
|
||||||
def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after='', infotext=None, restrict_api=False):
|
def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after='', infotext=None, restrict_api=False, category_id=None):
|
||||||
self.default = default
|
self.default = default
|
||||||
self.label = label
|
self.label = label
|
||||||
self.component = component
|
self.component = component
|
||||||
self.component_args = component_args
|
self.component_args = component_args
|
||||||
self.onchange = onchange
|
self.onchange = onchange
|
||||||
self.section = section
|
self.section = section
|
||||||
|
self.category_id = category_id
|
||||||
self.refresh = refresh
|
self.refresh = refresh
|
||||||
self.do_not_save = False
|
self.do_not_save = False
|
||||||
|
|
||||||
@ -63,7 +65,11 @@ class OptionHTML(OptionInfo):
|
|||||||
|
|
||||||
def options_section(section_identifier, options_dict):
|
def options_section(section_identifier, options_dict):
|
||||||
for v in options_dict.values():
|
for v in options_dict.values():
|
||||||
|
if len(section_identifier) == 2:
|
||||||
v.section = section_identifier
|
v.section = section_identifier
|
||||||
|
elif len(section_identifier) == 3:
|
||||||
|
v.section = section_identifier[0:2]
|
||||||
|
v.category_id = section_identifier[2]
|
||||||
|
|
||||||
return options_dict
|
return options_dict
|
||||||
|
|
||||||
@ -158,7 +164,7 @@ class Options:
|
|||||||
assert not cmd_opts.freeze_settings, "saving settings is disabled"
|
assert not cmd_opts.freeze_settings, "saving settings is disabled"
|
||||||
|
|
||||||
with open(filename, "w", encoding="utf8") as file:
|
with open(filename, "w", encoding="utf8") as file:
|
||||||
json.dump(self.data, file, indent=4)
|
json.dump(self.data, file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
def same_type(self, x, y):
|
def same_type(self, x, y):
|
||||||
if x is None or y is None:
|
if x is None or y is None:
|
||||||
@ -206,6 +212,17 @@ class Options:
|
|||||||
d = {k: self.data.get(k, v.default) for k, v in self.data_labels.items()}
|
d = {k: self.data.get(k, v.default) for k, v in self.data_labels.items()}
|
||||||
d["_comments_before"] = {k: v.comment_before for k, v in self.data_labels.items() if v.comment_before is not None}
|
d["_comments_before"] = {k: v.comment_before for k, v in self.data_labels.items() if v.comment_before is not None}
|
||||||
d["_comments_after"] = {k: v.comment_after for k, v in self.data_labels.items() if v.comment_after is not None}
|
d["_comments_after"] = {k: v.comment_after for k, v in self.data_labels.items() if v.comment_after is not None}
|
||||||
|
|
||||||
|
item_categories = {}
|
||||||
|
for item in self.data_labels.values():
|
||||||
|
category = categories.mapping.get(item.category_id)
|
||||||
|
category = "Uncategorized" if category is None else category.label
|
||||||
|
if category not in item_categories:
|
||||||
|
item_categories[category] = item.section[1]
|
||||||
|
|
||||||
|
# _categories is a list of pairs: [section, category]. Each section (a setting page) will get a special heading above it with the category as text.
|
||||||
|
d["_categories"] = [[v, k] for k, v in item_categories.items()] + [["Defaults", "Other"]]
|
||||||
|
|
||||||
return json.dumps(d)
|
return json.dumps(d)
|
||||||
|
|
||||||
def add_option(self, key, info):
|
def add_option(self, key, info):
|
||||||
@ -214,15 +231,40 @@ class Options:
|
|||||||
self.data[key] = info.default
|
self.data[key] = info.default
|
||||||
|
|
||||||
def reorder(self):
|
def reorder(self):
|
||||||
"""reorder settings so that all items related to section always go together"""
|
"""Reorder settings so that:
|
||||||
|
- all items related to section always go together
|
||||||
|
- all sections belonging to a category go together
|
||||||
|
- sections inside a category are ordered alphabetically
|
||||||
|
- categories are ordered by creation order
|
||||||
|
|
||||||
|
Category is a superset of sections: for category "postprocessing" there could be multiple sections: "face restoration", "upscaling".
|
||||||
|
|
||||||
|
This function also changes items' category_id so that all items belonging to a section have the same category_id.
|
||||||
|
"""
|
||||||
|
|
||||||
|
category_ids = {}
|
||||||
|
section_categories = {}
|
||||||
|
|
||||||
section_ids = {}
|
|
||||||
settings_items = self.data_labels.items()
|
settings_items = self.data_labels.items()
|
||||||
for _, item in settings_items:
|
for _, item in settings_items:
|
||||||
if item.section not in section_ids:
|
if item.section not in section_categories:
|
||||||
section_ids[item.section] = len(section_ids)
|
section_categories[item.section] = item.category_id
|
||||||
|
|
||||||
self.data_labels = dict(sorted(settings_items, key=lambda x: section_ids[x[1].section]))
|
for _, item in settings_items:
|
||||||
|
item.category_id = section_categories.get(item.section)
|
||||||
|
|
||||||
|
for category_id in categories.mapping:
|
||||||
|
if category_id not in category_ids:
|
||||||
|
category_ids[category_id] = len(category_ids)
|
||||||
|
|
||||||
|
def sort_key(x):
|
||||||
|
item: OptionInfo = x[1]
|
||||||
|
category_order = category_ids.get(item.category_id, len(category_ids))
|
||||||
|
section_order = item.section[1]
|
||||||
|
|
||||||
|
return category_order, section_order
|
||||||
|
|
||||||
|
self.data_labels = dict(sorted(settings_items, key=sort_key))
|
||||||
|
|
||||||
def cast_value(self, key, value):
|
def cast_value(self, key, value):
|
||||||
"""casts an arbitrary to the same type as this setting's value with key
|
"""casts an arbitrary to the same type as this setting's value with key
|
||||||
@ -245,3 +287,22 @@ class Options:
|
|||||||
value = expected_type(value)
|
value = expected_type(value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class OptionsCategory:
|
||||||
|
id: str
|
||||||
|
label: str
|
||||||
|
|
||||||
|
class OptionsCategories:
|
||||||
|
def __init__(self):
|
||||||
|
self.mapping = {}
|
||||||
|
|
||||||
|
def register_category(self, category_id, label):
|
||||||
|
if category_id in self.mapping:
|
||||||
|
return category_id
|
||||||
|
|
||||||
|
self.mapping[category_id] = OptionsCategory(category_id, label)
|
||||||
|
|
||||||
|
|
||||||
|
categories = OptionsCategories()
|
||||||
|
@ -679,8 +679,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter
|
|||||||
"Size": f"{p.width}x{p.height}",
|
"Size": f"{p.width}x{p.height}",
|
||||||
"Model hash": p.sd_model_hash if opts.add_model_hash_to_info else None,
|
"Model hash": p.sd_model_hash if opts.add_model_hash_to_info else None,
|
||||||
"Model": p.sd_model_name if opts.add_model_name_to_info else None,
|
"Model": p.sd_model_name if opts.add_model_name_to_info else None,
|
||||||
"VAE hash": p.sd_vae_hash if opts.add_model_hash_to_info else None,
|
"VAE hash": p.sd_vae_hash if opts.add_vae_hash_to_info else None,
|
||||||
"VAE": p.sd_vae_name if opts.add_model_name_to_info else None,
|
"VAE": p.sd_vae_name if opts.add_vae_name_to_info else None,
|
||||||
"Variation seed": (None if p.subseed_strength == 0 else (p.all_subseeds[0] if use_main_prompt else all_subseeds[index])),
|
"Variation seed": (None if p.subseed_strength == 0 else (p.all_subseeds[0] if use_main_prompt else all_subseeds[index])),
|
||||||
"Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength),
|
"Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength),
|
||||||
"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}"),
|
"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}"),
|
||||||
|
@ -560,17 +560,25 @@ class ScriptRunner:
|
|||||||
on_after.clear()
|
on_after.clear()
|
||||||
|
|
||||||
def create_script_ui(self, script):
|
def create_script_ui(self, script):
|
||||||
import modules.api.models as api_models
|
|
||||||
|
|
||||||
script.args_from = len(self.inputs)
|
script.args_from = len(self.inputs)
|
||||||
script.args_to = len(self.inputs)
|
script.args_to = len(self.inputs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.create_script_ui_inner(script)
|
||||||
|
except Exception:
|
||||||
|
errors.report(f"Error creating UI for {script.name}: ", exc_info=True)
|
||||||
|
|
||||||
|
def create_script_ui_inner(self, script):
|
||||||
|
import modules.api.models as api_models
|
||||||
|
|
||||||
controls = wrap_call(script.ui, script.filename, "ui", script.is_img2img)
|
controls = wrap_call(script.ui, script.filename, "ui", script.is_img2img)
|
||||||
|
|
||||||
if controls is None:
|
if controls is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
script.name = wrap_call(script.title, script.filename, "title", default=script.filename).lower()
|
script.name = wrap_call(script.title, script.filename, "title", default=script.filename).lower()
|
||||||
|
|
||||||
api_args = []
|
api_args = []
|
||||||
|
|
||||||
for control in controls:
|
for control in controls:
|
||||||
|
@ -38,9 +38,6 @@ ldm.models.diffusion.ddpm.print = shared.ldm_print
|
|||||||
optimizers = []
|
optimizers = []
|
||||||
current_optimizer: sd_hijack_optimizations.SdOptimization = None
|
current_optimizer: sd_hijack_optimizations.SdOptimization = None
|
||||||
|
|
||||||
ldm_original_forward = patches.patch(__file__, ldm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
|
|
||||||
sgm_original_forward = patches.patch(__file__, sgm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
|
|
||||||
|
|
||||||
def list_optimizers():
|
def list_optimizers():
|
||||||
new_optimizers = script_callbacks.list_optimizers_callback()
|
new_optimizers = script_callbacks.list_optimizers_callback()
|
||||||
|
|
||||||
@ -258,6 +255,9 @@ class StableDiffusionModelHijack:
|
|||||||
|
|
||||||
import modules.models.diffusion.ddpm_edit
|
import modules.models.diffusion.ddpm_edit
|
||||||
|
|
||||||
|
ldm_original_forward = patches.patch(__file__, ldm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
|
||||||
|
sgm_original_forward = patches.patch(__file__, sgm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
|
||||||
|
|
||||||
if isinstance(m, ldm.models.diffusion.ddpm.LatentDiffusion):
|
if isinstance(m, ldm.models.diffusion.ddpm.LatentDiffusion):
|
||||||
sd_unet.original_forward = ldm_original_forward
|
sd_unet.original_forward = ldm_original_forward
|
||||||
elif isinstance(m, modules.models.diffusion.ddpm_edit.LatentDiffusion):
|
elif isinstance(m, modules.models.diffusion.ddpm_edit.LatentDiffusion):
|
||||||
@ -303,6 +303,9 @@ class StableDiffusionModelHijack:
|
|||||||
self.layers = None
|
self.layers = None
|
||||||
self.clip = None
|
self.clip = None
|
||||||
|
|
||||||
|
patches.undo(__file__, ldm.modules.diffusionmodules.openaimodel.UNetModel, "forward")
|
||||||
|
patches.undo(__file__, sgm.modules.diffusionmodules.openaimodel.UNetModel, "forward")
|
||||||
|
|
||||||
sd_unet.original_forward = None
|
sd_unet.original_forward = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -230,15 +230,19 @@ def select_checkpoint():
|
|||||||
return checkpoint_info
|
return checkpoint_info
|
||||||
|
|
||||||
|
|
||||||
checkpoint_dict_replacements = {
|
checkpoint_dict_replacements_sd1 = {
|
||||||
'cond_stage_model.transformer.embeddings.': 'cond_stage_model.transformer.text_model.embeddings.',
|
'cond_stage_model.transformer.embeddings.': 'cond_stage_model.transformer.text_model.embeddings.',
|
||||||
'cond_stage_model.transformer.encoder.': 'cond_stage_model.transformer.text_model.encoder.',
|
'cond_stage_model.transformer.encoder.': 'cond_stage_model.transformer.text_model.encoder.',
|
||||||
'cond_stage_model.transformer.final_layer_norm.': 'cond_stage_model.transformer.text_model.final_layer_norm.',
|
'cond_stage_model.transformer.final_layer_norm.': 'cond_stage_model.transformer.text_model.final_layer_norm.',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkpoint_dict_replacements_sd2_turbo = { # Converts SD 2.1 Turbo from SGM to LDM format.
|
||||||
|
'conditioner.embedders.0.': 'cond_stage_model.',
|
||||||
|
}
|
||||||
|
|
||||||
def transform_checkpoint_dict_key(k):
|
|
||||||
for text, replacement in checkpoint_dict_replacements.items():
|
def transform_checkpoint_dict_key(k, replacements):
|
||||||
|
for text, replacement in replacements.items():
|
||||||
if k.startswith(text):
|
if k.startswith(text):
|
||||||
k = replacement + k[len(text):]
|
k = replacement + k[len(text):]
|
||||||
|
|
||||||
@ -249,9 +253,14 @@ def get_state_dict_from_checkpoint(pl_sd):
|
|||||||
pl_sd = pl_sd.pop("state_dict", pl_sd)
|
pl_sd = pl_sd.pop("state_dict", pl_sd)
|
||||||
pl_sd.pop("state_dict", None)
|
pl_sd.pop("state_dict", None)
|
||||||
|
|
||||||
|
is_sd2_turbo = 'conditioner.embedders.0.model.ln_final.weight' in pl_sd and pl_sd['conditioner.embedders.0.model.ln_final.weight'].size()[0] == 1024
|
||||||
|
|
||||||
sd = {}
|
sd = {}
|
||||||
for k, v in pl_sd.items():
|
for k, v in pl_sd.items():
|
||||||
new_key = transform_checkpoint_dict_key(k)
|
if is_sd2_turbo:
|
||||||
|
new_key = transform_checkpoint_dict_key(k, checkpoint_dict_replacements_sd2_turbo)
|
||||||
|
else:
|
||||||
|
new_key = transform_checkpoint_dict_key(k, checkpoint_dict_replacements_sd1)
|
||||||
|
|
||||||
if new_key is not None:
|
if new_key is not None:
|
||||||
sd[new_key] = v
|
sd[new_key] = v
|
||||||
|
@ -66,6 +66,22 @@ def reload_hypernetworks():
|
|||||||
shared.hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir)
|
shared.hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def get_infotext_names():
|
||||||
|
from modules import generation_parameters_copypaste, shared
|
||||||
|
res = {}
|
||||||
|
|
||||||
|
for info in shared.opts.data_labels.values():
|
||||||
|
if info.infotext:
|
||||||
|
res[info.infotext] = 1
|
||||||
|
|
||||||
|
for tab_data in generation_parameters_copypaste.paste_fields.values():
|
||||||
|
for _, name in tab_data.get("fields") or []:
|
||||||
|
if isinstance(name, str):
|
||||||
|
res[name] = 1
|
||||||
|
|
||||||
|
return list(res)
|
||||||
|
|
||||||
|
|
||||||
ui_reorder_categories_builtin_items = [
|
ui_reorder_categories_builtin_items = [
|
||||||
"prompt",
|
"prompt",
|
||||||
"image",
|
"image",
|
||||||
|
@ -3,7 +3,7 @@ import gradio as gr
|
|||||||
from modules import localization, ui_components, shared_items, shared, interrogate, shared_gradio_themes
|
from modules import localization, ui_components, shared_items, shared, interrogate, shared_gradio_themes
|
||||||
from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # noqa: F401
|
from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # noqa: F401
|
||||||
from modules.shared_cmd_options import cmd_opts
|
from modules.shared_cmd_options import cmd_opts
|
||||||
from modules.options import options_section, OptionInfo, OptionHTML
|
from modules.options import options_section, OptionInfo, OptionHTML, categories
|
||||||
|
|
||||||
options_templates = {}
|
options_templates = {}
|
||||||
hide_dirs = shared.hide_dirs
|
hide_dirs = shared.hide_dirs
|
||||||
@ -21,7 +21,14 @@ restricted_opts = {
|
|||||||
"outdir_init_images"
|
"outdir_init_images"
|
||||||
}
|
}
|
||||||
|
|
||||||
options_templates.update(options_section(('saving-images', "Saving images/grids"), {
|
categories.register_category("saving", "Saving images")
|
||||||
|
categories.register_category("sd", "Stable Diffusion")
|
||||||
|
categories.register_category("ui", "User Interface")
|
||||||
|
categories.register_category("system", "System")
|
||||||
|
categories.register_category("postprocessing", "Postprocessing")
|
||||||
|
categories.register_category("training", "Training")
|
||||||
|
|
||||||
|
options_templates.update(options_section(('saving-images', "Saving images/grids", "saving"), {
|
||||||
"samples_save": OptionInfo(True, "Always save all generated images"),
|
"samples_save": OptionInfo(True, "Always save all generated images"),
|
||||||
"samples_format": OptionInfo('png', 'File format for images'),
|
"samples_format": OptionInfo('png', 'File format for images'),
|
||||||
"samples_filename_pattern": OptionInfo("", "Images filename pattern", component_args=hide_dirs).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory"),
|
"samples_filename_pattern": OptionInfo("", "Images filename pattern", component_args=hide_dirs).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory"),
|
||||||
@ -39,8 +46,6 @@ options_templates.update(options_section(('saving-images', "Saving images/grids"
|
|||||||
"grid_text_inactive_color": OptionInfo("#999999", "Inactive text color for image grids", ui_components.FormColorPicker, {}),
|
"grid_text_inactive_color": OptionInfo("#999999", "Inactive text color for image grids", ui_components.FormColorPicker, {}),
|
||||||
"grid_background_color": OptionInfo("#ffffff", "Background color for image grids", ui_components.FormColorPicker, {}),
|
"grid_background_color": OptionInfo("#ffffff", "Background color for image grids", ui_components.FormColorPicker, {}),
|
||||||
|
|
||||||
"enable_pnginfo": OptionInfo(True, "Save text information about generation parameters as chunks to png files"),
|
|
||||||
"save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."),
|
|
||||||
"save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration."),
|
"save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration."),
|
||||||
"save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix."),
|
"save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix."),
|
||||||
"save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"),
|
"save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"),
|
||||||
@ -67,7 +72,7 @@ options_templates.update(options_section(('saving-images', "Saving images/grids"
|
|||||||
"notification_volume": OptionInfo(100, "Notification sound volume", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}).info("in %"),
|
"notification_volume": OptionInfo(100, "Notification sound volume", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}).info("in %"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('saving-paths', "Paths for saving"), {
|
options_templates.update(options_section(('saving-paths', "Paths for saving", "saving"), {
|
||||||
"outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs),
|
"outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs),
|
||||||
"outdir_txt2img_samples": OptionInfo("outputs/txt2img-images", 'Output directory for txt2img images', component_args=hide_dirs),
|
"outdir_txt2img_samples": OptionInfo("outputs/txt2img-images", 'Output directory for txt2img images', component_args=hide_dirs),
|
||||||
"outdir_img2img_samples": OptionInfo("outputs/img2img-images", 'Output directory for img2img images', component_args=hide_dirs),
|
"outdir_img2img_samples": OptionInfo("outputs/img2img-images", 'Output directory for img2img images', component_args=hide_dirs),
|
||||||
@ -79,7 +84,7 @@ options_templates.update(options_section(('saving-paths', "Paths for saving"), {
|
|||||||
"outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs),
|
"outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('saving-to-dirs', "Saving to a directory"), {
|
options_templates.update(options_section(('saving-to-dirs', "Saving to a directory", "saving"), {
|
||||||
"save_to_dirs": OptionInfo(True, "Save images to a subdirectory"),
|
"save_to_dirs": OptionInfo(True, "Save images to a subdirectory"),
|
||||||
"grid_save_to_dirs": OptionInfo(True, "Save grids to a subdirectory"),
|
"grid_save_to_dirs": OptionInfo(True, "Save grids to a subdirectory"),
|
||||||
"use_save_to_dirs_for_ui": OptionInfo(False, "When using \"Save\" button, save images to a subdirectory"),
|
"use_save_to_dirs_for_ui": OptionInfo(False, "When using \"Save\" button, save images to a subdirectory"),
|
||||||
@ -87,21 +92,21 @@ options_templates.update(options_section(('saving-to-dirs', "Saving to a directo
|
|||||||
"directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}),
|
"directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('upscaling', "Upscaling"), {
|
options_templates.update(options_section(('upscaling', "Upscaling", "postprocessing"), {
|
||||||
"ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"),
|
"ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"),
|
||||||
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"),
|
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"),
|
||||||
"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()}),
|
"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(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}),
|
"upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('face-restoration', "Face restoration"), {
|
options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), {
|
||||||
"face_restoration": OptionInfo(False, "Restore faces", infotext='Face restoration').info("will use a third-party model on generation result to reconstruct faces"),
|
"face_restoration": OptionInfo(False, "Restore faces", infotext='Face restoration').info("will use a third-party model on generation result to reconstruct faces"),
|
||||||
"face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in shared.face_restorers]}),
|
"face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in shared.face_restorers]}),
|
||||||
"code_former_weight": OptionInfo(0.5, "CodeFormer weight", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}).info("0 = maximum effect; 1 = minimum effect"),
|
"code_former_weight": OptionInfo(0.5, "CodeFormer weight", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}).info("0 = maximum effect; 1 = minimum effect"),
|
||||||
"face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"),
|
"face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('system', "System"), {
|
options_templates.update(options_section(('system', "System", "system"), {
|
||||||
"auto_launch_browser": OptionInfo("Local", "Automatically open webui in browser on startup", gr.Radio, lambda: {"choices": ["Disable", "Local", "Remote"]}),
|
"auto_launch_browser": OptionInfo("Local", "Automatically open webui in browser on startup", gr.Radio, lambda: {"choices": ["Disable", "Local", "Remote"]}),
|
||||||
"enable_console_prompts": OptionInfo(shared.cmd_opts.enable_console_prompts, "Print prompts to console when generating with txt2img and img2img."),
|
"enable_console_prompts": OptionInfo(shared.cmd_opts.enable_console_prompts, "Print prompts to console when generating with txt2img and img2img."),
|
||||||
"show_warnings": OptionInfo(False, "Show warnings in console.").needs_reload_ui(),
|
"show_warnings": OptionInfo(False, "Show warnings in console.").needs_reload_ui(),
|
||||||
@ -116,13 +121,13 @@ options_templates.update(options_section(('system', "System"), {
|
|||||||
"dump_stacks_on_signal": OptionInfo(False, "Print stack traces before exiting the program with ctrl+c."),
|
"dump_stacks_on_signal": OptionInfo(False, "Print stack traces before exiting the program with ctrl+c."),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('API', "API"), {
|
options_templates.update(options_section(('API', "API", "system"), {
|
||||||
"api_enable_requests": OptionInfo(True, "Allow http:// and https:// URLs for input images in API", restrict_api=True),
|
"api_enable_requests": OptionInfo(True, "Allow http:// and https:// URLs for input images in API", restrict_api=True),
|
||||||
"api_forbid_local_requests": OptionInfo(True, "Forbid URLs to local resources", restrict_api=True),
|
"api_forbid_local_requests": OptionInfo(True, "Forbid URLs to local resources", restrict_api=True),
|
||||||
"api_useragent": OptionInfo("", "User agent for requests", restrict_api=True),
|
"api_useragent": OptionInfo("", "User agent for requests", restrict_api=True),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('training', "Training"), {
|
options_templates.update(options_section(('training', "Training", "training"), {
|
||||||
"unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."),
|
"unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."),
|
||||||
"pin_memory": OptionInfo(False, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."),
|
"pin_memory": OptionInfo(False, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."),
|
||||||
"save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training of embedding or HN can be resumed with the matching optim file."),
|
"save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training of embedding or HN can be resumed with the matching optim file."),
|
||||||
@ -137,7 +142,7 @@ options_templates.update(options_section(('training', "Training"), {
|
|||||||
"training_tensorboard_flush_every": OptionInfo(120, "How often, in seconds, to flush the pending tensorboard events and summaries to disk."),
|
"training_tensorboard_flush_every": OptionInfo(120, "How often, in seconds, to flush the pending tensorboard events and summaries to disk."),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('sd', "Stable Diffusion"), {
|
options_templates.update(options_section(('sd', "Stable Diffusion", "sd"), {
|
||||||
"sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": shared_items.list_checkpoint_tiles(shared.opts.sd_checkpoint_dropdown_use_short)}, refresh=shared_items.refresh_checkpoints, infotext='Model hash'),
|
"sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": shared_items.list_checkpoint_tiles(shared.opts.sd_checkpoint_dropdown_use_short)}, refresh=shared_items.refresh_checkpoints, infotext='Model hash'),
|
||||||
"sd_checkpoints_limit": OptionInfo(1, "Maximum number of checkpoints loaded at the same time", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1}),
|
"sd_checkpoints_limit": OptionInfo(1, "Maximum number of checkpoints loaded at the same time", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1}),
|
||||||
"sd_checkpoints_keep_in_cpu": OptionInfo(True, "Only keep one model on device").info("will keep models other than the currently used one in RAM rather than VRAM"),
|
"sd_checkpoints_keep_in_cpu": OptionInfo(True, "Only keep one model on device").info("will keep models other than the currently used one in RAM rather than VRAM"),
|
||||||
@ -154,14 +159,14 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), {
|
|||||||
"hires_fix_refiner_pass": OptionInfo("second pass", "Hires fix: which pass to enable refiner for", gr.Radio, {"choices": ["first pass", "second pass", "both passes"]}, infotext="Hires refiner"),
|
"hires_fix_refiner_pass": OptionInfo("second pass", "Hires fix: which pass to enable refiner for", gr.Radio, {"choices": ["first pass", "second pass", "both passes"]}, infotext="Hires refiner"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('sdxl', "Stable Diffusion XL"), {
|
options_templates.update(options_section(('sdxl', "Stable Diffusion XL", "sd"), {
|
||||||
"sdxl_crop_top": OptionInfo(0, "crop top coordinate"),
|
"sdxl_crop_top": OptionInfo(0, "crop top coordinate"),
|
||||||
"sdxl_crop_left": OptionInfo(0, "crop left coordinate"),
|
"sdxl_crop_left": OptionInfo(0, "crop left coordinate"),
|
||||||
"sdxl_refiner_low_aesthetic_score": OptionInfo(2.5, "SDXL low aesthetic score", gr.Number).info("used for refiner model negative prompt"),
|
"sdxl_refiner_low_aesthetic_score": OptionInfo(2.5, "SDXL low aesthetic score", gr.Number).info("used for refiner model negative prompt"),
|
||||||
"sdxl_refiner_high_aesthetic_score": OptionInfo(6.0, "SDXL high aesthetic score", gr.Number).info("used for refiner model prompt"),
|
"sdxl_refiner_high_aesthetic_score": OptionInfo(6.0, "SDXL high aesthetic score", gr.Number).info("used for refiner model prompt"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('vae', "VAE"), {
|
options_templates.update(options_section(('vae', "VAE", "sd"), {
|
||||||
"sd_vae_explanation": OptionHTML("""
|
"sd_vae_explanation": OptionHTML("""
|
||||||
<abbr title='Variational autoencoder'>VAE</abbr> is a neural network that transforms a standard <abbr title='red/green/blue'>RGB</abbr>
|
<abbr title='Variational autoencoder'>VAE</abbr> is a neural network that transforms a standard <abbr title='red/green/blue'>RGB</abbr>
|
||||||
image into latent space representation and back. Latent space representation is what stable diffusion is working on during sampling
|
image into latent space representation and back. Latent space representation is what stable diffusion is working on during sampling
|
||||||
@ -176,7 +181,7 @@ For img2img, VAE is used to process user's input image before the sampling, and
|
|||||||
"sd_vae_decode_method": OptionInfo("Full", "VAE type for decode", gr.Radio, {"choices": ["Full", "TAESD"]}, infotext='VAE Decoder').info("method to decode latent to image"),
|
"sd_vae_decode_method": OptionInfo("Full", "VAE type for decode", gr.Radio, {"choices": ["Full", "TAESD"]}, infotext='VAE Decoder').info("method to decode latent to image"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('img2img', "img2img"), {
|
options_templates.update(options_section(('img2img', "img2img", "sd"), {
|
||||||
"inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Conditional mask weight'),
|
"inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Conditional mask weight'),
|
||||||
"initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for img2img", gr.Slider, {"minimum": 0.0, "maximum": 1.5, "step": 0.001}, infotext='Noise multiplier'),
|
"initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for img2img", gr.Slider, {"minimum": 0.0, "maximum": 1.5, "step": 0.001}, infotext='Noise multiplier'),
|
||||||
"img2img_extra_noise": OptionInfo(0.0, "Extra noise multiplier for img2img and hires fix", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Extra noise').info("0 = disabled (default); should be lower than denoising strength"),
|
"img2img_extra_noise": OptionInfo(0.0, "Extra noise multiplier for img2img and hires fix", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Extra noise').info("0 = disabled (default); should be lower than denoising strength"),
|
||||||
@ -192,7 +197,7 @@ options_templates.update(options_section(('img2img', "img2img"), {
|
|||||||
"img2img_batch_show_results_limit": OptionInfo(32, "Show the first N batch img2img results in UI", gr.Slider, {"minimum": -1, "maximum": 1000, "step": 1}).info('0: disable, -1: show all images. Too many images can cause lag'),
|
"img2img_batch_show_results_limit": OptionInfo(32, "Show the first N batch img2img results in UI", gr.Slider, {"minimum": -1, "maximum": 1000, "step": 1}).info('0: disable, -1: show all images. Too many images can cause lag'),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('optimizations', "Optimizations"), {
|
options_templates.update(options_section(('optimizations', "Optimizations", "sd"), {
|
||||||
"cross_attention_optimization": OptionInfo("Automatic", "Cross attention optimization", gr.Dropdown, lambda: {"choices": shared_items.cross_attention_optimizations()}),
|
"cross_attention_optimization": OptionInfo("Automatic", "Cross attention optimization", gr.Dropdown, lambda: {"choices": shared_items.cross_attention_optimizations()}),
|
||||||
"s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"),
|
"s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"),
|
||||||
"token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"),
|
"token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"),
|
||||||
@ -205,7 +210,7 @@ options_templates.update(options_section(('optimizations', "Optimizations"), {
|
|||||||
"cache_fp16_weight": OptionInfo(False, "Cache FP16 weight for LoRA").info("Cache fp16 weight when enabling FP8, will increase the quality of LoRA. Use more system ram."),
|
"cache_fp16_weight": OptionInfo(False, "Cache FP16 weight for LoRA").info("Cache fp16 weight when enabling FP8, will increase the quality of LoRA. Use more system ram."),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('compatibility', "Compatibility"), {
|
options_templates.update(options_section(('compatibility', "Compatibility", "sd"), {
|
||||||
"use_old_emphasis_implementation": OptionInfo(False, "Use old emphasis implementation. Can be useful to reproduce old seeds."),
|
"use_old_emphasis_implementation": OptionInfo(False, "Use old emphasis implementation. Can be useful to reproduce old seeds."),
|
||||||
"use_old_karras_scheduler_sigmas": OptionInfo(False, "Use old karras scheduler sigmas (0.1 to 10)."),
|
"use_old_karras_scheduler_sigmas": OptionInfo(False, "Use old karras scheduler sigmas (0.1 to 10)."),
|
||||||
"no_dpmpp_sde_batch_determinism": OptionInfo(False, "Do not make DPM++ SDE deterministic across different batch sizes."),
|
"no_dpmpp_sde_batch_determinism": OptionInfo(False, "Do not make DPM++ SDE deterministic across different batch sizes."),
|
||||||
@ -230,8 +235,9 @@ options_templates.update(options_section(('interrogate', "Interrogate"), {
|
|||||||
"deepbooru_filter_tags": OptionInfo("", "deepbooru: filter out those tags").info("separate by comma"),
|
"deepbooru_filter_tags": OptionInfo("", "deepbooru: filter out those tags").info("separate by comma"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('extra_networks', "Extra Networks"), {
|
options_templates.update(options_section(('extra_networks', "Extra Networks", "sd"), {
|
||||||
"extra_networks_show_hidden_directories": OptionInfo(True, "Show hidden directories").info("directory is hidden if its name starts with \".\"."),
|
"extra_networks_show_hidden_directories": OptionInfo(True, "Show hidden directories").info("directory is hidden if its name starts with \".\"."),
|
||||||
|
"extra_networks_dir_button_function": OptionInfo(False, "Add a '/' to the beginning of directory buttons").info("Buttons will display the contents of the selected directory without acting as a search filter."),
|
||||||
"extra_networks_hidden_models": OptionInfo("When searched", "Show cards for models in hidden directories", gr.Radio, {"choices": ["Always", "When searched", "Never"]}).info('"When searched" option will only show the item when the search string has 4 characters or more'),
|
"extra_networks_hidden_models": OptionInfo("When searched", "Show cards for models in hidden directories", gr.Radio, {"choices": ["Always", "When searched", "Never"]}).info('"When searched" option will only show the item when the search string has 4 characters or more'),
|
||||||
"extra_networks_default_multiplier": OptionInfo(1.0, "Default multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}),
|
"extra_networks_default_multiplier": OptionInfo(1.0, "Default multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}),
|
||||||
"extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"),
|
"extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"),
|
||||||
@ -247,47 +253,64 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), {
|
|||||||
"sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", *shared.hypernetworks]}, refresh=shared_items.reload_hypernetworks),
|
"sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", *shared.hypernetworks]}, refresh=shared_items.reload_hypernetworks),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('ui', "User interface"), {
|
options_templates.update(options_section(('ui_prompt_editing', "Prompt editing", "ui"), {
|
||||||
"localization": OptionInfo("None", "Localization", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)).needs_reload_ui(),
|
"keyedit_precision_attention": OptionInfo(0.1, "Precision for (attention:1.1) when editing the prompt with Ctrl+up/down", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
|
||||||
"gradio_theme": OptionInfo("Default", "Gradio theme", ui_components.DropdownEditable, lambda: {"choices": ["Default"] + shared_gradio_themes.gradio_hf_hub_themes}).info("you can also manually enter any of themes from the <a href='https://huggingface.co/spaces/gradio/theme-gallery'>gallery</a>.").needs_reload_ui(),
|
"keyedit_precision_extra": OptionInfo(0.05, "Precision for <extra networks:0.9> when editing the prompt with Ctrl+up/down", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
|
||||||
"gradio_themes_cache": OptionInfo(True, "Cache gradio themes locally").info("disable to update the selected Gradio theme"),
|
"keyedit_delimiters": OptionInfo(r".,\/!?%^*;:{}=`~() ", "Word delimiters when editing the prompt with Ctrl+up/down"),
|
||||||
"gallery_height": OptionInfo("", "Gallery height", gr.Textbox).info("an be any valid CSS value").needs_reload_ui(),
|
"disable_token_counters": OptionInfo(False, "Disable prompt token counters").needs_reload_ui(),
|
||||||
"return_grid": OptionInfo(True, "Show grid in results for web"),
|
}))
|
||||||
"do_not_show_images": OptionInfo(False, "Do not show any images in results for web"),
|
|
||||||
"send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"),
|
options_templates.update(options_section(('ui_gallery', "Gallery", "ui"), {
|
||||||
"send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"),
|
"return_grid": OptionInfo(True, "Show grid in gallery"),
|
||||||
"js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"),
|
"do_not_show_images": OptionInfo(False, "Do not show any images in gallery"),
|
||||||
"js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"),
|
"js_modal_lightbox": OptionInfo(True, "Full page image viewer: enable"),
|
||||||
"js_modal_lightbox_gamepad": OptionInfo(False, "Navigate image viewer with gamepad"),
|
"js_modal_lightbox_initially_zoomed": OptionInfo(True, "Full page image viewer: show images zoomed in by default"),
|
||||||
"js_modal_lightbox_gamepad_repeat": OptionInfo(250, "Gamepad repeat period, in milliseconds"),
|
"js_modal_lightbox_gamepad": OptionInfo(False, "Full page image viewer: navigate with gamepad"),
|
||||||
"show_progress_in_title": OptionInfo(True, "Show generation progress in window title."),
|
"js_modal_lightbox_gamepad_repeat": OptionInfo(250, "Full page image viewer: gamepad repeat period").info("in milliseconds"),
|
||||||
|
"gallery_height": OptionInfo("", "Gallery height", gr.Textbox).info("can be any valid CSS value, for example 768px or 20em").needs_reload_ui(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
options_templates.update(options_section(('ui_alternatives', "UI alternatives", "ui"), {
|
||||||
|
"compact_prompt_box": OptionInfo(False, "Compact prompt layout").info("puts prompt and negative prompt inside the Generate tab, leaving more vertical space for the image on the right").needs_reload_ui(),
|
||||||
"samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group").needs_reload_ui(),
|
"samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group").needs_reload_ui(),
|
||||||
"dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row").needs_reload_ui(),
|
"dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row").needs_reload_ui(),
|
||||||
"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 <extra networks:0.9>", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
|
|
||||||
"keyedit_delimiters": OptionInfo(r".,\/!?%^*;:{}=`~() ", "Ctrl+up/down word delimiters"),
|
|
||||||
"keyedit_delimiters_whitespace": OptionInfo(["Tab", "Carriage Return", "Line Feed"], "Ctrl+up/down whitespace delimiters", gr.CheckboxGroup, lambda: {"choices": ["Tab", "Carriage Return", "Line Feed"]}),
|
|
||||||
"keyedit_move": OptionInfo(True, "Alt+left/right moves prompt elements"),
|
|
||||||
"quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that appear at the top of page rather than in settings tab").needs_reload_ui(),
|
|
||||||
"ui_tab_order": OptionInfo([], "UI tab order", ui_components.DropdownMulti, lambda: {"choices": list(shared.tab_names)}).needs_reload_ui(),
|
|
||||||
"hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": list(shared.tab_names)}).needs_reload_ui(),
|
|
||||||
"ui_reorder_list": OptionInfo([], "txt2img/img2img UI item order", ui_components.DropdownMulti, lambda: {"choices": list(shared_items.ui_reorder_categories())}).info("selected items appear first").needs_reload_ui(),
|
|
||||||
"sd_checkpoint_dropdown_use_short": OptionInfo(False, "Checkpoint dropdown: use filenames without paths").info("models in subdirectories like photo/sd15.ckpt will be listed as just sd15.ckpt"),
|
"sd_checkpoint_dropdown_use_short": OptionInfo(False, "Checkpoint dropdown: use filenames without paths").info("models in subdirectories like photo/sd15.ckpt will be listed as just sd15.ckpt"),
|
||||||
"hires_fix_show_sampler": OptionInfo(False, "Hires fix: show hires checkpoint and sampler selection").needs_reload_ui(),
|
"hires_fix_show_sampler": OptionInfo(False, "Hires fix: show hires checkpoint and sampler selection").needs_reload_ui(),
|
||||||
"hires_fix_show_prompts": OptionInfo(False, "Hires fix: show hires prompt and negative prompt").needs_reload_ui(),
|
"hires_fix_show_prompts": OptionInfo(False, "Hires fix: show hires prompt and negative prompt").needs_reload_ui(),
|
||||||
"disable_token_counters": OptionInfo(False, "Disable prompt token counters").needs_reload_ui(),
|
|
||||||
"txt2img_settings_accordion": OptionInfo(False, "Settings in txt2img hidden under Accordion").needs_reload_ui(),
|
"txt2img_settings_accordion": OptionInfo(False, "Settings in txt2img hidden under Accordion").needs_reload_ui(),
|
||||||
"img2img_settings_accordion": OptionInfo(False, "Settings in img2img hidden under Accordion").needs_reload_ui(),
|
"img2img_settings_accordion": OptionInfo(False, "Settings in img2img hidden under Accordion").needs_reload_ui(),
|
||||||
"compact_prompt_box": OptionInfo(False, "Compact prompt layout").info("puts prompt and negative prompt inside the Generate tab, leaving more vertical space for the image on the right").needs_reload_ui(),
|
}))
|
||||||
|
|
||||||
|
options_templates.update(options_section(('ui', "User interface", "ui"), {
|
||||||
|
"localization": OptionInfo("None", "Localization", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)).needs_reload_ui(),
|
||||||
|
"quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that appear at the top of page rather than in settings tab").needs_reload_ui(),
|
||||||
|
"ui_tab_order": OptionInfo([], "UI tab order", ui_components.DropdownMulti, lambda: {"choices": list(shared.tab_names)}).needs_reload_ui(),
|
||||||
|
"hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": list(shared.tab_names)}).needs_reload_ui(),
|
||||||
|
"ui_reorder_list": OptionInfo([], "UI item order for txt2img/img2img tabs", ui_components.DropdownMulti, lambda: {"choices": list(shared_items.ui_reorder_categories())}).info("selected items appear first").needs_reload_ui(),
|
||||||
|
"gradio_theme": OptionInfo("Default", "Gradio theme", ui_components.DropdownEditable, lambda: {"choices": ["Default"] + shared_gradio_themes.gradio_hf_hub_themes}).info("you can also manually enter any of themes from the <a href='https://huggingface.co/spaces/gradio/theme-gallery'>gallery</a>.").needs_reload_ui(),
|
||||||
|
"gradio_themes_cache": OptionInfo(True, "Cache gradio themes locally").info("disable to update the selected Gradio theme"),
|
||||||
|
"show_progress_in_title": OptionInfo(True, "Show generation progress in window title."),
|
||||||
|
"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"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
options_templates.update(options_section(('infotext', "Infotext"), {
|
options_templates.update(options_section(('infotext', "Infotext", "ui"), {
|
||||||
"add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"),
|
"infotext_explanation": OptionHTML("""
|
||||||
"add_model_name_to_info": OptionInfo(True, "Add model name to generation information"),
|
Infotext is what this software calls the text that contains generation parameters and can be used to generate the same picture again.
|
||||||
"add_user_name_to_info": OptionInfo(False, "Add user name to generation information when authenticated"),
|
It is displayed in UI below the image. To use infotext, paste it into the prompt and click the ↙️ paste button.
|
||||||
"add_version_to_infotext": OptionInfo(True, "Add program version to generation information"),
|
"""),
|
||||||
|
"enable_pnginfo": OptionInfo(True, "Write infotext to metadata of the generated image"),
|
||||||
|
"save_txt": OptionInfo(False, "Create a text file with infotext next to every generated image"),
|
||||||
|
|
||||||
|
"add_model_name_to_info": OptionInfo(True, "Add model name to infotext"),
|
||||||
|
"add_model_hash_to_info": OptionInfo(True, "Add model hash to infotext"),
|
||||||
|
"add_vae_name_to_info": OptionInfo(True, "Add VAE name to infotext"),
|
||||||
|
"add_vae_hash_to_info": OptionInfo(True, "Add VAE hash to infotext"),
|
||||||
|
"add_user_name_to_info": OptionInfo(False, "Add user name to infotext when authenticated"),
|
||||||
|
"add_version_to_infotext": OptionInfo(True, "Add program version to infotext"),
|
||||||
"disable_weights_auto_swap": OptionInfo(True, "Disregard checkpoint information from pasted infotext").info("when reading generation parameters from text into UI"),
|
"disable_weights_auto_swap": OptionInfo(True, "Disregard checkpoint information from pasted infotext").info("when reading generation parameters from text into UI"),
|
||||||
|
"infotext_skip_pasting": OptionInfo([], "Disregard fields from pasted infotext", ui_components.DropdownMulti, lambda: {"choices": shared_items.get_infotext_names()}),
|
||||||
"infotext_styles": OptionInfo("Apply if any", "Infer styles from prompts of pasted infotext", gr.Radio, {"choices": ["Ignore", "Apply", "Discard", "Apply if any"]}).info("when reading generation parameters from text into UI)").html("""<ul style='margin-left: 1.5em'>
|
"infotext_styles": OptionInfo("Apply if any", "Infer styles from prompts of pasted infotext", gr.Radio, {"choices": ["Ignore", "Apply", "Discard", "Apply if any"]}).info("when reading generation parameters from text into UI)").html("""<ul style='margin-left: 1.5em'>
|
||||||
<li>Ignore: keep prompt and styles dropdown as it is.</li>
|
<li>Ignore: keep prompt and styles dropdown as it is.</li>
|
||||||
<li>Apply: remove style text from prompt, always replace styles dropdown value with found styles (even if none are found).</li>
|
<li>Apply: remove style text from prompt, always replace styles dropdown value with found styles (even if none are found).</li>
|
||||||
@ -297,7 +320,7 @@ options_templates.update(options_section(('infotext', "Infotext"), {
|
|||||||
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('ui', "Live previews"), {
|
options_templates.update(options_section(('ui', "Live previews", "ui"), {
|
||||||
"show_progressbar": OptionInfo(True, "Show progressbar"),
|
"show_progressbar": OptionInfo(True, "Show progressbar"),
|
||||||
"live_previews_enable": OptionInfo(True, "Show live previews of the created image"),
|
"live_previews_enable": OptionInfo(True, "Show live previews of the created image"),
|
||||||
"live_previews_image_format": OptionInfo("png", "Live preview file format", gr.Radio, {"choices": ["jpeg", "png", "webp"]}),
|
"live_previews_image_format": OptionInfo("png", "Live preview file format", gr.Radio, {"choices": ["jpeg", "png", "webp"]}),
|
||||||
@ -310,7 +333,7 @@ options_templates.update(options_section(('ui', "Live previews"), {
|
|||||||
"live_preview_fast_interrupt": OptionInfo(False, "Return image with chosen live preview method on interrupt").info("makes interrupts faster"),
|
"live_preview_fast_interrupt": OptionInfo(False, "Return image with chosen live preview method on interrupt").info("makes interrupts faster"),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('sampler-params', "Sampler parameters"), {
|
options_templates.update(options_section(('sampler-params', "Sampler parameters", "sd"), {
|
||||||
"hide_samplers": OptionInfo([], "Hide samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in shared_items.list_samplers()]}).needs_reload_ui(),
|
"hide_samplers": OptionInfo([], "Hide samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in shared_items.list_samplers()]}).needs_reload_ui(),
|
||||||
"eta_ddim": OptionInfo(0.0, "Eta for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Eta DDIM').info("noise multiplier; higher = more unpredictable results"),
|
"eta_ddim": OptionInfo(0.0, "Eta for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Eta DDIM').info("noise multiplier; higher = more unpredictable results"),
|
||||||
"eta_ancestral": OptionInfo(1.0, "Eta for k-diffusion samplers", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Eta').info("noise multiplier; currently only applies to ancestral samplers (i.e. Euler a) and SDE samplers"),
|
"eta_ancestral": OptionInfo(1.0, "Eta for k-diffusion samplers", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Eta').info("noise multiplier; currently only applies to ancestral samplers (i.e. Euler a) and SDE samplers"),
|
||||||
@ -332,7 +355,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters"
|
|||||||
'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'),
|
'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
options_templates.update(options_section(('postprocessing', "Postprocessing"), {
|
options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), {
|
||||||
'postprocessing_enable_in_main_ui': OptionInfo([], "Enable postprocessing operations in txt2img and img2img tabs", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
|
'postprocessing_enable_in_main_ui': OptionInfo([], "Enable postprocessing operations in txt2img and img2img tabs", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
|
||||||
'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
|
'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
|
||||||
'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
|
'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import csv
|
import csv
|
||||||
|
import fnmatch
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
@ -10,6 +11,23 @@ class PromptStyle(typing.NamedTuple):
|
|||||||
name: str
|
name: str
|
||||||
prompt: str
|
prompt: str
|
||||||
negative_prompt: str
|
negative_prompt: str
|
||||||
|
path: str = None
|
||||||
|
|
||||||
|
|
||||||
|
def clean_text(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Iterating through a list of regular expressions and replacement strings, we
|
||||||
|
clean up the prompt and style text to make it easier to match against each
|
||||||
|
other.
|
||||||
|
"""
|
||||||
|
re_list = [
|
||||||
|
("multiple commas", re.compile("(,+\s+)+,?"), ", "),
|
||||||
|
("multiple spaces", re.compile("\s{2,}"), " "),
|
||||||
|
]
|
||||||
|
for _, regex, replace in re_list:
|
||||||
|
text = regex.sub(replace, text)
|
||||||
|
|
||||||
|
return text.strip(", ")
|
||||||
|
|
||||||
|
|
||||||
def merge_prompts(style_prompt: str, prompt: str) -> str:
|
def merge_prompts(style_prompt: str, prompt: str) -> str:
|
||||||
@ -26,41 +44,64 @@ def apply_styles_to_prompt(prompt, styles):
|
|||||||
for style in styles:
|
for style in styles:
|
||||||
prompt = merge_prompts(style, prompt)
|
prompt = merge_prompts(style, prompt)
|
||||||
|
|
||||||
return prompt
|
return clean_text(prompt)
|
||||||
|
|
||||||
|
|
||||||
re_spaces = re.compile(" +")
|
def unwrap_style_text_from_prompt(style_text, prompt):
|
||||||
|
"""
|
||||||
|
Checks the prompt to see if the style text is wrapped around it. If so,
|
||||||
|
returns True plus the prompt text without the style text. Otherwise, returns
|
||||||
|
False with the original prompt.
|
||||||
|
|
||||||
|
Note that the "cleaned" version of the style text is only used for matching
|
||||||
def extract_style_text_from_prompt(style_text, prompt):
|
purposes here. It isn't returned; the original style text is not modified.
|
||||||
stripped_prompt = re.sub(re_spaces, " ", prompt.strip())
|
"""
|
||||||
stripped_style_text = re.sub(re_spaces, " ", style_text.strip())
|
stripped_prompt = clean_text(prompt)
|
||||||
|
stripped_style_text = clean_text(style_text)
|
||||||
if "{prompt}" in stripped_style_text:
|
if "{prompt}" in stripped_style_text:
|
||||||
|
# Work out whether the prompt is wrapped in the style text. If so, we
|
||||||
|
# return True and the "inner" prompt text that isn't part of the style.
|
||||||
|
try:
|
||||||
left, right = stripped_style_text.split("{prompt}", 2)
|
left, right = stripped_style_text.split("{prompt}", 2)
|
||||||
|
except ValueError as e:
|
||||||
|
# If the style text has multple "{prompt}"s, we can't split it into
|
||||||
|
# two parts. This is an error, but we can't do anything about it.
|
||||||
|
print(f"Unable to compare style text to prompt:\n{style_text}")
|
||||||
|
print(f"Error: {e}")
|
||||||
|
return False, prompt
|
||||||
if stripped_prompt.startswith(left) and stripped_prompt.endswith(right):
|
if stripped_prompt.startswith(left) and stripped_prompt.endswith(right):
|
||||||
prompt = stripped_prompt[len(left):len(stripped_prompt)-len(right)]
|
prompt = stripped_prompt[len(left) : len(stripped_prompt) - len(right)]
|
||||||
return True, prompt
|
return True, prompt
|
||||||
else:
|
else:
|
||||||
|
# Work out whether the given prompt ends with the style text. If so, we
|
||||||
|
# return True and the prompt text up to where the style text starts.
|
||||||
if stripped_prompt.endswith(stripped_style_text):
|
if stripped_prompt.endswith(stripped_style_text):
|
||||||
prompt = stripped_prompt[:len(stripped_prompt)-len(stripped_style_text)]
|
prompt = stripped_prompt[: len(stripped_prompt) - len(stripped_style_text)]
|
||||||
|
if prompt.endswith(", "):
|
||||||
if prompt.endswith(', '):
|
|
||||||
prompt = prompt[:-2]
|
prompt = prompt[:-2]
|
||||||
|
|
||||||
return True, prompt
|
return True, prompt
|
||||||
|
|
||||||
return False, prompt
|
return False, prompt
|
||||||
|
|
||||||
|
|
||||||
def extract_style_from_prompts(style: PromptStyle, prompt, negative_prompt):
|
def extract_original_prompts(style: PromptStyle, prompt, negative_prompt):
|
||||||
|
"""
|
||||||
|
Takes a style and compares it to the prompt and negative prompt. If the style
|
||||||
|
matches, returns True plus the prompt and negative prompt with the style text
|
||||||
|
removed. Otherwise, returns False with the original prompt and negative prompt.
|
||||||
|
"""
|
||||||
if not style.prompt and not style.negative_prompt:
|
if not style.prompt and not style.negative_prompt:
|
||||||
return False, prompt, negative_prompt
|
return False, prompt, negative_prompt
|
||||||
|
|
||||||
match_positive, extracted_positive = extract_style_text_from_prompt(style.prompt, prompt)
|
match_positive, extracted_positive = unwrap_style_text_from_prompt(
|
||||||
|
style.prompt, prompt
|
||||||
|
)
|
||||||
if not match_positive:
|
if not match_positive:
|
||||||
return False, prompt, negative_prompt
|
return False, prompt, negative_prompt
|
||||||
|
|
||||||
match_negative, extracted_negative = extract_style_text_from_prompt(style.negative_prompt, negative_prompt)
|
match_negative, extracted_negative = unwrap_style_text_from_prompt(
|
||||||
|
style.negative_prompt, negative_prompt
|
||||||
|
)
|
||||||
if not match_negative:
|
if not match_negative:
|
||||||
return False, prompt, negative_prompt
|
return False, prompt, negative_prompt
|
||||||
|
|
||||||
@ -69,25 +110,88 @@ def extract_style_from_prompts(style: PromptStyle, prompt, negative_prompt):
|
|||||||
|
|
||||||
class StyleDatabase:
|
class StyleDatabase:
|
||||||
def __init__(self, path: str):
|
def __init__(self, path: str):
|
||||||
self.no_style = PromptStyle("None", "", "")
|
self.no_style = PromptStyle("None", "", "", None)
|
||||||
self.styles = {}
|
self.styles = {}
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
|
folder, file = os.path.split(self.path)
|
||||||
|
self.default_file = file.split("*")[0] + ".csv"
|
||||||
|
if self.default_file == ".csv":
|
||||||
|
self.default_file = "styles.csv"
|
||||||
|
self.default_path = os.path.join(folder, self.default_file)
|
||||||
|
|
||||||
|
self.prompt_fields = [field for field in PromptStyle._fields if field != "path"]
|
||||||
|
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
|
"""
|
||||||
|
Clears the style database and reloads the styles from the CSV file(s)
|
||||||
|
matching the path used to initialize the database.
|
||||||
|
"""
|
||||||
self.styles.clear()
|
self.styles.clear()
|
||||||
|
|
||||||
if not os.path.exists(self.path):
|
path, filename = os.path.split(self.path)
|
||||||
return
|
|
||||||
|
|
||||||
with open(self.path, "r", encoding="utf-8-sig", newline='') as file:
|
if "*" in filename:
|
||||||
|
fileglob = filename.split("*")[0] + "*.csv"
|
||||||
|
filelist = []
|
||||||
|
for file in os.listdir(path):
|
||||||
|
if fnmatch.fnmatch(file, fileglob):
|
||||||
|
filelist.append(file)
|
||||||
|
# Add a visible divider to the style list
|
||||||
|
half_len = round(len(file) / 2)
|
||||||
|
divider = f"{'-' * (20 - half_len)} {file.upper()}"
|
||||||
|
divider = f"{divider} {'-' * (40 - len(divider))}"
|
||||||
|
self.styles[divider] = PromptStyle(
|
||||||
|
f"{divider}", None, None, "do_not_save"
|
||||||
|
)
|
||||||
|
# Add styles from this CSV file
|
||||||
|
self.load_from_csv(os.path.join(path, file))
|
||||||
|
if len(filelist) == 0:
|
||||||
|
print(f"No styles found in {path} matching {fileglob}")
|
||||||
|
return
|
||||||
|
elif not os.path.exists(self.path):
|
||||||
|
print(f"Style database not found: {self.path}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.load_from_csv(self.path)
|
||||||
|
|
||||||
|
def load_from_csv(self, path: str):
|
||||||
|
with open(path, "r", encoding="utf-8-sig", newline="") as file:
|
||||||
reader = csv.DictReader(file, skipinitialspace=True)
|
reader = csv.DictReader(file, skipinitialspace=True)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
|
# Ignore empty rows or rows starting with a comment
|
||||||
|
if not row or row["name"].startswith("#"):
|
||||||
|
continue
|
||||||
# Support loading old CSV format with "name, text"-columns
|
# Support loading old CSV format with "name, text"-columns
|
||||||
prompt = row["prompt"] if "prompt" in row else row["text"]
|
prompt = row["prompt"] if "prompt" in row else row["text"]
|
||||||
negative_prompt = row.get("negative_prompt", "")
|
negative_prompt = row.get("negative_prompt", "")
|
||||||
self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt)
|
# Add style to database
|
||||||
|
self.styles[row["name"]] = PromptStyle(
|
||||||
|
row["name"], prompt, negative_prompt, path
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_style_paths(self) -> list():
|
||||||
|
"""
|
||||||
|
Returns a list of all distinct paths, including the default path, of
|
||||||
|
files that styles are loaded from."""
|
||||||
|
# Update any styles without a path to the default path
|
||||||
|
for style in list(self.styles.values()):
|
||||||
|
if not style.path:
|
||||||
|
self.styles[style.name] = style._replace(path=self.default_path)
|
||||||
|
|
||||||
|
# Create a list of all distinct paths, including the default path
|
||||||
|
style_paths = set()
|
||||||
|
style_paths.add(self.default_path)
|
||||||
|
for _, style in self.styles.items():
|
||||||
|
if style.path:
|
||||||
|
style_paths.add(style.path)
|
||||||
|
|
||||||
|
# Remove any paths for styles that are just list dividers
|
||||||
|
style_paths.remove("do_not_save")
|
||||||
|
|
||||||
|
return list(style_paths)
|
||||||
|
|
||||||
def get_style_prompts(self, styles):
|
def get_style_prompts(self, styles):
|
||||||
return [self.styles.get(x, self.no_style).prompt for x in styles]
|
return [self.styles.get(x, self.no_style).prompt for x in styles]
|
||||||
@ -96,20 +200,53 @@ class StyleDatabase:
|
|||||||
return [self.styles.get(x, self.no_style).negative_prompt for x in styles]
|
return [self.styles.get(x, self.no_style).negative_prompt for x in styles]
|
||||||
|
|
||||||
def apply_styles_to_prompt(self, prompt, styles):
|
def apply_styles_to_prompt(self, prompt, styles):
|
||||||
return apply_styles_to_prompt(prompt, [self.styles.get(x, self.no_style).prompt for x in styles])
|
return apply_styles_to_prompt(
|
||||||
|
prompt, [self.styles.get(x, self.no_style).prompt for x in styles]
|
||||||
|
)
|
||||||
|
|
||||||
def apply_negative_styles_to_prompt(self, prompt, styles):
|
def apply_negative_styles_to_prompt(self, prompt, styles):
|
||||||
return apply_styles_to_prompt(prompt, [self.styles.get(x, self.no_style).negative_prompt for x in styles])
|
return apply_styles_to_prompt(
|
||||||
|
prompt, [self.styles.get(x, self.no_style).negative_prompt for x in styles]
|
||||||
|
)
|
||||||
|
|
||||||
def save_styles(self, path: str) -> None:
|
def save_styles(self, path: str = None) -> None:
|
||||||
|
# The path argument is deprecated, but kept for backwards compatibility
|
||||||
|
_ = path
|
||||||
|
|
||||||
|
# Update any styles without a path to the default path
|
||||||
|
for style in list(self.styles.values()):
|
||||||
|
if not style.path:
|
||||||
|
self.styles[style.name] = style._replace(path=self.default_path)
|
||||||
|
|
||||||
|
# Create a list of all distinct paths, including the default path
|
||||||
|
style_paths = set()
|
||||||
|
style_paths.add(self.default_path)
|
||||||
|
for _, style in self.styles.items():
|
||||||
|
if style.path:
|
||||||
|
style_paths.add(style.path)
|
||||||
|
|
||||||
|
# Remove any paths for styles that are just list dividers
|
||||||
|
style_paths.remove("do_not_save")
|
||||||
|
|
||||||
|
csv_names = [os.path.split(path)[1].lower() for path in style_paths]
|
||||||
|
|
||||||
|
for style_path in style_paths:
|
||||||
# Always keep a backup file around
|
# Always keep a backup file around
|
||||||
if os.path.exists(path):
|
if os.path.exists(style_path):
|
||||||
shutil.copy(path, f"{path}.bak")
|
shutil.copy(style_path, f"{style_path}.bak")
|
||||||
|
|
||||||
with open(path, "w", encoding="utf-8-sig", newline='') as file:
|
# Write the styles to the CSV file
|
||||||
writer = csv.DictWriter(file, fieldnames=PromptStyle._fields)
|
with open(style_path, "w", encoding="utf-8-sig", newline="") as file:
|
||||||
|
writer = csv.DictWriter(file, fieldnames=self.prompt_fields)
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
writer.writerows(style._asdict() for k, style in self.styles.items())
|
for style in (s for s in self.styles.values() if s.path == style_path):
|
||||||
|
# Skip style list dividers, e.g. "STYLES.CSV"
|
||||||
|
if style.name.lower().strip("# ") in csv_names:
|
||||||
|
continue
|
||||||
|
# Write style fields, ignoring the path field
|
||||||
|
writer.writerow(
|
||||||
|
{k: v for k, v in style._asdict().items() if k != "path"}
|
||||||
|
)
|
||||||
|
|
||||||
def extract_styles_from_prompt(self, prompt, negative_prompt):
|
def extract_styles_from_prompt(self, prompt, negative_prompt):
|
||||||
extracted = []
|
extracted = []
|
||||||
@ -120,7 +257,9 @@ class StyleDatabase:
|
|||||||
found_style = None
|
found_style = None
|
||||||
|
|
||||||
for style in applicable_styles:
|
for style in applicable_styles:
|
||||||
is_match, new_prompt, new_neg_prompt = extract_style_from_prompts(style, prompt, negative_prompt)
|
is_match, new_prompt, new_neg_prompt = extract_original_prompts(
|
||||||
|
style, prompt, negative_prompt
|
||||||
|
)
|
||||||
if is_match:
|
if is_match:
|
||||||
found_style = style
|
found_style = style
|
||||||
prompt = new_prompt
|
prompt = new_prompt
|
||||||
|
@ -3,6 +3,8 @@ import requests
|
|||||||
import os
|
import os
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import ImageDraw
|
from PIL import ImageDraw
|
||||||
|
from modules import paths_internal
|
||||||
|
from pkg_resources import parse_version
|
||||||
|
|
||||||
GREEN = "#0F0"
|
GREEN = "#0F0"
|
||||||
BLUE = "#00F"
|
BLUE = "#00F"
|
||||||
@ -25,7 +27,6 @@ def crop_image(im, settings):
|
|||||||
elif is_portrait(settings.crop_width, settings.crop_height):
|
elif is_portrait(settings.crop_width, settings.crop_height):
|
||||||
scale_by = settings.crop_height / im.height
|
scale_by = settings.crop_height / im.height
|
||||||
|
|
||||||
|
|
||||||
im = im.resize((int(im.width * scale_by), int(im.height * scale_by)))
|
im = im.resize((int(im.width * scale_by), int(im.height * scale_by)))
|
||||||
im_debug = im.copy()
|
im_debug = im.copy()
|
||||||
|
|
||||||
@ -69,6 +70,7 @@ def crop_image(im, settings):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def focal_point(im, settings):
|
def focal_point(im, settings):
|
||||||
corner_points = image_corner_points(im, settings) if settings.corner_points_weight > 0 else []
|
corner_points = image_corner_points(im, settings) if settings.corner_points_weight > 0 else []
|
||||||
entropy_points = image_entropy_points(im, settings) if settings.entropy_points_weight > 0 else []
|
entropy_points = image_entropy_points(im, settings) if settings.entropy_points_weight > 0 else []
|
||||||
@ -110,7 +112,7 @@ def focal_point(im, settings):
|
|||||||
if corner_centroid is not None:
|
if corner_centroid is not None:
|
||||||
color = BLUE
|
color = BLUE
|
||||||
box = corner_centroid.bounding(max_size * corner_centroid.weight)
|
box = corner_centroid.bounding(max_size * corner_centroid.weight)
|
||||||
d.text((box[0], box[1]-15), f"Edge: {corner_centroid.weight:.02f}", fill=color)
|
d.text((box[0], box[1] - 15), f"Edge: {corner_centroid.weight:.02f}", fill=color)
|
||||||
d.ellipse(box, outline=color)
|
d.ellipse(box, outline=color)
|
||||||
if len(corner_points) > 1:
|
if len(corner_points) > 1:
|
||||||
for f in corner_points:
|
for f in corner_points:
|
||||||
@ -118,7 +120,7 @@ def focal_point(im, settings):
|
|||||||
if entropy_centroid is not None:
|
if entropy_centroid is not None:
|
||||||
color = "#ff0"
|
color = "#ff0"
|
||||||
box = entropy_centroid.bounding(max_size * entropy_centroid.weight)
|
box = entropy_centroid.bounding(max_size * entropy_centroid.weight)
|
||||||
d.text((box[0], box[1]-15), f"Entropy: {entropy_centroid.weight:.02f}", fill=color)
|
d.text((box[0], box[1] - 15), f"Entropy: {entropy_centroid.weight:.02f}", fill=color)
|
||||||
d.ellipse(box, outline=color)
|
d.ellipse(box, outline=color)
|
||||||
if len(entropy_points) > 1:
|
if len(entropy_points) > 1:
|
||||||
for f in entropy_points:
|
for f in entropy_points:
|
||||||
@ -126,7 +128,7 @@ def focal_point(im, settings):
|
|||||||
if face_centroid is not None:
|
if face_centroid is not None:
|
||||||
color = RED
|
color = RED
|
||||||
box = face_centroid.bounding(max_size * face_centroid.weight)
|
box = face_centroid.bounding(max_size * face_centroid.weight)
|
||||||
d.text((box[0], box[1]-15), f"Face: {face_centroid.weight:.02f}", fill=color)
|
d.text((box[0], box[1] - 15), f"Face: {face_centroid.weight:.02f}", fill=color)
|
||||||
d.ellipse(box, outline=color)
|
d.ellipse(box, outline=color)
|
||||||
if len(face_points) > 1:
|
if len(face_points) > 1:
|
||||||
for f in face_points:
|
for f in face_points:
|
||||||
@ -159,8 +161,8 @@ def image_face_points(im, settings):
|
|||||||
PointOfInterest(
|
PointOfInterest(
|
||||||
int(x + (w * 0.5)), # face focus left/right is center
|
int(x + (w * 0.5)), # face focus left/right is center
|
||||||
int(y + (h * 0.33)), # face focus up/down is close to the top of the head
|
int(y + (h * 0.33)), # face focus up/down is close to the top of the head
|
||||||
size = w,
|
size=w,
|
||||||
weight = 1/len(faces[1])
|
weight=1 / len(faces[1])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
@ -169,27 +171,29 @@ def image_face_points(im, settings):
|
|||||||
gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY)
|
gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
tries = [
|
tries = [
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ],
|
[f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01],
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ],
|
[f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05],
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ],
|
[f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05],
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ],
|
[f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05],
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ],
|
[f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05],
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ],
|
[f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05],
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ],
|
[f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05],
|
||||||
[ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ]
|
[f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05]
|
||||||
]
|
]
|
||||||
for t in tries:
|
for t in tries:
|
||||||
classifier = cv2.CascadeClassifier(t[0])
|
classifier = cv2.CascadeClassifier(t[0])
|
||||||
minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side
|
minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side
|
||||||
try:
|
try:
|
||||||
faces = classifier.detectMultiScale(gray, scaleFactor=1.1,
|
faces = classifier.detectMultiScale(gray, scaleFactor=1.1,
|
||||||
minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE)
|
minNeighbors=7, minSize=(minsize, minsize),
|
||||||
|
flags=cv2.CASCADE_SCALE_IMAGE)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if faces:
|
if faces:
|
||||||
rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces]
|
rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces]
|
||||||
return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2]), weight=1/len(rects)) for r in rects]
|
return [PointOfInterest((r[0] + r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0] - r[2]),
|
||||||
|
weight=1 / len(rects)) for r in rects]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@ -198,7 +202,7 @@ def image_corner_points(im, settings):
|
|||||||
|
|
||||||
# naive attempt at preventing focal points from collecting at watermarks near the bottom
|
# naive attempt at preventing focal points from collecting at watermarks near the bottom
|
||||||
gd = ImageDraw.Draw(grayscale)
|
gd = ImageDraw.Draw(grayscale)
|
||||||
gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999")
|
gd.rectangle([0, im.height * .9, im.width, im.height], fill="#999")
|
||||||
|
|
||||||
np_im = np.array(grayscale)
|
np_im = np.array(grayscale)
|
||||||
|
|
||||||
@ -206,7 +210,7 @@ def image_corner_points(im, settings):
|
|||||||
np_im,
|
np_im,
|
||||||
maxCorners=100,
|
maxCorners=100,
|
||||||
qualityLevel=0.04,
|
qualityLevel=0.04,
|
||||||
minDistance=min(grayscale.width, grayscale.height)*0.06,
|
minDistance=min(grayscale.width, grayscale.height) * 0.06,
|
||||||
useHarrisDetector=False,
|
useHarrisDetector=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -216,7 +220,7 @@ def image_corner_points(im, settings):
|
|||||||
focal_points = []
|
focal_points = []
|
||||||
for point in points:
|
for point in points:
|
||||||
x, y = point.ravel()
|
x, y = point.ravel()
|
||||||
focal_points.append(PointOfInterest(x, y, size=4, weight=1/len(points)))
|
focal_points.append(PointOfInterest(x, y, size=4, weight=1 / len(points)))
|
||||||
|
|
||||||
return focal_points
|
return focal_points
|
||||||
|
|
||||||
@ -247,8 +251,8 @@ def image_entropy_points(im, settings):
|
|||||||
crop_current[move_idx[0]] += 4
|
crop_current[move_idx[0]] += 4
|
||||||
crop_current[move_idx[1]] += 4
|
crop_current[move_idx[1]] += 4
|
||||||
|
|
||||||
x_mid = int(crop_best[0] + settings.crop_width/2)
|
x_mid = int(crop_best[0] + settings.crop_width / 2)
|
||||||
y_mid = int(crop_best[1] + settings.crop_height/2)
|
y_mid = int(crop_best[1] + settings.crop_height / 2)
|
||||||
|
|
||||||
return [PointOfInterest(x_mid, y_mid, size=25, weight=1.0)]
|
return [PointOfInterest(x_mid, y_mid, size=25, weight=1.0)]
|
||||||
|
|
||||||
@ -294,22 +298,23 @@ def is_square(w, h):
|
|||||||
return w == h
|
return w == h
|
||||||
|
|
||||||
|
|
||||||
def download_and_cache_models(dirname):
|
model_dir_opencv = os.path.join(paths_internal.models_path, 'opencv')
|
||||||
download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true'
|
if parse_version(cv2.__version__) >= parse_version('4.8'):
|
||||||
model_file_name = 'face_detection_yunet.onnx'
|
model_file_path = os.path.join(model_dir_opencv, 'face_detection_yunet_2023mar.onnx')
|
||||||
|
model_url = 'https://github.com/opencv/opencv_zoo/blob/b6e370b10f641879a87890d44e42173077154a05/models/face_detection_yunet/face_detection_yunet_2023mar.onnx?raw=true'
|
||||||
|
else:
|
||||||
|
model_file_path = os.path.join(model_dir_opencv, 'face_detection_yunet.onnx')
|
||||||
|
model_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true'
|
||||||
|
|
||||||
os.makedirs(dirname, exist_ok=True)
|
|
||||||
|
|
||||||
cache_file = os.path.join(dirname, model_file_name)
|
def download_and_cache_models():
|
||||||
if not os.path.exists(cache_file):
|
if not os.path.exists(model_file_path):
|
||||||
print(f"downloading face detection model from '{download_url}' to '{cache_file}'")
|
os.makedirs(model_dir_opencv, exist_ok=True)
|
||||||
response = requests.get(download_url)
|
print(f"downloading face detection model from '{model_url}' to '{model_file_path}'")
|
||||||
with open(cache_file, "wb") as f:
|
response = requests.get(model_url)
|
||||||
|
with open(model_file_path, "wb") as f:
|
||||||
f.write(response.content)
|
f.write(response.content)
|
||||||
|
return model_file_path
|
||||||
if os.path.exists(cache_file):
|
|
||||||
return cache_file
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class PointOfInterest:
|
class PointOfInterest:
|
||||||
|
@ -3,7 +3,7 @@ from PIL import Image, ImageOps
|
|||||||
import math
|
import math
|
||||||
import tqdm
|
import tqdm
|
||||||
|
|
||||||
from modules import paths, shared, images, deepbooru
|
from modules import shared, images, deepbooru
|
||||||
from modules.textual_inversion import autocrop
|
from modules.textual_inversion import autocrop
|
||||||
|
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre
|
|||||||
|
|
||||||
dnn_model_path = None
|
dnn_model_path = None
|
||||||
try:
|
try:
|
||||||
dnn_model_path = autocrop.download_and_cache_models(os.path.join(paths.models_path, "opencv"))
|
dnn_model_path = autocrop.download_and_cache_models()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", e)
|
print("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", e)
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ def save_config_state(name):
|
|||||||
filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json")
|
filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json")
|
||||||
print(f"Saving backup of webui/extension state to {filename}.")
|
print(f"Saving backup of webui/extension state to {filename}.")
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
json.dump(current_config_state, f, indent=4)
|
json.dump(current_config_state, f, indent=4, ensure_ascii=False)
|
||||||
config_states.list_config_states()
|
config_states.list_config_states()
|
||||||
new_value = next(iter(config_states.all_config_states.keys()), "Current")
|
new_value = next(iter(config_states.all_config_states.keys()), "Current")
|
||||||
new_choices = ["Current"] + list(config_states.all_config_states.keys())
|
new_choices = ["Current"] + list(config_states.all_config_states.keys())
|
||||||
@ -335,6 +335,11 @@ def normalize_git_url(url):
|
|||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def get_extension_dirname_from_url(url):
|
||||||
|
*parts, last_part = url.split('/')
|
||||||
|
return normalize_git_url(last_part)
|
||||||
|
|
||||||
|
|
||||||
def install_extension_from_url(dirname, url, branch_name=None):
|
def install_extension_from_url(dirname, url, branch_name=None):
|
||||||
check_access()
|
check_access()
|
||||||
|
|
||||||
@ -346,10 +351,7 @@ def install_extension_from_url(dirname, url, branch_name=None):
|
|||||||
assert url, 'No URL specified'
|
assert url, 'No URL specified'
|
||||||
|
|
||||||
if dirname is None or dirname == "":
|
if dirname is None or dirname == "":
|
||||||
*parts, last_part = url.split('/')
|
dirname = get_extension_dirname_from_url(url)
|
||||||
last_part = normalize_git_url(last_part)
|
|
||||||
|
|
||||||
dirname = last_part
|
|
||||||
|
|
||||||
target_dir = os.path.join(extensions.extensions_dir, dirname)
|
target_dir = os.path.join(extensions.extensions_dir, dirname)
|
||||||
assert not os.path.exists(target_dir), f'Extension directory already exists: {target_dir}'
|
assert not os.path.exists(target_dir), f'Extension directory already exists: {target_dir}'
|
||||||
@ -449,7 +451,8 @@ def get_date(info: dict, key):
|
|||||||
|
|
||||||
def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=""):
|
def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=""):
|
||||||
extlist = available_extensions["extensions"]
|
extlist = available_extensions["extensions"]
|
||||||
installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions}
|
installed_extensions = {extension.name for extension in extensions.extensions}
|
||||||
|
installed_extension_urls = {normalize_git_url(extension.remote) for extension in extensions.extensions if extension.remote is not None}
|
||||||
|
|
||||||
tags = available_extensions.get("tags", {})
|
tags = available_extensions.get("tags", {})
|
||||||
tags_to_hide = set(hide_tags)
|
tags_to_hide = set(hide_tags)
|
||||||
@ -482,7 +485,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text="
|
|||||||
if url is None:
|
if url is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
existing = installed_extension_urls.get(normalize_git_url(url), None)
|
existing = get_extension_dirname_from_url(url) in installed_extensions or normalize_git_url(url) in installed_extension_urls
|
||||||
extension_tags = extension_tags + ["installed"] if existing else extension_tags
|
extension_tags = extension_tags + ["installed"] if existing else extension_tags
|
||||||
|
|
||||||
if any(x for x in extension_tags if x in tags_to_hide):
|
if any(x for x in extension_tags if x in tags_to_hide):
|
||||||
|
@ -151,6 +151,11 @@ class ExtraNetworksPage:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/")
|
subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/")
|
||||||
|
|
||||||
|
if shared.opts.extra_networks_dir_button_function:
|
||||||
|
if not subdir.startswith("/"):
|
||||||
|
subdir = "/" + subdir
|
||||||
|
else:
|
||||||
while subdir.startswith("/"):
|
while subdir.startswith("/"):
|
||||||
subdir = subdir[1:]
|
subdir = subdir[1:]
|
||||||
|
|
||||||
@ -370,6 +375,9 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
|
|||||||
|
|
||||||
for page in ui.stored_extra_pages:
|
for page in ui.stored_extra_pages:
|
||||||
with gr.Tab(page.title, elem_id=f"{tabname}_{page.id_page}", elem_classes=["extra-page"]) as tab:
|
with gr.Tab(page.title, elem_id=f"{tabname}_{page.id_page}", elem_classes=["extra-page"]) as tab:
|
||||||
|
with gr.Column(elem_id=f"{tabname}_{page.id_page}_prompts", elem_classes=["extra-page-prompts"]):
|
||||||
|
pass
|
||||||
|
|
||||||
elem_id = f"{tabname}_{page.id_page}_cards_html"
|
elem_id = f"{tabname}_{page.id_page}_cards_html"
|
||||||
page_elem = gr.HTML('Loading...', elem_id=elem_id)
|
page_elem = gr.HTML('Loading...', elem_id=elem_id)
|
||||||
ui.pages.append(page_elem)
|
ui.pages.append(page_elem)
|
||||||
@ -400,7 +408,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
|
|||||||
allow_prompt = "true" if page.allow_prompt else "false"
|
allow_prompt = "true" if page.allow_prompt else "false"
|
||||||
allow_negative_prompt = "true" if page.allow_negative_prompt else "false"
|
allow_negative_prompt = "true" if page.allow_negative_prompt else "false"
|
||||||
|
|
||||||
jscode = 'extraNetworksTabSelected("' + tabname + '", "' + f"{tabname}_{page.id_page}" + '", ' + allow_prompt + ', ' + allow_negative_prompt + ');'
|
jscode = 'extraNetworksTabSelected("' + tabname + '", "' + f"{tabname}_{page.id_page}_prompts" + '", ' + allow_prompt + ', ' + allow_negative_prompt + ');'
|
||||||
|
|
||||||
tab.select(fn=lambda: [gr.update(visible=True) for _ in tab_controls], _js='function(){ ' + jscode + ' }', inputs=[], outputs=tab_controls, show_progress=False)
|
tab.select(fn=lambda: [gr.update(visible=True) for _ in tab_controls], _js='function(){ ' + jscode + ' }', inputs=[], outputs=tab_controls, show_progress=False)
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ class UserMetadataEditor:
|
|||||||
basename, ext = os.path.splitext(filename)
|
basename, ext = os.path.splitext(filename)
|
||||||
|
|
||||||
with open(basename + '.json', "w", encoding="utf8") as file:
|
with open(basename + '.json', "w", encoding="utf8") as file:
|
||||||
json.dump(metadata, file, indent=4)
|
json.dump(metadata, file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
def save_user_metadata(self, name, desc, notes):
|
def save_user_metadata(self, name, desc, notes):
|
||||||
user_metadata = self.get_user_metadata(name)
|
user_metadata = self.get_user_metadata(name)
|
||||||
|
@ -141,7 +141,7 @@ class UiLoadsave:
|
|||||||
|
|
||||||
def write_to_file(self, current_ui_settings):
|
def write_to_file(self, current_ui_settings):
|
||||||
with open(self.filename, "w", encoding="utf8") as file:
|
with open(self.filename, "w", encoding="utf8") as file:
|
||||||
json.dump(current_ui_settings, file, indent=4)
|
json.dump(current_ui_settings, file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
def dump_defaults(self):
|
def dump_defaults(self):
|
||||||
"""saves default values to a file unless tjhe file is present and there was an error loading default values at start"""
|
"""saves default values to a file unless tjhe file is present and there was an error loading default values at start"""
|
||||||
|
23
style.css
23
style.css
@ -462,6 +462,15 @@ div.toprow-compact-tools{
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings > div.tab-nav .settings-category{
|
||||||
|
display: block;
|
||||||
|
margin: 1em 0 0.25em 0;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
#settings_result{
|
#settings_result{
|
||||||
height: 1.4em;
|
height: 1.4em;
|
||||||
margin: 0 1.2em;
|
margin: 0 1.2em;
|
||||||
@ -637,6 +646,8 @@ table.popup-table .link{
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
|
max-height: 90%;
|
||||||
|
max-width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fullpage image viewer */
|
/* fullpage image viewer */
|
||||||
@ -840,8 +851,16 @@ footer {
|
|||||||
|
|
||||||
/* extra networks UI */
|
/* extra networks UI */
|
||||||
|
|
||||||
.extra-page .prompt{
|
.extra-page > div.gap{
|
||||||
margin: 0 0 0.5em 0;
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-page-prompts{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-page-prompts.extra-page-prompts-active{
|
||||||
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.extra-network-cards{
|
.extra-network-cards{
|
||||||
|
Loading…
Reference in New Issue
Block a user