jumbo patch

This commit is contained in:
Vladimir Mandic
2023-05-17 14:15:38 -04:00
parent 554f26296e
commit 0ccda9bc8b
56 changed files with 686 additions and 612 deletions
+16 -5
View File
@@ -25,8 +25,6 @@ Stuff to be added...
Stuff to be investigated...
- TXT2IMG: <https://github.com/vladmandic/automatic/discussions/814>
- `TensorRT`
## Merge PRs
@@ -51,6 +49,7 @@ Tech that can be integrated as part of the core workflow...
- [Custom diffusion](https://github.com/guaneec/custom-diffusion-webui), [Custom diffusion](https://www.cs.cmu.edu/~custom-diffusion/)
- [Dream artist](https://github.com/7eu7d7/DreamArtist-sd-webui-extension)
- [QuickEmbedding](https://github.com/ethansmith2000/QuickEmbedding)
- `TensorRT`
## Random
@@ -58,7 +57,19 @@ Tech that can be integrated as part of the core workflow...
### Pending Code Updates
- add `--safe` mode which skips loading user extensions
This is a massive one due to huge number of changes, but hopefully it will fo ok...
- new **prompt parsers**
select in UI -> Settings -> Stable Diffusion
- **Full**: my new implementation
- **A1111**: for backward compatibility
- **Compel**: as used in ComfyUI and InvokeAI (a.k.a *Temporal Weighting*)
- **Fixed**: for really old backward compatibility
- added `--safe` command line flag mode which skips loading user extensions
please try to use it before opening new issue
- add option in settings: **Prompt attention parser**
to allow for backward compatibility with a1111 (broken) parser
- reintroduce `--api-only` mode to start server without ui
- monitor **extensions** install/startup and
log if they modify any packages/requirements
this is a *deep-experimental* python hack, but i think its worth it as extensions modifying requirements is one of most common causes of issues
- port *all* upstream code from [A1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
up to today - commit hash `89f9faa`
@@ -20,7 +20,6 @@ def before_ui():
ui_extra_networks.register_page(ui_extra_networks_lora.ExtraNetworksPageLora())
extra_networks.register_extra_network(extra_networks_lora.ExtraNetworkLora())
if not hasattr(torch.nn, 'Linear_forward_before_lora'):
torch.nn.Linear_forward_before_lora = torch.nn.Linear.forward
@@ -14,7 +14,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
def list_items(self):
for name, lora_on_disk in lora.available_loras.items():
path, ext = os.path.splitext(lora_on_disk.filename)
path, _ext = os.path.splitext(lora_on_disk.filename)
if shared.opts.lora_preferred_name == "Filename" or lora_on_disk.alias.lower() in lora.forbidden_lora_aliases:
alias = name
@@ -34,4 +34,3 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
def allowed_directories_for_previews(self):
return [shared.cmd_opts.lora_dir]
+14 -1
View File
@@ -43,7 +43,7 @@ args = Dot({
'version': False,
'ignore': False,
})
git_commit = "unknown"
# setup console and file logging
def setup_logging(clean=False):
@@ -369,6 +369,10 @@ def list_extensions(folder):
# run installer for each installed and enabled extension and optionally update them
def install_extensions():
import pkg_resources
pkg_resources._initialize_master_working_set() # pylint: disable=protected-access
pkgs = [f'{p.project_name}=={p._version}' for p in pkg_resources.working_set] # pylint: disable=protected-access,not-an-iterable
log.debug(f'Installed packages: {len(pkgs)}')
from modules.paths_internal import extensions_builtin_dir, extensions_dir
extensions_duplicates = []
extensions_enabled = []
@@ -390,6 +394,12 @@ def install_extensions():
log.error(f'Error updating extension: {os.path.join(folder, ext)}')
if not args.skip_extensions:
run_extension_installer(os.path.join(folder, ext))
pkg_resources._initialize_master_working_set() # pylint: disable=protected-access
updated = [f'{p.project_name}=={p._version}' for p in pkg_resources.working_set] # pylint: disable=protected-access,not-an-iterable
diff = [x for x in updated if x not in pkgs]
pkgs = updated
if len(diff) > 0:
log.info(f'Extension installed packages: {ext} {diff}')
log.info(f'Extensions enabled: {extensions_enabled}')
if len(extensions_duplicates) > 0:
log.warning(f'Extensions duplicates: {extensions_duplicates}')
@@ -500,6 +510,8 @@ def check_version(offline=False, reset=True): # pylint: disable=unused-argument
if args.version:
return
commit = git('rev-parse HEAD')
global git_commit # pylint: disable=global-statement
git_commit = commit[:7]
try:
import requests
except ImportError:
@@ -583,6 +595,7 @@ def add_args():
group.add_argument('--debug', default = False, action='store_true', help = "Run installer with debug logging, default: %(default)s")
group.add_argument('--reset', default = False, action='store_true', help = "Reset main repository to latest version, default: %(default)s")
group.add_argument('--upgrade', default = False, action='store_true', help = "Upgrade main repository to latest version, default: %(default)s")
group.add_argument('--api-only', default = False, action='store_true', help = "Run in API only mode without starting UI")
group.add_argument("--use-ipex", default = False, action='store_true', help="Use Intel OneAPI XPU backend, default: %(default)s")
group.add_argument('--use-directml', default = False, action='store_true', help = "Use DirectML if no compatible GPU is detected, default: %(default)s")
group.add_argument("--use-cuda", default=False, action='store_true', help="Force use nVidia CUDA backend, default: %(default)s")
+2 -1
View File
@@ -16,6 +16,7 @@
"no-unused-vars":"off",
"no-plusplus":"off",
"no-param-reassign":"off",
"no-restricted-syntax":"off"
"no-restricted-syntax":"off",
"no-mixed-operators":"off"
}
}
+7 -18
View File
@@ -1,3 +1,5 @@
/* global gradioApp, onUiUpdate, get_tab_index */
let currentWidth = null;
let currentHeight = null;
let arFrameTimeout = setTimeout(() => {}, 0);
@@ -9,7 +11,7 @@ function dimensionChange(e, is_width, is_height) {
if (!inImg2img) return;
let targetElement = null;
const tabIndex = get_tab_index('mode_img2img');
if (tabIndex === 0) targetElement = gradioApp().querySelector('#img2img_image div[data-testid=image] img'); // img2img
if (tabIndex === 0) targetElement = gradioApp().querySelector('#img2img_image div[data-testid=image] img'); // img2img
else if (tabIndex === 1) targetElement = gradioApp().querySelector('#img2img_sketch div[data-testid=image] img'); // Sketch
else if (tabIndex === 2) targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] img'); // Inpaint
else if (tabIndex === 3) targetElement = gradioApp().querySelector('#inpaint_sketch div[data-testid=image] img'); // Inpaint sketch
@@ -23,26 +25,20 @@ function dimensionChange(e, is_width, is_height) {
}
const viewportOffset = targetElement.getBoundingClientRect();
const viewportscale = Math.min(targetElement.clientWidth / targetElement.naturalWidth, targetElement.clientHeight / targetElement.naturalHeight);
const scaledx = targetElement.naturalWidth * viewportscale;
const scaledy = targetElement.naturalHeight * viewportscale;
const cleintRectTop = (viewportOffset.top + window.scrollY);
const cleintRectLeft = (viewportOffset.left + window.scrollX);
const cleintRectCentreY = cleintRectTop + (targetElement.clientHeight / 2);
const cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth / 2);
const arscale = Math.min(scaledx / currentWidth, scaledy / currentHeight);
const arscaledx = currentWidth * arscale;
const arscaledy = currentHeight * arscale;
const arRectTop = cleintRectCentreY - (arscaledy / 2);
const arRectLeft = cleintRectCentreX - (arscaledx / 2);
const arRectWidth = arscaledx;
const arRectHeight = arscaledy;
arPreviewRect.style.top = `${arRectTop}px`;
arPreviewRect.style.left = `${arRectLeft}px`;
arPreviewRect.style.width = `${arRectWidth}px`;
@@ -58,9 +54,7 @@ function dimensionChange(e, is_width, is_height) {
onUiUpdate(() => {
const arPreviewRect = gradioApp().querySelector('#imageARPreview');
if (arPreviewRect) {
arPreviewRect.style.display = 'none';
}
if (arPreviewRect) arPreviewRect.style.display = 'none';
const tabImg2img = gradioApp().querySelector('#tab_img2img');
if (tabImg2img) {
const inImg2img = tabImg2img.style.display === 'block';
@@ -69,17 +63,12 @@ onUiUpdate(() => {
inputs.forEach((e) => {
const is_width = e.parentElement.id === 'img2img_width';
const is_height = e.parentElement.id === 'img2img_height';
if ((is_width || is_height) && !e.classList.contains('scrollwatch')) {
e.addEventListener('input', (e) => { dimensionChange(e, is_width, is_height); });
e.addEventListener('input', (evt) => { dimensionChange(evt, is_width, is_height); });
e.classList.add('scrollwatch');
}
if (is_width) {
currentWidth = e.value * 1.0;
}
if (is_height) {
currentHeight = e.value * 1.0;
}
if (is_width) currentWidth = e.value * 1.0;
if (is_height) currentHeight = e.value * 1.0;
});
}
}
+17 -47
View File
@@ -1,23 +1,18 @@
contextMenuInit = function () {
/* global gradioApp, uiCurrentTab, onUiUpdate, get_uiCurrentTabContent */
const contextMenuInit = () => {
let eventListenerApplied = false;
const menuSpecs = new Map();
const uid = function () {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
};
const uid = () => Date.now().toString(36) + Math.random().toString(36).substring(2);
function showContextMenu(event, element, menuEntries) {
const posx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
const posy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
const oldMenu = gradioApp().querySelector('#context-menu');
if (oldMenu) {
oldMenu.remove();
}
if (oldMenu) oldMenu.remove();
const tabButton = uiCurrentTab;
const baseStyle = window.getComputedStyle(tabButton);
const contextMenu = document.createElement('nav');
contextMenu.id = 'context-menu';
contextMenu.style.background = baseStyle.background;
@@ -25,40 +20,26 @@ contextMenuInit = function () {
contextMenu.style.fontFamily = baseStyle.fontFamily;
contextMenu.style.top = `${posy}px`;
contextMenu.style.left = `${posx}px`;
const contextMenuList = document.createElement('ul');
contextMenuList.className = 'context-menu-items';
contextMenu.append(contextMenuList);
menuEntries.forEach((entry) => {
const contextMenuEntry = document.createElement('a');
contextMenuEntry.innerHTML = entry.name;
contextMenuEntry.addEventListener('click', (e) => {
entry.func();
});
contextMenuEntry.addEventListener('click', (e) => entry.func());
contextMenuList.append(contextMenuEntry);
});
gradioApp().appendChild(contextMenu);
const menuWidth = contextMenu.offsetWidth + 4;
const menuHeight = contextMenu.offsetHeight + 4;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
if ((windowWidth - posx) < menuWidth) {
contextMenu.style.left = `${windowWidth - menuWidth}px`;
}
if ((windowHeight - posy) < menuHeight) {
contextMenu.style.top = `${windowHeight - menuHeight}px`;
}
if ((windowWidth - posx) < menuWidth) contextMenu.style.left = `${windowWidth - menuWidth}px`;
if ((windowHeight - posy) < menuHeight) contextMenu.style.top = `${windowHeight - menuHeight}px`;
}
function appendContextMenuOption(targetElementSelector, entryName, entryFunction) {
currentItems = menuSpecs.get(targetElementSelector);
let currentItems = menuSpecs.get(targetElementSelector);
if (!currentItems) {
currentItems = [];
menuSpecs.set(targetElementSelector, currentItems);
@@ -69,7 +50,6 @@ contextMenuInit = function () {
func: entryFunction,
isNew: true,
};
currentItems.push(newItem);
return newItem.id;
}
@@ -89,15 +69,11 @@ contextMenuInit = function () {
gradioApp().addEventListener('click', (e) => {
if (!e.isTrusted) return;
const oldMenu = gradioApp().querySelector('#context-menu');
if (oldMenu) {
oldMenu.remove();
}
if (oldMenu) oldMenu.remove();
});
gradioApp().addEventListener('contextmenu', (e) => {
const oldMenu = gradioApp().querySelector('#context-menu');
if (oldMenu) {
oldMenu.remove();
}
if (oldMenu) oldMenu.remove();
menuSpecs.forEach((v, k) => {
if (e.composedPath()[0].matches(k)) {
showContextMenu(e, e.composedPath()[0], v);
@@ -107,14 +83,13 @@ contextMenuInit = function () {
});
eventListenerApplied = true;
}
return [appendContextMenuOption, removeContextMenuOption, addContextMenuEventListener];
};
initResponse = contextMenuInit();
appendContextMenuOption = initResponse[0];
removeContextMenuOption = initResponse[1];
addContextMenuEventListener = initResponse[2];
const initResponse = contextMenuInit();
const appendContextMenuOption = initResponse[0];
const removeContextMenuOption = initResponse[1];
const addContextMenuEventListener = initResponse[2];
(function () {
// Start example Context Menu Items
@@ -128,9 +103,7 @@ addContextMenuEventListener = initResponse[2];
window.generateOnRepeatInterval = setInterval(
() => {
const busy = document.getElementById('progressbar')?.style.display === 'block';
if (!busy) {
genbutton.click();
}
if (!busy) genbutton.click();
},
500,
);
@@ -151,7 +124,6 @@ addContextMenuEventListener = initResponse[2];
appendContextMenuOption('#txt2img_generate', 'Cancel generate forever', cancelGenerateForever);
appendContextMenuOption('#img2img_interrupt', 'Cancel generate forever', cancelGenerateForever);
appendContextMenuOption('#img2img_generate', 'Cancel generate forever', cancelGenerateForever);
appendContextMenuOption(
'#roll',
'Roll three',
@@ -165,6 +137,4 @@ addContextMenuEventListener = initResponse[2];
}());
// End example Context Menu Items
onUiUpdate(() => {
addContextMenuEventListener();
});
onUiUpdate(() => addContextMenuEventListener());
+7 -9
View File
@@ -1,16 +1,14 @@
function setInactive(elem, inactive) {
if (inactive) elem.classList.add('inactive');
else elem.classList.remove('inactive');
}
/* global gradioApp, opts */
function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y) {
function setInactive(elem, inactive) {
elem.classList.toggle('inactive', !!inactive);
}
const hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale');
const hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x');
const hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y');
gradioApp().getElementById('txt2img_hires_fix_row2').style.display = opts.use_old_hires_fix_width_height ? 'none' : '';
setInactive(hrUpscaleBy, opts.use_old_hires_fix_width_height || hr_resize_x > 0 || hr_resize_y > 0);
setInactive(hrResizeX, opts.use_old_hires_fix_width_height || hr_resize_x === 0);
setInactive(hrResizeY, opts.use_old_hires_fix_width_height || hr_resize_y === 0);
// return [enable, width, height, hr_scale, hr_resize_x, hr_resize_y];
setTimeout(() => [enable, width, height, hr_scale, hr_resize_x, hr_resize_y], 100);
setInactive(hrResizeX, opts.use_old_hires_fix_width_height || hr_resize_x == 0);
setInactive(hrResizeY, opts.use_old_hires_fix_width_height || hr_resize_y == 0);
return [enable, width, height, hr_scale, hr_resize_x, hr_resize_y];
}
+5 -5
View File
@@ -1,3 +1,4 @@
/* global gradioApp, onUiUpdate */
/**
* temporary fix for https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/668
* @see https://github.com/gradio-app/gradio/issues/1721
@@ -5,7 +6,6 @@
function imageMaskResize() {
const canvases = gradioApp().querySelectorAll('#img2maskimg .touch-none canvas');
if (!canvases.length) {
canvases_fixed = false;
window.removeEventListener('resize', imageMaskResize);
return;
}
@@ -14,7 +14,7 @@ function imageMaskResize() {
const previewImage = wrapper.previousElementSibling;
if (!previewImage.complete) {
previewImage.addEventListener('load', () => imageMaskResize());
previewImage.addEventListener('load', imageMaskResize);
return;
}
@@ -23,7 +23,6 @@ function imageMaskResize() {
const nw = previewImage.naturalWidth;
const nh = previewImage.naturalHeight;
const portrait = nh > nw;
const factor = portrait;
const wW = Math.min(w, portrait ? h / nh * nw : w / nw * nw);
const wH = Math.min(h, portrait ? h / nh * nh : w / nw * nh);
@@ -34,12 +33,13 @@ function imageMaskResize() {
wrapper.style.top = '0px';
canvases.forEach((c) => {
c.style.width = c.style.height = '';
c.style.width = '';
c.style.height = '';
c.style.maxWidth = '100%';
c.style.maxHeight = '100%';
c.style.objectFit = 'contain';
});
}
onUiUpdate(imageMaskResize);
window.addEventListener('resize', imageMaskResize);
onUiUpdate(() => imageMaskResize());
+1 -3
View File
@@ -1,12 +1,10 @@
/* global gradioApp, get_tab_index */
window.onload = (function () {
window.addEventListener('drop', (e) => {
const target = e.composedPath()[0];
if (!target.placeholder) return;
const idx = selected_gallery_index();
if (target.placeholder.indexOf('Prompt') == -1) return;
const prompt_target = get_tab_index('tabs') == 1 ? 'img2img_prompt_image' : 'txt2img_prompt_image';
e.stopPropagation();
e.preventDefault();
const imgParent = gradioApp().getElementById(prompt_target);
+8 -8
View File
@@ -1,3 +1,4 @@
/* global gradioApp, onUiUpdate */
// A full size 'lightbox' preview modal shown when left clicking on gallery previews
function closeModal() {
gradioApp().getElementById('lightboxModal').style.display = 'none';
@@ -96,6 +97,11 @@ function modalKeyHandler(event) {
}
}
function modalZoomSet(modalImage, enable) {
localStorage.setItem('modalZoom', enable ? 'yes' : 'no');
if (modalImage) modalImage.classList.toggle('modalImageFullscreen', !!enable);
}
function setupImageForLightbox(e) {
if (e.dataset.modded) return;
e.dataset.modded = true;
@@ -106,21 +112,15 @@ function setupImageForLightbox(e) {
const event = isFirefox ? 'mousedown' : 'click';
e.addEventListener(event, (evt) => {
if (evt.button != 0) return;
initialZoom = (localStorage.getItem('modalZoom') || true) == 'yes';
const initialZoom = (localStorage.getItem('modalZoom') || true) == 'yes';
modalZoomSet(gradioApp().getElementById('modalImage'), initialZoom);
evt.preventDefault();
showModal(evt);
}, true);
}
function modalZoomSet(modalImage, enable) {
if (enable) modalImage.classList.add('modalImageFullscreen');
else modalImage.classList.remove('modalImageFullscreen');
localStorage.setItem('modalZoom', enable ? 'yes' : 'no');
}
function modalZoomToggle(event) {
modalImage = gradioApp().getElementById('modalImage');
const modalImage = gradioApp().getElementById('modalImage');
modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen'));
event.stopPropagation();
}
+106 -106
View File
@@ -1,133 +1,133 @@
/* global opts */
function rememberGallerySelection(id_gallery) {}
function getGallerySelectedIndex(id_gallery) {}
function request(url, data, handler, errorHandler) {
var xhr = new XMLHttpRequest();
var url = url;
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
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()
}
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
const js = JSON.parse(xhr.responseText);
handler(js);
} catch (error) {
console.error(error);
errorHandler();
}
};
var js = JSON.stringify(data);
xhr.send(js);
} else {
errorHandler();
}
}
};
const js = JSON.stringify(data);
xhr.send(js);
}
function pad2(x) {
return x<10 ? '0'+x : x
return x < 10 ? `0${x}` : x;
}
function formatTime(secs) {
if(secs > 3600) return pad2(Math.floor(secs/60/60)) + ":" + pad2(Math.floor(secs/60)%60) + ":" + pad2(Math.floor(secs)%60)
else if(secs > 60) return pad2(Math.floor(secs/60)) + ":" + pad2(Math.floor(secs)%60)
else return Math.floor(secs) + "s"
if (secs > 3600) return `${pad2(Math.floor(secs / 60 / 60))}:${pad2(Math.floor(secs / 60) % 60)}:${pad2(Math.floor(secs) % 60)}`;
if (secs > 60) return `${pad2(Math.floor(secs / 60))}:${pad2(Math.floor(secs) % 60)}`;
return `${Math.floor(secs)}s`;
}
function setTitle(progress) {
var title = 'SD.Next'
if (progress) title += ' ' + progress.split(' ')[0].trim();
if (document.title != title) document.title = title;
let title = 'SD.Next';
if (progress) title += ` ${progress.split(' ')[0].trim()}`;
if (document.title != title) document.title = title;
}
function randomId() {
return "task(" + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7)+")"
return `task(${Math.random().toString(36).slice(2, 7)}${Math.random().toString(36).slice(2, 7)}${Math.random().toString(36).slice(2, 7)})`;
}
// starts sending progress requests to "/internal/progress" uri, creating progressbar above progressbarContainer element and
// preview inside gallery element. Cleans up all created stuff when the task is over and calls atEnd.
// calls onProgress every time there is a progress update
function requestProgress(id_task, progressbarContainer, gallery, atEnd = null, onProgress = null, once = false) {
var hasStarted = false
var dateStart = new Date()
var prevProgress = null
var parentProgressbar = progressbarContainer.parentNode
var parentGallery = gallery ? gallery.parentNode : null
var divProgress = document.createElement('div')
divProgress.className='progressDiv'
divProgress.id = 'progressbar'
divProgress.style.display = opts.show_progressbar ? "block" : "none"
var divInner = document.createElement('div')
divInner.className='progress'
divProgress.appendChild(divInner)
parentProgressbar.insertBefore(divProgress, progressbarContainer)
localStorage.setItem('task', id_task);
console.debug('task active:', id_task)
if (parentGallery) {
var livePreview = document.createElement('div')
livePreview.className='livePreview'
parentGallery.insertBefore(livePreview, gallery)
}
let hasStarted = false;
const dateStart = new Date();
const prevProgress = null;
const parentProgressbar = progressbarContainer.parentNode;
const parentGallery = gallery ? gallery.parentNode : null;
const divProgress = document.createElement('div');
divProgress.className = 'progressDiv';
divProgress.id = 'progressbar';
divProgress.style.display = opts.show_progressbar ? 'block' : 'none';
const divInner = document.createElement('div');
divInner.className = 'progress';
divProgress.appendChild(divInner);
parentProgressbar.insertBefore(divProgress, progressbarContainer);
localStorage.setItem('task', id_task);
console.debug('task active:', id_task);
if (parentGallery) {
const livePreview = document.createElement('div');
livePreview.className = 'livePreview';
parentGallery.insertBefore(livePreview, gallery);
}
var removeProgressBar = function() {
console.debug('task end: ', id_task)
localStorage.removeItem('task');
setTitle("")
if (divProgress) parentProgressbar.removeChild(divProgress)
if (parentGallery) parentGallery.removeChild(livePreview)
if (atEnd) atEnd()
}
const removeProgressBar = function () {
console.debug('task end: ', id_task);
localStorage.removeItem('task');
setTitle('');
if (divProgress) parentProgressbar.removeChild(divProgress);
if (parentGallery) parentGallery.removeChild(livePreview);
if (atEnd) atEnd();
};
var fun = function(id_task, id_live_preview){
request("./internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){
var elapsedFromStart = (new Date() - dateStart) / 1000
if (res.completed) {
removeProgressBar()
return
}
var rect = progressbarContainer.getBoundingClientRect()
if (rect.width) divProgress.style.width = rect.width + "px";
progressText = ""
divInner.style.width = ((res.progress || 0) * 100.0) + '%'
divInner.style.background = res.progress ? "" : "transparent"
if (res.progress > 0) progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%'
if (res.eta) progressText += " ETA: " + formatTime(res.eta)
setTitle(progressText)
if (res.textinfo && res.textinfo.indexOf("\n") == -1) progressText = res.textinfo + " " + progressText
divInner.textContent = progressText
hasStarted |= res.active
if (!res.active && (hasStarted || once)) {
removeProgressBar()
return
}
if (res.completed) {
removeProgressBar()
return
}
if (elapsedFromStart > 30 && !res.queued && res.progress == prevProgress) {
removeProgressBar()
return
}
if (res.live_preview && gallery) {
var rect = gallery.getBoundingClientRect()
if(rect.width){
livePreview.style.width = rect.width + "px"
livePreview.style.height = rect.height + "px"
}
var img = new Image();
img.onload = function() {
livePreview.appendChild(img)
if (livePreview.childElementCount > 2) livePreview.removeChild(livePreview.firstElementChild)
}
img.src = res.live_preview;
}
if (onProgress) onProgress(res)
setTimeout(() => fun(id_task, res.id_live_preview), opts.live_preview_refresh_period || 250)
}, function() {
removeProgressBar()
})
}
fun(id_task, 0)
const fun = function (id_task, id_live_preview) {
request('./internal/progress', { id_task, id_live_preview }, (res) => {
const elapsedFromStart = (new Date() - dateStart) / 1000;
if (res.completed) {
removeProgressBar();
return;
}
var rect = progressbarContainer.getBoundingClientRect();
if (rect.width) divProgress.style.width = `${rect.width}px`;
progressText = '';
divInner.style.width = `${(res.progress || 0) * 100.0}%`;
divInner.style.background = res.progress ? '' : 'transparent';
if (res.progress > 0) progressText = `${((res.progress || 0) * 100.0).toFixed(0)}%`;
if (res.eta) progressText += ` ETA: ${formatTime(res.eta)}`;
setTitle(progressText);
if (res.textinfo && res.textinfo.indexOf('\n') == -1) progressText = `${res.textinfo} ${progressText}`;
divInner.textContent = progressText;
hasStarted |= res.active;
if (!res.active && (hasStarted || once)) {
removeProgressBar();
return;
}
if (res.completed) {
removeProgressBar();
return;
}
if (elapsedFromStart > 30 && !res.queued && res.progress == prevProgress) {
removeProgressBar();
return;
}
if (res.live_preview && gallery) {
var rect = gallery.getBoundingClientRect();
if (rect.width) {
livePreview.style.width = `${rect.width}px`;
livePreview.style.height = `${rect.height}px`;
}
const img = new Image();
img.onload = function () {
livePreview.appendChild(img);
if (livePreview.childElementCount > 2) livePreview.removeChild(livePreview.firstElementChild);
};
img.src = res.live_preview;
}
if (onProgress) onProgress(res);
setTimeout(() => fun(id_task, res.id_live_preview), opts.live_preview_refresh_period || 250);
}, () => {
removeProgressBar();
});
};
fun(id_task, 0);
}
+21 -1
View File
@@ -108,7 +108,12 @@ button.custom-button{
}
}
#txt2img_gallery img, #img2img_gallery img{
a{
font-weight: bold;
cursor: pointer;
}
#txt2img_gallery img, #img2img_gallery img, #extras_gallery img{
object-fit: scale-down;
}
#txt2img_actions_column, #img2img_actions_column {
@@ -406,6 +411,21 @@ div#extras_scale_to_tab div.form{
#lightboxModal > img.modalImageFullscreen{
object-fit: contain;
height: 100%;
width: 100%;
min-height: 0;
}
table.settings-value-table{
background: white;
border-collapse: collapse;
margin: 1em;
border: 4px solid white;
}
table.settings-value-table td{
padding: 0.4em;
border: 1px solid #ccc;
max-width: 36em;
}
.modalPrev,
+4 -1
View File
@@ -118,7 +118,10 @@ def start_server(immediate=True, server=None):
installer.log.info("Test only")
server.wants_restart = False
else:
server = server.webui()
if args.api_only:
server = server.api_only()
else:
server = server.webui()
installer.log.info(f'Memory {get_memory_stats()}')
return server
+1
View File
@@ -43,6 +43,7 @@ group.add_argument("--use-ipex", default = False, action='store_true', help="Use
group.add_argument('--use-directml', default = False, action='store_true', help = "Use DirectML if no compatible GPU is detected, default: %(default)s")
group.add_argument("--use-cuda", default=False, action='store_true', help="Force use nVidia CUDA backend, default: %(default)s")
group.add_argument("--use-rocm", default=False, action='store_true', help="Force use AMD ROCm backend, default: %(default)s")
group.add_argument('--subpath', type=str, help='Customize the URL subpath for usage with reverse proxy')
# removed args are added here as hidden in fixed format for compatbility reasons
group.add_argument("-f", action='store_true', help=argparse.SUPPRESS) # allows running as root; implemented outside of webui
+2 -2
View File
@@ -60,8 +60,8 @@ def get_device_for(task):
return get_optimal_device()
def torch_gc():
if shared.opts.disable_gc:
def torch_gc(force=False):
if shared.opts.disable_gc and not force:
return
gc.collect()
if shared.cmd_opts.use_ipex:
+7 -4
View File
@@ -156,13 +156,16 @@ class UpscalerESRGAN(Upscaler):
def load_model(self, path: str):
if "http" in path:
filename = load_file_from_url(url=self.model_url, model_dir=self.model_path,
file_name="%s.pth" % self.model_name,
progress=True)
filename = load_file_from_url(
url=self.model_url,
model_dir=self.model_path,
file_name=f"{self.model_name}.pth",
progress=True,
)
else:
filename = path
if not os.path.exists(filename) or filename is None:
print("Unable to load %s from %s" % (self.model_path, filename))
print(f"Unable to load {self.model_path} from {filename}")
return None
state_dict = torch.load(filename, map_location='cpu' if devices.device_esrgan.type == 'mps' else None)
+9 -9
View File
@@ -36,7 +36,7 @@ class RRDBNet(nn.Module):
elif upsample_mode == 'pixelshuffle':
upsample_block = pixelshuffle_block
else:
raise NotImplementedError('upsample mode [{:s}] is not found'.format(upsample_mode))
raise NotImplementedError(f'upsample mode [{upsample_mode}] is not found')
if upscale == 3:
upsampler = upsample_block(nf, nf, 3, act_type=act_type, convtype=convtype)
else:
@@ -169,7 +169,7 @@ class GaussianNoise(nn.Module):
scale = self.sigma * x.detach() if self.is_relative_detach else self.sigma * x
sampled_noise = self.noise.repeat(*x.size()).normal_() * scale
x = x + sampled_noise
return x
return x
def conv1x1(in_planes, out_planes, stride=1):
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
@@ -259,10 +259,10 @@ class Upsample(nn.Module):
def extra_repr(self):
if self.scale_factor is not None:
info = 'scale_factor=' + str(self.scale_factor)
info = f'scale_factor={self.scale_factor}'
else:
info = 'size=' + str(self.size)
info += ', mode=' + self.mode
info = f'size={self.size}'
info += f', mode={self.mode}'
return info
@@ -348,7 +348,7 @@ def act(act_type, inplace=True, neg_slope=0.2, n_prelu=1, beta=1.0):
elif act_type == 'sigmoid': # [0, 1] range output
layer = nn.Sigmoid()
else:
raise NotImplementedError('activation layer [{:s}] is not found'.format(act_type))
raise NotImplementedError(f'activation layer [{act_type}] is not found')
return layer
@@ -370,7 +370,7 @@ def norm(norm_type, nc):
elif norm_type == 'none':
def norm_layer(x): return Identity()
else:
raise NotImplementedError('normalization layer [{:s}] is not found'.format(norm_type))
raise NotImplementedError(f'normalization layer [{norm_type}] is not found')
return layer
@@ -386,7 +386,7 @@ def pad(pad_type, padding):
elif pad_type == 'zero':
layer = nn.ZeroPad2d(padding)
else:
raise NotImplementedError('padding layer [{:s}] is not implemented'.format(pad_type))
raise NotImplementedError(f'padding layer [{pad_type}] is not implemented')
return layer
@@ -431,7 +431,7 @@ def conv_block(in_nc, out_nc, kernel_size, stride=1, dilation=1, groups=1, bias=
pad_type='zero', norm_type=None, act_type='relu', mode='CNA', convtype='Conv2D',
spectral_norm=False):
""" Conv layer with padding, normalization, activation """
assert mode in ['CNA', 'NAC', 'CNAC'], 'Wrong conv mode [{:s}]'.format(mode)
assert mode in ['CNA', 'NAC', 'CNAC'], f'Wrong conv mode [{mode}]'
padding = get_valid_padding(kernel_size, dilation)
p = pad(pad_type, padding) if pad_type and pad_type != 'zero' else None
padding = padding if pad_type == 'zero' else 0
+2 -1
View File
@@ -10,7 +10,8 @@ class ExtraNetworkHypernet(extra_networks.ExtraNetwork):
additional = shared.opts.sd_hypernetwork
if additional != "None" and additional in shared.hypernetworks and len([x for x in params_list if x.items[0] == additional]) == 0:
p.all_prompts = [x + f"<hypernet:{additional}:{shared.opts.extra_networks_default_multiplier}>" for x in p.all_prompts]
hypernet_prompt_text = f"<hypernet:{additional}:{shared.opts.extra_networks_default_multiplier}>"
p.all_prompts = [f"{prompt}{hypernet_prompt_text}" for prompt in p.all_prompts]
params_list.append(extra_networks.ExtraNetworkParams(items=[additional, shared.opts.extra_networks_default_multiplier]))
names = []
+7 -2
View File
@@ -51,6 +51,7 @@ def image_from_url_text(filedata):
filename = filedata["name"]
is_in_right_dir = ui_tempdir.check_tmp_file(shared.demo, filename)
if is_in_right_dir:
filename = filename.rsplit('?', 1)[0]
image = Image.open(filename)
geninfo, _items = images.read_info_from_image(image)
image.info['parameters'] = geninfo
@@ -134,6 +135,7 @@ def connect_paste_params_buttons():
_js=jsfunc,
inputs=[binding.source_image_component],
outputs=[destination_image_component, destination_width_component, destination_height_component] if destination_width_component else [destination_image_component],
show_progress=False,
)
if binding.source_text_component is not None and fields is not None:
connect_paste(binding.paste_button, fields, binding.source_text_component, override_settings_component, binding.tabname)
@@ -149,6 +151,7 @@ def connect_paste_params_buttons():
_js=f"switch_to_{binding.tabname}",
inputs=[],
outputs=[],
show_progress=False,
)
@@ -257,8 +260,8 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
v = v[1:-1] if v[0] == '"' and v[-1] == '"' else v
m = re_imagesize.match(v)
if m is not None:
res[k+"-1"] = m.group(1)
res[k+"-2"] = m.group(2)
res[f"{k}-1"] = m.group(1)
res[f"{k}-2"] = m.group(2)
else:
res[k] = v
# Missing CLIP skip means it was set to 1 (the default)
@@ -404,10 +407,12 @@ def connect_paste(button, local_paste_fields, input_comp, override_settings_comp
fn=paste_func,
inputs=[input_comp],
outputs=[x[0] for x in local_paste_fields],
show_progress=False,
)
button.click(
fn=None,
_js=f"recalculate_prompts_{tabname}",
inputs=[],
outputs=[],
show_progress=False,
)
+2 -2
View File
@@ -11,7 +11,7 @@ cache_data = None
def dump_cache():
with filelock.FileLock(cache_filename+".lock"):
with filelock.FileLock(f"{cache_filename}.lock"):
with open(cache_filename, "w", encoding="utf8") as file:
json.dump(cache_data, file, indent=4)
@@ -19,7 +19,7 @@ def dump_cache():
def cache(subsection):
global cache_data # pylint: disable=global-statement
if cache_data is None:
with filelock.FileLock(cache_filename+".lock"):
with filelock.FileLock(f"{cache_filename}.lock"):
if not os.path.isfile(cache_filename):
cache_data = {}
else:
+1 -1
View File
@@ -235,7 +235,7 @@ class Hypernetwork:
if shared.opts.save_optimizer_state and self.optimizer_state_dict:
optimizer_saved_dict['hash'] = self.shorthash()
optimizer_saved_dict['optimizer_state_dict'] = self.optimizer_state_dict
torch.save(optimizer_saved_dict, filename + '.optim')
torch.save(optimizer_saved_dict, f"{filename}.optim")
def load(self, filename):
self.filename = filename
+4 -3
View File
@@ -308,6 +308,7 @@ class FilenameGenerator:
'generation_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.n_iter == 1 and self.p.batch_size == 1 else self.p.iteration * self.p.batch_size + self.p.batch_index + 1,
'hasprompt': lambda self, *args: self.hasprompt(*args), # accepts formats:[hasprompt<prompt1|default><prompt2>..]
'clip_skip': lambda self: shared.opts.data["CLIP_stop_at_last_layers"],
'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING_AND_SKIP_PREVIOUS_TEXT,
}
default_time_format = '%Y%m%d%H%M%S'
@@ -401,7 +402,7 @@ def get_next_sequence_number(path, basename):
"""
result = -1
if basename != '':
basename = basename + "-"
basename = f"{basename}-"
prefix_length = len(basename)
for p in os.listdir(path):
if p.startswith(basename):
@@ -448,7 +449,7 @@ def atomically_save_image():
# additional metadata saved in files
if shared.opts.save_txt and len(exifinfo_data) > 0:
with open(txt_fullfn, "w", encoding="utf8") as file:
file.write(exifinfo_data + "\n")
file.write(f"{exifinfo_data}\n")
with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file:
file.write(exifinfo_data)
if shared.opts.save_log_fn != '' and len(exifinfo_data) > 0:
@@ -524,7 +525,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i
file_decoration = shared.opts.samples_filename_pattern or "[seed]-[prompt_spaces]"
add_number = shared.opts.save_images_add_number or file_decoration == ''
if file_decoration != "" and add_number:
file_decoration = "-" + file_decoration
file_decoration = f"-{file_decoration}"
file_decoration = namegen.apply(file_decoration) + suffix
if add_number:
basecount = get_next_sequence_number(path, basename)
+2 -1
View File
@@ -33,7 +33,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args):
break
try:
img = Image.open(image)
except UnidentifiedImageError:
except UnidentifiedImageError as e:
shared.log.error(f"Image error: {e}")
continue
# Use the EXIF orientation of photos taken by smartphones.
img = ImageOps.exif_transpose(img)
+2 -2
View File
@@ -26,7 +26,7 @@ def category_types():
def download_default_clip_interrogate_categories(content_dir):
shared.log.info("Downloading CLIP categories...")
tmpdir = content_dir + "_tmp"
tmpdir = f"{content_dir}_tmp"
cat_types = ["artists", "flavors", "mediums", "movements"]
try:
@@ -211,7 +211,7 @@ class InterrogateModels:
if shared.opts.interrogate_return_ranks:
res += f", ({match}:{score/100:.3f})"
else:
res += ", " + match
res += f", {match}"
except Exception as e:
errors.display(e, 'interrogate')
+7 -2
View File
@@ -53,6 +53,11 @@ if has_mps:
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)
CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps')
# MPS workaround for https://github.com/pytorch/pytorch/issues/92311
if platform.processor() == 'i386':
for funcName in ['torch.argmax', 'torch.Tensor.argmax']:
CondFunc(funcName, lambda _, input, *args, **kwargs: torch.max(input.float() if input.dtype == torch.int64 else input, *args, **kwargs)[1], lambda _, input, *args, **kwargs: input.device.type == 'mps')
+2
View File
@@ -1,3 +1,4 @@
import ssl
import time
import datetime
import logging
@@ -17,6 +18,7 @@ errors.install()
def setup_middleware(app: FastAPI, cmd_opts):
log.info('Initializing middleware')
ssl._create_default_https_context = ssl._create_unverified_context # pylint: disable=protected-access
uvicorn_logger=logging.getLogger("uvicorn.error")
uvicorn_logger.disabled = True
from fastapi.middleware.cors import CORSMiddleware
+17 -36
View File
@@ -22,9 +22,6 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None
"""
output = []
if ext_filter is None:
ext_filter = []
try:
places = []
@@ -39,22 +36,14 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None
places.append(model_path)
for place in places:
if os.path.exists(place):
for file in glob.iglob(os.path.join(place, '**/**'), recursive=True):
full_path = file
if os.path.isdir(full_path):
continue
if os.path.islink(full_path) and not os.path.exists(full_path):
print(f"Skipping broken symlink: {full_path}")
continue
if ext_blacklist is not None and any([full_path.endswith(x) for x in ext_blacklist]):
continue
if len(ext_filter) != 0:
_model_name, extension = os.path.splitext(file)
if extension not in ext_filter:
continue
if file not in output:
output.append(full_path)
for full_path in shared.walk_files(place, allowed_extensions=ext_filter):
if os.path.islink(full_path) and not os.path.exists(full_path):
print(f"Skipping broken symlink: {full_path}")
continue
if ext_blacklist is not None and any([full_path.endswith(x) for x in ext_blacklist]):
continue
if full_path not in output:
output.append(full_path)
if model_url is not None and len(output) == 0:
if download_name is not None:
@@ -131,20 +120,6 @@ def move_files(src_path: str, dest_path: str, ext_filter: str = None):
pass
builtin_upscaler_classes = []
forbidden_upscaler_classes = set()
def list_builtin_upscalers():
load_upscalers()
builtin_upscaler_classes.clear()
builtin_upscaler_classes.extend(Upscaler.__subclasses__())
def forbid_loaded_nonbuiltin_upscalers():
for cls in Upscaler.__subclasses__():
if cls not in builtin_upscaler_classes:
forbidden_upscaler_classes.add(cls)
def load_upscalers():
# We can only do this 'magic' method to dynamically load upscalers if they are referenced,
@@ -161,10 +136,16 @@ def load_upscalers():
datas = []
commandline_options = vars(shared.cmd_opts)
for cls in Upscaler.__subclasses__():
if cls in forbidden_upscaler_classes:
continue
# some of upscaler classes will not go away after reloading their modules, and we'll end
# up with two copies of those classes. The newest copy will always be the last in the list,
# so we go from end to beginning and ignore duplicates
used_classes = {}
for cls in reversed(Upscaler.__subclasses__()):
classname = str(cls)
if classname not in used_classes:
used_classes[classname] = cls
for cls in reversed(used_classes.values()):
name = cls.__name__
cmd_name = f"{name.lower().replace('upscaler', '')}_models_path"
scaler = cls(commandline_options.get(cmd_name, None))
+2 -2
View File
@@ -223,7 +223,7 @@ class DDPM(pl.LightningModule):
for k in keys:
for ik in ignore_keys:
if k.startswith(ik):
print("Deleting key {} from state_dict.".format(k))
print(f"Deleting key {k} from state_dict.")
del sd[k]
missing, unexpected = self.load_state_dict(sd, strict=False) if not only_model else self.model.load_state_dict(
sd, strict=False)
@@ -386,7 +386,7 @@ class DDPM(pl.LightningModule):
_, loss_dict_no_ema = self.shared_step(batch)
with self.ema_scope():
_, loss_dict_ema = self.shared_step(batch)
loss_dict_ema = {key + '_ema': loss_dict_ema[key] for key in loss_dict_ema}
loss_dict_ema = {f"{key}_ema": loss_dict_ema[key] for key in loss_dict_ema}
self.log_dict(loss_dict_no_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True)
self.log_dict(loss_dict_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True)
+2 -2
View File
@@ -95,7 +95,7 @@ class NoiseScheduleVP:
"""
if schedule not in ['discrete', 'linear', 'cosine']:
raise ValueError("Unsupported noise schedule {}. The schedule needs to be 'discrete' or 'linear' or 'cosine'".format(schedule))
raise ValueError(f"Unsupported noise schedule {schedule}. The schedule needs to be 'discrete' or 'linear' or 'cosine'")
self.schedule = schedule
if schedule == 'discrete':
@@ -382,7 +382,7 @@ def get_time_steps(noise_schedule, skip_type, t_T, t_0, N, device):
t = torch.linspace(t_T**(1. / t_order), t_0**(1. / t_order), N + 1).pow(t_order).to(device)
return t
else:
raise ValueError("Unsupported skip_type {}, need to be 'logSNR' or 'time_uniform' or 'time_quadratic'".format(skip_type))
raise ValueError(f"Unsupported skip_type {skip_type}, need to be 'logSNR' or 'time_uniform' or 'time_quadratic'")
class UniPC:
def __init__(
+1 -1
View File
@@ -23,7 +23,7 @@ for possible_sd_path in possible_sd_paths:
sd_path = os.path.abspath(possible_sd_path)
break
assert sd_path is not None, "Couldn't find Stable Diffusion in any of: " + str(possible_sd_paths)
assert sd_path is not None, f"Couldn't find Stable Diffusion in any of: {possible_sd_paths}"
path_dirs = [
(sd_path, 'ldm', 'Stable Diffusion', []),
+14 -33
View File
@@ -14,6 +14,7 @@ from ldm.data.util import AddMiDaS
from ldm.models.diffusion.ddpm import LatentDepth2ImageDiffusion
from einops import repeat, rearrange
from blendmodes.blend import blendLayers, BlendType
from installer import git_commit
import modules.sd_hijack
from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, script_callbacks, extra_networks, sd_vae_approx, scripts # pylint: disable=unused-import
from modules.sd_hijack import model_hijack
@@ -40,56 +41,41 @@ def setup_color_correction(image):
def apply_color_correction(correction, original_image):
logging.info("Applying color correction.")
image = Image.fromarray(cv2.cvtColor(exposure.match_histograms(
cv2.cvtColor(
np.asarray(original_image),
cv2.COLOR_RGB2LAB
),
cv2.cvtColor(np.asarray(original_image), cv2.COLOR_RGB2LAB),
correction,
channel_axis=2
), cv2.COLOR_LAB2RGB).astype("uint8"))
image = blendLayers(image, original_image, BlendType.LUMINOSITY)
return image
def apply_overlay(image, paste_loc, index, overlays):
if overlays is None or index >= len(overlays):
return image
overlay = overlays[index]
if paste_loc is not None:
x, y, w, h = paste_loc
base_image = Image.new('RGBA', (overlay.width, overlay.height))
image = images.resize_image(1, image, w, h)
base_image.paste(image, (x, y))
image = base_image
image = image.convert('RGBA')
image.alpha_composite(overlay)
image = image.convert('RGB')
return image
def txt2img_image_conditioning(sd_model, x, width, height):
if sd_model.model.conditioning_key in {'hybrid', 'concat'}: # Inpainting models
# The "masked-image" in this case will just be all zeros since the entire image is masked.
image_conditioning = torch.zeros(x.shape[0], 3, height, width, device=x.device)
image_conditioning = sd_model.get_first_stage_encoding(sd_model.encode_first_stage(image_conditioning))
# Add the fake full 1s mask to the first dimension.
image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0)
image_conditioning = image_conditioning.to(x.dtype)
return image_conditioning
elif sd_model.model.conditioning_key == "crossattn-adm": # UnCLIP models
return x.new_zeros(x.shape[0], 2*sd_model.noise_augmentor.time_embed.dim, dtype=x.dtype, device=x.device)
else:
# Dummy zero conditioning if we're not using inpainting or unclip models.
# Still takes up a bit of memory, but no encoder call.
@@ -165,7 +151,6 @@ class StableDiffusionProcessing:
def txt2img_image_conditioning(self, x, width=None, height=None):
self.is_using_inpainting_conditioning = self.sd_model.model.conditioning_key in {'hybrid', 'concat'}
return txt2img_image_conditioning(self.sd_model, x, width or self.width, height or self.height)
def depth2img_image_conditioning(self, source_image):
@@ -174,7 +159,6 @@ class StableDiffusionProcessing:
transformed = transformer({"jpg": rearrange(source_image[0], "c h w -> h w c")})
midas_in = torch.from_numpy(transformed["midas_in"][None, ...]).to(device=shared.device)
midas_in = repeat(midas_in, "1 ... -> n ...", n=self.batch_size)
conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(source_image))
conditioning = torch.nn.functional.interpolate(
self.sd_model.depth_model(midas_in),
@@ -182,14 +166,12 @@ class StableDiffusionProcessing:
mode="bicubic",
align_corners=False,
)
(depth_min, depth_max) = torch.aminmax(conditioning)
conditioning = 2. * (conditioning - depth_min) / (depth_max - depth_min) - 1.
return conditioning
def edit_image_conditioning(self, source_image):
conditioning_image = self.sd_model.encode_first_stage(source_image).mode()
return conditioning_image
def unclip_image_conditioning(self, source_image):
@@ -202,7 +184,6 @@ class StableDiffusionProcessing:
def inpainting_image_conditioning(self, source_image, latent_image, image_mask=None):
self.is_using_inpainting_conditioning = True
# Handle the different mask inputs
if image_mask is not None:
if torch.is_tensor(image_mask):
@@ -216,7 +197,6 @@ class StableDiffusionProcessing:
conditioning_mask = torch.round(conditioning_mask)
else:
conditioning_mask = source_image.new_ones(1, 1, *source_image.shape[-2:])
# Create another latent image, this time with a masked version of the original input.
# Smoothly interpolate between the masked and unmasked latent conditioning image using a parameter.
conditioning_mask = conditioning_mask.to(device=source_image.device, dtype=source_image.dtype)
@@ -225,35 +205,27 @@ class StableDiffusionProcessing:
source_image * (1.0 - conditioning_mask),
getattr(self, "inpainting_mask_weight", shared.opts.inpainting_mask_weight)
)
# Encode the new masked image using first stage of network.
conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(conditioning_image))
# Create the concatenated conditioning tensor to be fed to `c_concat`
conditioning_mask = torch.nn.functional.interpolate(conditioning_mask, size=latent_image.shape[-2:])
conditioning_mask = conditioning_mask.expand(conditioning_image.shape[0], -1, -1, -1)
image_conditioning = torch.cat([conditioning_mask, conditioning_image], dim=1)
image_conditioning = image_conditioning.to(shared.device).type(self.sd_model.dtype)
return image_conditioning
def img2img_image_conditioning(self, source_image, latent_image, image_mask=None):
source_image = devices.cond_cast_float(source_image)
# HACK: Using introspection as the Depth2Image model doesn't appear to uniquely
# identify itself with a field common to all models. The conditioning_key is also hybrid.
if isinstance(self.sd_model, LatentDepth2ImageDiffusion):
return self.depth2img_image_conditioning(source_image)
if self.sd_model.cond_stage_key == "edit":
return self.edit_image_conditioning(source_image)
if self.sampler.conditioning_key in {'hybrid', 'concat'}:
return self.inpainting_image_conditioning(source_image, latent_image, image_mask=image_mask)
if self.sampler.conditioning_key == "crossattn-adm":
return self.unclip_image_conditioning(source_image)
# Dummy zero conditioning if we're not using inpainting or depth model.
return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1)
@@ -344,7 +316,6 @@ class Processed:
"clip_skip": self.clip_skip,
"is_using_inpainting_conditioning": self.is_using_inpainting_conditioning,
}
return json.dumps(obj)
def infotext(self, p: StableDiffusionProcessing, index):
@@ -468,6 +439,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
"Clip skip": p.clip_skip,
"ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta,
"Init image hash": getattr(p, 'init_img_hash', None),
"Version": git_commit,
"Token merging ratio": None if not (opts.token_merging or cmd_opts.token_merging) or opts.token_merging_hr_only else opts.token_merging_ratio,
"Token merging ratio hr": None if not (opts.token_merging or cmd_opts.token_merging) else opts.token_merging_ratio_hr,
"Token merging random": None if opts.token_merging_random is False else opts.token_merging_random,
@@ -479,7 +451,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
}
generation_params.update(p.extra_generation_params)
generation_params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in generation_params.items() if v is not None])
negative_prompt_text = "\nNegative prompt: " + p.all_negative_prompts[index] if p.all_negative_prompts[index] else ""
negative_prompt_text = f"\nNegative prompt: {p.all_negative_prompts[index]}" if p.all_negative_prompts[index] else ""
return f"{all_prompts[index]}{negative_prompt_text}\n{generation_params_text}".strip()
@@ -726,7 +698,16 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if not p.disable_extra_networks and extra_network_data:
extra_networks.deactivate(p, extra_network_data)
devices.torch_gc()
res = Processed(p, output_images, p.all_seeds[0], infotext(), comments="".join(["\n\n" + x for x in comments]), subseed=p.all_subseeds[0], index_of_first_image=index_of_first_image, infotexts=infotexts)
res = Processed(
p,
images_list=output_images,
seed=p.all_seeds[0],
info=infotext(),
comments="".join(f"\n\n{comment}" for comment in comments),
subseed=p.all_subseeds[0],
index_of_first_image=index_of_first_image,
infotexts=infotexts,
)
if p.scripts is not None:
p.scripts.postprocess(p, res)
return res
+4 -6
View File
@@ -1,12 +1,10 @@
import os
import sys
import numpy as np
from PIL import Image
from basicsr.utils.download_util import load_file_from_url
from modules.upscaler import Upscaler, UpscalerData
from modules.shared import cmd_opts, opts, device
from modules.shared import opts, device
from modules import modelloader
import modules.errors as errors
@@ -27,9 +25,9 @@ class UpscalerRealESRGAN(Upscaler):
for scaler in scalers:
if scaler.local_data_path.startswith("http"):
filename = modelloader.friendly_name(scaler.local_data_path)
local = next(iter([local_model for local_model in local_model_paths if local_model.endswith(filename + '.pth')]), None)
if local:
scaler.local_data_path = local
local_model_candidates = [local_model for local_model in local_model_paths if local_model.endswith(f"{filename}.pth")]
if local_model_candidates:
scaler.local_data_path = local_model_candidates[0]
if scaler.name in opts.realesrgan_enabled_models:
self.scalers.append(scaler)
+1 -1
View File
@@ -38,7 +38,7 @@ class RestrictedUnpickler(pickle.Unpickler):
return getattr(collections, name)
if module == 'torch._utils' and name in ['_rebuild_tensor_v2', '_rebuild_parameter', '_rebuild_device_tensor_from_numpy']:
return getattr(torch._utils, name) # pylint: disable=protected-access
if module == 'torch' and name in ['FloatStorage', 'HalfStorage', 'IntStorage', 'LongStorage', 'DoubleStorage', 'ByteStorage', 'float32']:
if module == 'torch' and name in ['FloatStorage', 'HalfStorage', 'IntStorage', 'LongStorage', 'DoubleStorage', 'ByteStorage', 'float32', 'BFloat16Storage']:
return getattr(torch, name)
if module == 'torch.nn.modules.container' and name in ['ParameterDict']:
return getattr(torch.nn.modules.container, name)
+3 -2
View File
@@ -142,7 +142,8 @@ class Script:
def elem_id(self, item_id):
"""helper function to generate id for a HTML element, constructs final id out of script name, tab and user-supplied item_id"""
need_tabname = self.show(True) == self.show(False)
tabname = ('img2img' if self.is_img2img else 'txt2txt') + "_" if need_tabname else ""
tabkind = 'img2img' if self.is_img2img else 'txt2txt'
tabname = f"{tabkind}_" if need_tabname else ""
title = re.sub(r'[^a-z_0-9]', '', re.sub(r'\s', '_', self.title().lower()))
return f'script_{tabname}{title}_{item_id}'
@@ -481,7 +482,7 @@ def add_classes_to_gradio_component(comp):
elem_classes = comp.elem_classes
if elem_classes is None:
elem_classes = []
comp.elem_classes = ["gradio-" + comp.get_block_name(), *(elem_classes)]
comp.elem_classes = [f"gradio-{comp.get_block_name()}", *(comp.elem_classes or [])]
if getattr(comp, 'multiselect', False):
comp.elem_classes.append('multiselect')
+2 -1
View File
@@ -237,8 +237,9 @@ class StableDiffusionModelHijack:
self.comments = []
def get_prompt_lengths(self, text):
if self.clip is None:
return 0, 0
_, token_count = self.clip.process_texts([text])
return token_count, self.clip.get_target_prompt_token_count(token_count)
+2 -1
View File
@@ -75,7 +75,8 @@ def forward_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, text
self.hijack.comments += hijack_comments
if len(used_custom_terms) > 0:
self.hijack.comments.append("Used embeddings: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms]))
embedding_names = ", ".join(f"{word} [{checksum}]" for word, checksum in used_custom_terms)
self.hijack.comments.append(f"Used embeddings: {embedding_names}")
self.hijack.fixes = hijack_fixes
return self.process_tokens(remade_batch_tokens, batch_multipliers)
+3
View File
@@ -275,6 +275,9 @@ def sub_quad_attention_forward(self, x, context=None, mask=None):
k = k.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1)
v = v.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1)
if q.device.type == 'mps':
q, k, v = q.contiguous(), k.contiguous(), v.contiguous()
dtype = q.dtype
if shared.opts.upcast_attn:
q, k = q.float(), k.float()
+1 -1
View File
@@ -19,7 +19,7 @@ class TorchHijackForUnet:
if hasattr(torch, item):
return getattr(torch, item)
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, item))
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'")
def cat(self, tensors, *args, **kwargs):
if len(tensors) == 2:
+39 -16
View File
@@ -2,6 +2,7 @@ import collections
import os.path
import re
import io
import threading
from os import mkdir
from urllib import request
from rich import progress # pylint: disable=redefined-builtin
@@ -41,7 +42,7 @@ class CheckpointInfo:
self.name_for_extra = os.path.splitext(os.path.basename(filename))[0]
self.model_name = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]
self.hash = model_hash(filename)
self.sha256 = hashes.sha256_from_cache(self.filename, "checkpoint/" + name)
self.sha256 = hashes.sha256_from_cache(self.filename, f"checkpoint/{name}")
self.shorthash = self.sha256[0:10] if self.sha256 else None
self.title = name if self.shorthash is None else f'{name} [{self.shorthash}]'
self.ids = [self.hash, self.model_name, self.title, name, f'{name} [{self.hash}]'] + ([self.shorthash, self.sha256, f'{self.name} [{self.shorthash}]'] if self.shorthash else [])
@@ -59,7 +60,7 @@ class CheckpointInfo:
checkpoint_aliases[i] = self
def calculate_shorthash(self):
self.sha256 = hashes.sha256(self.filename, "checkpoint/" + self.name)
self.sha256 = hashes.sha256(self.filename, f"checkpoint/{self.name}")
if self.sha256 is None:
return
self.shorthash = self.sha256[0:10]
@@ -349,6 +350,29 @@ sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embe
sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight'
class SdModelData:
def __init__(self):
self.sd_model = None
self.lock = threading.Lock()
def get_sd_model(self):
if self.sd_model is None:
with self.lock:
try:
load_model()
except Exception as e:
shared.log.error("Failed to load stable diffusion model")
errors.display(e, "loading stable diffusion model")
self.sd_model = None
return self.sd_model
def set_sd_model(self, v):
self.sd_model = v
model_data = SdModelData()
def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None):
shared.log.debug(f'Load model: info={checkpoint_info is not None} dict={already_loaded_state_dict is not None}')
from modules import lowvram, sd_hijack
@@ -358,9 +382,11 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None)
if timer is None:
timer = Timer()
current_checkpoint_info = None
if shared.sd_model:
current_checkpoint_info = shared.sd_model.sd_checkpoint_info
if model_data.sd_model is not None:
sd_hijack.model_hijack.undo_hijack(model_data.sd_model)
current_checkpoint_info = model_data.sd_model.sd_checkpoint_info
unload_model_weights()
model_data.sd_model = None
do_inpainting_hijack()
devices.set_cuda_params()
if already_loaded_state_dict is not None:
@@ -405,14 +431,14 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None)
if shared.cmd_opts.use_ipex:
sd_model = torch.xpu.optimize(sd_model, dtype=devices.dtype)
shared.log.info("Applied IPEX Optimize")
shared.sd_model = sd_model
model_data.sd_model = sd_model
sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True) # Reload embeddings after model load as they may or may not fit the model
timer.record("embeddings")
script_callbacks.model_loaded_callback(sd_model)
timer.record("callbacks")
shared.log.info(f"Model loaded in {timer.summary()}")
current_checkpoint_info = None
devices.torch_gc()
devices.torch_gc(force=True)
shared.log.info(f'Model load finished: {memory_stats()}')
@@ -426,7 +452,7 @@ def reload_model_weights(sd_model=None, info=None):
from modules import lowvram, sd_hijack
checkpoint_info = info or select_checkpoint()
if not sd_model:
sd_model = shared.sd_model
sd_model = model_data.sd_model
if sd_model is None: # previous model load failed
current_checkpoint_info = None
else:
@@ -443,7 +469,6 @@ def reload_model_weights(sd_model=None, info=None):
else:
unload_model_weights()
sd_model = None
shared.sd_model = None
timer = Timer()
state_dict = get_checkpoint_state_dict(checkpoint_info, timer)
checkpoint_config = sd_models_config.find_checkpoint_config(state_dict, checkpoint_info)
@@ -452,7 +477,7 @@ def reload_model_weights(sd_model=None, info=None):
del sd_model
checkpoints_loaded.clear()
load_model(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer)
return shared.sd_model
return model_data.sd_model
try:
load_model_weights(sd_model, checkpoint_info, state_dict, timer)
except Exception:
@@ -471,14 +496,12 @@ def reload_model_weights(sd_model=None, info=None):
def unload_model_weights(sd_model=None, _info=None):
from modules import sd_hijack
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
if model_data.sd_model:
model_data.sd_model.to(devices.cpu)
sd_hijack.model_hijack.undo_hijack(model_data.sd_model)
model_data.sd_model = None
sd_model = None
devices.torch_gc()
devices.torch_gc(force=True)
shared.log.debug(f'Model weights unloaded: {memory_stats()}')
return sd_model
+1 -1
View File
@@ -109,7 +109,7 @@ def find_checkpoint_config_near_filename(info):
if info is None:
return None
config = os.path.splitext(info.filename)[0] + ".yaml"
config = f"{os.path.splitext(info.filename)[0]}.yaml"
if os.path.exists(config):
return config
+1 -1
View File
@@ -206,7 +206,7 @@ class TorchHijack:
if hasattr(torch, item):
return getattr(torch, item)
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, item))
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'")
def randn_like(self, x):
if self.sampler_noises:
+1 -1
View File
@@ -82,7 +82,7 @@ def refresh_vae_list():
def find_vae_near_checkpoint(checkpoint_file):
checkpoint_path = os.path.splitext(checkpoint_file)[0]
for vae_location in [checkpoint_path + ".vae.pt", checkpoint_path + ".vae.ckpt", checkpoint_path + ".vae.safetensors"]:
for vae_location in [f"{checkpoint_path}.vae.pt", f"{checkpoint_path}.vae.ckpt", f"{checkpoint_path}.vae.safetensors"]:
if os.path.isfile(vae_location):
return vae_location
+22 -2
View File
@@ -7,6 +7,7 @@ import urllib.request
import gradio as gr
import tqdm
import requests
from ldm.models.diffusion.ddpm import LatentDiffusion
from modules import errors, ui_components, shared_items, cmd_args
from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611
import modules.interrogate
@@ -27,7 +28,6 @@ cmd_opts, _ = parser.parse_known_args()
hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config}
is_device_dml = False
xformers_available = False
sd_model = None
clip_model = None
interrogator = modules.interrogate.InterrogateModels("interrogate")
sd_upscalers = []
@@ -411,7 +411,7 @@ options_templates.update(options_section(('ui', "User interface"), {
"keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
"keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing <extra networks:0.9>", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
"keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), # pylint: disable=anomalous-backslash-in-string
"quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"),
"quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}),
"hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}),
"ui_tab_reorder": OptionInfo("From Text, From Image, Process Image", "UI tabs order"),
"ui_scripts_reorder": OptionInfo("Enable Dynamic Thresholding, ControlNet", "UI scripts order"),
@@ -550,6 +550,8 @@ class Options:
def load(self, filename):
with open(filename, "r", encoding="utf8") as file:
self.data = json.load(file)
if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None:
self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')]
bad_settings = 0
for k, v in self.data.items():
info = self.data_labels.get(k, None)
@@ -750,3 +752,21 @@ def html(filename):
with open(path, encoding="utf8") as file:
return file.read()
return ""
class Shared(sys.modules[__name__].__class__):
# this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than at program startup.
sd_model_val = None
@property
def sd_model(self):
import modules.sd_models # pylint: disable=W0621
# return modules.sd_models.model_data.sd_model
return modules.sd_models.model_data.get_sd_model()
@sd_model.setter
def sd_model(self, value):
import modules.sd_models # pylint: disable=W0621
modules.sd_models.model_data.set_sd_model(value)
sd_model: LatentDiffusion = None # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead
sys.modules[__name__].__class__ = Shared
+190 -192
View File
@@ -1,10 +1,8 @@
import os
import cv2
import requests
import os
from collections import defaultdict
from math import log, sqrt
import numpy as np
from PIL import Image, ImageDraw
from PIL import ImageDraw
GREEN = "#0F0"
BLUE = "#00F"
@@ -12,63 +10,63 @@ RED = "#F00"
def crop_image(im, settings):
""" Intelligently crop an image to the subject matter """
""" Intelligently crop an image to the subject matter """
scale_by = 1
if is_landscape(im.width, im.height):
scale_by = settings.crop_height / im.height
elif is_portrait(im.width, im.height):
scale_by = settings.crop_width / im.width
elif is_square(im.width, im.height):
if is_square(settings.crop_width, settings.crop_height):
scale_by = settings.crop_width / im.width
elif is_landscape(settings.crop_width, settings.crop_height):
scale_by = settings.crop_width / im.width
elif is_portrait(settings.crop_width, settings.crop_height):
scale_by = settings.crop_height / im.height
scale_by = 1
if is_landscape(im.width, im.height):
scale_by = settings.crop_height / im.height
elif is_portrait(im.width, im.height):
scale_by = settings.crop_width / im.width
elif is_square(im.width, im.height):
if is_square(settings.crop_width, settings.crop_height):
scale_by = settings.crop_width / im.width
elif is_landscape(settings.crop_width, settings.crop_height):
scale_by = settings.crop_width / im.width
elif is_portrait(settings.crop_width, settings.crop_height):
scale_by = settings.crop_height / im.height
im = im.resize((int(im.width * scale_by), int(im.height * scale_by)))
im_debug = im.copy()
im = im.resize((int(im.width * scale_by), int(im.height * scale_by)))
im_debug = im.copy()
focus = focal_point(im_debug, settings)
focus = focal_point(im_debug, settings)
# take the focal point and turn it into crop coordinates that try to center over the focal
# point but then get adjusted back into the frame
y_half = int(settings.crop_height / 2)
x_half = int(settings.crop_width / 2)
# take the focal point and turn it into crop coordinates that try to center over the focal
# point but then get adjusted back into the frame
y_half = int(settings.crop_height / 2)
x_half = int(settings.crop_width / 2)
x1 = focus.x - x_half
if x1 < 0:
x1 = 0
elif x1 + settings.crop_width > im.width:
x1 = im.width - settings.crop_width
x1 = focus.x - x_half
if x1 < 0:
x1 = 0
elif x1 + settings.crop_width > im.width:
x1 = im.width - settings.crop_width
y1 = focus.y - y_half
if y1 < 0:
y1 = 0
elif y1 + settings.crop_height > im.height:
y1 = im.height - settings.crop_height
y1 = focus.y - y_half
if y1 < 0:
y1 = 0
elif y1 + settings.crop_height > im.height:
y1 = im.height - settings.crop_height
x2 = x1 + settings.crop_width
y2 = y1 + settings.crop_height
x2 = x1 + settings.crop_width
y2 = y1 + settings.crop_height
crop = [x1, y1, x2, y2]
crop = [x1, y1, x2, y2]
results = []
results = []
results.append(im.crop(tuple(crop)))
results.append(im.crop(tuple(crop)))
if settings.annotate_image:
d = ImageDraw.Draw(im_debug)
rect = list(crop)
rect[2] -= 1
rect[3] -= 1
d.rectangle(rect, outline=GREEN)
results.append(im_debug)
if settings.destop_view_image:
im_debug.show()
if settings.annotate_image:
d = ImageDraw.Draw(im_debug)
rect = list(crop)
rect[2] -= 1
rect[3] -= 1
d.rectangle(rect, outline=GREEN)
results.append(im_debug)
if settings.destop_view_image:
im_debug.show()
return results
return results
def focal_point(im, settings):
corner_points = image_corner_points(im, settings) if settings.corner_points_weight > 0 else []
@@ -79,118 +77,118 @@ def focal_point(im, settings):
weight_pref_total = 0
if len(corner_points) > 0:
weight_pref_total += settings.corner_points_weight
weight_pref_total += settings.corner_points_weight
if len(entropy_points) > 0:
weight_pref_total += settings.entropy_points_weight
weight_pref_total += settings.entropy_points_weight
if len(face_points) > 0:
weight_pref_total += settings.face_points_weight
weight_pref_total += settings.face_points_weight
corner_centroid = None
if len(corner_points) > 0:
corner_centroid = centroid(corner_points)
corner_centroid.weight = settings.corner_points_weight / weight_pref_total
pois.append(corner_centroid)
corner_centroid = centroid(corner_points)
corner_centroid.weight = settings.corner_points_weight / weight_pref_total
pois.append(corner_centroid)
entropy_centroid = None
if len(entropy_points) > 0:
entropy_centroid = centroid(entropy_points)
entropy_centroid.weight = settings.entropy_points_weight / weight_pref_total
pois.append(entropy_centroid)
entropy_centroid = centroid(entropy_points)
entropy_centroid.weight = settings.entropy_points_weight / weight_pref_total
pois.append(entropy_centroid)
face_centroid = None
if len(face_points) > 0:
face_centroid = centroid(face_points)
face_centroid.weight = settings.face_points_weight / weight_pref_total
pois.append(face_centroid)
face_centroid = centroid(face_points)
face_centroid.weight = settings.face_points_weight / weight_pref_total
pois.append(face_centroid)
average_point = poi_average(pois, settings)
if settings.annotate_image:
d = ImageDraw.Draw(im)
max_size = min(im.width, im.height) * 0.07
if corner_centroid is not None:
color = BLUE
box = corner_centroid.bounding(max_size * corner_centroid.weight)
d.text((box[0], box[1]-15), "Edge: %.02f" % corner_centroid.weight, fill=color)
d.ellipse(box, outline=color)
if len(corner_points) > 1:
for f in corner_points:
d.rectangle(f.bounding(4), outline=color)
if entropy_centroid is not None:
color = "#ff0"
box = entropy_centroid.bounding(max_size * entropy_centroid.weight)
d.text((box[0], box[1]-15), "Entropy: %.02f" % entropy_centroid.weight, fill=color)
d.ellipse(box, outline=color)
if len(entropy_points) > 1:
for f in entropy_points:
d.rectangle(f.bounding(4), outline=color)
if face_centroid is not None:
color = RED
box = face_centroid.bounding(max_size * face_centroid.weight)
d.text((box[0], box[1]-15), "Face: %.02f" % face_centroid.weight, fill=color)
d.ellipse(box, outline=color)
if len(face_points) > 1:
for f in face_points:
d.rectangle(f.bounding(4), outline=color)
d = ImageDraw.Draw(im)
max_size = min(im.width, im.height) * 0.07
if corner_centroid is not None:
color = BLUE
box = corner_centroid.bounding(max_size * corner_centroid.weight)
d.text((box[0], box[1]-15), f"Edge: {corner_centroid.weight:.02f}", fill=color)
d.ellipse(box, outline=color)
if len(corner_points) > 1:
for f in corner_points:
d.rectangle(f.bounding(4), outline=color)
if entropy_centroid is not None:
color = "#ff0"
box = entropy_centroid.bounding(max_size * entropy_centroid.weight)
d.text((box[0], box[1]-15), f"Entropy: {entropy_centroid.weight:.02f}", fill=color)
d.ellipse(box, outline=color)
if len(entropy_points) > 1:
for f in entropy_points:
d.rectangle(f.bounding(4), outline=color)
if face_centroid is not None:
color = RED
box = face_centroid.bounding(max_size * face_centroid.weight)
d.text((box[0], box[1]-15), f"Face: {face_centroid.weight:.02f}", fill=color)
d.ellipse(box, outline=color)
if len(face_points) > 1:
for f in face_points:
d.rectangle(f.bounding(4), outline=color)
d.ellipse(average_point.bounding(max_size), outline=GREEN)
d.ellipse(average_point.bounding(max_size), outline=GREEN)
return average_point
def image_face_points(im, settings):
if settings.dnn_model_path is not None:
detector = cv2.FaceDetectorYN.create(
settings.dnn_model_path,
"",
(im.width, im.height),
0.9, # score threshold
0.3, # nms threshold
5000 # keep top k before nms
)
faces = detector.detect(np.array(im))
results = []
if faces[1] is not None:
for face in faces[1]:
x = face[0]
y = face[1]
w = face[2]
h = face[3]
results.append(
PointOfInterest(
int(x + (w * 0.5)), # face focus left/right is center
int(y + (h * 0.33)), # face focus up/down is close to the top of the head
size = w,
weight = 1/len(faces[1])
)
)
return results
detector = cv2.FaceDetectorYN.create(
settings.dnn_model_path,
"",
(im.width, im.height),
0.9, # score threshold
0.3, # nms threshold
5000 # keep top k before nms
)
faces = detector.detect(np.array(im))
results = []
if faces[1] is not None:
for face in faces[1]:
x = face[0]
y = face[1]
w = face[2]
h = face[3]
results.append(
PointOfInterest(
int(x + (w * 0.5)), # face focus left/right is center
int(y + (h * 0.33)), # face focus up/down is close to the top of the head
size = w,
weight = 1/len(faces[1])
)
)
return results
else:
np_im = np.array(im)
gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY)
np_im = np.array(im)
gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY)
tries = [
[ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ]
]
for t in tries:
classifier = cv2.CascadeClassifier(t[0])
minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side
try:
faces = classifier.detectMultiScale(gray, scaleFactor=1.1,
minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE)
except:
continue
tries = [
[ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ],
[ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ]
]
for t in tries:
classifier = cv2.CascadeClassifier(t[0])
minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side
try:
faces = classifier.detectMultiScale(gray, scaleFactor=1.1,
minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE)
except:
continue
if len(faces) > 0:
rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces]
return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2]), weight=1/len(rects)) for r in rects]
if len(faces) > 0:
rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces]
return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2]), weight=1/len(rects)) for r in rects]
return []
@@ -204,11 +202,11 @@ def image_corner_points(im, settings):
np_im = np.array(grayscale)
points = cv2.goodFeaturesToTrack(
np_im,
maxCorners=100,
qualityLevel=0.04,
minDistance=min(grayscale.width, grayscale.height)*0.06,
useHarrisDetector=False,
np_im,
maxCorners=100,
qualityLevel=0.04,
minDistance=min(grayscale.width, grayscale.height)*0.06,
useHarrisDetector=False,
)
if points is None:
@@ -216,8 +214,8 @@ def image_corner_points(im, settings):
focal_points = []
for point in points:
x, y = point.ravel()
focal_points.append(PointOfInterest(x, y, size=4, weight=1/len(points)))
x, y = point.ravel()
focal_points.append(PointOfInterest(x, y, size=4, weight=1/len(points)))
return focal_points
@@ -226,13 +224,13 @@ def image_entropy_points(im, settings):
landscape = im.height < im.width
portrait = im.height > im.width
if landscape:
move_idx = [0, 2]
move_max = im.size[0]
move_idx = [0, 2]
move_max = im.size[0]
elif portrait:
move_idx = [1, 3]
move_max = im.size[1]
move_idx = [1, 3]
move_max = im.size[1]
else:
return []
return []
e_max = 0
crop_current = [0, 0, settings.crop_width, settings.crop_height]
@@ -241,9 +239,9 @@ def image_entropy_points(im, settings):
crop = im.crop(tuple(crop_current))
e = image_entropy(crop)
if (e > e_max):
e_max = e
crop_best = list(crop_current)
if e > e_max:
e_max = e
crop_best = list(crop_current)
crop_current[move_idx[0]] += 4
crop_current[move_idx[1]] += 4
@@ -263,9 +261,9 @@ def image_entropy(im):
return -np.log2(hist / hist.sum()).sum()
def centroid(pois):
x = [poi.x for poi in pois]
y = [poi.y for poi in pois]
return PointOfInterest(sum(x)/len(pois), sum(y)/len(pois))
x = [poi.x for poi in pois]
y = [poi.y for poi in pois]
return PointOfInterest(sum(x)/len(pois), sum(y)/len(pois))
def poi_average(pois, settings):
@@ -283,59 +281,59 @@ def poi_average(pois, settings):
def is_landscape(w, h):
return w > h
return w > h
def is_portrait(w, h):
return h > w
return h > w
def is_square(w, h):
return w == h
return w == h
def download_and_cache_models(dirname):
download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true'
model_file_name = 'face_detection_yunet.onnx'
download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true'
model_file_name = 'face_detection_yunet.onnx'
if not os.path.exists(dirname):
os.makedirs(dirname)
if not os.path.exists(dirname):
os.makedirs(dirname)
cache_file = os.path.join(dirname, model_file_name)
if not os.path.exists(cache_file):
print(f"downloading face detection model from '{download_url}' to '{cache_file}'")
response = requests.get(download_url)
with open(cache_file, "wb") as f:
f.write(response.content)
cache_file = os.path.join(dirname, model_file_name)
if not os.path.exists(cache_file):
print(f"downloading face detection model from '{download_url}' to '{cache_file}'")
response = requests.get(download_url, timeout=60*60*2)
with open(cache_file, "wb") as f:
f.write(response.content)
if os.path.exists(cache_file):
return cache_file
return None
if os.path.exists(cache_file):
return cache_file
return None
class PointOfInterest:
def __init__(self, x, y, weight=1.0, size=10):
self.x = x
self.y = y
self.weight = weight
self.size = size
def __init__(self, x, y, weight=1.0, size=10):
self.x = x
self.y = y
self.weight = weight
self.size = size
def bounding(self, size):
return [
self.x - size//2,
self.y - size//2,
self.x + size//2,
self.y + size//2
]
def bounding(self, size):
return [
self.x - size//2,
self.y - size//2,
self.x + size//2,
self.y + size//2
]
class Settings:
def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False, dnn_model_path=None):
self.crop_width = crop_width
self.crop_height = crop_height
self.corner_points_weight = corner_points_weight
self.entropy_points_weight = entropy_points_weight
self.face_points_weight = face_points_weight
self.annotate_image = annotate_image
self.destop_view_image = False
self.dnn_model_path = dnn_model_path
def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False, dnn_model_path=None):
self.crop_width = crop_width
self.crop_height = crop_height
self.corner_points_weight = corner_points_weight
self.entropy_points_weight = entropy_points_weight
self.face_points_weight = face_points_weight
self.annotate_image = annotate_image
self.destop_view_image = False
self.dnn_model_path = dnn_model_path
+11 -14
View File
@@ -1,19 +1,16 @@
import os
import re
import random
from collections import defaultdict
import numpy as np
import PIL
import torch
from PIL import Image
from torch.utils.data import Dataset, DataLoader, Sampler
from torchvision import transforms
from collections import defaultdict
from random import shuffle, choices
import random
import tqdm
from modules import devices, shared
import re
from ldm.modules.distributions.distributions import DiagonalGaussianDistribution
from modules import devices, shared
re_numbers_at_start = re.compile(r"^[-\d]+\s*")
@@ -72,7 +69,7 @@ class PersonalizedBase(Dataset):
except Exception:
continue
text_filename = os.path.splitext(path)[0] + ".txt"
text_filename = f"{os.path.splitext(path)[0]}.txt"
filename = os.path.basename(path)
if os.path.exists(text_filename):
@@ -118,7 +115,7 @@ class PersonalizedBase(Dataset):
weight = torch.ones(latent_sample.shape)
else:
weight = None
if latent_sampling_method == "random":
entry = DatasetEntry(filename=path, filename_text=filename_text, latent_dist=latent_dist, weight=weight)
else:
@@ -193,16 +190,16 @@ class GroupedBatchSampler(Sampler):
b = self.batch_size
for g in self.groups:
shuffle(g)
random.shuffle(g)
batches = []
for g in self.groups:
batches.extend(g[i*b:(i+1)*b] for i in range(len(g) // b))
for _ in range(self.n_rand_batches):
rand_group = choices(self.groups, self.probs)[0]
batches.append(choices(rand_group, k=b))
rand_group = random.choices(self.groups, self.probs)[0]
batches.append(random.choices(rand_group, k=b))
shuffle(batches)
random.shuffle(batches)
yield from batches
@@ -243,4 +240,4 @@ class BatchLoaderRandom(BatchLoader):
return self
def collate_wrapper_random(batch):
return BatchLoaderRandom(batch)
return BatchLoaderRandom(batch)
+1 -1
View File
@@ -16,7 +16,7 @@ def save_settings_to_file(log_directory, all_params):
if all_params.get('preview_from_txt2img'):
keys = keys | saved_params_previews
params.update({k: v for k, v in all_params.items() if k in keys})
filename = f"{params['embedding_name']}-{now.strftime('%Y-%m-%d_%H-%M-%S')}.json"
filename = f"settings-{now.strftime('%Y-%m-%d_%H-%M-%S')}.json"
with open(os.path.join(log_directory, filename), "w", encoding='utf-8') as file:
print(f'Training settings file: {os.path.join(log_directory, filename)}')
json.dump(params, file, indent=2)
+3 -3
View File
@@ -58,9 +58,9 @@ def save_pic_with_caption(image, index, params: PreprocessParams, existing_capti
image.save(os.path.join(params.dstdir, f"{basename}.png"))
if params.preprocess_txt_action == 'prepend' and existing_caption:
caption = existing_caption + ' ' + caption
caption = f"{existing_caption} {caption}"
elif params.preprocess_txt_action == 'append' and existing_caption:
caption = caption + ' ' + existing_caption
caption = f"{caption} {existing_caption}"
elif params.preprocess_txt_action == 'copy' and existing_caption:
caption = existing_caption
caption = caption.strip()
@@ -173,7 +173,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre
params.src = filename
existing_caption = None
existing_caption_filename = os.path.splitext(filename)[0] + '.txt'
existing_caption_filename = f"{os.path.splitext(filename)[0]}.txt"
if os.path.exists(existing_caption_filename):
with open(existing_caption_filename, 'r', encoding="utf8") as file:
existing_caption = file.read()
@@ -60,7 +60,7 @@ class Embedding:
'hash': self.checksum(),
'optimizer_state_dict': self.optimizer_state_dict,
}
torch.save(optimizer_saved_dict, filename + '.optim')
torch.save(optimizer_saved_dict, f"{filename}.optim")
def checksum(self):
if self.cached_checksum is not None:
@@ -419,8 +419,8 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st
optimizer = torch.optim.AdamW([embedding.vec], lr=scheduler.learn_rate, weight_decay=0.0)
if shared.opts.save_optimizer_state:
optimizer_state_dict = None
if os.path.exists(filename + '.optim'):
optimizer_saved_dict = torch.load(filename + '.optim', map_location='cpu')
if os.path.exists(f"{filename}.optim"):
optimizer_saved_dict = torch.load(f"{filename}.optim", map_location='cpu')
if embedding.checksum() == optimizer_saved_dict.get('hash', None):
optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None)
if optimizer_state_dict is not None:
+43 -30
View File
@@ -76,7 +76,7 @@ def visit(x, func, path=""):
for c in x.children:
visit(c, func, path)
elif x.label is not None:
func(path + "/" + str(x.label), x)
func(f"{path}/{x.label}", x)
def add_style(name: str, prompt: str, negative_prompt: str):
@@ -127,7 +127,7 @@ def process_interrogate(interrogation_function, mode, ii_input_dir, ii_output_di
img = Image.open(image)
filename = os.path.basename(image)
left, _ = os.path.splitext(filename)
print(interrogation_function(img), file=open(os.path.join(ii_output_dir, left + ".txt"), 'a', encoding='utf-8'))
print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a', encoding='utf-8'))
return [gr.update(), None]
@@ -147,21 +147,21 @@ def change_clip_skip(val):
def create_seed_inputs(target_interface):
with FormRow(elem_id=target_interface + '_seed_row', variant="compact"):
seed = gr.Number(label='Seed', value=-1, elem_id=target_interface + '_seed')
with FormRow(elem_id=f"{target_interface}_seed_row", variant="compact"):
seed = gr.Number(label='Seed', value=-1, elem_id=f"{target_interface}_seed")
seed.style(container=False)
random_seed = ToolButton(random_symbol, elem_id=target_interface + '_random_seed', label='Random seed')
reuse_seed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_seed', label='Reuse seed')
seed_checkbox = gr.Checkbox(label='Extra', elem_id=target_interface + '_subseed_show', value=False, visible=False) # Ghost checkbox, so it still gets sent. For compatibility with extensions that call txt2img or img2img manually
with FormRow(visible=True, elem_id=target_interface + '_subseed_row'):
subseed = gr.Number(label='Variation seed', value=-1, elem_id=target_interface + '_subseed')
random_seed = ToolButton(random_symbol, elem_id=f"{target_interface}_random_seed", label='Random seed')
reuse_seed = ToolButton(reuse_symbol, elem_id=f"{target_interface}_reuse_seed", label='Reuse seed')
seed_checkbox = gr.Checkbox(label='Extra', elem_id=f"{target_interface}_subseed_show", value=False) # Ghost checkbox for compatibility
with FormRow(visible=True, elem_id=f"{target_interface}_subseed_row"):
subseed = gr.Number(label='Variation seed', value=-1, elem_id=f"{target_interface}_subseed")
subseed.style(container=False)
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='Strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=target_interface + '_subseed_strength')
random_subseed = ToolButton(random_symbol, elem_id=f"{target_interface}_random_subseed")
reuse_subseed = ToolButton(reuse_symbol, elem_id=f"{target_interface}_reuse_subseed")
subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=f"{target_interface}_subseed_strength")
with FormRow(visible=False):
seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from width", value=0, elem_id=target_interface + '_seed_resize_from_w')
seed_resize_from_h = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from height", value=0, elem_id=target_interface + '_seed_resize_from_h')
seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from width", value=0, elem_id=f"{target_interface}_seed_resize_from_w")
seed_resize_from_h = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from height", value=0, elem_id=f"{target_interface}_seed_resize_from_h")
random_seed.click(fn=lambda: [-1, -1], show_progress=False, inputs=[], outputs=[seed, subseed])
random_subseed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[subseed])
return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox
@@ -615,7 +615,7 @@ def create_ui():
)
button.click(
fn=lambda: None,
_js="switch_to_"+name.replace(" ", "_"),
_js=f"switch_to_{name.replace(' ', '_')}",
inputs=[],
outputs=[],
)
@@ -679,7 +679,7 @@ def create_ui():
with FormGroup():
with FormRow():
cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=6.0, elem_id="img2img_cfg_scale")
image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale', value=1.5, elem_id="img2img_image_cfg_scale", visible=modules.shared.sd_model and modules.shared.sd_model.cond_stage_key == "edit")
image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale', value=1.5, elem_id="img2img_image_cfg_scale", visible=False)
denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength")
clip_skip = gr.Slider(label='CLIP Skip', value=modules.shared.opts.CLIP_stop_at_last_layers, minimum=1, maximum=4, step=1, elem_id='img2img_clip_skip', interactive=True)
clip_skip.change(fn=change_clip_skip, show_progress=False, inputs=clip_skip)
@@ -1271,16 +1271,16 @@ def create_ui():
elif t == bool:
comp = gr.Checkbox
else:
raise ValueError(f'bad options item type: {str(t)} for key {key}')
elem_id = "setting_"+key
raise ValueError(f'bad options item type: {t} for key {key}')
elem_id = f"setting_{key}"
if info.refresh is not None:
if is_quicksettings:
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key)
create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
else:
with FormRow():
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key)
create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
else:
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
return res
@@ -1331,7 +1331,7 @@ def create_ui():
result = gr.HTML(elem_id="settings_result")
quicksettings_names = [x.strip() for x in opts.quicksettings.split(",")]
quicksettings_names = opts.quicksettings_list
quicksettings_names = {x: i for i, x in enumerate(quicksettings_names) if x != 'quicksettings'}
quicksettings_list = []
previous_section = None
@@ -1366,7 +1366,7 @@ def create_ui():
request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications", visible=False)
_show_all_pages = gr.Button(value="Show all pages", variant='primary', elem_id="settings_show_all_pages")
with gr.TabItem("Licenses", id="licenses"):
with gr.TabItem("Licenses", id="licenses", elem_id="settings_tab_licenses"):
gr.HTML(modules.shared.html("licenses.html"), elem_id="licenses")
def unload_sd_weights():
@@ -1443,7 +1443,7 @@ def create_ui():
for interface, label, ifid in interfaces:
if label in modules.shared.opts.hidden_tabs:
continue
with gr.TabItem(label, id=ifid, elem_id='tab_' + ifid):
with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"):
interface.render()
if opts.notification_audio_enable and os.path.exists(os.path.join(script_path, opts.notification_audio_path)):
@@ -1471,11 +1471,9 @@ def create_ui():
show_progress=info.refresh is not None,
)
text_settings.change(
fn=lambda: gr.update(visible=modules.shared.sd_model and modules.shared.sd_model.cond_stage_key == "edit"),
inputs=[],
outputs=[image_cfg_scale],
)
update_image_cfg_scale_visibility = lambda: gr.update(visible=modules.shared.sd_model and modules.shared.sd_model.cond_stage_key == "edit") # pylint: disable=unnecessary-lambda-assignment
text_settings.change(fn=update_image_cfg_scale_visibility, inputs=[], outputs=[image_cfg_scale])
demo.load(fn=update_image_cfg_scale_visibility, inputs=[], outputs=[image_cfg_scale])
button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False)
button_set_checkpoint.click(
@@ -1549,10 +1547,10 @@ def create_ui():
def loadsave(path, x):
def apply_field(obj, field, condition=None, init_field=None):
key = path + "/" + field
key = f"{path}/{field}"
if getattr(obj, 'custom_script_source', None) is not None:
key = 'customscript/' + obj.custom_script_source + '/' + key
key = f"customscript/{obj.custom_script_source}/{key}"
if getattr(obj, 'do_not_save_to_config', False):
return
@@ -1699,5 +1697,20 @@ def reload_javascript():
gradio.routes.templates.TemplateResponse = template_response
def setup_ui_api(app):
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
from typing import List
class QuicksettingsHint(BaseModel):
name: str = Field(title="Name of the quicksettings field")
label: str = Field(title="Label of the quicksettings field")
def quicksettings_hint():
return [QuicksettingsHint(name=k, label=v.label) for k, v in opts.data_labels.items()]
app.add_api_route("/internal/quicksettings-hint", quicksettings_hint, methods=["GET"], response_model=List[QuicksettingsHint])
app.add_api_route("/internal/ping", lambda: {}, methods=["GET"])
if not hasattr(modules.shared, 'GradioTemplateResponseOriginal'):
modules.shared.GradioTemplateResponseOriginal = gradio.routes.templates.TemplateResponse
+18 -14
View File
@@ -1,6 +1,5 @@
import json
import html
import glob
import os.path
import urllib.parse
from pathlib import Path
@@ -63,7 +62,9 @@ class ExtraNetworksPage:
pass
def link_preview(self, filename):
return "./sd_extra_networks/thumb?filename=" + urllib.parse.quote(filename.replace('\\', '/')) + "&mtime=" + str(os.path.getmtime(filename))
quoted_filename = urllib.parse.quote(filename.replace('\\', '/'))
mtime = os.path.getmtime(filename)
return f"./sd_extra_networks/thumb?filename={quoted_filename}&mtime={mtime}"
def search_terms_from_path(self, filename, possible_directories=None):
abspath = os.path.abspath(filename)
@@ -78,17 +79,20 @@ class ExtraNetworksPage:
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):
if not os.path.isdir(x):
continue
subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/")
while subdir.startswith("/"):
subdir = subdir[1:]
is_empty = len(os.listdir(x)) == 0
if not is_empty and not subdir.endswith("/"):
subdir = subdir + "/"
subdirs[subdir] = 1
allowed_folders = [os.path.abspath(x) for x in self.allowed_directories_for_previews()]
for parentdir in [*set(allowed_folders)]:
for root, dirs, _files in os.walk(parentdir):
for dirname in dirs:
x = os.path.join(root, dirname)
if not os.path.isdir(x):
continue
subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/")
while subdir.startswith("/"):
subdir = subdir[1:]
is_empty = len(os.listdir(x)) == 0
if not is_empty and not subdir.endswith("/"):
subdir = subdir + "/"
subdirs[subdir] = 1
if subdirs:
subdirs = {"": 1, **subdirs}
subdirs_html = "".join([f"""
@@ -181,7 +185,7 @@ def intialize():
class ExtraNetworksUi:
def __init__(self):
self.pages = None
self.stored_extra_pages = None
self.stored_extra_pages = []
self.button_save_preview = None
self.preview_target_filename = None
self.button_save_description = None
+1 -1
View File
@@ -39,7 +39,7 @@ def save_pil_to_file(pil_image, dir=None): # pylint: disable=redefined-builtin
already_saved_as = getattr(pil_image, 'already_saved_as', None)
if already_saved_as and os.path.isfile(already_saved_as):
register_tmp_file(shared.demo, already_saved_as)
file_obj = Savedfile(already_saved_as)
file_obj = Savedfile(f'{already_saved_as}?{os.path.getmtime(already_saved_as)}')
return file_obj
if shared.opts.temp_dir != "":
dir = shared.opts.temp_dir
+1 -1
View File
@@ -77,7 +77,7 @@ return process_images(p)
module.display = display
indent = " " * indent_level
indented = code.replace('\n', '\n' + indent)
indented = code.replace('\n', f"\n{indent}")
body = f"""def __webuitemp__():
{indent}{indented}
__webuitemp__()"""
+1 -1
View File
@@ -84,7 +84,7 @@ class Script(scripts.Script):
p.color_corrections = initial_color_corrections
if append_interrogation != "None":
p.prompt = original_prompt + ", " if original_prompt != "" else ""
p.prompt = f"{original_prompt}, " if original_prompt else ""
if append_interrogation == "CLIP":
p.prompt += shared.interrogator.interrogate(p.init_images[0])
elif append_interrogation == "DeepBooru":
+2 -2
View File
@@ -227,7 +227,7 @@ axis_options = [
AxisOptionTxt2Img("Hires upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]),
AxisOptionTxt2Img("Fallback latent upscaler sampler", str, apply_fallback, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]),
AxisOptionImg2Img("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight")),
AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: list(sd_vae.vae_dict)),
AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: ['None'] + 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, fmt=format_value),
@@ -446,7 +446,7 @@ class Script(scripts.Script):
z_type.change(fn=select_axis, inputs=[z_type,z_values_dropdown], outputs=[fill_z_button,z_values,z_values_dropdown])
def get_dropdown_update_from_params(axis,params):
val_key = axis + " Values"
val_key = f"{axis} Values"
vals = params.get(val_key,"")
valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals))) if x]
return gr.update(value = valslist)
+39 -6
View File
@@ -5,6 +5,7 @@ import signal
import asyncio
import logging
import warnings
from threading import Thread
from modules import timer, errors
startup_timer = timer.Timer()
@@ -27,6 +28,7 @@ warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvisi
startup_timer.record("torch")
from modules import import_hook # pylint: disable=W0611,C0411,C0412
from fastapi import FastAPI # pylint: disable=W0611,C0411
import gradio # pylint: disable=W0611,C0411
startup_timer.record("gradio")
errors.install([gradio])
@@ -148,6 +150,8 @@ def initialize():
def load_model():
shared.state.begin()
shared.state.job = 'load model'
"""
try:
modules.sd_models.load_model()
modules.sd_models.skip_next_load = True
@@ -155,17 +159,22 @@ def load_model():
errors.display(e, "loading stable diffusion model")
log.error("Stable diffusion model failed to load")
exit(1)
"""
Thread(target=lambda: shared.sd_model).start()
if shared.sd_model is None:
log.warning("No stable diffusion model loaded")
# exit(1)
else:
shared.opts.data["sd_model_checkpoint"] = shared.sd_model.sd_checkpoint_info.title
shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights()))
shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights()), call=False)
shared.state.end()
startup_timer.record("checkpoint")
def create_api(app):
log.debug('Creating API')
from modules.api.api import Api
api = Api(app, queue_lock)
return api
@@ -177,7 +186,6 @@ def monkey_patch_docs():
self.redoc_url = "/redoc"
self.setup_original()
from fastapi import FastAPI
setup_original = getattr(FastAPI, "setup_original", None)
if setup_original is None:
FastAPI.setup_original = FastAPI.setup
@@ -199,8 +207,8 @@ def async_policy():
asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
def start_ui():
log.debug('Entering StartUI')
def start_common():
log.debug('Entering start sequence')
logging.disable(logging.NOTSET if cmd_opts.debug else logging.DEBUG)
create_paths(opts)
async_policy()
@@ -208,6 +216,10 @@ def start_ui():
if shared.opts.clean_temp_dir_at_start:
ui_tempdir.cleanup_tmpdr()
startup_timer.record("cleanup")
def start_ui():
log.debug('Creating UI')
modules.script_callbacks.before_ui_callback()
startup_timer.record("scripts before_ui_callback")
shared.demo = modules.ui.create_ui()
@@ -250,6 +262,12 @@ def start_ui():
shared.demo.server.wants_restart = False
setup_middleware(app, cmd_opts)
if cmd_opts.subpath:
redirector = FastAPI()
redirector.get("/")
_mounted_app = gradio.mount_gradio_app(redirector, shared.demo, path=f"/{cmd_opts.subpath}")
shared.log.info('Redirector mounted: /{cmd_opts.subpath}')
cmd_opts.autolaunch = False
startup_timer.record("start")
@@ -262,12 +280,27 @@ def start_ui():
def webui():
log.debug('Entering WebUI')
start_common()
start_ui()
load_model()
log.info(f"Startup time: {startup_timer.summary()}")
return shared.demo.server
def api_only():
start_common()
app = FastAPI()
setup_middleware(app, cmd_opts)
api = create_api(app)
api.wants_restart = False
modules.script_callbacks.app_started_callback(None, app)
log.info(f"Startup time: {startup_timer.summary()}")
api.launch(server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", port=cmd_opts.port if cmd_opts.port else 7861)
return api
if __name__ == "__main__":
webui()
if cmd_opts.api_only:
api_only()
else:
webui()