diff options
49 files changed, 1035 insertions, 830 deletions
diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index cb8f1d36..7c371deb 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -3,7 +3,9 @@ import os import re
import torch
-from modules import shared, devices, sd_models
+from modules import shared, devices, sd_models, errors
+
+metadata_tags_order = {"ss_sd_model_name": 1, "ss_resolution": 2, "ss_clip_skip": 3, "ss_num_train_images": 10, "ss_tag_frequency": 20}
re_digits = re.compile(r"\d+")
re_unet_down_blocks = re.compile(r"lora_unet_down_blocks_(\d+)_attentions_(\d+)_(.+)")
@@ -43,6 +45,23 @@ class LoraOnDisk: def __init__(self, name, filename):
self.name = name
self.filename = filename
+ self.metadata = {}
+
+ _, ext = os.path.splitext(filename)
+ if ext.lower() == ".safetensors":
+ try:
+ self.metadata = sd_models.read_metadata_from_safetensors(filename)
+ except Exception as e:
+ errors.display(e, f"reading lora {filename}")
+
+ if self.metadata:
+ m = {}
+ for k, v in sorted(self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)):
+ m[k] = v
+
+ self.metadata = m
+
+ self.ssmd_cover_images = self.metadata.pop('ssmd_cover_images', None) # those are cover images and they are too big to display in UI as text
class LoraModule:
@@ -159,6 +178,7 @@ def load_loras(names, multipliers=None): def lora_forward(module, input, res):
+ input = devices.cond_cast_unet(input)
if len(loaded_loras) == 0:
return res
diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 8d32052e..68b11332 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -23,6 +23,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): "search_term": self.search_terms_from_path(lora_on_disk.filename),
"prompt": json.dumps(f"<lora:{name}:") + " + opts.extra_networks_default_multiplier + " + json.dumps(">"),
"local_preview": f"{path}.{shared.opts.samples_format}",
+ "metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None,
}
def allowed_directories_for_previews(self):
diff --git a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js index 4a85c8eb..f0918e26 100644 --- a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js +++ b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js @@ -89,22 +89,15 @@ function checkBrackets(evt, textArea, counterElt) { function setupBracketChecking(id_prompt, id_counter){ var textarea = gradioApp().querySelector("#" + id_prompt + " > label > textarea"); var counter = gradioApp().getElementById(id_counter) + textarea.addEventListener("input", function(evt){ checkBrackets(evt, textarea, counter) }); } -var shadowRootLoaded = setInterval(function() { - var shadowRoot = document.querySelector('gradio-app').shadowRoot; - if(! shadowRoot) return false; - - var shadowTextArea = shadowRoot.querySelectorAll('#txt2img_prompt > label > textarea'); - if(shadowTextArea.length < 1) return false; - - clearInterval(shadowRootLoaded); - +onUiLoaded(function(){ setupBracketChecking('txt2img_prompt', 'txt2img_token_counter') setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter') - setupBracketChecking('img2img_prompt', 'imgimg_token_counter') + setupBracketChecking('img2img_prompt', 'img2img_token_counter') setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter') -}, 1000); +})
\ No newline at end of file diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index 8612396d..1bf3fc30 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,4 +1,6 @@ <div class='card' {preview_html} onclick={card_clicked}> + {metadata_button} + <div class='actions'> <div class='additional'> <ul> diff --git a/html/licenses.html b/html/licenses.html index bddbf466..bc995aa0 100644 --- a/html/licenses.html +++ b/html/licenses.html @@ -635,4 +635,30 @@ SOFTWARE. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+</pre>
+
+<h2><a href="https://github.com/explosion/curated-transformers/blob/main/LICENSE">Curated transformers</a></h2>
+<small>The MPS workaround for nn.Linear on macOS 13.2.X is based on the MPS workaround for nn.Linear created by danieldk for Curated transformers</small>
+<pre>
+The MIT License (MIT)
+
+Copyright (C) 2021 ExplosionAI GmbH
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
</pre>
\ No newline at end of file diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index 11bcce1b..06f505b0 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -43,7 +43,7 @@ contextMenuInit = function(){ })
- gradioApp().getRootNode().appendChild(contextMenu)
+ gradioApp().appendChild(contextMenu)
let menuWidth = contextMenu.offsetWidth + 4;
let menuHeight = contextMenu.offsetHeight + 4;
diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index 619bb1fa..20a5aadf 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -1,6 +1,6 @@ function keyupEditAttention(event){
let target = event.originalTarget || event.composedPath()[0];
- if (!target.matches("[id*='_toprow'] textarea.gr-text-input[placeholder]")) return;
+ if (! target.matches("[id*='_toprow'] [id*='_prompt'] textarea")) return;
if (! (event.metaKey || event.ctrlKey)) return;
let isPlus = event.key == "ArrowUp"
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 5781df4f..25322138 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -5,12 +5,10 @@ function setupExtraNetworksForTab(tabname){ var tabs = gradioApp().querySelector('#'+tabname+'_extra_tabs > div')
var search = gradioApp().querySelector('#'+tabname+'_extra_search textarea')
var refresh = gradioApp().getElementById(tabname+'_extra_refresh')
- var close = gradioApp().getElementById(tabname+'_extra_close')
search.classList.add('search')
tabs.appendChild(search)
tabs.appendChild(refresh)
- tabs.appendChild(close)
search.addEventListener("input", function(evt){
searchTerm = search.value.toLowerCase()
@@ -104,4 +102,78 @@ function extraNetworksSearchButton(tabs_id, event){ searchTextarea.value = text
updateInput(searchTextarea)
-}
\ No newline at end of file +}
+
+var globalPopup = null;
+var globalPopupInner = null;
+function popup(contents){
+ if(! globalPopup){
+ globalPopup = document.createElement('div')
+ globalPopup.onclick = function(){ globalPopup.style.display = "none"; };
+ globalPopup.classList.add('global-popup');
+
+ var close = document.createElement('div')
+ close.classList.add('global-popup-close');
+ close.onclick = function(){ globalPopup.style.display = "none"; };
+ close.title = "Close";
+ globalPopup.appendChild(close)
+
+ globalPopupInner = document.createElement('div')
+ globalPopupInner.onclick = function(event){ event.stopPropagation(); return false; };
+ globalPopupInner.classList.add('global-popup-inner');
+ globalPopup.appendChild(globalPopupInner)
+
+ gradioApp().appendChild(globalPopup);
+ }
+
+ globalPopupInner.innerHTML = '';
+ globalPopupInner.appendChild(contents);
+
+ globalPopup.style.display = "flex";
+}
+
+function extraNetworksShowMetadata(text){
+ elem = document.createElement('pre')
+ elem.classList.add('popup-metadata');
+ elem.textContent = text;
+
+ popup(elem);
+}
+
+function requestGet(url, data, handler, errorHandler){
+ var xhr = new XMLHttpRequest();
+ var args = Object.keys(data).map(function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }).join('&')
+ xhr.open("GET", url + "?" + args, true);
+
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200) {
+ try {
+ var js = JSON.parse(xhr.responseText);
+ handler(js)
+ } catch (error) {
+ console.error(error);
+ errorHandler()
+ }
+ } else{
+ errorHandler()
+ }
+ }
+ };
+ var js = JSON.stringify(data);
+ xhr.send(js);
+}
+
+function extraNetworksRequestMetadata(event, extraPage, cardName){
+ showError = function(){ extraNetworksShowMetadata("there was an error getting metadata"); }
+
+ requestGet("./sd_extra_networks/metadata", {"page": extraPage, "item": cardName}, function(data){
+ if(data && data.metadata){
+ extraNetworksShowMetadata(data.metadata)
+ } else{
+ showError()
+ }
+ }, showError)
+
+ event.stopPropagation()
+}
diff --git a/javascript/hints.js b/javascript/hints.js index 7f4101b2..b3f3d08d 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -18,7 +18,7 @@ titles = { "\u2199\ufe0f": "Read generation parameters from prompt or last generation if prompt is empty into user interface.", "\u{1f4c2}": "Open images output directory", "\u{1f4be}": "Save style", - "\u{1f5d1}": "Clear prompt", + "\u{1f5d1}\ufe0f": "Clear prompt", "\u{1f4cb}": "Apply selected styles to current prompt", "\u{1f4d2}": "Paste available values into the field", "\u{1f3b4}": "Show extra networks", @@ -40,8 +40,7 @@ titles = { "Inpaint at full resolution": "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image", "Denoising strength": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", - "Denoising strength change factor": "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.", - + "Skip": "Stop processing current image and continue processing.", "Interrupt": "Stop processing images and return any results accumulated so far.", "Save": "Write image to a directory (default - log/images) and generation parameters into csv file.", @@ -71,8 +70,10 @@ titles = { "Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg],[prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime<Format>], [datetime<Format><Time Zone>], [job_timestamp]; leave empty for default.", "Max prompt words": "Set the maximum number of words to be used in the [prompt_words] option; ATTENTION: If the words are too long, they may exceed the maximum length of the file path that the system can handle", - "Loopback": "Process an image, use it as an input, repeat.", - "Loops": "How many times to repeat processing an image and using it as input for the next iteration", + "Loopback": "Performs img2img processing multiple times. Output images are used as input for the next loop.", + "Loops": "How many times to process an image. Each output is used as the input of the next loop. If set to 1, behavior will be as if this script were not used.", + "Final denoising strength": "The denoising strength for the final loop of each image in the batch.", + "Denoising strength curve": "The denoising curve controls the rate of denoising strength change each loop. Aggressive: Most of the change will happen towards the start of the loops. Linear: Change will be constant through all loops. Lazy: Most of the change will happen towards the end of the loops.", "Style 1": "Style to apply; styles have components for both positive and negative prompts and apply to both", "Style 2": "Style to apply; styles have components for both positive and negative prompts and apply to both", diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index aac2ee82..7547e771 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -11,7 +11,7 @@ function showModal(event) { if (modalImage.style.display === 'none') { lb.style.setProperty('background-image', 'url(' + source.src + ')'); } - lb.style.display = "block"; + lb.style.display = "flex"; lb.focus() const tabTxt2Img = gradioApp().getElementById("tab_txt2img") @@ -50,7 +50,7 @@ function updateOnBackgroundChange() { } function modalImageSwitch(offset) { - var allgalleryButtons = gradioApp().querySelectorAll(".gallery-item.transition-all") + var allgalleryButtons = gradioApp().querySelectorAll(".gradio-gallery .thumbnail-item") var galleryButtons = [] allgalleryButtons.forEach(function(elem) { if (elem.parentElement.offsetParent) { @@ -59,7 +59,7 @@ function modalImageSwitch(offset) { }) if (galleryButtons.length > 1) { - var allcurrentButtons = gradioApp().querySelectorAll(".gallery-item.transition-all.\\!ring-2") + var allcurrentButtons = gradioApp().querySelectorAll(".gradio-gallery .thumbnail-item.selected") var currentButton = null allcurrentButtons.forEach(function(elem) { if (elem.parentElement.offsetParent) { @@ -136,37 +136,29 @@ function modalKeyHandler(event) { } } -function showGalleryImage() { - setTimeout(function() { - fullImg_preview = gradioApp().querySelectorAll('img.w-full.object-contain') - - if (fullImg_preview != null) { - fullImg_preview.forEach(function function_name(e) { - if (e.dataset.modded) - return; - e.dataset.modded = true; - if(e && e.parentElement.tagName == 'DIV'){ - e.style.cursor='pointer' - e.style.userSelect='none' - - var isFirefox = isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 - - // For Firefox, listening on click first switched to next image then shows the lightbox. - // If you know how to fix this without switching to mousedown event, please. - // For other browsers the event is click to make it possiblr to drag picture. - var event = isFirefox ? 'mousedown' : 'click' - - e.addEventListener(event, function (evt) { - if(!opts.js_modal_lightbox || evt.button != 0) return; - modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed) - evt.preventDefault() - showModal(evt) - }, true); - } - }); - } +function setupImageForLightbox(e) { + if (e.dataset.modded) + return; + + e.dataset.modded = true; + e.style.cursor='pointer' + e.style.userSelect='none' + + var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 + + // For Firefox, listening on click first switched to next image then shows the lightbox. + // If you know how to fix this without switching to mousedown event, please. + // For other browsers the event is click to make it possiblr to drag picture. + var event = isFirefox ? 'mousedown' : 'click' + + e.addEventListener(event, function (evt) { + if(!opts.js_modal_lightbox || evt.button != 0) return; + + modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed) + evt.preventDefault() + showModal(evt) + }, true); - }, 100); } function modalZoomSet(modalImage, enable) { @@ -199,21 +191,21 @@ function modalTileImageToggle(event) { } function galleryImageHandler(e) { - if (e && e.parentElement.tagName == 'BUTTON') { + //if (e && e.parentElement.tagName == 'BUTTON') { e.onclick = showGalleryImage; - } + //} } onUiUpdate(function() { - fullImg_preview = gradioApp().querySelectorAll('img.w-full') + fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > div > img') if (fullImg_preview != null) { - fullImg_preview.forEach(galleryImageHandler); + fullImg_preview.forEach(setupImageForLightbox); } updateOnBackgroundChange(); }) document.addEventListener("DOMContentLoaded", function() { - const modalFragment = document.createDocumentFragment(); + //const modalFragment = document.createDocumentFragment(); const modal = document.createElement('div') modal.onclick = closeModal; modal.id = "lightboxModal"; @@ -277,9 +269,9 @@ document.addEventListener("DOMContentLoaded", function() { modal.appendChild(modalNext) + gradioApp().appendChild(modal) - gradioApp().getRootNode().appendChild(modal) - document.body.appendChild(modalFragment); + document.body.appendChild(modal); }); diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 9ccc9da4..4ac9b8db 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -1,78 +1,13 @@ // code related to showing and updating progressbar shown as the image is being made - -galleries = {} -storedGallerySelections = {} -galleryObservers = {} - function rememberGallerySelection(id_gallery){ - storedGallerySelections[id_gallery] = getGallerySelectedIndex(id_gallery) -} -function getGallerySelectedIndex(id_gallery){ - let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item') - let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2') - - let currentlySelectedIndex = -1 - galleryButtons.forEach(function(v, i){ if(v==galleryBtnSelected) { currentlySelectedIndex = i } }) - - return currentlySelectedIndex } -// this is a workaround for https://github.com/gradio-app/gradio/issues/2984 -function check_gallery(id_gallery){ - let gallery = gradioApp().getElementById(id_gallery) - // if gallery has no change, no need to setting up observer again. - if (gallery && galleries[id_gallery] !== gallery){ - galleries[id_gallery] = gallery; - if(galleryObservers[id_gallery]){ - galleryObservers[id_gallery].disconnect(); - } +function getGallerySelectedIndex(id_gallery){ - storedGallerySelections[id_gallery] = -1 - - galleryObservers[id_gallery] = new MutationObserver(function (){ - let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item') - let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2') - let currentlySelectedIndex = getGallerySelectedIndex(id_gallery) - prevSelectedIndex = storedGallerySelections[id_gallery] - storedGallerySelections[id_gallery] = -1 - - if (prevSelectedIndex !== -1 && galleryButtons.length>prevSelectedIndex && !galleryBtnSelected) { - // automatically re-open previously selected index (if exists) - activeElement = gradioApp().activeElement; - let scrollX = window.scrollX; - let scrollY = window.scrollY; - - galleryButtons[prevSelectedIndex].click(); - showGalleryImage(); - - // When the gallery button is clicked, it gains focus and scrolls itself into view - // We need to scroll back to the previous position - setTimeout(function (){ - window.scrollTo(scrollX, scrollY); - }, 50); - - if(activeElement){ - // i fought this for about an hour; i don't know why the focus is lost or why this helps recover it - // if someone has a better solution please by all means - setTimeout(function (){ - activeElement.focus({ - preventScroll: true // Refocus the element that was focused before the gallery was opened without scrolling to it - }) - }, 1); - } - } - }) - galleryObservers[id_gallery].observe( gallery, { childList:true, subtree:false }) - } } -onUiUpdate(function(){ - check_gallery('txt2img_gallery') - check_gallery('img2img_gallery') -}) - function request(url, data, handler, errorHandler){ var xhr = new XMLHttpRequest(); var url = url; diff --git a/javascript/ui.js b/javascript/ui.js index b7a8268a..fcaf5608 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -86,7 +86,7 @@ function get_tab_index(tabId){ var res = 0 gradioApp().getElementById(tabId).querySelector('div').querySelectorAll('button').forEach(function(button, i){ - if(button.className.indexOf('bg-white') != -1) + if(button.className.indexOf('selected') != -1) res = i }) @@ -255,7 +255,6 @@ onUiUpdate(function(){ } prompt.parentElement.insertBefore(counter, prompt) - counter.classList.add("token-counter") prompt.parentElement.style.position = "relative" promptTokecountUpdateFuncs[id] = function(){ update_token_counter(id_button); } @@ -8,6 +8,14 @@ import platform import argparse
import json
+parser = argparse.ArgumentParser(add_help=False)
+parser.add_argument("--ui-settings-file", type=str, default='config.json')
+parser.add_argument("--data-dir", type=str, default=os.path.dirname(os.path.realpath(__file__)))
+args, _ = parser.parse_known_args(sys.argv)
+
+script_path = os.path.dirname(__file__)
+data_path = args.data_dir
+
dir_repos = "repositories"
dir_extensions = "extensions"
python = sys.executable
@@ -123,7 +131,7 @@ def is_installed(package): def repo_dir(name):
- return os.path.join(dir_repos, name)
+ return os.path.join(script_path, dir_repos, name)
def run_python(code, desc=None, errdesc=None):
@@ -216,7 +224,7 @@ def list_extensions(settings_file): disabled_extensions = set(settings.get('disabled_extensions', []))
- return [x for x in os.listdir(dir_extensions) if x not in disabled_extensions]
+ return [x for x in os.listdir(os.path.join(data_path, dir_extensions)) if x not in disabled_extensions]
def run_extensions_installers(settings_file):
@@ -224,7 +232,7 @@ def run_extensions_installers(settings_file): return
for dirname_extension in list_extensions(settings_file):
- run_extension_installer(os.path.join(dir_extensions, dirname_extension))
+ run_extension_installer(os.path.join(data_path, dir_extensions, dirname_extension))
def prepare_environment():
@@ -253,10 +261,6 @@ def prepare_environment(): sys.argv += shlex.split(commandline_args)
- parser = argparse.ArgumentParser(add_help=False)
- parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui settings", default='config.json')
- args, _ = parser.parse_known_args(sys.argv)
-
sys.argv, _ = extract_arg(sys.argv, '-f')
sys.argv, update_all_extensions = extract_arg(sys.argv, '--update-all-extensions')
sys.argv, skip_torch_cuda_test = extract_arg(sys.argv, '--skip-torch-cuda-test')
@@ -307,7 +311,7 @@ def prepare_environment(): if not is_installed("pyngrok") and ngrok:
run_pip("install pyngrok", "ngrok")
- os.makedirs(dir_repos, exist_ok=True)
+ os.makedirs(os.path.join(script_path, dir_repos), exist_ok=True)
git_clone(stable_diffusion_repo, repo_dir('stable-diffusion-stability-ai'), "Stable Diffusion", stable_diffusion_commit_hash)
git_clone(taming_transformers_repo, repo_dir('taming-transformers'), "Taming Transformers", taming_transformers_commit_hash)
@@ -316,9 +320,11 @@ def prepare_environment(): git_clone(blip_repo, repo_dir('BLIP'), "BLIP", blip_commit_hash)
if not is_installed("lpips"):
- run_pip(f"install -r {os.path.join(repo_dir('CodeFormer'), 'requirements.txt')}", "requirements for CodeFormer")
+ run_pip(f"install -r \"{os.path.join(repo_dir('CodeFormer'), 'requirements.txt')}\"", "requirements for CodeFormer")
- run_pip(f"install -r {requirements_file}", "requirements for Web UI")
+ if not os.path.isfile(requirements_file):
+ requirements_file = os.path.join(script_path, requirements_file)
+ run_pip(f"install -r \"{requirements_file}\"", "requirements for Web UI")
run_extensions_installers(settings_file=args.ui_settings_file)
@@ -326,7 +332,7 @@ def prepare_environment(): version_check(commit)
if update_all_extensions:
- git_pull_recursive(dir_extensions)
+ git_pull_recursive(os.path.join(data_path, dir_extensions))
if "--exit" in sys.argv:
print("Exiting because of --exit argument")
@@ -342,7 +348,7 @@ def tests(test_dir): sys.argv.append("--api")
if "--ckpt" not in sys.argv:
sys.argv.append("--ckpt")
- sys.argv.append("./test/test_files/empty.pt")
+ sys.argv.append(os.path.join(script_path, "test/test_files/empty.pt"))
if "--skip-torch-cuda-test" not in sys.argv:
sys.argv.append("--skip-torch-cuda-test")
if "--disable-nan-check" not in sys.argv:
@@ -351,7 +357,7 @@ def tests(test_dir): print(f"Launching Web UI in another process for testing with arguments: {' '.join(sys.argv[1:])}")
os.environ['COMMANDLINE_ARGS'] = ""
- with open('test/stdout.txt', "w", encoding="utf8") as stdout, open('test/stderr.txt', "w", encoding="utf8") as stderr:
+ with open(os.path.join(script_path, 'test/stdout.txt'), "w", encoding="utf8") as stdout, open(os.path.join(script_path, 'test/stderr.txt'), "w", encoding="utf8") as stderr:
proc = subprocess.Popen([sys.executable, *sys.argv], stdout=stdout, stderr=stderr)
import test.server_poll
diff --git a/modules/api/api.py b/modules/api/api.py index 376f7f04..f52f7fef 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -18,7 +18,7 @@ from modules.textual_inversion.textual_inversion import create_embedding, train_ from modules.textual_inversion.preprocess import preprocess from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork from PIL import PngImagePlugin,Image -from modules.sd_models import checkpoints_list +from modules.sd_models import checkpoints_list, unload_model_weights, reload_model_weights from modules.sd_models_config import find_checkpoint_config_near_filename from modules.realesrgan_model import get_realesrgan_models from modules import devices @@ -150,6 +150,8 @@ class Api: self.add_api_route("/sdapi/v1/train/embedding", self.train_embedding, methods=["POST"], response_model=TrainResponse) self.add_api_route("/sdapi/v1/train/hypernetwork", self.train_hypernetwork, methods=["POST"], response_model=TrainResponse) self.add_api_route("/sdapi/v1/memory", self.get_memory, methods=["GET"], response_model=MemoryResponse) + self.add_api_route("/sdapi/v1/unload-checkpoint", self.unloadapi, methods=["POST"]) + self.add_api_route("/sdapi/v1/reload-checkpoint", self.reloadapi, methods=["POST"]) self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=ScriptsList) def add_api_route(self, path: str, endpoint, **kwargs): @@ -164,14 +166,10 @@ class Api: raise HTTPException(status_code=401, detail="Incorrect username or password", headers={"WWW-Authenticate": "Basic"}) - def get_script(self, script_name, script_runner): - if script_name is None: + def get_selectable_script(self, script_name, script_runner): + if script_name is None or script_name == "": return None, None - if not script_runner.scripts: - script_runner.initialize_scripts(False) - ui.create_ui() - script_idx = script_name_to_index(script_name, script_runner.selectable_scripts) script = script_runner.selectable_scripts[script_idx] return script, script_idx @@ -182,8 +180,49 @@ class Api: return ScriptsList(txt2img = t2ilist, img2img = i2ilist) + def get_script(self, script_name, script_runner): + if script_name is None or script_name == "": + return None, None + + script_idx = script_name_to_index(script_name, script_runner.scripts) + return script_runner.scripts[script_idx] + + def init_script_args(self, request, selectable_scripts, selectable_idx, script_runner): + #find max idx from the scripts in runner and generate a none array to init script_args + last_arg_index = 1 + for script in script_runner.scripts: + if last_arg_index < script.args_to: + last_arg_index = script.args_to + # None everywhere except position 0 to initialize script args + script_args = [None]*last_arg_index + # position 0 in script_arg is the idx+1 of the selectable script that is going to be run when using scripts.scripts_*2img.run() + if selectable_scripts: + script_args[selectable_scripts.args_from:selectable_scripts.args_to] = request.script_args + script_args[0] = selectable_idx + 1 + else: + # when [0] = 0 no selectable script to run + script_args[0] = 0 + + # Now check for always on scripts + if request.alwayson_scripts and (len(request.alwayson_scripts) > 0): + for alwayson_script_name in request.alwayson_scripts.keys(): + alwayson_script = self.get_script(alwayson_script_name, script_runner) + if alwayson_script == None: + raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") + # Selectable script in always on script param check + if alwayson_script.alwayson == False: + raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") + # always on script with no arg should always run so you don't really need to add them to the requests + if "args" in request.alwayson_scripts[alwayson_script_name]: + script_args[alwayson_script.args_from:alwayson_script.args_to] = request.alwayson_scripts[alwayson_script_name]["args"] + return script_args + def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): - script, script_idx = self.get_script(txt2imgreq.script_name, scripts.scripts_txt2img) + script_runner = scripts.scripts_txt2img + if not script_runner.scripts: + script_runner.initialize_scripts(False) + ui.create_ui() + selectable_scripts, selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner) populate = txt2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), @@ -195,20 +234,26 @@ class Api: args = vars(populate) args.pop('script_name', None) + args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them + args.pop('alwayson_scripts', None) + + script_args = self.init_script_args(txt2imgreq, selectable_scripts, selectable_script_idx, script_runner) send_images = args.pop('send_images', True) args.pop('save_images', None) with self.queue_lock: p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) + p.scripts = script_runner p.outpath_grids = opts.outdir_txt2img_grids p.outpath_samples = opts.outdir_txt2img_samples shared.state.begin() - if script is not None: - p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args - processed = scripts.scripts_txt2img.run(p, *p.script_args) + if selectable_scripts != None: + p.script_args = script_args + processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here else: + p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end() @@ -221,12 +266,16 @@ class Api: if init_images is None: raise HTTPException(status_code=404, detail="Init image not found") - script, script_idx = self.get_script(img2imgreq.script_name, scripts.scripts_img2img) - mask = img2imgreq.mask if mask: mask = decode_base64_to_image(mask) + script_runner = scripts.scripts_img2img + if not script_runner.scripts: + script_runner.initialize_scripts(True) + ui.create_ui() + selectable_scripts, selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner) + populate = img2imgreq.copy(update={ # Override __init__ params "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), "do_not_save_samples": not img2imgreq.save_images, @@ -239,6 +288,10 @@ class Api: args = vars(populate) args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model, but it's for a reason that I cannot determine. args.pop('script_name', None) + args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them + args.pop('alwayson_scripts', None) + + script_args = self.init_script_args(img2imgreq, selectable_scripts, selectable_script_idx, script_runner) send_images = args.pop('send_images', True) args.pop('save_images', None) @@ -246,14 +299,16 @@ class Api: with self.queue_lock: p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) p.init_images = [decode_base64_to_image(x) for x in init_images] + p.scripts = script_runner p.outpath_grids = opts.outdir_img2img_grids p.outpath_samples = opts.outdir_img2img_samples shared.state.begin() - if script is not None: - p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args - processed = scripts.scripts_img2img.run(p, *p.script_args) + if selectable_scripts != None: + p.script_args = script_args + processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here else: + p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end() @@ -359,6 +414,16 @@ class Api: return {} + def unloadapi(self): + unload_model_weights() + + return {} + + def reloadapi(self): + reload_model_weights() + + return {} + def skip(self): shared.state.skip() diff --git a/modules/api/models.py b/modules/api/models.py index fa1c40df..4a70f440 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -106,6 +106,7 @@ StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( {"key": "script_args", "type": list, "default": []}, {"key": "send_images", "type": bool, "default": True}, {"key": "save_images", "type": bool, "default": False}, + {"key": "alwayson_scripts", "type": dict, "default": {}}, ] ).generate_model() @@ -122,6 +123,7 @@ StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( {"key": "script_args", "type": list, "default": []}, {"key": "send_images", "type": bool, "default": True}, {"key": "save_images", "type": bool, "default": False}, + {"key": "alwayson_scripts", "type": dict, "default": {}}, ] ).generate_model() diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 7c0b5b4e..6df76858 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -401,9 +401,14 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component, button.click(
fn=paste_func,
- _js=f"recalculate_prompts_{tabname}",
inputs=[input_comp],
outputs=[x[0] for x in paste_fields],
)
+ button.click(
+ fn=None,
+ _js=f"recalculate_prompts_{tabname}",
+ inputs=[],
+ outputs=[],
+ )
diff --git a/modules/images.py b/modules/images.py index 7df2b08c..7030aaaa 100644 --- a/modules/images.py +++ b/modules/images.py @@ -573,6 +573,11 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i os.replace(temp_file_path, filename_without_extension + extension)
fullfn_without_extension, extension = os.path.splitext(params.filename)
+ if hasattr(os, 'statvfs'):
+ max_name_len = os.statvfs(path).f_namemax
+ fullfn_without_extension = fullfn_without_extension[:max_name_len - max(4, len(extension))]
+ params.filename = fullfn_without_extension + extension
+ fullfn = params.filename
_atomically_save_image(image, fullfn_without_extension, extension)
image.already_saved_as = fullfn
@@ -640,6 +645,8 @@ Steps: {json_info["steps"]}, Sampler: {sampler}, CFG scale: {json_info["scale"]} def image_data(data):
+ import gradio as gr
+
try:
image = Image.open(io.BytesIO(data))
textinfo, _ = read_info_from_image(image)
@@ -655,7 +662,7 @@ def image_data(data): except Exception:
pass
- return '', None
+ return gr.update(), None
def flatten(img, bgcolor):
diff --git a/modules/mac_specific.py b/modules/mac_specific.py index ddcea53b..6fe8dea0 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -1,4 +1,5 @@ import torch +import platform from modules import paths from modules.sd_hijack_utils import CondFunc from packaging import version @@ -23,7 +24,7 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): output_dtype = kwargs.get('dtype', input.dtype) if output_dtype == torch.int64: return cumsum_func(input.cpu(), *args, **kwargs).to(input.device) - elif cumsum_needs_bool_fix and output_dtype == torch.bool or cumsum_needs_int_fix and (output_dtype == torch.int8 or output_dtype == torch.int16): + elif output_dtype == torch.bool or cumsum_needs_int_fix and (output_dtype == torch.int8 or output_dtype == torch.int16): return cumsum_func(input.to(torch.int32), *args, **kwargs).to(torch.int64) return cumsum_func(input, *args, **kwargs) @@ -32,6 +33,10 @@ if has_mps: # MPS fix for randn in torchsde CondFunc('torchsde._brownian.brownian_interval._randn', lambda _, size, dtype, device, seed: torch.randn(size, dtype=dtype, device=torch.device("cpu"), generator=torch.Generator(torch.device("cpu")).manual_seed(int(seed))).to(device), lambda _, size, dtype, device, seed: device.type == 'mps') + 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) + CondFunc('torch.nn.functional.linear', lambda _, input, weight, bias: (torch.matmul(input, weight.t()) + bias) if bias is not None else torch.matmul(input, weight.t()), lambda _, input, weight, bias: input.numel() > 10485760) + if version.parse(torch.__version__) < version.parse("1.13"): # PyTorch 1.13 doesn't need these fixes but unfortunately is slower and has regressions that prevent training from working @@ -45,9 +50,10 @@ if has_mps: CondFunc('torch.Tensor.numpy', lambda orig_func, self, *args, **kwargs: orig_func(self.detach(), *args, **kwargs), lambda _, self, *args, **kwargs: self.requires_grad) elif version.parse(torch.__version__) > version.parse("1.13.1"): cumsum_needs_int_fix = not torch.Tensor([1,2]).to(torch.device("mps")).equal(torch.ShortTensor([1,1]).to(torch.device("mps")).cumsum(0)) - cumsum_needs_bool_fix = not torch.BoolTensor([True,True]).to(device=torch.device("mps"), dtype=torch.int64).equal(torch.BoolTensor([True,False]).to(torch.device("mps")).cumsum(0)) cumsum_fix_func = lambda orig_func, input, *args, **kwargs: cumsum_fix(input, orig_func, *args, **kwargs) CondFunc('torch.cumsum', cumsum_fix_func, None) CondFunc('torch.Tensor.cumsum', cumsum_fix_func, None) CondFunc('torch.narrow', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).clone(), None) - + if version.parse(torch.__version__) == version.parse("2.0"): + # 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 *args, **kwargs: len(args) == 6) diff --git a/modules/memmon.py b/modules/memmon.py index a7060f58..4018edcc 100644 --- a/modules/memmon.py +++ b/modules/memmon.py @@ -23,12 +23,16 @@ class MemUsageMonitor(threading.Thread): self.data = defaultdict(int) try: - torch.cuda.mem_get_info() + self.cuda_mem_get_info() torch.cuda.memory_stats(self.device) except Exception as e: # AMD or whatever print(f"Warning: caught exception '{e}', memory monitor disabled") self.disabled = True + def cuda_mem_get_info(self): + index = self.device.index if self.device.index is not None else torch.cuda.current_device() + return torch.cuda.mem_get_info(index) + def run(self): if self.disabled: return @@ -43,10 +47,10 @@ class MemUsageMonitor(threading.Thread): self.run_flag.clear() continue - self.data["min_free"] = torch.cuda.mem_get_info()[0] + self.data["min_free"] = self.cuda_mem_get_info()[0] while self.run_flag.is_set(): - free, total = torch.cuda.mem_get_info() # calling with self.device errors, torch bug? + free, total = self.cuda_mem_get_info() self.data["min_free"] = min(self.data["min_free"], free) time.sleep(1 / self.opts.memmon_poll_rate) @@ -70,7 +74,7 @@ class MemUsageMonitor(threading.Thread): def read(self): if not self.disabled: - free, total = torch.cuda.mem_get_info() + free, total = self.cuda_mem_get_info() self.data["free"] = free self.data["total"] = total diff --git a/modules/modelloader.py b/modules/modelloader.py index e351d808..522affc6 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -4,7 +4,6 @@ import shutil import importlib from urllib.parse import urlparse -from basicsr.utils.download_util import load_file_from_url from modules import shared from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone from modules.paths import script_path, models_path @@ -59,6 +58,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None if model_url is not None and len(output) == 0: if download_name is not None: + from basicsr.utils.download_util import load_file_from_url dl = load_file_from_url(model_url, model_path, True, download_name) output.append(dl) else: diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index bf346ff4..a241c8a7 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -71,7 +71,7 @@ class UniPCSampler(object): # sampling C, H, W = shape size = (batch_size, C, H, W) - print(f'Data shape for UniPC sampling is {size}') + # print(f'Data shape for UniPC sampling is {size}') device = self.model.betas.device if x_T is None: diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index df63d1bc..eb5f4e76 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -1,6 +1,7 @@ import torch import torch.nn.functional as F import math +from tqdm.auto import trange class NoiseScheduleVP: @@ -719,7 +720,7 @@ class UniPC: x_t = x_t_ - expand_dims(alpha_t * B_h, dims) * (corr_res + rhos_c[-1] * D1_t) else: x_t_ = ( - expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dimss) * x + expand_dims(torch.exp(log_alpha_t - log_alpha_prev_0), dims) * x - expand_dims(sigma_t * h_phi_1, dims) * model_prev_0 ) if x_t is None: @@ -750,7 +751,7 @@ class UniPC: if method == 'multistep': assert steps >= order, "UniPC order must be < sampling steps" timesteps = self.get_time_steps(skip_type=skip_type, t_T=t_T, t_0=t_0, N=steps, device=device) - print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps, order {order}") + #print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps, order {order}") assert timesteps.shape[0] - 1 == steps with torch.no_grad(): vec_t = timesteps[0].expand((x.shape[0])) @@ -766,7 +767,7 @@ class UniPC: self.after_update(x, model_x) model_prev_list.append(model_x) t_prev_list.append(vec_t) - for step in range(order, steps + 1): + for step in trange(order, steps + 1): vec_t = timesteps[step].expand(x.shape[0]) if lower_order_final: step_order = min(order, steps + 1 - step) diff --git a/modules/processing.py b/modules/processing.py index 06e7a440..2e5a363f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -583,6 +583,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if state.job_count == -1:
state.job_count = p.n_iter
+ extra_network_data = None
for n in range(p.n_iter):
p.iteration = n
@@ -688,6 +689,22 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: image.info["parameters"] = text
output_images.append(image)
+ if hasattr(p, 'mask_for_overlay') and p.mask_for_overlay:
+ image_mask = p.mask_for_overlay.convert('RGB')
+ image_mask_composite = Image.composite(image.convert('RGBA').convert('RGBa'), Image.new('RGBa', image.size), p.mask_for_overlay.convert('L')).convert('RGBA')
+
+ if opts.save_mask:
+ images.save_image(image_mask, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-mask")
+
+ if opts.save_mask_composite:
+ images.save_image(image_mask_composite, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-mask-composite")
+
+ if opts.return_mask:
+ output_images.append(image_mask)
+
+ if opts.return_mask_composite:
+ output_images.append(image_mask_composite)
+
del x_samples_ddim
devices.torch_gc()
@@ -712,7 +729,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if opts.grid_save:
images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True)
- if not p.disable_extra_networks:
+ if not p.disable_extra_networks and extra_network_data:
extra_networks.deactivate(p, extra_network_data)
devices.torch_gc()
diff --git a/modules/scripts.py b/modules/scripts.py index 8de19884..d661be4f 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -239,7 +239,15 @@ def load_scripts(): elif issubclass(script_class, scripts_postprocessing.ScriptPostprocessing):
postprocessing_scripts_data.append(ScriptClassData(script_class, scriptfile.path, scriptfile.basedir, module))
- for scriptfile in sorted(scripts_list):
+ def orderby(basedir):
+ # 1st webui, 2nd extensions-builtin, 3rd extensions
+ priority = {os.path.join(paths.script_path, "extensions-builtin"):1, paths.script_path:0}
+ for key in priority:
+ if basedir.startswith(key):
+ return priority[key]
+ return 9999
+
+ for scriptfile in sorted(scripts_list, key=lambda x: [orderby(x.basedir), x]):
try:
if scriptfile.basedir != paths.script_path:
sys.path = [scriptfile.basedir] + sys.path
@@ -513,6 +521,18 @@ def reload_scripts(): scripts_postproc = scripts_postprocessing.ScriptPostprocessingRunner()
+def add_classes_to_gradio_component(comp):
+ """
+ this adds gradio-* to the component for css styling (ie gradio-button to gr.Button), as well as some others
+ """
+
+ comp.elem_classes = ["gradio-" + comp.get_block_name(), *(comp.elem_classes or [])]
+
+ if getattr(comp, 'multiselect', False):
+ comp.elem_classes.append('multiselect')
+
+
+
def IOComponent_init(self, *args, **kwargs):
if scripts_current is not None:
scripts_current.before_component(self, **kwargs)
@@ -521,6 +541,8 @@ def IOComponent_init(self, *args, **kwargs): res = original_IOComponent_init(self, *args, **kwargs)
+ add_classes_to_gradio_component(self)
+
script_callbacks.after_component_callback(self, **kwargs)
if scripts_current is not None:
diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index ce0ebb61..b11568c0 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -109,7 +109,7 @@ class ScriptPostprocessingRunner: inputs = []
for script in self.scripts_in_preferred_order():
- with gr.Box() as group:
+ with gr.Row() as group:
self.create_script_ui(script, inputs)
script.group = group
diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 2e307b5d..372555ff 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -337,7 +337,7 @@ def xformers_attention_forward(self, x, context=None, mask=None): dtype = q.dtype
if shared.opts.upcast_attn:
- q, k = q.float(), k.float()
+ q, k, v = q.float(), k.float(), v.float()
out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=get_xformers_flash_attention_op(q, k, v))
@@ -372,7 +372,7 @@ def scaled_dot_product_attention_forward(self, x, context=None, mask=None): dtype = q.dtype
if shared.opts.upcast_attn:
- q, k = q.float(), k.float()
+ q, k, v = q.float(), k.float(), v.float()
# the output of sdp = (batch, num_heads, seq_len, head_dim)
hidden_states = torch.nn.functional.scaled_dot_product_attention(
diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 843ab66c..15858263 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -67,7 +67,7 @@ def hijack_ddpm_edit(): unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast
CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model, unet_needs_upcast)
CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast)
-if version.parse(torch.__version__) <= version.parse("1.13.1"):
+if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_available():
CondFunc('ldm.modules.diffusionmodules.util.GroupNorm32.forward', lambda orig_func, self, *args, **kwargs: orig_func(self.float(), *args, **kwargs), unet_needs_upcast)
CondFunc('ldm.modules.attention.GEGLU.forward', lambda orig_func, self, x: orig_func(self.float(), x.float()).to(devices.dtype_unet), unet_needs_upcast)
CondFunc('open_clip.transformer.ResidualAttentionBlock.__init__', lambda orig_func, *args, **kwargs: kwargs.update({'act_layer': GELUHijack}) and False or orig_func(*args, **kwargs), lambda _, *args, **kwargs: kwargs.get('act_layer') is None or kwargs['act_layer'] == torch.nn.GELU)
diff --git a/modules/sd_models.py b/modules/sd_models.py index 93959f55..86218c08 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -178,7 +178,7 @@ def select_checkpoint(): return checkpoint_info
-chckpoint_dict_replacements = {
+checkpoint_dict_replacements = {
'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.final_layer_norm.': 'cond_stage_model.transformer.text_model.final_layer_norm.',
@@ -186,7 +186,7 @@ chckpoint_dict_replacements = { def transform_checkpoint_dict_key(k):
- for text, replacement in chckpoint_dict_replacements.items():
+ for text, replacement in checkpoint_dict_replacements.items():
if k.startswith(text):
k = replacement + k[len(text):]
@@ -210,6 +210,30 @@ def get_state_dict_from_checkpoint(pl_sd): return pl_sd
+def read_metadata_from_safetensors(filename):
+ import json
+
+ with open(filename, mode="rb") as file:
+ metadata_len = file.read(8)
+ metadata_len = int.from_bytes(metadata_len, "little")
+ json_start = file.read(2)
+
+ assert metadata_len > 2 and json_start in (b'{"', b"{'"), f"{filename} is not a safetensors file"
+ json_data = json_start + file.read(metadata_len-2)
+ json_obj = json.loads(json_data)
+
+ res = {}
+ for k, v in json_obj.get("__metadata__", {}).items():
+ res[k] = v
+ if isinstance(v, str) and v[0:1] == '{':
+ try:
+ res[k] = json.loads(v)
+ except Exception as e:
+ pass
+
+ return res
+
+
def read_state_dict(checkpoint_file, print_global_state=False, map_location=None):
_, extension = os.path.splitext(checkpoint_file)
if extension.lower() == ".safetensors":
@@ -470,7 +494,7 @@ def reload_model_weights(sd_model=None, info=None): if sd_model is None or checkpoint_config != sd_model.used_config:
del sd_model
checkpoints_loaded.clear()
- load_model(checkpoint_info, already_loaded_state_dict=state_dict, time_taken_to_load_state_dict=timer.records["load weights from disk"])
+ load_model(checkpoint_info, already_loaded_state_dict=state_dict)
return shared.sd_model
try:
@@ -493,3 +517,23 @@ def reload_model_weights(sd_model=None, info=None): print(f"Weights loaded in {timer.summary()}.")
return sd_model
+
+def unload_model_weights(sd_model=None, info=None):
+ from modules import lowvram, devices, sd_hijack
+ timer = Timer()
+
+ if shared.sd_model:
+
+ # shared.sd_model.cond_stage_model.to(devices.cpu)
+ # shared.sd_model.first_stage_model.to(devices.cpu)
+ shared.sd_model.to(devices.cpu)
+ sd_hijack.model_hijack.undo_hijack(shared.sd_model)
+ shared.sd_model = None
+ sd_model = None
+ gc.collect()
+ devices.torch_gc()
+ torch.cuda.empty_cache()
+
+ print(f"Unloaded weights {timer.summary()}.")
+
+ return sd_model
\ No newline at end of file diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index 0027343a..e2f00468 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -35,8 +35,11 @@ def model(): global sd_vae_approx_model
if sd_vae_approx_model is None:
+ model_path = os.path.join(paths.models_path, "VAE-approx", "model.pt")
sd_vae_approx_model = VAEApprox()
- sd_vae_approx_model.load_state_dict(torch.load(os.path.join(paths.models_path, "VAE-approx", "model.pt"), map_location='cpu' if devices.device.type != 'cuda' else None))
+ if not os.path.exists(model_path):
+ model_path = os.path.join(paths.script_path, "models", "VAE-approx", "model.pt")
+ sd_vae_approx_model.load_state_dict(torch.load(model_path, map_location='cpu' if devices.device.type != 'cuda' else None))
sd_vae_approx_model.eval()
sd_vae_approx_model.to(devices.device, devices.dtype)
diff --git a/modules/shared.py b/modules/shared.py index 2fb9e3b5..e2ca12c1 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -107,7 +107,8 @@ parser.add_argument("--cors-allow-origins-regex", type=str, help="Allowed CORS o parser.add_argument("--tls-keyfile", type=str, help="Partially enables TLS, requires --tls-certfile to fully function", default=None)
parser.add_argument("--tls-certfile", type=str, help="Partially enables TLS, requires --tls-keyfile to fully function", default=None)
parser.add_argument("--server-name", type=str, help="Sets hostname of server", default=None)
-parser.add_argument("--gradio-queue", action='store_true', help="Uses gradio queue; experimental option; breaks restart UI button")
+parser.add_argument("--gradio-queue", action='store_true', help="does not do anything", default=True)
+parser.add_argument("--no-gradio-queue", action='store_true', help="Disables gradio queue; causes the webpage to use http requests instead of websockets; was the defaul in earlier versions")
parser.add_argument("--skip-version-check", action='store_true', help="Do not check versions of torch and xformers")
parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False)
parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False)
@@ -332,6 +333,8 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "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_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"),
+ "save_mask": OptionInfo(False, "For inpainting, save a copy of the greyscale mask"),
+ "save_mask_composite": OptionInfo(False, "For inpainting, save a masked composite"),
"jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}),
"webp_lossless": OptionInfo(False, "Use lossless compression for webp images"),
"export_for_4chan": OptionInfo(True, "If the saved image file size is above the limit, or its either width or height are above the limit, save a downscaled copy as JPG"),
@@ -454,6 +457,8 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), { options_templates.update(options_section(('ui', "User interface"), {
"return_grid": OptionInfo(True, "Show grid in results for web"),
+ "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"),
+ "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"),
"do_not_show_images": OptionInfo(False, "Do not show any images in results for web"),
"add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"),
"add_model_name_to_info": OptionInfo(True, "Add model name to generation information"),
@@ -714,6 +719,7 @@ class TotalTQDM: def clear(self):
if self._tqdm is not None:
+ self._tqdm.refresh()
self._tqdm.close()
self._tqdm = None
diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index c63c7d1d..d2e62e58 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -152,7 +152,11 @@ class EmbeddingDatabase: name = data.get('name', name)
else:
data = extract_image_data_embed(embed_image)
- name = data.get('name', name)
+ if data:
+ name = data.get('name', name)
+ else:
+ # if data is None, means this is not an embeding, just a preview image
+ return
elif ext in ['.BIN', '.PT']:
data = torch.load(path, map_location="cpu")
elif ext in ['.SAFETENSORS']:
diff --git a/modules/timer.py b/modules/timer.py index 57a4f17a..ba92be33 100644 --- a/modules/timer.py +++ b/modules/timer.py @@ -33,3 +33,6 @@ class Timer: res += ")"
return res
+
+ def reset(self):
+ self.__init__()
diff --git a/modules/ui.py b/modules/ui.py index 621ae952..af8546c2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -20,7 +20,7 @@ from PIL import Image, PngImagePlugin from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call
from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, postprocessing, ui_components, ui_common, ui_postprocessing
-from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML
+from modules.ui_components import FormRow, FormColumn, FormGroup, ToolButton, FormHTML
from modules.paths import script_path, data_path
from modules.shared import opts, cmd_opts, restricted_opts
@@ -89,7 +89,7 @@ paste_symbol = '\u2199\ufe0f' # ↙ refresh_symbol = '\U0001f504' # 🔄
save_style_symbol = '\U0001f4be' # 💾
apply_style_symbol = '\U0001f4cb' # 📋
-clear_prompt_symbol = '\U0001F5D1' # 🗑️
+clear_prompt_symbol = '\U0001f5d1\ufe0f' # 🗑️
extra_networks_symbol = '\U0001F3B4' # 🎴
switch_values_symbol = '\U000021C5' # ⇅
@@ -179,14 +179,13 @@ def interrogate_deepbooru(image): def create_seed_inputs(target_interface):
- with FormRow(elem_id=target_interface + '_seed_row'):
+ with FormRow(elem_id=target_interface + '_seed_row', variant="compact"):
seed = (gr.Textbox if cmd_opts.use_textbox_seed else gr.Number)(label='Seed', value=-1, elem_id=target_interface + '_seed')
seed.style(container=False)
- random_seed = gr.Button(random_symbol, elem_id=target_interface + '_random_seed')
- reuse_seed = gr.Button(reuse_symbol, elem_id=target_interface + '_reuse_seed')
+ random_seed = ToolButton(random_symbol, elem_id=target_interface + '_random_seed')
+ reuse_seed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_seed')
- with gr.Group(elem_id=target_interface + '_subseed_show_box'):
- seed_checkbox = gr.Checkbox(label='Extra', elem_id=target_interface + '_subseed_show', value=False)
+ seed_checkbox = gr.Checkbox(label='Extra', elem_id=target_interface + '_subseed_show', value=False)
# Components to show/hide based on the 'Extra' checkbox
seed_extras = []
@@ -195,8 +194,8 @@ def create_seed_inputs(target_interface): seed_extras.append(seed_extra_row_1)
subseed = gr.Number(label='Variation seed', value=-1, elem_id=target_interface + '_subseed')
subseed.style(container=False)
- random_subseed = gr.Button(random_symbol, elem_id=target_interface + '_random_subseed')
- reuse_subseed = gr.Button(reuse_symbol, elem_id=target_interface + '_reuse_subseed')
+ random_subseed = ToolButton(random_symbol, elem_id=target_interface + '_random_subseed')
+ reuse_subseed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_subseed')
subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=target_interface + '_subseed_strength')
with FormRow(visible=False) as seed_extra_row_2:
@@ -291,19 +290,19 @@ def create_toprow(is_img2img): with gr.Row():
with gr.Column(scale=80):
with gr.Row():
- negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=2, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)")
+ negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)")
button_interrogate = None
button_deepbooru = None
if is_img2img:
- with gr.Column(scale=1, elem_id="interrogate_col"):
+ with gr.Column(scale=1, elem_classes="interrogate-col"):
button_interrogate = gr.Button('Interrogate\nCLIP', elem_id="interrogate")
button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id="deepbooru")
with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"):
- with gr.Row(elem_id=f"{id_part}_generate_box"):
- interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt")
- skip = gr.Button('Skip', elem_id=f"{id_part}_skip")
+ with gr.Row(elem_id=f"{id_part}_generate_box", elem_classes="generate-box"):
+ interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt", elem_classes="generate-box-interrupt")
+ skip = gr.Button('Skip', elem_id=f"{id_part}_skip", elem_classes="generate-box-skip")
submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary')
skip.click(
@@ -325,9 +324,9 @@ def create_toprow(is_img2img): prompt_style_apply = ToolButton(value=apply_style_symbol, elem_id=f"{id_part}_style_apply")
save_style = ToolButton(value=save_style_symbol, elem_id=f"{id_part}_style_create")
- token_counter = gr.HTML(value="<span></span>", elem_id=f"{id_part}_token_counter")
+ token_counter = gr.HTML(value="<span>0/75</span>", elem_id=f"{id_part}_token_counter", elem_classes=["token-counter"])
token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button")
- negative_token_counter = gr.HTML(value="<span></span>", elem_id=f"{id_part}_negative_token_counter")
+ negative_token_counter = gr.HTML(value="<span>0/75</span>", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"])
negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button")
clear_prompt_button.click(
@@ -479,7 +478,9 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width")
height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height")
- res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn")
+ with gr.Column(elem_id="txt2img_dimensions_row", scale=1, elem_classes="dimensions-tools"):
+ res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn")
+
if opts.dimensions_and_batch_together:
with gr.Column(elem_id="txt2img_column_batch"):
batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count")
@@ -492,7 +493,7 @@ def create_ui(): seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('txt2img')
elif category == "checkboxes":
- with FormRow(elem_id="txt2img_checkboxes", variant="compact"):
+ with FormRow(elem_classes="checkboxes-row", variant="compact"):
restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="txt2img_restore_faces")
tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling")
enable_hr = gr.Checkbox(label='Hires. fix', value=False, elem_id="txt2img_enable_hr")
@@ -586,7 +587,7 @@ def create_ui(): txt2img_prompt.submit(**txt2img_args)
submit.click(**txt2img_args)
- res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height])
+ res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False)
txt_prompt_img.change(
fn=modules.images.image_data,
@@ -757,7 +758,9 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width")
height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height")
- res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn")
+ with gr.Column(elem_id="img2img_dimensions_row", scale=1, elem_classes="dimensions-tools"):
+ res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn")
+
if opts.dimensions_and_batch_together:
with gr.Column(elem_id="img2img_column_batch"):
batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count")
@@ -774,7 +777,7 @@ def create_ui(): seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('img2img')
elif category == "checkboxes":
- with FormRow(elem_id="img2img_checkboxes", variant="compact"):
+ with FormRow(elem_classes="checkboxes-row", variant="compact"):
restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="img2img_restore_faces")
tiling = gr.Checkbox(label='Tiling', value=False, elem_id="img2img_tiling")
@@ -904,7 +907,7 @@ def create_ui(): img2img_prompt.submit(**img2img_args)
submit.click(**img2img_args)
- res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height])
+ res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False)
img2img_interrogate.click(
fn=lambda *args: process_interrogate(interrogate, *args),
@@ -1491,11 +1494,33 @@ def create_ui(): request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications")
download_localization = gr.Button(value='Download localization template', elem_id="download_localization")
reload_script_bodies = gr.Button(value='Reload custom script bodies (No ui updates, No restart)', variant='secondary', elem_id="settings_reload_script_bodies")
+ with gr.Row():
+ unload_sd_model = gr.Button(value='Unload SD checkpoint to free VRAM', elem_id="sett_unload_sd_model")
+ reload_sd_model = gr.Button(value='Reload the last SD checkpoint back into VRAM', elem_id="sett_reload_sd_model")
with gr.TabItem("Licenses"):
gr.HTML(shared.html("licenses.html"), elem_id="licenses")
gr.Button(value="Show all pages", elem_id="settings_show_all_pages")
+
+
+ def unload_sd_weights():
+ modules.sd_models.unload_model_weights()
+
+ def reload_sd_weights():
+ modules.sd_models.reload_model_weights()
+
+ unload_sd_model.click(
+ fn=unload_sd_weights,
+ inputs=[],
+ outputs=[]
+ )
+
+ reload_sd_model.click(
+ fn=reload_sd_weights,
+ inputs=[],
+ outputs=[]
+ )
request_notifications.click(
fn=lambda: None,
@@ -1598,11 +1623,13 @@ def create_ui(): for i, k, item in quicksettings_list:
component = component_dict[k]
+ info = opts.data_labels[k]
component.change(
fn=lambda value, k=k: run_settings_single(value, key=k),
inputs=[component],
outputs=[component, text_settings],
+ show_progress=info.refresh is not None,
)
text_settings.change(
@@ -1751,7 +1778,8 @@ def create_ui(): def reload_javascript():
- head = f'<script type="text/javascript" src="file={os.path.abspath("script.js")}?{os.path.getmtime("script.js")}"></script>\n'
+ script_js = os.path.join(script_path, "script.js")
+ head = f'<script type="text/javascript" src="file={os.path.abspath(script_js)}?{os.path.getmtime(script_js)}"></script>\n'
inline = f"{localization.localization_js(shared.opts.localization)};"
if cmd_opts.theme is not None:
diff --git a/modules/ui_common.py b/modules/ui_common.py index a12433d2..7b752b45 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -129,8 +129,8 @@ Requested path was: {f} generation_info = None
with gr.Column():
- with gr.Row(elem_id=f"image_buttons_{tabname}"):
- open_folder_button = gr.Button(folder_symbol, elem_id="hidden_element" if shared.cmd_opts.hide_ui_dir_config else f'open_folder_{tabname}')
+ with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"):
+ open_folder_button = gr.Button(folder_symbol, visible=not shared.cmd_opts.hide_ui_dir_config)
if tabname != "extras":
save = gr.Button('Save', elem_id=f'save_{tabname}')
@@ -160,6 +160,7 @@ Requested path was: {f} _js="function(x, y, z){ return [x, y, selected_gallery_index()] }",
inputs=[generation_info, html_info, html_info],
outputs=[html_info, html_info],
+ show_progress=False,
)
save.click(
diff --git a/modules/ui_components.py b/modules/ui_components.py index 284ca0cf..2b1da2cb 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -1,55 +1,61 @@ import gradio as gr
-class ToolButton(gr.Button, gr.components.FormComponent):
- """Small button with single emoji as text, fits inside gradio forms"""
+class FormComponent:
+ def get_expected_parent(self):
+ return gr.components.Form
- def __init__(self, **kwargs):
- super().__init__(variant="tool", **kwargs)
- def get_block_name(self):
- return "button"
+gr.Dropdown.get_expected_parent = FormComponent.get_expected_parent
-class ToolButtonTop(gr.Button, gr.components.FormComponent):
- """Small button with single emoji as text, with extra margin at top, fits inside gradio forms"""
+class ToolButton(FormComponent, gr.Button):
+ """Small button with single emoji as text, fits inside gradio forms"""
- def __init__(self, **kwargs):
- super().__init__(variant="tool-top", **kwargs)
+ def __init__(self, *args, **kwargs):
+ classes = kwargs.pop("elem_classes", [])
+ super().__init__(*args, elem_classes=["tool", *classes], **kwargs)
def get_block_name(self):
return "button"
-class FormRow(gr.Row, gr.components.FormComponent):
+class FormRow(FormComponent, gr.Row):
"""Same as gr.Row but fits inside gradio forms"""
def get_block_name(self):
return "row"
-class FormGroup(gr.Group, gr.components.FormComponent):
+class FormColumn(FormComponent, gr.Column):
+ """Same as gr.Column but fits inside gradio forms"""
+
+ def get_block_name(self):
+ return "column"
+
+
+class FormGroup(FormComponent, gr.Group):
"""Same as gr.Row but fits inside gradio forms"""
def get_block_name(self):
return "group"
-class FormHTML(gr.HTML, gr.components.FormComponent):
+class FormHTML(FormComponent, gr.HTML):
"""Same as gr.HTML but fits inside gradio forms"""
def get_block_name(self):
return "html"
-class FormColorPicker(gr.ColorPicker, gr.components.FormComponent):
+class FormColorPicker(FormComponent, gr.ColorPicker):
"""Same as gr.ColorPicker but fits inside gradio forms"""
def get_block_name(self):
return "colorpicker"
-class DropdownMulti(gr.Dropdown):
+class DropdownMulti(FormComponent, gr.Dropdown):
"""Same as gr.Dropdown but always multiselect"""
def __init__(self, **kwargs):
super().__init__(multiselect=True, **kwargs)
diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index bd4308ef..4a502974 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -1,6 +1,5 @@ import json
import os.path
-import shutil
import sys
import time
import traceback
@@ -141,22 +140,20 @@ def install_extension_from_url(dirname, url): try:
shutil.rmtree(tmpdir, True)
-
- repo = git.Repo.clone_from(url, tmpdir)
- repo.remote().fetch()
-
+ with git.Repo.clone_from(url, tmpdir) as repo:
+ repo.remote().fetch()
+ for submodule in repo.submodules:
+ submodule.update()
try:
os.rename(tmpdir, target_dir)
except OSError as err:
- # TODO what does this do on windows? I think it'll be a different error code but I don't have a system to check it
- # Shouldn't cause any new issues at least but we probably want to handle it there too.
if err.errno == errno.EXDEV:
# Cross device link, typical in docker or when tmp/ and extensions/ are on different file systems
# Since we can't use a rename, do the slower but more versitile shutil.move()
shutil.move(tmpdir, target_dir)
else:
# Something else, not enough free space, permissions, etc. rethrow it so that it gets handled.
- raise(err)
+ raise err
import launch
launch.run_extension_installer(target_dir)
@@ -244,7 +241,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column): hidden += 1
continue
- install_code = f"""<input onclick="install_extension_from_index(this, '{html.escape(url)}')" type="button" value="{"Install" if not existing else "Installed"}" {"disabled=disabled" if existing else ""} class="gr-button gr-button-lg gr-button-secondary">"""
+ install_code = f"""<button onclick="install_extension_from_index(this, '{html.escape(url)}')" {"disabled=disabled" if existing else ""} class="lg secondary gradio-button custom-button">{"Install" if not existing else "Installed"}</button>"""
tags_text = ", ".join([f"<span class='extension-tag' title='{tags.get(x, '')}'>{x}</span>" for x in extension_tags])
@@ -304,7 +301,7 @@ def create_ui(): with gr.TabItem("Available"):
with gr.Row():
refresh_available_extensions_button = gr.Button(value="Load from:", variant="primary")
- available_extensions_index = gr.Text(value="https://raw.githubusercontent.com/wiki/AUTOMATIC1111/stable-diffusion-webui/Extensions-index.md", label="Extension index URL").style(container=False)
+ available_extensions_index = gr.Text(value="https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json", label="Extension index URL").style(container=False)
extension_to_install = gr.Text(elem_id="extension_to_install", visible=False)
install_extension_button = gr.Button(elem_id="install_extension_button", visible=False)
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 85f0af4c..8418147b 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -22,21 +22,37 @@ def register_page(page): allowed_dirs.update(set(sum([x.allowed_directories_for_previews() for x in extra_pages], [])))
-def add_pages_to_demo(app):
- def fetch_file(filename: str = ""):
- from starlette.responses import FileResponse
+def fetch_file(filename: str = ""):
+ from starlette.responses import FileResponse
+
+ if not any([Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs]):
+ raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.")
+
+ ext = os.path.splitext(filename)[1].lower()
+ if ext not in (".png", ".jpg", ".webp"):
+ raise ValueError(f"File cannot be fetched: {filename}. Only png and jpg and webp.")
+
+ # would profit from returning 304
+ return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
+
+
+def get_metadata(page: str = "", item: str = ""):
+ from starlette.responses import JSONResponse
- if not any([Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs]):
- raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.")
+ page = next(iter([x for x in extra_pages if x.name == page]), None)
+ if page is None:
+ return JSONResponse({})
- ext = os.path.splitext(filename)[1].lower()
- if ext not in (".png", ".jpg"):
- raise ValueError(f"File cannot be fetched: {filename}. Only png and jpg.")
+ metadata = page.metadata.get(item)
+ if metadata is None:
+ return JSONResponse({})
- # would profit from returning 304
- return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
+ return JSONResponse({"metadata": metadata})
+
+def add_pages_to_demo(app):
app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"])
+ app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"])
class ExtraNetworksPage:
@@ -45,6 +61,7 @@ class ExtraNetworksPage: self.name = title.lower()
self.card_page = shared.html("extra-networks-card.html")
self.allow_negative_prompt = False
+ self.metadata = {}
def refresh(self):
pass
@@ -66,6 +83,8 @@ class ExtraNetworksPage: view = shared.opts.extra_networks_default_view
items_html = ''
+ self.metadata = {}
+
subdirs = {}
for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]:
for x in glob.glob(os.path.join(parentdir, '**/*'), recursive=True):
@@ -86,12 +105,16 @@ class ExtraNetworksPage: subdirs = {"": 1, **subdirs}
subdirs_html = "".join([f"""
-<button class='gr-button gr-button-lg gr-button-secondary{" search-all" if subdir=="" else ""}' onclick='extraNetworksSearchButton("{tabname}_extra_tabs", event)'>
+<button class='lg secondary gradio-button custom-button{" search-all" if subdir=="" else ""}' onclick='extraNetworksSearchButton("{tabname}_extra_tabs", event)'>
{html.escape(subdir if subdir!="" else "all")}
</button>
""" for subdir in subdirs])
for item in self.list_items():
+ metadata = item.get("metadata")
+ if metadata:
+ self.metadata[item["name"]] = metadata
+
items_html += self.create_html_for_item(item, tabname)
if items_html == '':
@@ -124,6 +147,11 @@ class ExtraNetworksPage: if onclick is None:
onclick = '"' + html.escape(f"""return cardClicked({json.dumps(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"'
+ metadata_button = ""
+ metadata = item.get("metadata")
+ if metadata:
+ metadata_button = f"<div class='metadata-button' title='Show metadata' onclick='extraNetworksRequestMetadata(event, {json.dumps(self.name)}, {json.dumps(item['name'])})'></div>"
+
args = {
"preview_html": "style='background-image: url(\"" + html.escape(preview) + "\")'" if preview else '',
"prompt": item.get("prompt", None),
@@ -134,6 +162,7 @@ class ExtraNetworksPage: "card_clicked": onclick,
"save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"',
"search_term": item.get("search_term", ""),
+ "metadata_button": metadata_button,
}
return self.card_page.format(**args)
@@ -208,12 +237,12 @@ def create_ui(container, button, tabname): with gr.Tabs(elem_id=tabname+"_extra_tabs") as tabs:
for page in ui.stored_extra_pages:
with gr.Tab(page.title):
+
page_elem = gr.HTML(page.create_html(ui.tabname))
ui.pages.append(page_elem)
filter = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False)
button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh")
- button_close = gr.Button('Close', elem_id=tabname+"_extra_close")
ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False)
ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False)
@@ -224,7 +253,6 @@ def create_ui(container, button, tabname): state_visible = gr.State(value=False)
button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container])
- button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container])
def refresh():
res = []
diff --git a/requirements.txt b/requirements.txt index 6d53f089..3c40fd07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ basicsr fonts
font-roboto
gfpgan
-gradio==3.16.2
+gradio==3.23
invisible-watermark
numpy
omegaconf
diff --git a/requirements_versions.txt b/requirements_versions.txt index 41e0ccc5..77e3da4e 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -3,7 +3,7 @@ transformers==4.25.1 accelerate==0.12.0
basicsr==1.4.2
gfpgan==1.3.8
-gradio==3.16.2
+gradio==3.23
numpy==1.23.3
Pillow==9.4.0
realesrgan==0.3.0
@@ -27,4 +27,4 @@ GitPython==3.1.30 torchsde==0.2.5
safetensors==0.2.7
httpcore<=0.15
-fastapi==0.90.1
+fastapi==0.94.0
@@ -1,7 +1,9 @@ function gradioApp() { const elems = document.getElementsByTagName('gradio-app') - const gradioShadowRoot = elems.length == 0 ? null : elems[0].shadowRoot - return !!gradioShadowRoot ? gradioShadowRoot : document; + const elem = elems.length == 0 ? document : elems[0] + + elem.getElementById = function(id){ return document.getElementById(id) } + return elem.shadowRoot ? elem.shadowRoot : elem } function get_uiCurrentTab() { diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index 2572443f..bb00fb3f 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -6,23 +6,21 @@ from tqdm import trange import modules.scripts as scripts
import gradio as gr
-from modules import processing, shared, sd_samplers, prompt_parser, sd_samplers_common
-from modules.processing import Processed
-from modules.shared import opts, cmd_opts, state
+from modules import processing, shared, sd_samplers, sd_samplers_common
import torch
import k_diffusion as K
-from PIL import Image
-from torch import autocast
-from einops import rearrange, repeat
-
-
def find_noise_for_image(p, cond, uncond, cfg_scale, steps):
x = p.init_latent
s_in = x.new_ones([x.shape[0]])
- dnw = K.external.CompVisDenoiser(shared.sd_model)
+ if shared.sd_model.parameterization == "v":
+ dnw = K.external.CompVisVDenoiser(shared.sd_model)
+ skip = 1
+ else:
+ dnw = K.external.CompVisDenoiser(shared.sd_model)
+ skip = 0
sigmas = dnw.get_sigmas(steps).flip(0)
shared.state.sampling_steps = steps
@@ -37,7 +35,7 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps): image_conditioning = torch.cat([p.image_conditioning] * 2)
cond_in = {"c_concat": [image_conditioning], "c_crossattn": [cond_in]}
- c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)]
+ c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)[skip:]]
t = dnw.sigma_to_t(sigma_in)
eps = shared.sd_model.apply_model(x_in * c_in, t, cond=cond_in)
@@ -69,7 +67,12 @@ def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): x = p.init_latent
s_in = x.new_ones([x.shape[0]])
- dnw = K.external.CompVisDenoiser(shared.sd_model)
+ if shared.sd_model.parameterization == "v":
+ dnw = K.external.CompVisVDenoiser(shared.sd_model)
+ skip = 1
+ else:
+ dnw = K.external.CompVisDenoiser(shared.sd_model)
+ skip = 0
sigmas = dnw.get_sigmas(steps).flip(0)
shared.state.sampling_steps = steps
@@ -84,7 +87,7 @@ def find_noise_for_image_sigma_adjustment(p, cond, uncond, cfg_scale, steps): image_conditioning = torch.cat([p.image_conditioning] * 2)
cond_in = {"c_concat": [image_conditioning], "c_crossattn": [cond_in]}
- c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)]
+ c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)[skip:]]
if i == 1:
t = dnw.sigma_to_t(torch.cat([sigmas[i] * s_in] * 2))
@@ -125,7 +128,7 @@ class Script(scripts.Script): def show(self, is_img2img):
return is_img2img
- def ui(self, is_img2img):
+ def ui(self, is_img2img):
info = gr.Markdown('''
* `CFG Scale` should be 2 or lower.
''')
@@ -213,4 +216,3 @@ class Script(scripts.Script): processed = processing.process_images(p)
return processed
-
diff --git a/scripts/loopback.py b/scripts/loopback.py index ec1f85e5..9c388aa8 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -1,14 +1,10 @@ -import numpy as np
-from tqdm import trange
+import math
-import modules.scripts as scripts
import gradio as gr
-
-from modules import processing, shared, sd_samplers, images
+import modules.scripts as scripts
+from modules import deepbooru, images, processing, shared
from modules.processing import Processed
-from modules.sd_samplers import samplers
-from modules.shared import opts, cmd_opts, state
-from modules import deepbooru
+from modules.shared import opts, state
class Script(scripts.Script):
@@ -20,39 +16,68 @@ class Script(scripts.Script): def ui(self, is_img2img):
loops = gr.Slider(minimum=1, maximum=32, step=1, label='Loops', value=4, elem_id=self.elem_id("loops"))
- denoising_strength_change_factor = gr.Slider(minimum=0.9, maximum=1.1, step=0.01, label='Denoising strength change factor', value=1, elem_id=self.elem_id("denoising_strength_change_factor"))
+ final_denoising_strength = gr.Slider(minimum=0, maximum=1, step=0.01, label='Final denoising strength', value=0.5, elem_id=self.elem_id("final_denoising_strength"))
+ denoising_curve = gr.Dropdown(label="Denoising strength curve", choices=["Aggressive", "Linear", "Lazy"], value="Linear")
append_interrogation = gr.Dropdown(label="Append interrogated prompt at each iteration", choices=["None", "CLIP", "DeepBooru"], value="None")
- return [loops, denoising_strength_change_factor, append_interrogation]
+ return [loops, final_denoising_strength, denoising_curve, append_interrogation]
- def run(self, p, loops, denoising_strength_change_factor, append_interrogation):
+ def run(self, p, loops, final_denoising_strength, denoising_curve, append_interrogation):
processing.fix_seed(p)
batch_count = p.n_iter
p.extra_generation_params = {
- "Denoising strength change factor": denoising_strength_change_factor,
+ "Final denoising strength": final_denoising_strength,
+ "Denoising curve": denoising_curve
}
p.batch_size = 1
p.n_iter = 1
- output_images, info = None, None
+ info = None
initial_seed = None
initial_info = None
+ initial_denoising_strength = p.denoising_strength
grids = []
all_images = []
original_init_image = p.init_images
original_prompt = p.prompt
+ original_inpainting_fill = p.inpainting_fill
state.job_count = loops * batch_count
initial_color_corrections = [processing.setup_color_correction(p.init_images[0])]
- for n in range(batch_count):
- history = []
+ def calculate_denoising_strength(loop):
+ strength = initial_denoising_strength
+
+ if loops == 1:
+ return strength
+
+ progress = loop / (loops - 1)
+ match denoising_curve:
+ case "Aggressive":
+ strength = math.sin((progress) * math.pi * 0.5)
+
+ case "Lazy":
+ strength = 1 - math.cos((progress) * math.pi * 0.5)
+
+ case _:
+ strength = progress
+
+ change = (final_denoising_strength - initial_denoising_strength) * strength
+ return initial_denoising_strength + change
+
+ history = []
+ for n in range(batch_count):
# Reset to original init image at the start of each batch
p.init_images = original_init_image
+ # Reset to original denoising strength
+ p.denoising_strength = initial_denoising_strength
+
+ last_image = None
+
for i in range(loops):
p.n_iter = 1
p.batch_size = 1
@@ -72,26 +97,46 @@ class Script(scripts.Script): processed = processing.process_images(p)
+ # Generation cancelled.
+ if state.interrupted:
+ break
+
if initial_seed is None:
initial_seed = processed.seed
initial_info = processed.info
- init_img = processed.images[0]
-
- p.init_images = [init_img]
p.seed = processed.seed + 1
- p.denoising_strength = min(max(p.denoising_strength * denoising_strength_change_factor, 0.1), 1)
- history.append(processed.images[0])
+ p.denoising_strength = calculate_denoising_strength(i + 1)
+
+ if state.skipped:
+ break
+ last_image = processed.images[0]
+ p.init_images = [last_image]
+ p.inpainting_fill = 1 # Set "masked content" to "original" for next loop.
+
+ if batch_count == 1:
+ history.append(last_image)
+ all_images.append(last_image)
+
+ if batch_count > 1 and not state.skipped and not state.interrupted:
+ history.append(last_image)
+ all_images.append(last_image)
+
+ p.inpainting_fill = original_inpainting_fill
+
+ if state.interrupted:
+ break
+
+ if len(history) > 1:
grid = images.image_grid(history, rows=1)
if opts.grid_save:
images.save_image(grid, p.outpath_grids, "grid", initial_seed, p.prompt, opts.grid_format, info=info, short_filename=not opts.grid_extended_filename, grid=True, p=p)
- grids.append(grid)
- all_images += history
-
- if opts.return_grid:
- all_images = grids + all_images
+ if opts.return_grid:
+ grids.append(grid)
+
+ all_images = grids + all_images
processed = Processed(p, all_images, initial_seed, initial_info)
diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 8842bd91..11eab31a 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -17,22 +17,24 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): def ui(self):
selected_tab = gr.State(value=0)
- with gr.Tabs(elem_id="extras_resize_mode"):
- with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by:
- upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize")
-
- with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to:
- with FormRow():
- upscaling_resize_w = gr.Number(label="Width", value=512, precision=0, elem_id="extras_upscaling_resize_w")
- upscaling_resize_h = gr.Number(label="Height", value=512, precision=0, elem_id="extras_upscaling_resize_h")
- upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop")
-
- with FormRow():
- extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
-
- with FormRow():
- extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
- extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility")
+ with gr.Column():
+ with FormRow():
+ with gr.Tabs(elem_id="extras_resize_mode"):
+ with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by:
+ upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize")
+
+ with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to:
+ with FormRow():
+ upscaling_resize_w = gr.Number(label="Width", value=512, precision=0, elem_id="extras_upscaling_resize_w")
+ upscaling_resize_h = gr.Number(label="Height", value=512, precision=0, elem_id="extras_upscaling_resize_h")
+ upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop")
+
+ with FormRow():
+ extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
+
+ with FormRow():
+ extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
+ extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility")
tab_scale_by.select(fn=lambda: 0, inputs=[], outputs=[selected_tab])
tab_scale_to.select(fn=lambda: 1, inputs=[], outputs=[selected_tab])
diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 9a0678fa..3551aca0 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -132,6 +132,20 @@ def apply_uni_pc_order(p, x, xs): opts.data["uni_pc_order"] = min(x, p.steps - 1)
+def apply_face_restore(p, opt, x):
+ opt = opt.lower()
+ if opt == 'codeformer':
+ is_active = True
+ p.face_restoration_model = 'CodeFormer'
+ elif opt == 'gfpgan':
+ is_active = True
+ p.face_restoration_model = 'GFPGAN'
+ else:
+ is_active = opt in ('true', 'yes', 'y', '1')
+
+ p.restore_faces = is_active
+
+
def format_value_add_label(p, opt, x):
if type(x) == float:
x = round(x, 8)
@@ -210,6 +224,7 @@ axis_options = [ AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: list(sd_vae.vae_dict)),
AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)),
AxisOption("UniPC Order", int, apply_uni_pc_order, cost=0.5),
+ AxisOption("Face restore", str, apply_face_restore, format_value=format_value),
]
@@ -232,7 +247,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend state.job = f"{index(ix, iy, iz) + 1} out of {list_size}"
- processed: Processed = cell(x, y, z)
+ processed: Processed = cell(x, y, z, ix, iy, iz)
if processed_result is None:
# Use our first processed result object as a template container to hold our full results
@@ -543,8 +558,6 @@ class Script(scripts.Script): print(f"X/Y/Z plot will create {len(xs) * len(ys) * len(zs) * image_cell_count} images on {len(zs)} {len(xs)}x{len(ys)} grid{plural_s}{cell_console_text}. (Total steps to process: {total_steps})")
shared.total_tqdm.updateTotal(total_steps)
- grid_infotext = [None]
-
state.xyz_plot_x = AxisInfo(x_opt, xs)
state.xyz_plot_y = AxisInfo(y_opt, ys)
state.xyz_plot_z = AxisInfo(z_opt, zs)
@@ -573,7 +586,9 @@ class Script(scripts.Script): else:
second_axes_processed = 'y'
- def cell(x, y, z):
+ grid_infotext = [None] * (1 + len(zs))
+
+ def cell(x, y, z, ix, iy, iz):
if shared.state.interrupted:
return Processed(p, [], p.seed, "")
@@ -585,7 +600,9 @@ class Script(scripts.Script): res = process_images(pc)
- if grid_infotext[0] is None:
+ # Sets subgrid infotexts
+ subgrid_index = 1 + iz
+ if grid_infotext[subgrid_index] is None and ix == 0 and iy == 0:
pc.extra_generation_params = copy(pc.extra_generation_params)
pc.extra_generation_params['Script'] = self.title()
@@ -601,6 +618,12 @@ class Script(scripts.Script): if y_opt.label in ["Seed", "Var. seed"] and not no_fixed_seeds:
pc.extra_generation_params["Fixed Y Values"] = ", ".join([str(y) for y in ys])
+ grid_infotext[subgrid_index] = processing.create_infotext(pc, pc.all_prompts, pc.all_seeds, pc.all_subseeds)
+
+ # Sets main grid infotext
+ if grid_infotext[0] is None and ix == 0 and iy == 0 and iz == 0:
+ pc.extra_generation_params = copy(pc.extra_generation_params)
+
if z_opt.label != 'Nothing':
pc.extra_generation_params["Z Type"] = z_opt.label
pc.extra_generation_params["Z Values"] = z_values
@@ -635,6 +658,9 @@ class Script(scripts.Script): z_count = len(zs)
+ # Set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids)
+ processed.infotexts[:1+z_count] = grid_infotext[:1+z_count]
+
if not include_lone_images:
# Don't need sub-images anymore, drop from list:
processed.images = processed.images[:z_count+1]
@@ -1,270 +1,313 @@ -.container {
- max-width: 100%;
-}
-.token-counter{
- position: absolute;
- display: inline-block;
- right: 2em;
- min-width: 0 !important;
- width: auto;
- z-index: 100;
-}
+/* general gradio fixes */
-.token-counter.error span{
- box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075);
- border: 2px solid rgba(255,0,0,0.4) !important;
+:root, .dark{
+ --checkbox-label-gap: 0.25em 0.1em;
+ --section-header-text-size: 12pt;
+ --block-background-fill: transparent;
}
-.token-counter div{
- display: inline;
+.block.padded{
+ padding: 0 !important;
}
-.token-counter span{
- padding: 0.1em 0.75em;
+div.gradio-container{
+ max-width: unset !important;
}
-#sh{
- min-width: 2em;
- min-height: 2em;
- max-width: 2em;
- max-height: 2em;
- flex-grow: 0;
- padding-left: 0.25em;
- padding-right: 0.25em;
- margin: 0.1em 0;
- opacity: 0%;
- cursor: default;
+.hidden{
+ display: none;
}
-.output-html p {margin: 0 0.5em;}
-
-.row > *,
-.row > .gr-form > * {
- min-width: min(120px, 100%);
- flex: 1 1 0%;
+.compact{
+ background: transparent !important;
+ padding: 0 !important;
}
-.performance {
- font-size: 0.85em;
- color: #444;
+div.form{
+ border-width: 0;
+ box-shadow: none;
+ background: transparent;
+ overflow: visible;
+ gap: 0.5em;
}
-.performance p{
- display: inline-block;
+.block.gradio-dropdown,
+.block.gradio-slider,
+.block.gradio-checkbox,
+.block.gradio-textbox,
+.block.gradio-radio,
+.block.gradio-checkboxgroup,
+.block.gradio-number,
+.block.gradio-colorpicker
+{
+ border-width: 0 !important;
+ box-shadow: none !important;
}
-.performance .time {
- margin-right: 0;
+.gap.compact{
+ padding: 0;
+ gap: 0.2em 0;
}
-.performance .vram {
+div.compact{
+ gap: 1em;
}
-#txt2img_generate, #img2img_generate {
- min-height: 4.5em;
+.gradio-dropdown ul.options{
+ z-index: 3000;
}
-@media screen and (min-width: 2500px) {
- #txt2img_gallery, #img2img_gallery {
- min-height: 768px;
- }
+.gradio-dropdown label span:not(.has-info),
+.gradio-textbox label span:not(.has-info),
+.gradio-number label span:not(.has-info)
+{
+ margin-bottom: 0;
}
-#txt2img_gallery img, #img2img_gallery img{
- object-fit: scale-down;
+.gradio-dropdown div.wrap.wrap.wrap.wrap{
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
-#txt2img_actions_column, #img2img_actions_column {
- margin: 0.35rem 0.75rem 0.35rem 0;
+
+.gradio-dropdown .wrap-inner.wrap-inner.wrap-inner{
+ flex-wrap: unset;
}
-#script_list {
- padding: .625rem .75rem 0 .625rem;
+
+.gradio-dropdown .single-select{
+ white-space: nowrap;
+ overflow: hidden;
}
-.justify-center.overflow-x-scroll {
- justify-content: left;
+
+.gradio-dropdown .token-remove.remove-all.remove-all{
+ display: none;
}
-.justify-center.overflow-x-scroll button:first-of-type {
- margin-left: auto;
+.gradio-dropdown.multiselect .token-remove.remove-all.remove-all{
+ display: flex;
}
-.justify-center.overflow-x-scroll button:last-of-type {
- margin-right: auto;
+.gradio-slider input[type="number"]{
+ width: 6em;
}
-[id$=_random_seed], [id$=_random_subseed], [id$=_reuse_seed], [id$=_reuse_subseed], #open_folder{
- min-width: 2.3em;
- height: 2.5em;
- flex-grow: 0;
- padding-left: 0.25em;
- padding-right: 0.25em;
+.block.gradio-checkbox {
+ margin: 0.75em 1.5em 0 0;
}
-#hidden_element{
- display: none;
+.gradio-html div.wrap{
+ height: 100%;
+}
+div.gradio-html.min{
+ min-height: 0;
}
-[id$=_seed_row], [id$=_subseed_row]{
- gap: 0.5rem;
- padding: 0.6em;
+.block.gradio-gallery{
+ background: var(--input-background-fill);
}
-[id$=_subseed_show_box]{
- min-width: auto;
- flex-grow: 0;
+.gradio-container .prose a, .gradio-container .prose a:visited{
+ color: unset;
+ text-decoration: none;
}
-[id$=_subseed_show_box] > div{
- border: 0;
- height: 100%;
+
+
+/* general styled components */
+
+.gradio-button.tool{
+ max-width: 2.2em;
+ min-width: 2.2em !important;
+ height: 2.4em;
+ align-self: end;
+ line-height: 1em;
+ border-radius: 0.5em;
}
-[id$=_subseed_show]{
+.checkboxes-row{
+ margin-bottom: 0.5em;
+ margin-left: 0em;
+}
+.checkboxes-row > div{
+ flex: 0;
+ white-space: nowrap;
min-width: auto;
- flex-grow: 0;
- padding: 0;
}
-[id$=_subseed_show] label{
- height: 100%;
+button.custom-button{
+ border-radius: var(--button-large-radius);
+ padding: var(--button-large-padding);
+ font-weight: var(--button-large-text-weight);
+ border: var(--button-border-width) solid var(--button-secondary-border-color);
+ background: var(--button-secondary-background-fill);
+ color: var(--button-secondary-text-color);
+ font-size: var(--button-large-text-size);
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ transition: var(--button-transition);
+ box-shadow: var(--button-shadow);
+ text-align: center;
}
-#txt2img_actions_column, #img2img_actions_column{
- gap: 0;
- margin-right: .75rem;
-}
-#txt2img_tools, #img2img_tools{
- gap: 0.4em;
-}
+/* txt2img/img2img specific */
-#interrogate_col{
+.block.token-counter{
+ position: absolute;
+ display: inline-block;
+ right: 1em;
min-width: 0 !important;
- max-width: 8em !important;
- margin-right: 1em;
- gap: 0;
+ width: auto;
+ z-index: 100;
+ top: -0.75em;
}
-#interrogate, #deepbooru{
- margin: 0em 0.25em 0.5em 0.25em;
- min-width: 8em;
- max-width: 8em;
+
+.block.token-counter span{
+ background: var(--input-background-fill) !important;
+ box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075);
+ border: 2px solid rgba(192,192,192,0.4) !important;
+ border-radius: 0.4em;
}
-#style_pos_col, #style_neg_col{
- min-width: 8em !important;
+.block.token-counter.error span{
+ box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075);
+ border: 2px solid rgba(255,0,0,0.4) !important;
}
-#txt2img_styles_row, #img2img_styles_row{
- gap: 0.25em;
- margin-top: 0.3em;
+.block.token-counter div{
+ display: inline;
}
-#txt2img_styles_row > button, #img2img_styles_row > button{
- margin: 0;
+.block.token-counter span{
+ padding: 0.1em 0.75em;
}
-#txt2img_styles, #img2img_styles{
- padding: 0;
+[id$=_subseed_show]{
+ min-width: auto !important;
+ flex-grow: 0 !important;
+ display: flex;
}
-#txt2img_styles > label > div, #img2img_styles > label > div{
- min-height: 3.2em;
+[id$=_subseed_show] label{
+ margin-bottom: 0.5em;
+ align-self: end;
}
-ul.list-none{
- max-height: 35em;
- z-index: 2000;
+.performance {
+ font-size: 0.85em;
+ color: #444;
}
-.gr-form{
- background: transparent;
+.performance p{
+ display: inline-block;
}
-.my-4{
- margin-top: 0;
- margin-bottom: 0;
+.performance .time {
+ margin-right: 0;
}
-#resize_mode{
- flex: 1.5;
+.performance .vram {
}
-button{
- align-self: stretch !important;
+#txt2img_generate, #img2img_generate {
+ min-height: 4.5em;
}
-.overflow-hidden, .gr-panel{
- overflow: visible !important;
+@media screen and (min-width: 2500px) {
+ #txt2img_gallery, #img2img_gallery {
+ min-height: 768px;
+ }
+}
+
+#txt2img_gallery img, #img2img_gallery img{
+ object-fit: scale-down;
+}
+#txt2img_actions_column, #img2img_actions_column {
+ gap: 0.5em;
+}
+#txt2img_tools, #img2img_tools{
+ gap: 0.4em;
}
-#x_type, #y_type{
- max-width: 10em;
+.interrogate-col{
+ min-width: 0 !important;
+ max-width: fit-content;
+ gap: 0.5em;
+}
+.interrogate-col > button{
+ flex: 1;
}
-#txt2img_preview, #img2img_preview, #ti_preview{
+.generate-box{
+ position: relative;
+}
+.gradio-button.generate-box-skip, .gradio-button.generate-box-interrupt{
position: absolute;
- width: 320px;
+ width: 50%;
+ height: 100%;
+ display: none;
+ background: #b4c0cc;
+}
+.gradio-button.generate-box-skip:hover, .gradio-button.generate-box-interrupt:hover{
+ background: #c2cfdb;
+}
+.gradio-button.generate-box-interrupt{
left: 0;
+ border-radius: 0.5rem 0 0 0.5rem;
+}
+.gradio-button.generate-box-skip{
right: 0;
- margin-left: auto;
- margin-right: auto;
- margin-top: 34px;
- z-index: 100;
- border: none;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
+ border-radius: 0 0.5rem 0.5rem 0;
}
-@media screen and (min-width: 768px) {
- #txt2img_preview, #img2img_preview, #ti_preview {
- position: absolute;
- }
+#txtimg_hr_finalres{
+ min-height: 0 !important;
+ padding: .625rem .75rem;
+ margin-left: -0.75em
}
-@media screen and (max-width: 767px) {
- #txt2img_preview, #img2img_preview, #ti_preview {
- position: relative;
- }
+#txtimg_hr_finalres .resolution{
+ font-weight: bold;
}
-#txt2img_preview div.left-0.top-0, #img2img_preview div.left-0.top-0, #ti_preview div.left-0.top-0{
- display: none;
+.inactive{
+ opacity: 0.5;
}
-fieldset span.text-gray-500, .gr-block.gr-box span.text-gray-500, label.block span{
- position: absolute;
- top: -0.7em;
- line-height: 1.2em;
- padding: 0;
- margin: 0 0.5em;
-
- background-color: white;
- box-shadow: 6px 0 6px 0px white, -6px 0 6px 0px white;
+[id$=_column_batch]{
+ min-width: min(13.5em, 100%) !important;
+}
- z-index: 300;
+div.dimensions-tools{
+ min-width: 0 !important;
+ max-width: fit-content;
+ flex-direction: row;
+ align-content: center;
}
-.dark fieldset span.text-gray-500, .dark .gr-block.gr-box span.text-gray-500, .dark label.block span{
- background-color: rgb(31, 41, 55);
- box-shadow: none;
- border: 1px solid rgba(128, 128, 128, 0.1);
- border-radius: 6px;
- padding: 0.1em 0.5em;
+#mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{
+ height: 480px !important;
+ max-height: 480px !important;
+ min-height: 480px !important;
}
-#txt2img_column_batch, #img2img_column_batch{
- min-width: min(13.5em, 100%) !important;
+.image-buttons button{
+ min-width: auto;
}
-#settings fieldset span.text-gray-500, #settings .gr-block.gr-box span.text-gray-500, #settings label.block span{
- position: relative;
- border: none;
- margin-right: 8em;
+
+/* settings */
+#quicksettings {
+ width: fit-content;
}
-#settings .gr-panel div.flex-col div.justify-between div{
- position: relative;
- z-index: 200;
+#quicksettings > div, #quicksettings > fieldset{
+ max-width: 24em;
+ min-width: 24em;
+ padding: 0;
+ border: none;
+ box-shadow: none;
+ background: none;
}
#settings{
@@ -276,17 +319,18 @@ fieldset span.text-gray-500, .gr-block.gr-box span.text-gray-500, label.block s margin-left: 10em;
}
-#settings > div.flex-wrap{
+#settings > div.tab-nav{
float: left;
display: block;
margin-left: 0;
width: 10em;
}
-#settings > div.flex-wrap button{
+#settings > div.tab-nav button{
display: block;
border: none;
text-align: left;
+ white-space: initial;
}
#settings_result{
@@ -294,29 +338,8 @@ fieldset span.text-gray-500, .gr-block.gr-box span.text-gray-500, label.block s margin: 0 1.2em;
}
-input[type="range"]{
- margin: 0.5em 0 -0.3em 0;
-}
-
-#mask_bug_info {
- text-align: center;
- display: block;
- margin-top: -0.75em;
- margin-bottom: -0.75em;
-}
-
-#txt2img_negative_prompt, #img2img_negative_prompt{
-}
-
-/* gradio 3.8 adds opacity to progressbar which makes it blink; disable it here */
-.transition.opacity-20 {
- opacity: 1 !important;
-}
-
-/* more gradio's garbage cleanup */
-.min-h-\[4rem\] { min-height: unset !important; }
-.min-h-\[6rem\] { min-height: unset !important; }
+/* live preview */
.progressDiv{
position: relative;
height: 20px;
@@ -362,89 +385,96 @@ input[type="range"]{ height: 100%;
}
-#lightboxModal{
- display: none;
- position: fixed;
- z-index: 1001;
- padding-top: 100px;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- overflow: auto;
- background-color: rgba(20, 20, 20, 0.95);
- user-select: none;
- -webkit-user-select: none;
+/* fullscreen popup (ie in Lora's (i) button) */
+
+.popup-metadata{
+ color: black;
+ background: white;
+ display: inline-block;
+ padding: 1em;
+ white-space: pre-wrap;
}
-.modalControls {
- display: grid;
- grid-template-columns: 32px 32px 32px 1fr 32px;
- grid-template-areas: "zoom tile save space close";
- position: absolute;
- top: 0;
+.global-popup{
+ display: flex;
+ position: fixed;
+ z-index: 1001;
left: 0;
- right: 0;
- padding: 16px;
- gap: 16px;
- background-color: rgba(0,0,0,0.2);
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(20, 20, 20, 0.95);
}
-.modalClose {
- grid-area: close;
-}
-.modalZoom {
- grid-area: zoom;
+.global-popup-close:before {
+ content: "×";
}
-.modalSave {
- grid-area: save;
+.global-popup-close{
+ position: fixed;
+ right: 0.25em;
+ top: 0;
+ cursor: pointer;
+ color: white;
+ font-size: 32pt;
}
-.modalTileImage {
- grid-area: tile;
+.global-popup-inner{
+ display: inline-block;
+ margin: auto;
+ padding: 2em;
}
-.modalClose,
-.modalZoom,
-.modalTileImage {
- color: white;
- font-size: 35px;
- font-weight: bold;
- cursor: pointer;
+/* fullpage image viewer */
+
+#lightboxModal{
+ display: none;
+ position: fixed;
+ z-index: 1001;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(20, 20, 20, 0.95);
+ user-select: none;
+ -webkit-user-select: none;
+ flex-direction: column;
}
-.modalSave {
+.modalControls {
+ display: flex;
+ gap: 1em;
+ padding: 1em;
+ background-color: rgba(0,0,0,0.2);
+}
+.modalClose {
+ margin-left: auto;
+}
+.modalControls span{
color: white;
- font-size: 28px;
- margin-top: 8px;
+ font-size: 35px;
font-weight: bold;
cursor: pointer;
+ width: 1em;
}
-.modalClose:hover,
-.modalClose:focus,
-.modalSave:hover,
-.modalSave:focus,
-.modalZoom:hover,
-.modalZoom:focus {
- color: #999;
- text-decoration: none;
- cursor: pointer;
+.modalControls span:hover, .modalControls span:focus{
+ color: #999;
+ text-decoration: none;
}
-#modalImage {
+#lightboxModal > img {
display: block;
- margin-left: auto;
- margin-right: auto;
- margin-top: auto;
+ margin: auto;
width: auto;
}
-.modalImageFullscreen {
+#lightboxModal > img.modalImageFullscreen{
object-fit: contain;
- height: 90%;
+ height: 100%;
}
.modalPrev,
@@ -474,45 +504,7 @@ input[type="range"]{ background-color: rgba(0, 0, 0, 0.8);
}
-#imageARPreview{
- position:absolute;
- top:0px;
- left:0px;
- border:2px solid red;
- background:rgba(255, 0, 0, 0.3);
- z-index: 900;
- pointer-events:none;
- display:none
-}
-
-#txt2img_generate_box, #img2img_generate_box{
- position: relative;
-}
-
-#txt2img_interrupt, #img2img_interrupt, #txt2img_skip, #img2img_skip{
- position: absolute;
- width: 50%;
- height: 100%;
- background: #b4c0cc;
- display: none;
-}
-
-#txt2img_interrupt, #img2img_interrupt{
- left: 0;
- border-radius: 0.5rem 0 0 0.5rem;
-}
-#txt2img_skip, #img2img_skip{
- right: 0;
- border-radius: 0 0.5rem 0.5rem 0;
-}
-
-.red {
- color: red;
-}
-
-.gallery-item {
- --tw-bg-opacity: 0 !important;
-}
+/* context menu (ie for the generate button) */
#context-menu{
z-index:9999;
@@ -541,61 +533,8 @@ input[type="range"]{ background: #a55000;
}
-#quicksettings {
- width: fit-content;
-}
-#quicksettings > div, #quicksettings > fieldset{
- max-width: 24em;
- min-width: 24em;
- padding: 0;
- border: none;
- box-shadow: none;
- background: none;
- margin-right: 10px;
-}
-
-#quicksettings > div > div > div > label > span {
- position: relative;
- margin-right: 9em;
- margin-bottom: -1em;
-}
-
-canvas[key="mask"] {
- z-index: 12 !important;
- filter: invert();
- mix-blend-mode: multiply;
- pointer-events: none;
-}
-
-
-/* gradio 3.4.1 stuff for editable scrollbar values */
-.gr-box > div > div > input.gr-text-input{
- position: absolute;
- right: 0.5em;
- top: -0.6em;
- z-index: 400;
- width: 6em;
-}
-#quicksettings .gr-box > div > div > input.gr-text-input {
- top: -1.12em;
-}
-
-.row.gr-compact{
- overflow: visible;
-}
-
-#img2img_image, #img2img_image > .h-60, #img2img_image > .h-60 > div, #img2img_image > .h-60 > div > img,
-#img2img_sketch, #img2img_sketch > .h-60, #img2img_sketch > .h-60 > div, #img2img_sketch > .h-60 > div > img,
-#img2maskimg, #img2maskimg > .h-60, #img2maskimg > .h-60 > div, #img2maskimg > .h-60 > div > img,
-#inpaint_sketch, #inpaint_sketch > .h-60, #inpaint_sketch > .h-60 > div, #inpaint_sketch > .h-60 > div > img
-{
- height: 480px !important;
- max-height: 480px !important;
- min-height: 480px !important;
-}
-
-/* Extensions */
+/* extensions */
#tab_extensions table{
border-collapse: collapse;
@@ -608,6 +547,7 @@ canvas[key="mask"] { #tab_extensions table input[type="checkbox"]{
margin-right: 0.5em;
+ appearance: checkbox;
}
#tab_extensions button{
@@ -632,74 +572,7 @@ canvas[key="mask"] { font-size: 90%;
}
-#image_buttons_txt2img button, #image_buttons_img2img button, #image_buttons_extras button{
- min-width: auto;
- padding-left: 0.5em;
- padding-right: 0.5em;
-}
-
-.gr-form{
- background-color: white;
-}
-
-.dark .gr-form{
- background-color: rgb(31 41 55 / var(--tw-bg-opacity));
-}
-
-.gr-button-tool, .gr-button-tool-top{
- max-width: 2.5em;
- min-width: 2.5em !important;
- height: 2.4em;
-}
-
-.gr-button-tool{
- margin: 0.6em 0em 0.55em 0;
-}
-
-.gr-button-tool-top, #settings .gr-button-tool{
- margin: 1.6em 0.7em 0.55em 0;
-}
-
-
-#modelmerger_results_container{
- margin-top: 1em;
- overflow: visible;
-}
-
-#modelmerger_models{
- gap: 0;
-}
-
-
-#quicksettings .gr-button-tool{
- margin: 0;
- border-color: unset;
- background-color: unset;
-}
-
-#modelmerger_interp_description>p {
- margin: 0!important;
- text-align: center;
-}
-#modelmerger_interp_description {
- margin: 0.35rem 0.75rem 1.23rem;
-}
-#img2img_settings > div.gr-form, #txt2img_settings > div.gr-form {
- padding-top: 0.9em;
- padding-bottom: 0.9em;
-}
-#txt2img_settings {
- padding-top: 1.16em;
- padding-bottom: 0.9em;
-}
-#img2img_settings {
- padding-bottom: 0.9em;
-}
-
-#img2img_settings div.gr-form .gr-form, #txt2img_settings div.gr-form .gr-form, #train_tabs div.gr-form .gr-form{
- border: none;
- padding-bottom: 0.5em;
-}
+/* replace original footer with ours */
footer {
display: none !important;
@@ -718,90 +591,7 @@ footer { opacity: 0.85;
}
-#txtimg_hr_finalres{
- min-height: 0 !important;
- padding: .625rem .75rem;
- margin-left: -0.75em
-
-}
-
-#txtimg_hr_finalres .resolution{
- font-weight: bold;
-}
-
-#txt2img_checkboxes, #img2img_checkboxes{
- margin-bottom: 0.5em;
- margin-left: 0em;
-}
-#txt2img_checkboxes > div, #img2img_checkboxes > div{
- flex: 0;
- white-space: nowrap;
- min-width: auto;
-}
-
-#img2img_copy_to_img2img, #img2img_copy_to_sketch, #img2img_copy_to_inpaint, #img2img_copy_to_inpaint_sketch{
- margin-left: 0em;
-}
-
-#axis_options {
- margin-left: 0em;
-}
-
-.inactive{
- opacity: 0.5;
-}
-
-[id*='_prompt_container']{
- gap: 0;
-}
-
-[id*='_prompt_container'] > div{
- margin: -0.4em 0 0 0;
-}
-
-.gr-compact {
- border: none;
-}
-
-.dark .gr-compact{
- background-color: rgb(31 41 55 / var(--tw-bg-opacity));
- margin-left: 0;
-}
-
-.gr-compact{
- overflow: visible;
-}
-
-.gr-compact > *{
-}
-
-.gr-compact .gr-block, .gr-compact .gr-form{
- border: none;
- box-shadow: none;
-}
-
-.gr-compact .gr-box{
- border-radius: .5rem !important;
- border-width: 1px !important;
-}
-
-#mode_img2img > div > div{
- gap: 0 !important;
-}
-
-[id*='img2img_copy_to_'] {
- border: none;
-}
-
-[id*='img2img_copy_to_'] > button {
-}
-
-[id*='img2img_label_copy_to_'] {
- font-size: 1.0em;
- font-weight: bold;
- text-align: center;
- line-height: 2.4em;
-}
+/* extra networks UI */
.extra-networks > div > [id *= '_extra_']{
margin: 0.3em;
@@ -814,12 +604,12 @@ footer { .extra-network-subdirs button{
margin: 0 0.15em;
}
-
-#txt2img_extra_networks .search, #img2img_extra_networks .search{
+.extra-networks .tab-nav .search{
display: inline-block;
max-width: 16em;
margin: 0.3em;
align-self: center;
+ width: 16em;
}
#txt2img_extra_view, #img2img_extra_view {
@@ -839,6 +629,28 @@ footer { margin-left: 0.5em;
}
+
+.extra-network-cards .card .metadata-button:before, .extra-network-thumbs .card .metadata-button:before{
+ content: "🛈";
+}
+.extra-network-cards .card .metadata-button, .extra-network-thumbs .card .metadata-button{
+ display: none;
+ position: absolute;
+ right: 0;
+ color: white;
+ text-shadow: 2px 2px 3px black;
+ padding: 0.25em;
+ font-size: 22pt;
+ width: 1.5em;
+}
+.extra-network-cards .card:hover .metadata-button, .extra-network-thumbs .card:hover .metadata-button{
+ display: inline-block;
+}
+.extra-network-cards .card .metadata-button:hover, .extra-network-thumbs .card .metadata-button:hover{
+ color: red;
+}
+
+
.extra-network-thumbs {
display: flex;
flex-flow: row wrap;
@@ -923,12 +735,15 @@ footer { left: 0;
right: 0;
padding: 0.5em;
- color: white;
background: rgba(0,0,0,0.5);
box-shadow: 0 0 0.25em 0.25em rgba(0,0,0,0.5);
text-shadow: 0 0 0.2em black;
}
+.extra-network-cards .card .actions *{
+ color: white;
+}
+
.extra-network-cards .card .actions:hover{
box-shadow: 0 0 0.75em 0.75em rgba(0,0,0,0.5) !important;
}
@@ -966,7 +781,3 @@ footer { .extra-network-cards .card ul a:hover{
color: red;
}
-
-[id*='_prompt_container'] > div {
- margin: 0!important;
-}
diff --git a/test/basic_features/extras_test.py b/test/basic_features/extras_test.py index 0170c511..8ed98747 100644 --- a/test/basic_features/extras_test.py +++ b/test/basic_features/extras_test.py @@ -1,7 +1,9 @@ +import os import unittest import requests from gradio.processing_utils import encode_pil_to_base64 from PIL import Image +from modules.paths import script_path class TestExtrasWorking(unittest.TestCase): def setUp(self): @@ -19,7 +21,7 @@ class TestExtrasWorking(unittest.TestCase): "upscaler_1": "None", "upscaler_2": "None", "extras_upscaler_2_visibility": 0, - "image": encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png")) + "image": encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) } def test_simple_upscaling_performed(self): @@ -31,7 +33,7 @@ class TestPngInfoWorking(unittest.TestCase): def setUp(self): self.url_png_info = "http://localhost:7860/sdapi/v1/extra-single-image" self.png_info = { - "image": encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png")) + "image": encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) } def test_png_info_performed(self): @@ -42,7 +44,7 @@ class TestInterrogateWorking(unittest.TestCase): def setUp(self): self.url_interrogate = "http://localhost:7860/sdapi/v1/extra-single-image" self.interrogate = { - "image": encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png")), + "image": encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))), "model": "clip" } diff --git a/test/basic_features/img2img_test.py b/test/basic_features/img2img_test.py index 08c5c903..5240ec36 100644 --- a/test/basic_features/img2img_test.py +++ b/test/basic_features/img2img_test.py @@ -1,14 +1,16 @@ +import os import unittest import requests from gradio.processing_utils import encode_pil_to_base64 from PIL import Image +from modules.paths import script_path class TestImg2ImgWorking(unittest.TestCase): def setUp(self): self.url_img2img = "http://localhost:7860/sdapi/v1/img2img" self.simple_img2img = { - "init_images": [encode_pil_to_base64(Image.open(r"test/test_files/img2img_basic.png"))], + "init_images": [encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png")))], "resize_mode": 0, "denoising_strength": 0.75, "mask": None, @@ -47,11 +49,11 @@ class TestImg2ImgWorking(unittest.TestCase): self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) def test_inpainting_masked_performed(self): - self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(r"test/test_files/mask_basic.png")) + self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) def test_inpainting_with_inverted_masked_performed(self): - self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(r"test/test_files/mask_basic.png")) + self.simple_img2img["mask"] = encode_pil_to_base64(Image.open(os.path.join(script_path, r"test/test_files/img2img_basic.png"))) self.simple_img2img["inpainting_mask_invert"] = True self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) diff --git a/test/server_poll.py b/test/server_poll.py index 42d56a4c..c732630f 100644 --- a/test/server_poll.py +++ b/test/server_poll.py @@ -1,6 +1,8 @@ import unittest import requests import time +import os +from modules.paths import script_path def run_tests(proc, test_dir): @@ -15,8 +17,8 @@ def run_tests(proc, test_dir): break if proc.poll() is None: if test_dir is None: - test_dir = "test" - suite = unittest.TestLoader().discover(test_dir, pattern="*_test.py", top_level_dir="test") + test_dir = os.path.join(script_path, "test") + suite = unittest.TestLoader().discover(test_dir, pattern="*_test.py", top_level_dir=test_dir) result = unittest.TextTestRunner(verbosity=2).run(suite) return len(result.failures) + len(result.errors) else: @@ -183,13 +183,16 @@ def initialize(): signal.signal(signal.SIGINT, sigint_handler)
-def setup_cors(app):
+def setup_middleware(app):
+ app.middleware_stack = None # reset current middleware to allow modifying user provided list
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
if cmd_opts.cors_allow_origins and cmd_opts.cors_allow_origins_regex:
app.add_middleware(CORSMiddleware, allow_origins=cmd_opts.cors_allow_origins.split(','), allow_origin_regex=cmd_opts.cors_allow_origins_regex, allow_methods=['*'], allow_credentials=True, allow_headers=['*'])
elif cmd_opts.cors_allow_origins:
app.add_middleware(CORSMiddleware, allow_origins=cmd_opts.cors_allow_origins.split(','), allow_methods=['*'], allow_credentials=True, allow_headers=['*'])
elif cmd_opts.cors_allow_origins_regex:
app.add_middleware(CORSMiddleware, allow_origin_regex=cmd_opts.cors_allow_origins_regex, allow_methods=['*'], allow_credentials=True, allow_headers=['*'])
+ app.build_middleware_stack() # rebuild middleware stack on-the-fly
def create_api(app):
@@ -213,8 +216,7 @@ def api_only(): initialize()
app = FastAPI()
- setup_cors(app)
- app.add_middleware(GZipMiddleware, minimum_size=1000)
+ setup_middleware(app)
api = create_api(app)
modules.script_callbacks.app_started_callback(None, app)
@@ -238,7 +240,7 @@ def webui(): shared.demo = modules.ui.create_ui()
startup_timer.record("create ui")
- if cmd_opts.gradio_queue:
+ if not cmd_opts.no_gradio_queue:
shared.demo.queue(64)
gradio_auth_creds = []
@@ -260,6 +262,9 @@ def webui(): inbrowser=cmd_opts.autolaunch,
prevent_thread_lock=True
)
+ for dep in shared.demo.dependencies:
+ dep['show_progress'] = False # disable gradio css animation on component update
+
# after initial launch, disable --autolaunch for subsequent restarts
cmd_opts.autolaunch = False
@@ -271,9 +276,7 @@ def webui(): # running its code. We disable this here. Suggested by RyotaK.
app.user_middleware = [x for x in app.user_middleware if x.cls.__name__ != 'CORSMiddleware']
- setup_cors(app)
-
- app.add_middleware(GZipMiddleware, minimum_size=1000)
+ setup_middleware(app)
modules.progress.setup_progress_api(app)
@@ -290,24 +293,35 @@ def webui(): wait_on_server(shared.demo)
print('Restarting UI...')
+ startup_timer.reset()
+
sd_samplers.set_samplers()
modules.script_callbacks.script_unloaded_callback()
extensions.list_extensions()
+ startup_timer.record("list extensions")
localization.list_localizations(cmd_opts.localizations_dir)
modelloader.forbid_loaded_nonbuiltin_upscalers()
modules.scripts.reload_scripts()
+ startup_timer.record("load scripts")
+
modules.script_callbacks.model_loaded_callback(shared.sd_model)
+ startup_timer.record("model loaded callback")
+
modelloader.load_upscalers()
+ startup_timer.record("load upscalers")
for module in [module for name, module in sys.modules.items() if name.startswith("modules.ui")]:
importlib.reload(module)
+ startup_timer.record("reload script modules")
modules.sd_models.list_models()
+ startup_timer.record("list SD models")
shared.reload_hypernetworks()
+ startup_timer.record("reload hypernetworks")
ui_extra_networks.intialize()
ui_extra_networks.register_page(ui_extra_networks_textual_inversion.ExtraNetworksPageTextualInversion())
@@ -316,6 +330,7 @@ def webui(): extra_networks.initialize()
extra_networks.register_extra_network(extra_networks_hypernet.ExtraNetworkHypernet())
+ startup_timer.record("initialize extra networks")
if __name__ == "__main__":
|