aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--extensions-builtin/Lora/networks.py22
-rw-r--r--extensions-builtin/Lora/scripts/lora_script.py3
-rw-r--r--extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js32
-rw-r--r--extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py1
-rw-r--r--extensions-builtin/extra-options-section/scripts/extra_options_section.py42
-rw-r--r--javascript/imageviewer.js5
-rw-r--r--javascript/inputAccordion.js37
-rw-r--r--modules/cache.py3
-rw-r--r--modules/cmd_args.py1
-rw-r--r--modules/devices.py89
-rw-r--r--modules/extensions.py4
-rw-r--r--modules/generation_parameters_copypaste.py44
-rw-r--r--modules/gradio_extensons.py4
-rw-r--r--modules/images.py28
-rw-r--r--modules/img2img.py8
-rw-r--r--modules/initialize.py168
-rw-r--r--modules/initialize_util.py183
-rw-r--r--modules/launch_utils.py45
-rw-r--r--modules/localization.py3
-rw-r--r--modules/logging_config.py16
-rw-r--r--modules/mac_specific.py4
-rw-r--r--modules/options.py238
-rw-r--r--modules/processing.py222
-rw-r--r--modules/rng.py170
-rw-r--r--modules/sd_hijack.py4
-rw-r--r--modules/sd_hijack_inpainting.py95
-rw-r--r--modules/sd_models.py47
-rw-r--r--modules/sd_models_config.py3
-rw-r--r--modules/sd_samplers.py19
-rw-r--r--modules/sd_samplers_cfg_denoiser.py222
-rw-r--r--modules/sd_samplers_common.py173
-rw-r--r--modules/sd_samplers_compvis.py224
-rw-r--r--modules/sd_samplers_kdiffusion.py365
-rw-r--r--modules/sd_samplers_timesteps.py158
-rw-r--r--modules/sd_samplers_timesteps_impl.py135
-rw-r--r--modules/sd_vae.py90
-rw-r--r--modules/shared_cmd_options.py18
-rw-r--r--modules/shared_gradio_themes.py66
-rw-r--r--modules/shared_init.py49
-rw-r--r--modules/shared_items.py49
-rw-r--r--modules/shared_options.py321
-rw-r--r--modules/shared_state.py159
-rw-r--r--modules/shared_total_tqdm.py37
-rw-r--r--modules/sysinfo.py8
-rw-r--r--modules/txt2img.py10
-rw-r--r--modules/ui.py81
-rw-r--r--modules/ui_common.py4
-rw-r--r--modules/ui_components.py49
-rw-r--r--modules/ui_extra_networks.py5
-rw-r--r--modules/ui_extra_networks_checkpoints_user_metadata.py8
-rw-r--r--modules/ui_extra_networks_user_metadata.py6
-rw-r--r--modules/ui_loadsave.py10
-rw-r--r--modules/ui_tempdir.py5
-rw-r--r--modules/util.py58
-rw-r--r--requirements.txt1
-rw-r--r--style.css38
-rw-r--r--test/conftest.py16
-rw-r--r--webui.py381
58 files changed, 2867 insertions, 1419 deletions
diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py
index 17cbe1bb..bc722e90 100644
--- a/extensions-builtin/Lora/networks.py
+++ b/extensions-builtin/Lora/networks.py
@@ -195,6 +195,15 @@ def load_network(name, network_on_disk):
return net
+def purge_networks_from_memory():
+ while len(networks_in_memory) > shared.opts.lora_in_memory_limit and len(networks_in_memory) > 0:
+ name = next(iter(networks_in_memory))
+ networks_in_memory.pop(name, None)
+
+ devices.torch_gc()
+
+
+
def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=None):
already_loaded = {}
@@ -212,15 +221,19 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No
failed_to_load_networks = []
- for i, name in enumerate(names):
+ for i, (network_on_disk, name) in enumerate(zip(networks_on_disk, names)):
net = already_loaded.get(name, None)
- network_on_disk = networks_on_disk[i]
-
if network_on_disk is not None:
+ if net is None:
+ net = networks_in_memory.get(name)
+
if net is None or os.path.getmtime(network_on_disk.filename) > net.mtime:
try:
net = load_network(name, network_on_disk)
+
+ networks_in_memory.pop(name, None)
+ networks_in_memory[name] = net
except Exception as e:
errors.display(e, f"loading network {network_on_disk.filename}")
continue
@@ -242,6 +255,8 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No
if failed_to_load_networks:
sd_hijack.model_hijack.comments.append("Failed to find networks: " + ", ".join(failed_to_load_networks))
+ purge_networks_from_memory()
+
def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]):
weights_backup = getattr(self, "network_weights_backup", None)
@@ -462,6 +477,7 @@ def infotext_pasted(infotext, params):
available_networks = {}
available_network_aliases = {}
loaded_networks = []
+networks_in_memory = {}
available_network_hash_lookup = {}
forbidden_network_aliases = {}
diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py
index cd28afc9..6ab8b6e7 100644
--- a/extensions-builtin/Lora/scripts/lora_script.py
+++ b/extensions-builtin/Lora/scripts/lora_script.py
@@ -65,6 +65,7 @@ shared.options_templates.update(shared.options_section(('extra_networks', "Extra
"lora_add_hashes_to_infotext": shared.OptionInfo(True, "Add Lora hashes to infotext"),
"lora_show_all": shared.OptionInfo(False, "Always show all networks on the Lora page").info("otherwise, those detected as for incompatible version of Stable Diffusion will be hidden"),
"lora_hide_unknown_for_versions": shared.OptionInfo([], "Hide networks of unknown versions for model versions", gr.CheckboxGroup, {"choices": ["SD1", "SD2", "SDXL"]}),
+ "lora_in_memory_limit": shared.OptionInfo(0, "Number of Lora networks to keep cached in memory", gr.Number, {"precision": 0}),
}))
@@ -121,3 +122,5 @@ def infotext_pasted(infotext, d):
script_callbacks.on_infotext_pasted(infotext_pasted)
+
+shared.opts.onchange("lora_in_memory_limit", networks.purge_networks_from_memory)
diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js
index 30199dcd..e7616b98 100644
--- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js
+++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js
@@ -42,6 +42,11 @@ onUiLoaded(async() => {
}
}
+ // Detect whether the element has a horizontal scroll bar
+ function hasHorizontalScrollbar(element) {
+ return element.scrollWidth > element.clientWidth;
+ }
+
// Function for defining the "Ctrl", "Shift" and "Alt" keys
function isModifierKey(event, key) {
switch (key) {
@@ -201,7 +206,8 @@ onUiLoaded(async() => {
canvas_hotkey_overlap: "KeyO",
canvas_disabled_functions: [],
canvas_show_tooltip: true,
- canvas_blur_prompt: false
+ canvas_auto_expand: true,
+ canvas_blur_prompt: false,
};
const functionMap = {
@@ -648,8 +654,32 @@ onUiLoaded(async() => {
mouseY = e.offsetY;
}
+ // Simulation of the function to put a long image into the screen.
+ // We detect if an image has a scroll bar or not, make a fullscreen to reveal the image, then reduce it to fit into the element.
+ // We hide the image and show it to the user when it is ready.
+ function autoExpand(e) {
+ const canvas = document.querySelector(`${elemId} canvas[key="interface"]`);
+ const isMainTab = activeElement === elementIDs.inpaint || activeElement === elementIDs.inpaintSketch || activeElement === elementIDs.sketch;
+
+ if (canvas && isMainTab) {
+ if (hasHorizontalScrollbar(targetElement)) {
+ targetElement.style.visibility = "hidden";
+ setTimeout(() => {
+ fitToScreen();
+ resetZoom();
+ targetElement.style.visibility = "visible";
+ }, 10);
+ }
+ }
+ }
+
targetElement.addEventListener("mousemove", getMousePosition);
+ // Apply auto expand if enabled
+ if (hotkeysConfig.canvas_auto_expand) {
+ targetElement.addEventListener("mousemove", autoExpand);
+ }
+
// Handle events only inside the targetElement
let isKeyDownHandlerAttached = false;
diff --git a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py
index 380176ce..2d8d2d1c 100644
--- a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py
+++ b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py
@@ -9,6 +9,7 @@ shared.options_templates.update(shared.options_section(('canvas_hotkey', "Canvas
"canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas positon"),
"canvas_hotkey_overlap": shared.OptionInfo("O", "Toggle overlap").info("Technical button, neededs for testing"),
"canvas_show_tooltip": shared.OptionInfo(True, "Enable tooltip on the canvas"),
+ "canvas_auto_expand": shared.OptionInfo(True, "Automatically expands an image that does not fit completely in the canvas area, similar to manually pressing the S and R buttons"),
"canvas_blur_prompt": shared.OptionInfo(False, "Take the focus off the prompt when working with a canvas"),
"canvas_disabled_functions": shared.OptionInfo(["Overlap"], "Disable function that you don't use", gr.CheckboxGroup, {"choices": ["Zoom","Adjust brush size", "Moving canvas","Fullscreen","Reset Zoom","Overlap"]}),
}))
diff --git a/extensions-builtin/extra-options-section/scripts/extra_options_section.py b/extensions-builtin/extra-options-section/scripts/extra_options_section.py
index 7bb0a1bb..588b64d2 100644
--- a/extensions-builtin/extra-options-section/scripts/extra_options_section.py
+++ b/extensions-builtin/extra-options-section/scripts/extra_options_section.py
@@ -1,5 +1,7 @@
+import math
+
import gradio as gr
-from modules import scripts, shared, ui_components, ui_settings
+from modules import scripts, shared, ui_components, ui_settings, generation_parameters_copypaste
from modules.ui_components import FormColumn
@@ -19,18 +21,37 @@ class ExtraOptionsSection(scripts.Script):
def ui(self, is_img2img):
self.comps = []
self.setting_names = []
+ self.infotext_fields = []
+
+ mapping = {k: v for v, k in generation_parameters_copypaste.infotext_to_setting_name_mapping}
with gr.Blocks() as interface:
- with gr.Accordion("Options", open=False) if shared.opts.extra_options_accordion and shared.opts.extra_options else gr.Group(), gr.Row():
- for setting_name in shared.opts.extra_options:
- with FormColumn():
- comp = ui_settings.create_setting_component(setting_name)
+ with gr.Accordion("Options", open=False) if shared.opts.extra_options_accordion and shared.opts.extra_options else gr.Group():
+
+ row_count = math.ceil(len(shared.opts.extra_options) / shared.opts.extra_options_cols)
+
+ for row in range(row_count):
+ with gr.Row():
+ for col in range(shared.opts.extra_options_cols):
+ index = row * shared.opts.extra_options_cols + col
+ if index >= len(shared.opts.extra_options):
+ break
+
+ setting_name = shared.opts.extra_options[index]
- self.comps.append(comp)
- self.setting_names.append(setting_name)
+ with FormColumn():
+ comp = ui_settings.create_setting_component(setting_name)
+
+ self.comps.append(comp)
+ self.setting_names.append(setting_name)
+
+ setting_infotext_name = mapping.get(setting_name)
+ if setting_infotext_name is not None:
+ self.infotext_fields.append((comp, setting_infotext_name))
def get_settings_values():
- return [ui_settings.get_value_for_setting(key) for key in self.setting_names]
+ res = [ui_settings.get_value_for_setting(key) for key in self.setting_names]
+ return res[0] if len(res) == 1 else res
interface.load(fn=get_settings_values, inputs=[], outputs=self.comps, queue=False, show_progress=False)
@@ -44,5 +65,8 @@ class ExtraOptionsSection(scripts.Script):
shared.options_templates.update(shared.options_section(('ui', "User interface"), {
"extra_options": shared.OptionInfo([], "Options in main UI", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in txt2img/img2img interfaces").needs_reload_ui(),
- "extra_options_accordion": shared.OptionInfo(False, "Place options in main UI into an accordion").needs_restart()
+ "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()
}))
+
+
diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js
index 677e95c1..c21d396e 100644
--- a/javascript/imageviewer.js
+++ b/javascript/imageviewer.js
@@ -136,6 +136,11 @@ function setupImageForLightbox(e) {
var event = isFirefox ? 'mousedown' : 'click';
e.addEventListener(event, function(evt) {
+ if (evt.button == 1) {
+ open(evt.target.src);
+ evt.preventDefault();
+ return;
+ }
if (!opts.js_modal_lightbox || evt.button != 0) return;
modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed);
diff --git a/javascript/inputAccordion.js b/javascript/inputAccordion.js
new file mode 100644
index 00000000..f2839852
--- /dev/null
+++ b/javascript/inputAccordion.js
@@ -0,0 +1,37 @@
+var observerAccordionOpen = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutationRecord) {
+ var elem = mutationRecord.target;
+ var open = elem.classList.contains('open');
+
+ var accordion = elem.parentNode;
+ accordion.classList.toggle('input-accordion-open', open);
+
+ var checkbox = gradioApp().querySelector('#' + accordion.id + "-checkbox input");
+ checkbox.checked = open;
+ updateInput(checkbox);
+
+ var extra = gradioApp().querySelector('#' + accordion.id + "-extra");
+ if (extra) {
+ extra.style.display = open ? "" : "none";
+ }
+ });
+});
+
+function inputAccordionChecked(id, checked) {
+ var label = gradioApp().querySelector('#' + id + " .label-wrap");
+ if (label.classList.contains('open') != checked) {
+ label.click();
+ }
+}
+
+onUiLoaded(function() {
+ for (var accordion of gradioApp().querySelectorAll('.input-accordion')) {
+ var labelWrap = accordion.querySelector('.label-wrap');
+ observerAccordionOpen.observe(labelWrap, {attributes: true, attributeFilter: ['class']});
+
+ var extra = gradioApp().querySelector('#' + accordion.id + "-extra");
+ if (extra) {
+ labelWrap.insertBefore(extra, labelWrap.lastElementChild);
+ }
+ }
+});
diff --git a/modules/cache.py b/modules/cache.py
index 71fe6302..a7cd3aeb 100644
--- a/modules/cache.py
+++ b/modules/cache.py
@@ -1,11 +1,12 @@
import json
+import os
import os.path
import threading
import time
from modules.paths import data_path, script_path
-cache_filename = os.path.join(data_path, "cache.json")
+cache_filename = os.environ.get('SD_WEBUI_CACHE_FILE', os.path.join(data_path, "cache.json"))
cache_data = None
cache_lock = threading.Lock()
diff --git a/modules/cmd_args.py b/modules/cmd_args.py
index 64f21e01..b0a11538 100644
--- a/modules/cmd_args.py
+++ b/modules/cmd_args.py
@@ -16,6 +16,7 @@ parser.add_argument("--test-server", action='store_true', help="launch.py argume
parser.add_argument("--log-startup", action='store_true', help="launch.py argument: print a detailed log of what's happening at startup")
parser.add_argument("--skip-prepare-environment", action='store_true', help="launch.py argument: skip all environment preparation")
parser.add_argument("--skip-install", action='store_true', help="launch.py argument: skip installation of packages")
+parser.add_argument("--loglevel", type=str, help="log level; one of: CRITICAL, ERROR, WARNING, INFO, DEBUG", default=None)
parser.add_argument("--do-not-download-clip", action='store_true', help="do not download CLIP model even if it's not included in the checkpoint")
parser.add_argument("--data-dir", type=str, default=os.path.dirname(os.path.dirname(os.path.realpath(__file__))), help="base path where all user data is stored")
parser.add_argument("--config", type=str, default=sd_default_config, help="path to config which constructs model",)
diff --git a/modules/devices.py b/modules/devices.py
index 00a00b18..c01f0602 100644
--- a/modules/devices.py
+++ b/modules/devices.py
@@ -3,7 +3,7 @@ import contextlib
from functools import lru_cache
import torch
-from modules import errors, rng_philox
+from modules import errors, shared
if sys.platform == "darwin":
from modules import mac_specific
@@ -17,8 +17,6 @@ def has_mps() -> bool:
def get_cuda_device_string():
- from modules import shared
-
if shared.cmd_opts.device_id is not None:
return f"cuda:{shared.cmd_opts.device_id}"
@@ -40,8 +38,6 @@ def get_optimal_device():
def get_device_for(task):
- from modules import shared
-
if task in shared.cmd_opts.use_cpu:
return cpu
@@ -96,87 +92,7 @@ def cond_cast_float(input):
nv_rng = None
-def randn(seed, shape):
- """Generate a tensor with random numbers from a normal distribution using seed.
-
- Uses the seed parameter to set the global torch seed; to generate more with that seed, use randn_like/randn_without_seed."""
-
- from modules.shared import opts
-
- manual_seed(seed)
-
- if opts.randn_source == "NV":
- return torch.asarray(nv_rng.randn(shape), device=device)
-
- if opts.randn_source == "CPU" or device.type == 'mps':
- return torch.randn(shape, device=cpu).to(device)
-
- return torch.randn(shape, device=device)
-
-
-def randn_local(seed, shape):
- """Generate a tensor with random numbers from a normal distribution using seed.
-
- Does not change the global random number generator. You can only generate the seed's first tensor using this function."""
-
- from modules.shared import opts
-
- if opts.randn_source == "NV":
- rng = rng_philox.Generator(seed)
- return torch.asarray(rng.randn(shape), device=device)
-
- local_device = cpu if opts.randn_source == "CPU" or device.type == 'mps' else device
- local_generator = torch.Generator(local_device).manual_seed(int(seed))
- return torch.randn(shape, device=local_device, generator=local_generator).to(device)
-
-
-def randn_like(x):
- """Generate a tensor with random numbers from a normal distribution using the previously initialized genrator.
-
- Use either randn() or manual_seed() to initialize the generator."""
-
- from modules.shared import opts
-
- if opts.randn_source == "NV":
- return torch.asarray(nv_rng.randn(x.shape), device=x.device, dtype=x.dtype)
-
- if opts.randn_source == "CPU" or x.device.type == 'mps':
- return torch.randn_like(x, device=cpu).to(x.device)
-
- return torch.randn_like(x)
-
-
-def randn_without_seed(shape):
- """Generate a tensor with random numbers from a normal distribution using the previously initialized genrator.
-
- Use either randn() or manual_seed() to initialize the generator."""
-
- from modules.shared import opts
-
- if opts.randn_source == "NV":
- return torch.asarray(nv_rng.randn(shape), device=device)
-
- if opts.randn_source == "CPU" or device.type == 'mps':
- return torch.randn(shape, device=cpu).to(device)
-
- return torch.randn(shape, device=device)
-
-
-def manual_seed(seed):
- """Set up a global random number generator using the specified seed."""
- from modules.shared import opts
-
- if opts.randn_source == "NV":
- global nv_rng
- nv_rng = rng_philox.Generator(seed)
- return
-
- torch.manual_seed(seed)
-
-
def autocast(disable=False):
- from modules import shared
-
if disable:
return contextlib.nullcontext()
@@ -195,8 +111,6 @@ class NansException(Exception):
def test_for_nans(x, where):
- from modules import shared
-
if shared.cmd_opts.disable_nan_check:
return
@@ -236,3 +150,4 @@ def first_time_calculation():
x = torch.zeros((1, 1, 3, 3)).to(device, dtype)
conv2d = torch.nn.Conv2d(1, 1, (3, 3)).to(device, dtype)
conv2d(x)
+
diff --git a/modules/extensions.py b/modules/extensions.py
index e4633af4..bf9a1878 100644
--- a/modules/extensions.py
+++ b/modules/extensions.py
@@ -1,7 +1,7 @@
import os
import threading
-from modules import shared, errors, cache
+from modules import shared, errors, cache, scripts
from modules.gitpython_hack import Repo
from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path # noqa: F401
@@ -90,8 +90,6 @@ class Extension:
self.have_info_from_repo = True
def list_files(self, subdir, extension):
- from modules import scripts
-
dirpath = os.path.join(self.path, subdir)
if not os.path.isdir(dirpath):
return []
diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py
index e71c9601..386517ac 100644
--- a/modules/generation_parameters_copypaste.py
+++ b/