mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 17:24:32 +02:00
@@ -1,5 +1,25 @@
|
||||
# Change Log for SD.Next
|
||||
|
||||
## Update for 2026-02-06
|
||||
|
||||
- **Upscalers**
|
||||
- add support for [spandrel](https://github.com/chaiNNer-org/spandrel)
|
||||
upscaling engine with suport for new upscaling model families
|
||||
- add two new ai upscalers: *RealPLKSR NomosWebPhoto* and *RealPLKSR AnimeSharpV2*
|
||||
- add two new interpolation methods: *HQX* and *ICB*
|
||||
- **Features**
|
||||
- pipelines: add **ZImageInpaint**, thanks @CalamitousFelicitousness
|
||||
- **UI**
|
||||
- ui: **themes** add *CTD-NT64Light* and *CTD-NT64Dark*, thanks @resonantsky
|
||||
- ui: **gallery** add option to auto-refresh gallery, thanks @awsr
|
||||
- **Internal**
|
||||
- refactor: reorganize `cli` scripts
|
||||
- **Fixes**
|
||||
- fix: add metadata restore to always-on scripts
|
||||
- fix: improve wildcard weights parsing, thanks @Tillerz
|
||||
- fix: ui gallery cace recursive cleanup, thanks @awsr
|
||||
- fix: `anima` model detection
|
||||
|
||||
## Update for 2026-02-04
|
||||
|
||||
### Highlights for 2026-02-04
|
||||
|
||||
@@ -41,12 +41,6 @@
|
||||
|
||||
TODO: Investigate which models are diffusers-compatible and prioritize!
|
||||
|
||||
### Upscalers
|
||||
|
||||
- [HQX](https://github.com/uier/py-hqx/blob/main/hqx.py)
|
||||
- [DCCI](https://every-algorithm.github.io/2024/11/06/directional_cubic_convolution_interpolation.html)
|
||||
- [ICBI](https://github.com/gyfastas/ICBI/blob/master/icbi.py)
|
||||
|
||||
### Image-Base
|
||||
- [Chroma Zeta](https://huggingface.co/lodestones/Zeta-Chroma): Image and video generator for creative effects and professional filters
|
||||
- [Chroma Radiance](https://huggingface.co/lodestones/Chroma1-Radiance): Pixel-space model eliminating VAE artifacts for high visual fidelity
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=no-member
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
import importlib
|
||||
import asyncio
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from util import Map, log
|
||||
from sdapi import get, post, close
|
||||
from generate import generate # pylint: disable=import-error
|
||||
grid = importlib.import_module('image-grid').grid
|
||||
|
||||
|
||||
options = Map({
|
||||
# used by extra networks
|
||||
'prompt': 'photo of <keyword> <embedding>, photograph, posing, pose, high detailed, intricate, elegant, sharp focus, skin texture, looking forward, facing camera, 135mm, shot on dslr, canon 5d, 4k, modelshoot style, cinematic lighting',
|
||||
# used by models
|
||||
'prompts': [
|
||||
('photo citiscape', 'cityscape during night, photorealistic, high detailed, sharp focus, depth of field, 4k'),
|
||||
('photo car', 'photo of a sports car, high detailed, sharp focus, dslr, cinematic lighting, realistic'),
|
||||
('photo woman', 'portrait photo of beautiful woman, high detailed, dslr, 35mm'),
|
||||
('photo naked', 'full body photo of beautiful sexy naked woman, high detailed, dslr, 35mm'),
|
||||
|
||||
('photo taylor', 'portrait photo of beautiful woman taylor swift, high detailed, sharp focus, depth of field, dslr, 35mm <lora:taylor-swift:1>'),
|
||||
('photo ti-mia', 'portrait photo of beautiful woman "ti-mia", naked, high detailed, dslr, 35mm'),
|
||||
('photo ti-vlado', 'portrait photo of man "ti-vlado", high detailed, dslr, 35mm'),
|
||||
('photo lora-vlado', 'portrait photo of man vlado, high detailed, dslr, 35mm <lora:vlado-original:1>'),
|
||||
|
||||
('wlop', 'a stunning portrait of sexy teen girl in a wet t-shirt, vivid color palette, digital painting, octane render, highly detailed, particles, light effect, volumetric lighting, art by wlop'),
|
||||
('greg rutkowski', 'beautiful woman, high detailed, sharp focus, depth of field, 4k, art by greg rutkowski'),
|
||||
('carne griffiths', 'beautiful woman taylor swift, high detailed, sharp focus, depth of field, art by carne griffiths <lora:taylor-swift:1>'),
|
||||
('carne griffiths', 'man vlado, high detailed, sharp focus, depth of field, art by carne griffiths <lora:vlado-full:1>'),
|
||||
],
|
||||
# save format
|
||||
'format': '.jpg',
|
||||
# used by generate script
|
||||
'paths': {
|
||||
"root": "/mnt/c/Users/mandi/OneDrive/Generative/Generate",
|
||||
"generate": "image",
|
||||
"upscale": "upscale",
|
||||
"grid": "grid",
|
||||
},
|
||||
# generate params
|
||||
'generate': {
|
||||
'detailer': True,
|
||||
'prompt': '',
|
||||
'negative_prompt': 'foggy, blurry, blurred, duplicate, ugly, mutilated, mutation, mutated, out of frame, bad anatomy, disfigured, deformed, censored, low res, low resolution, watermark, text, poorly drawn face, poorly drawn hands, signature',
|
||||
'steps': 20,
|
||||
'batch_size': 2,
|
||||
'n_iter': 1,
|
||||
'seed': -1,
|
||||
'sampler_name': 'UniPC',
|
||||
'cfg_scale': 6,
|
||||
'width': 512,
|
||||
'height': 512,
|
||||
},
|
||||
'lora': {
|
||||
'strength': 1.0,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
def preview_exists(folder, model):
|
||||
model = os.path.splitext(model)[0]
|
||||
for suffix in ['', '.preview']:
|
||||
for ext in ['.jpg', '.png', '.webp']:
|
||||
fn = os.path.join(folder, f'{model}{suffix}{ext}')
|
||||
if os.path.exists(fn):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def preview_models(params):
|
||||
data = await get('/sdapi/v1/sd-models')
|
||||
allmodels = [m['title'] for m in data]
|
||||
models = []
|
||||
excluded = []
|
||||
for m in allmodels: # loop through all registered models
|
||||
ok = True
|
||||
for e in params.exclude: # check if model is excluded
|
||||
if e in m:
|
||||
excluded.append(m)
|
||||
ok = False
|
||||
break
|
||||
if ok:
|
||||
short = m.split(' [')[0]
|
||||
short = short.replace('.ckpt', '').replace('.safetensors', '')
|
||||
models.append(short)
|
||||
if len(params.input) > 0: # check if model is included in cmd line
|
||||
filtered = []
|
||||
for m in params.input:
|
||||
if m in models:
|
||||
filtered.append(m)
|
||||
else:
|
||||
log.error({ 'model not found': m })
|
||||
return
|
||||
models = filtered
|
||||
log.info({ 'models preview' })
|
||||
log.info({ 'models': len(models), 'excluded': len(excluded) })
|
||||
opt = await get('/sdapi/v1/options')
|
||||
log.info({ 'total jobs': len(models) * options.generate.batch_size, 'per-model': options.generate.batch_size })
|
||||
log.info(json.dumps(options, indent=2))
|
||||
for model in models:
|
||||
if preview_exists(opt['ckpt_dir'], model) and len(params.input) == 0: # if model preview exists and not manually included
|
||||
log.info({ 'model preview exists': model })
|
||||
continue
|
||||
fn = os.path.join(opt['ckpt_dir'], os.path.splitext(model)[0] + options.format)
|
||||
log.info({ 'model load': model })
|
||||
|
||||
opt['sd_model_checkpoint'] = model
|
||||
del opt['sd_lora']
|
||||
del opt['sd_lyco']
|
||||
await post('/sdapi/v1/options', opt)
|
||||
opt = await get('/sdapi/v1/options')
|
||||
images = []
|
||||
labels = []
|
||||
t0 = time.time()
|
||||
for label, p in options.prompts:
|
||||
options.generate.prompt = p
|
||||
log.info({ 'model generating': model, 'label': label, 'prompt': options.generate.prompt })
|
||||
data = await generate(options = options, quiet=True)
|
||||
if 'image' in data:
|
||||
for img in data['image']:
|
||||
images.append(img)
|
||||
labels.append(label)
|
||||
else:
|
||||
log.error({ 'model': model, 'error': data })
|
||||
t1 = time.time()
|
||||
if len(images) == 0:
|
||||
log.error({ 'model': model, 'error': 'no images generated' })
|
||||
continue
|
||||
image = grid(images = images, labels = labels, border = 8)
|
||||
log.info({ 'saving preview': fn, 'images': len(images), 'size': [image.width, image.height] })
|
||||
image.save(fn)
|
||||
t = t1 - t0
|
||||
its = 1.0 * options.generate.steps * len(images) / t
|
||||
log.info({ 'model preview created': model, 'image': fn, 'images': len(images), 'grid': [image.width, image.height], 'time': round(t, 2), 'its': round(its, 2) })
|
||||
|
||||
opt = await get('/sdapi/v1/options')
|
||||
if opt['sd_model_checkpoint'] != params.model:
|
||||
log.info({ 'model set default': params.model })
|
||||
opt['sd_model_checkpoint'] = params.model
|
||||
del opt['sd_lora']
|
||||
del opt['sd_lyco']
|
||||
await post('/sdapi/v1/options', opt)
|
||||
|
||||
|
||||
async def lora(params):
|
||||
opt = await get('/sdapi/v1/options')
|
||||
folder = opt['lora_dir']
|
||||
if not os.path.exists(folder):
|
||||
log.error({ 'lora directory not found': folder })
|
||||
return
|
||||
models1 = list(Path(folder).glob('**/*.safetensors'))
|
||||
models2 = list(Path(folder).glob('**/*.ckpt'))
|
||||
models = [os.path.splitext(f)[0] for f in models1 + models2]
|
||||
log.info({ 'loras': len(models) })
|
||||
for model in models:
|
||||
if preview_exists('', model) and len(params.input) == 0: # if model preview exists and not manually included
|
||||
log.info({ 'lora preview exists': model })
|
||||
continue
|
||||
fn = model + options.format
|
||||
model = os.path.basename(model)
|
||||
images = []
|
||||
labels = []
|
||||
t0 = time.time()
|
||||
keywords = re.sub(r'\d', '', model)
|
||||
keywords = keywords.replace('-v', ' ').replace('-', ' ').strip().split(' ')
|
||||
keyword = '\"' + '\" \"'.join(keywords) + '\"'
|
||||
options.generate.prompt = options.prompt.replace('<keyword>', keyword)
|
||||
options.generate.prompt = options.generate.prompt.replace('<embedding>', '')
|
||||
options.generate.prompt += f' <lora:{model}:{options.lora.strength}>'
|
||||
log.info({ 'lora generating': model, 'keyword': keyword, 'prompt': options.generate.prompt })
|
||||
data = await generate(options = options, quiet=True)
|
||||
if 'image' in data:
|
||||
for img in data['image']:
|
||||
images.append(img)
|
||||
labels.append(keyword)
|
||||
else:
|
||||
log.error({ 'lora': model, 'keyword': keyword, 'error': data })
|
||||
t1 = time.time()
|
||||
if len(images) == 0:
|
||||
log.error({ 'model': model, 'error': 'no images generated' })
|
||||
continue
|
||||
image = grid(images = images, labels = labels, border = 8)
|
||||
log.info({ 'saving preview': fn, 'images': len(images), 'size': [image.width, image.height] })
|
||||
image.save(fn)
|
||||
t = t1 - t0
|
||||
its = 1.0 * options.generate.steps * len(images) / t
|
||||
log.info({ 'lora preview created': model, 'image': fn, 'images': len(images), 'grid': [image.width, image.height], 'time': round(t, 2), 'its': round(its, 2) })
|
||||
|
||||
|
||||
async def lyco(params):
|
||||
opt = await get('/sdapi/v1/options')
|
||||
folder = opt['lyco_dir']
|
||||
if not os.path.exists(folder):
|
||||
log.error({ 'lyco directory not found': folder })
|
||||
return
|
||||
models1 = list(Path(folder).glob('**/*.safetensors'))
|
||||
models2 = list(Path(folder).glob('**/*.ckpt'))
|
||||
models = [os.path.splitext(f)[0] for f in models1 + models2]
|
||||
log.info({ 'lycos': len(models) })
|
||||
for model in models:
|
||||
if preview_exists('', model) and len(params.input) == 0: # if model preview exists and not manually included
|
||||
log.info({ 'lyco preview exists': model })
|
||||
continue
|
||||
fn = model + options.format
|
||||
model = os.path.basename(model)
|
||||
images = []
|
||||
labels = []
|
||||
t0 = time.time()
|
||||
keywords = re.sub(r'\d', '', model)
|
||||
keywords = keywords.replace('-v', ' ').replace('-', ' ').strip().split(' ')
|
||||
keyword = '\"' + '\" \"'.join(keywords) + '\"'
|
||||
options.generate.prompt = options.prompt.replace('<keyword>', keyword)
|
||||
options.generate.prompt = options.generate.prompt.replace('<embedding>', '')
|
||||
options.generate.prompt += f' <lyco:{model}:{options.lora.strength}>'
|
||||
log.info({ 'lyco generating': model, 'keyword': keyword, 'prompt': options.generate.prompt })
|
||||
data = await generate(options = options, quiet=True)
|
||||
if 'image' in data:
|
||||
for img in data['image']:
|
||||
images.append(img)
|
||||
labels.append(keyword)
|
||||
else:
|
||||
log.error({ 'lyco': model, 'keyword': keyword, 'error': data })
|
||||
t1 = time.time()
|
||||
if len(images) == 0:
|
||||
log.error({ 'model': model, 'error': 'no images generated' })
|
||||
continue
|
||||
image = grid(images = images, labels = labels, border = 8)
|
||||
log.info({ 'saving preview': fn, 'images': len(images), 'size': [image.width, image.height] })
|
||||
image.save(fn)
|
||||
t = t1 - t0
|
||||
its = 1.0 * options.generate.steps * len(images) / t
|
||||
log.info({ 'lyco preview created': model, 'image': fn, 'images': len(images), 'grid': [image.width, image.height], 'time': round(t, 2), 'its': round(its, 2) })
|
||||
|
||||
|
||||
async def embedding(params):
|
||||
opt = await get('/sdapi/v1/options')
|
||||
folder = opt['embeddings_dir']
|
||||
if not os.path.exists(folder):
|
||||
log.error({ 'embeddings directory not found': folder })
|
||||
return
|
||||
models = [os.path.splitext(f)[0] for f in Path(folder).glob('**/*.pt')]
|
||||
log.info({ 'embeddings': len(models) })
|
||||
for model in models:
|
||||
if preview_exists(folder, model) and len(params.input) == 0: # if model preview exists and not manually included
|
||||
log.info({ 'embedding preview exists': model })
|
||||
continue
|
||||
fn = os.path.join(folder, model + '.preview' + options.format)
|
||||
images = []
|
||||
labels = []
|
||||
t0 = time.time()
|
||||
keyword = '\"' + re.sub(r'\d', '', model) + '\"'
|
||||
options.generate.batch_size = 4
|
||||
options.generate.prompt = options.prompt.replace('<keyword>', keyword)
|
||||
options.generate.prompt = options.generate.prompt.replace('<embedding>', '')
|
||||
log.info({ 'embedding generating': model, 'keyword': keyword, 'prompt': options.generate.prompt })
|
||||
data = await generate(options = options, quiet=True)
|
||||
if 'image' in data:
|
||||
for img in data['image']:
|
||||
images.append(img)
|
||||
labels.append(keyword)
|
||||
else:
|
||||
log.error({ 'embeding': model, 'keyword': keyword, 'error': data })
|
||||
t1 = time.time()
|
||||
if len(images) == 0:
|
||||
log.error({ 'model': model, 'error': 'no images generated' })
|
||||
continue
|
||||
image = grid(images = images, labels = labels, border = 8)
|
||||
log.info({ 'saving preview': fn, 'images': len(images), 'size': [image.width, image.height] })
|
||||
image.save(fn)
|
||||
t = t1 - t0
|
||||
its = 1.0 * options.generate.steps * len(images) / t
|
||||
log.info({ 'embeding preview created': model, 'image': fn, 'images': len(images), 'grid': [image.width, image.height], 'time': round(t, 2), 'its': round(its, 2) })
|
||||
|
||||
|
||||
async def create_previews(params):
|
||||
await preview_models(params)
|
||||
await lora(params)
|
||||
await lyco(params)
|
||||
await embedding(params)
|
||||
await close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description = 'generate model previews')
|
||||
parser.add_argument('--model', default='best/icbinp-icantbelieveIts-final.safetensors [73f48afbdc]', help="model used to create extra network previews")
|
||||
parser.add_argument('--exclude', default=['sd-v20', 'sd-v21', 'inpainting', 'pix2pix'], help="exclude models with keywords")
|
||||
parser.add_argument('--debug', default = False, action='store_true', help = 'print extra debug information')
|
||||
parser.add_argument('input', type = str, nargs = '*')
|
||||
args = parser.parse_args()
|
||||
if args.debug:
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.debug({ 'debug': True })
|
||||
log.debug({ 'args': args.__dict__ })
|
||||
asyncio.run(create_previews(args))
|
||||
Executable → Regular
+1
-1
@@ -221,7 +221,7 @@ def args(): # parse cmd arguments
|
||||
global random # pylint: disable=global-statement
|
||||
parser = argparse.ArgumentParser(description = 'sd pipeline')
|
||||
parser.add_argument('--config', type = str, default = 'generate.json', required = False, help = 'configuration file')
|
||||
parser.add_argument('--random', type = str, default = 'random.json', required = False, help = 'prompt file with randomized sections')
|
||||
parser.add_argument('--random', type = str, default = 'generate-random.json', required = False, help = 'prompt file with randomized sections')
|
||||
parser.add_argument('--max', type = int, default = 1, required = False, help = 'maximum number of generated images')
|
||||
parser.add_argument('--prompt', type = str, default = 'dynamic', required = False, help = 'prompt')
|
||||
parser.add_argument('--negative', type = str, default = 'dynamic', required = False, help = 'negative prompt')
|
||||
|
||||
Executable → Regular
Executable → Regular
+2
-2
@@ -12,7 +12,7 @@ from scipy.stats import beta
|
||||
|
||||
import util
|
||||
import sdapi
|
||||
import options
|
||||
import process_options as options
|
||||
|
||||
face_model = None
|
||||
body_model = None
|
||||
@@ -42,7 +42,7 @@ def detect_blur(image: Image):
|
||||
cx, cy = image.size[0] // 2, image.size[1] // 2
|
||||
fft = np.fft.fft2(bw)
|
||||
fftShift = np.fft.fftshift(fft)
|
||||
fftShift[cy - options.process.blur_samplesize: cy + options.process.blur_samplesize, cx - options.process.blur_samplesize: cx + options.process.blur_samplesize] = 0
|
||||
fftShift[cy - options.process.blur_samplesize: cy + options.process.blur_samplesize, cx - options.process.blur_samplesize: cx + options.process.blur_samplesize] = 0 # pylint: disable=unsupported-assignment-operation
|
||||
fftShift = np.fft.ifftshift(fftShift)
|
||||
recon = np.fft.ifft2(fftShift)
|
||||
magnitude = np.log(np.abs(recon))
|
||||
|
||||
Executable → Regular
+6
-8
@@ -89,7 +89,7 @@ class Page():
|
||||
return ''
|
||||
|
||||
def __str__(self):
|
||||
return f'Page(title="{self.title.strip()}" fn="{self.fn}" mtime={self.mtime} h1={[h.strip() for h in self.h1]} h2={len(self.h2)} h3={len(self.h3)} lines={len(self.lines)} size={self.size})'
|
||||
return f'Page(title="{self.title.strip()}" file="{self.fn}" mtime={self.mtime} h1={[h.strip() for h in self.h1]} h2={len(self.h2)} h3={len(self.h3)} lines={len(self.lines)} size={self.size})'
|
||||
|
||||
|
||||
class Pages():
|
||||
@@ -129,16 +129,14 @@ if __name__ == "__main__":
|
||||
sys.argv.pop(0)
|
||||
if len(sys.argv) < 1:
|
||||
log.error("Usage: python cli/docs.py <search_term>")
|
||||
text = ' '.join(sys.argv)
|
||||
topk = 10
|
||||
full = True
|
||||
log.info(f'Search: "{text}" topk={topk}, full={full}')
|
||||
term = ' '.join(sys.argv)
|
||||
log.info(f'Search: "{term}" topk=10, full=True')
|
||||
t0 = time.time()
|
||||
results = index.search(text, topk=topk, full=full)
|
||||
results = index.search(term, topk=10, full=True)
|
||||
t1 = time.time()
|
||||
log.info(f'Results: pages={len(results)} size={index.size} time={t1-t0:.3f}')
|
||||
for score, page in results:
|
||||
log.info(f'Score: {score:.2f} {page}')
|
||||
for _score, _page in results:
|
||||
log.info(f'Score: {_score:.2f} {_page}')
|
||||
# if len(results) > 0:
|
||||
# log.info('Top result:')
|
||||
# log.info(results[0][1].get())
|
||||
Submodule extensions-builtin/sdnext-modernui updated: 188dd69e75...ead16e1441
+1
-1
@@ -665,7 +665,7 @@ def check_diffusers():
|
||||
t_start = time.time()
|
||||
if args.skip_all:
|
||||
return
|
||||
sha = '430c557b6a66a3c2b5740fb186324cb8a9f0f2e9' # diffusers commit hash
|
||||
sha = '99e2cfff27dec514a43e260e885c5e6eca038b36' # diffusers commit hash
|
||||
# if args.use_rocm or args.use_zluda or args.use_directml:
|
||||
# sha = '043ab2520f6a19fce78e6e060a68dbc947edb9f9' # lock diffusers versions for now
|
||||
pkg = pkg_resources.working_set.by_key.get('diffusers', None)
|
||||
|
||||
+76
-11
@@ -315,6 +315,8 @@ class SimpleFunctionQueue {
|
||||
|
||||
class GalleryFolder extends HTMLElement {
|
||||
static folders = new Set();
|
||||
/** @type {GalleryFolder | null} */
|
||||
static #active = null;
|
||||
|
||||
constructor(folder) {
|
||||
super();
|
||||
@@ -339,20 +341,31 @@ class GalleryFolder extends HTMLElement {
|
||||
this.div.className = 'gallery-folder';
|
||||
this.div.innerHTML = `<span class="gallery-folder-icon">\uf03e</span> ${this.label}`;
|
||||
this.div.title = this.name; // Show full path on hover
|
||||
this.div.addEventListener('click', () => { this.updateSelected(); }); // Ensures 'this' isn't the div in the called method
|
||||
this.div.addEventListener('click', fetchFilesWS); // eslint-disable-line no-use-before-define
|
||||
this.addEventListener('click', this.updateSelected);
|
||||
this.addEventListener('click', fetchFilesWS); // eslint-disable-line no-use-before-define
|
||||
this.shadow.appendChild(this.div);
|
||||
GalleryFolder.folders.add(this);
|
||||
if (this.name === currentGalleryFolder) {
|
||||
this.updateSelected();
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectedCallback() {
|
||||
await Promise.resolve(); // Wait for other microtasks (such as element moving)
|
||||
if (this.isConnected) return;
|
||||
GalleryFolder.folders.delete(this);
|
||||
if (GalleryFolder.#active === this) {
|
||||
GalleryFolder.#active = null;
|
||||
}
|
||||
}
|
||||
|
||||
static getActive() {
|
||||
return GalleryFolder.#active;
|
||||
}
|
||||
|
||||
updateSelected() {
|
||||
this.div.classList.add('gallery-folder-selected');
|
||||
GalleryFolder.#active = this;
|
||||
for (const folder of GalleryFolder.folders) {
|
||||
if (folder !== this) {
|
||||
folder.div.classList.remove('gallery-folder-selected');
|
||||
@@ -391,13 +404,14 @@ class GalleryFile extends HTMLElement {
|
||||
this.folder = folder;
|
||||
this.name = file;
|
||||
this.#signal = signal;
|
||||
this.src = `${this.folder}/${this.name}`.replace(/\/+/g, '/'); // Ensure no //, ///, etc...
|
||||
this.fullFolder = this.src.replace(/\/[^/]+$/, '');
|
||||
this.size = 0;
|
||||
this.mtime = 0;
|
||||
this.hash = undefined;
|
||||
this.exif = '';
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.src = `${this.folder}/${this.name}`;
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
this.shadow.adoptedStyleSheets = [fileStylesheet];
|
||||
|
||||
@@ -418,9 +432,7 @@ class GalleryFile extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize path to ensure consistent hash regardless of which folder view is used
|
||||
const normalizedPath = this.src.replace(/\/+/g, '/').replace(/\/$/, '');
|
||||
this.hash = await getHash(`${normalizedPath}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define
|
||||
this.hash = await getHash(`${this.src}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define
|
||||
const cachedData = (this.hash && opts.browser_cache) ? await idbGet(this.hash).catch(() => undefined) : undefined;
|
||||
const img = document.createElement('img');
|
||||
img.className = 'gallery-file';
|
||||
@@ -458,7 +470,7 @@ class GalleryFile extends HTMLElement {
|
||||
if (opts.browser_cache) {
|
||||
await idbAdd({
|
||||
hash: this.hash,
|
||||
folder: this.folder,
|
||||
folder: this.fullFolder,
|
||||
file: this.name,
|
||||
size: this.size,
|
||||
mtime: this.mtime,
|
||||
@@ -999,7 +1011,9 @@ async function thumbCacheCleanup(folder, imgCount, controller, force = false) {
|
||||
log(`Thumbnail DB cleanup: Checking if "${folder}" needs cleaning`);
|
||||
const t0 = performance.now();
|
||||
const keptGalleryHashes = force ? new Set() : new Set(galleryHashes.values()); // External context should be safe since this function run is guarded by AbortController/AbortSignal in the SimpleFunctionQueue
|
||||
const cachedHashesCount = await idbCount(folder)
|
||||
const folderNormalized = folder.replace(/\/+/g, '/').replace(/\/$/, '');
|
||||
const recursiveFolder = IDBKeyRange.bound(folderNormalized, `${folderNormalized}\uffff`, false, true);
|
||||
const cachedHashesCount = await idbCount(recursiveFolder)
|
||||
.catch((e) => {
|
||||
error(`Thumbnail DB cleanup: Error when getting entry count for "${folder}".`, e);
|
||||
return Infinity; // Forces next check to fail if something went wrong
|
||||
@@ -1015,7 +1029,7 @@ async function thumbCacheCleanup(folder, imgCount, controller, force = false) {
|
||||
return;
|
||||
}
|
||||
const cb_clearMsg = showCleaningMsg(cleanupCount);
|
||||
await idbFolderCleanup(keptGalleryHashes, folder, controller.signal)
|
||||
await idbFolderCleanup(keptGalleryHashes, recursiveFolder, controller.signal)
|
||||
.then((delcount) => {
|
||||
const t1 = performance.now();
|
||||
log(`Thumbnail DB cleanup: folder=${folder} kept=${keptGalleryHashes.size} deleted=${delcount} time=${Math.floor(t1 - t0)}ms`);
|
||||
@@ -1150,7 +1164,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets
|
||||
let wsConnected = false;
|
||||
try {
|
||||
ws = new WebSocket(`${url}/sdapi/v1/browser/files`);
|
||||
wsConnected = await wsConnect(ws); // Warning. This changes "evt".
|
||||
wsConnected = await wsConnect(ws);
|
||||
} catch (err) {
|
||||
log('gallery: ws connect error', err);
|
||||
return;
|
||||
@@ -1258,6 +1272,43 @@ async function galleryClearInit() {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function initGalleryAutoRefresh() {
|
||||
const isModern = opts.theme_type?.toLowerCase() === 'modern';
|
||||
let galleryTab = isModern ? document.getElementById('gallery_tabitem') : document.getElementById('tab_gallery');
|
||||
let timeout = 0;
|
||||
while (!galleryTab && timeout++ < 60) {
|
||||
await new Promise((resolve) => { setTimeout(resolve, 1000); });
|
||||
galleryTab = isModern ? document.getElementById('gallery_tabitem') : document.getElementById('tab_gallery');
|
||||
}
|
||||
if (!galleryTab) {
|
||||
throw new Error('Timed out waiting for gallery tab element');
|
||||
}
|
||||
const displayNoneRegEx = /display:\s*none/;
|
||||
async function galleryAutoRefresh(mutations) {
|
||||
if (!opts.browser_gallery_autoupdate) return;
|
||||
for (const mutation of mutations) {
|
||||
switch (mutation.attributeName) {
|
||||
case 'class':
|
||||
if (mutation.oldValue.includes('hidden') && !mutation.target.classList.contains('hidden')) {
|
||||
await updateFolders();
|
||||
GalleryFolder.getActive()?.click();
|
||||
}
|
||||
break;
|
||||
case 'style':
|
||||
if (displayNoneRegEx.test(mutation.oldValue) && !displayNoneRegEx.test(mutation.target.style.display)) {
|
||||
await updateFolders();
|
||||
GalleryFolder.getActive()?.click();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const galleryVisObserver = new MutationObserver(galleryAutoRefresh);
|
||||
galleryVisObserver.observe(galleryTab, { attributeFilter: ['class', 'style'], attributeOldValue: true });
|
||||
}
|
||||
|
||||
async function blockQueueUntilReady() {
|
||||
// Add block to maintenanceQueue until cache is ready
|
||||
maintenanceQueue.enqueue({
|
||||
@@ -1302,7 +1353,21 @@ async function initGallery() { // triggered on gradio change to monitor when ui
|
||||
|
||||
monitorGalleries();
|
||||
updateFolders();
|
||||
monitorOption('browser_folders', updateFolders);
|
||||
[
|
||||
'browser_folders',
|
||||
'outdir_samples',
|
||||
'outdir_txt2img_samples',
|
||||
'outdir_img2img_samples',
|
||||
'outdir_control_samples',
|
||||
'outdir_extras_samples',
|
||||
'outdir_save',
|
||||
'outdir_video',
|
||||
'outdir_init_images',
|
||||
'outdir_grids',
|
||||
'outdir_txt2img_grids',
|
||||
'outdir_img2img_grids',
|
||||
'outdir_control_grids',
|
||||
].forEach((op) => { monitorOption(op, updateFolders); });
|
||||
}
|
||||
|
||||
// register on startup
|
||||
|
||||
+3
-10
@@ -144,10 +144,10 @@ async function idbGetAllKeys(index = null, query = null) {
|
||||
/**
|
||||
* Get the number of entries in the IndexedDB thumbnail cache.
|
||||
* @global
|
||||
* @param {?string} folder - If specified, get the count for this gallery folder. Otherwise get the total count.
|
||||
* @param {IDBValidKey | IDBKeyRange | undefined} folder - If specified, get the count for this gallery folder. Otherwise get the total count.
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
async function idbCount(folder = null) {
|
||||
async function idbCount(folder) {
|
||||
if (!db) return null;
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
@@ -173,18 +173,11 @@ async function idbCount(folder = null) {
|
||||
* Cleanup function for IndexedDB thumbnail cache.
|
||||
* @global
|
||||
* @param {Set<string>} keepSet - Set containing the hashes of the current files in the folder
|
||||
* @param {string} folder - Folder name/path
|
||||
* @param {IDBValidKey | IDBKeyRange} folder - Folder name/path or range
|
||||
* @param {AbortSignal} signal - Signal from the AbortController for thumbCacheCleanup()
|
||||
*/
|
||||
async function idbFolderCleanup(keepSet, folder, signal) {
|
||||
if (!db) return null;
|
||||
if (!(keepSet instanceof Set)) {
|
||||
throw new TypeError('IndexedDB cleaning function must be given a Set() of the current gallery hashes');
|
||||
}
|
||||
if (typeof folder !== 'string') {
|
||||
throw new Error('IndexedDB cleaning function must be told the current active folder');
|
||||
}
|
||||
|
||||
let removals = new Set(await idbGetAllKeys('folder', folder));
|
||||
removals = removals.difference(keepSet); // Don't need to keep full set in memory
|
||||
const totalRemovals = removals.size;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,225 @@
|
||||
'''
|
||||
This is the python implementation of icbi.m
|
||||
|
||||
Author: gyf
|
||||
Begin: 2019-1-16
|
||||
'''
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
def icbi(IM,ZK = 1,SZ = 8,PF = 1,ST = 20,TM = 100,TC = 50,SC = 1,TS = 100,AL = 1,BT = -1,GM = 5):
|
||||
'''
|
||||
|
||||
:param IM: Source image
|
||||
:param ZK: Power of zoom factor (default:1)
|
||||
:param SZ: Number of image bits per layer (default:8)
|
||||
:param PF: Potential to be minimized (default:1)
|
||||
:param ST: Maximum number of iterations (default:20)
|
||||
:param TM: Maximum edge step (default:100)
|
||||
:param TC: Edge continuity threshold (deafult:50).
|
||||
:param SC: Stopping criterion: 1 = change under threshold, 0 = ST iterations (default:1).
|
||||
:param TS: Threshold on image change for stopping iterations (default:100).
|
||||
:param AL: Weight for Curvature Continuity energy (default:1.0).
|
||||
:param BT: Weight for Curvature enhancement energy (default:-1.0).
|
||||
:param GM: Weight for Isophote smoothing energy (default:5.0).
|
||||
:return: EI: Enlarged image
|
||||
'''
|
||||
H = IM.shape[0]
|
||||
W = IM.shape[1]
|
||||
if ZK < 1:
|
||||
EI = cv2.resize(IM,(H*(2**ZK),W*(2**ZK)))
|
||||
|
||||
#check image type
|
||||
IDIM = np.ndim(IM)
|
||||
if IDIM == 3:
|
||||
CL = IM.shape[2] #number of colors
|
||||
|
||||
elif IDIM == 2:
|
||||
IM = np.reshape(IM,(H,W,1))
|
||||
CL = 1
|
||||
else:
|
||||
print('Unrecognized image type, please use RGB or grayscale images')
|
||||
return 0
|
||||
|
||||
|
||||
#calculate final size
|
||||
fm = H * (2**ZK) - (2**ZK - 1)
|
||||
fn = W * (2**ZK) - (2**ZK - 1)
|
||||
|
||||
#initialize output image
|
||||
if SZ>32:
|
||||
EI = np.zeros([fm,fn,CL],dtype= np.uint64)
|
||||
|
||||
elif SZ>16:
|
||||
EI = np.zeros([fm,fn,CL],dtype= np.uint32)
|
||||
|
||||
elif SZ>8:
|
||||
EI = np.zeros([fm,fn,CL],dtype= np.uint16)
|
||||
|
||||
else:
|
||||
EI = np.zeros([fm,fn,CL],dtype= np.uint8)
|
||||
|
||||
#each image color
|
||||
IMG = IM.copy()
|
||||
for CID in range(CL):
|
||||
IMG = IM[:,:,CID]
|
||||
#The image is enlarged by scaling factor 2**ZK-1 at each cycle
|
||||
for _ZF in range(ZK):
|
||||
|
||||
#size of enlarged image
|
||||
mm = 2*H - 1
|
||||
nn = 2*W - 1
|
||||
|
||||
#initialize expanded and support matrix
|
||||
IMGEXP = np.zeros([mm,nn])
|
||||
D1 = np.zeros([mm,nn])
|
||||
D2 = np.zeros([mm,nn])
|
||||
D3 = np.zeros([mm,nn])
|
||||
C1 = np.zeros([mm,nn])
|
||||
C2 = np.zeros([mm,nn])
|
||||
|
||||
#copy low resolution grid on high resolution grid
|
||||
IMGEXP[::2,::2] = IMG
|
||||
|
||||
#interpolation at borders (average value of 2 neighbors)
|
||||
for i in range(1,mm-1,2):
|
||||
#left col
|
||||
IMGEXP[i,0] = (IMGEXP[i-1,0]+IMGEXP[i+1,0])/2
|
||||
#right col
|
||||
IMGEXP[i,nn-1] = (IMGEXP[i-1,nn-1]+IMGEXP[i+1,nn-1])/2
|
||||
|
||||
for i in range(1,nn,2):
|
||||
#top row
|
||||
IMGEXP[0,i] = (IMGEXP[0,i-1] + IMGEXP[0,i+1])/2
|
||||
#bottom row
|
||||
IMGEXP[mm-1,i] = (IMGEXP[mm-1,i-1]+IMGEXP[mm-1,i+1])/2
|
||||
|
||||
#Calculate interpolated points in two steps
|
||||
#s = 0 calculates on diagonal directions
|
||||
#s = 1 calculates on vertical and horizontal directions
|
||||
for s in range(2):
|
||||
#FCBI (Fast Curvature Based Interpolation)
|
||||
for i in range(1,mm-s,2-s):
|
||||
for j in range(1+(s*(1-np.mod(i+1,2))),nn-s,2):
|
||||
v1 = np.abs(IMGEXP[i-1,j-1+s]-IMGEXP[i+1,j+1-s])
|
||||
v2 = np.abs(IMGEXP[i+1-s,j-1]-IMGEXP[i-1+s,j+1])
|
||||
p1 = (IMGEXP[i-1,j-1+s]+IMGEXP[i+1,j+1-s])/2
|
||||
p2 = (IMGEXP[i+1-s,j-1]+IMGEXP[i-1+s,j+1])/2
|
||||
if (v1<TM) and (v2<TM) and (i>2-s) and i<mm-4-s and j>2-s and j<nn-4-s and (np.abs(p1-p2)<TM):
|
||||
if np.abs( IMGEXP[i-1-s,j-3+2*s] + IMGEXP[i-3+s,j-1+2*s] + IMGEXP[i+1+s,j+3-2*s] +IMGEXP[i+3-s,j+1-2*s] + 2*p2-6*p1)> np.abs( IMGEXP[i-3+2*s,j+1+s] + IMGEXP[i-1+2*s,j+3-s] + IMGEXP[i+3-2*s,j-1-s] +IMGEXP[i+1-2*s,j-3+s] + 2*p1-6*p2):
|
||||
IMGEXP[i,j] = p1
|
||||
|
||||
else:
|
||||
IMGEXP[i,j] = p2
|
||||
|
||||
else:
|
||||
if v1<v2:
|
||||
IMGEXP[i,j] = p1
|
||||
else:
|
||||
IMGEXP[i,j] = p2
|
||||
|
||||
step = 4.0/(1+s)
|
||||
|
||||
#iterative refinement
|
||||
for g in range(ST):
|
||||
diff = 0
|
||||
|
||||
if g<ST/4 -1:
|
||||
step = 1
|
||||
elif g<ST/2 -1:
|
||||
step = 2
|
||||
elif g<3*ST/4 -1:
|
||||
step = 2
|
||||
|
||||
#computation of derivatives:
|
||||
for i in range(3-2*s,mm-3+s):
|
||||
for j in range(3-2*s+(1-s)*np.mod(i+1,2),nn-3+s,2-s):
|
||||
C1[i,j] = (IMGEXP[i-1+s,j-1] - IMGEXP[i+1-s,j+1])/2
|
||||
C2[i,j] = (IMGEXP[i+1-2*s,j-1+s] - IMGEXP[i-1+2*s,j+1-s])/2
|
||||
D1[i,j] = IMGEXP[i-1+s,j-1] + IMGEXP[i+1-s,j+1] - 2*IMGEXP[i,j]
|
||||
D2[i,j] = IMGEXP[i+1,j-1+s] + IMGEXP[i-1,j+1-s] - 2*IMGEXP[i,j]
|
||||
D3[i,j] = (IMGEXP[i-s,j-2+s] - IMGEXP[i-2+s,j+s] + IMGEXP[i+s,j+2-s] - IMGEXP[i+2-s,j-s])/2
|
||||
|
||||
|
||||
for i in range(5-3*s,mm-5+3*s,2-s):
|
||||
for j in range(5+s*(np.mod(i+1,2)-2),nn-5+3*s,2):
|
||||
c_1 = 1
|
||||
c_2 = 1
|
||||
c_3 = 1
|
||||
c_4 = 1
|
||||
if np.abs(IMGEXP[i+1-s,j+1] - IMGEXP[i,j])>TC:
|
||||
c_1 = 0
|
||||
|
||||
if np.abs(IMGEXP[i-1+s,j-1] - IMGEXP[i,j])>TC:
|
||||
c_2 = 0
|
||||
|
||||
if np.abs(IMGEXP[i+1,j-1+s] - IMGEXP[i,j])>TC:
|
||||
c_3 = 0
|
||||
|
||||
if np.abs(IMGEXP[i-1,j+1-s] - IMGEXP[i,j])>TC:
|
||||
c_4 = 0
|
||||
|
||||
|
||||
EN1 = c_1*np.abs(D1[i,j] - D1[i+1-s,j+1]) + c_2*np.abs(D1[i,j] - D1[i-1+s,j-1])
|
||||
EN2 = c_3*np.abs(D1[i,j] - D1[i+1,j-1+s]) + c_4*np.abs(D1[i,j] - D1[i-1,j+1-s])
|
||||
EN3 = c_1*np.abs(D2[i,j] - D2[i+1-s,j+1]) + c_2*np.abs(D2[i,j] - D2[i-1+s,j-1])
|
||||
EN4 = c_3*np.abs(D2[i,j] - D2[i+1,j-1+s]) + c_4*np.abs(D2[i,j] - D2[i-1,j+1-s])
|
||||
EN5 = np.abs(IMGEXP[i-2+2*s,j-2] + IMGEXP[i+2-2*s,j+2] - 2*IMGEXP[i,j])
|
||||
EN6 = np.abs(IMGEXP[i+2,j-2+2*s] + IMGEXP[i-2,j+2-2*s] - 2*IMGEXP[i,j])
|
||||
|
||||
EA1 = c_1*np.abs(D1[i,j] - D1[i+1-s,j+1] - 3*step) + c_2*np.abs(D1[i,j] - D1[i-1+s,j-1] - 3*step)
|
||||
EA2 = c_3*np.abs(D1[i,j] - D1[i+1,j-1+s] - 3*step) + c_4*np.abs(D1[i,j] - D1[i-1,j+1-s] - 3*step)
|
||||
EA3 = c_1*np.abs(D2[i,j] - D2[i+1-s,j+1] - 3*step) + c_2*np.abs(D2[i,j] - D2[i-1+s,j-1] - 3*step)
|
||||
EA4 = c_3*np.abs(D2[i,j] - D2[i+1,j-1+s] - 3*step) + c_4*np.abs(D2[i,j] - D2[i-1,j+1-s] - 3*step)
|
||||
EA5 = np.abs(IMGEXP[i-2+2*s,j-2] + IMGEXP[i+2-2*s,j+2] - 2*IMGEXP[i,j] - 2*step)
|
||||
EA6 = np.abs(IMGEXP[i+2,j-2+2*s] + IMGEXP[i-2,j+2-2*s] - 2*IMGEXP[i,j] - 2*step)
|
||||
|
||||
ES1 = c_1*np.abs(D1[i,j] - D1[i+1-s,j+1] + 3*step) + c_2*np.abs(D1[i,j] - D1[i-1+s,j-1] + 3*step)
|
||||
ES2 = c_3*np.abs(D1[i,j] - D1[i+1,j-1+s] + 3*step) + c_4*np.abs(D1[i,j] - D1[i-1,j+1-s] + 3*step)
|
||||
ES3 = c_1*np.abs(D2[i,j] - D2[i+1-s,j+1] + 3*step) + c_2*np.abs(D2[i,j] - D2[i-1+s,j-1] + 3*step)
|
||||
ES4 = c_3*np.abs(D2[i,j] - D2[i+1,j-1+s] + 3*step) + c_4*np.abs(D2[i,j] - D2[i-1,j+1-s] + 3*step)
|
||||
ES5 = np.abs(IMGEXP[i-2+2*s,j-2] + IMGEXP[i+2-2*s,j+2] - 2*IMGEXP[i,j] + 2*step)
|
||||
ES6 = np.abs(IMGEXP[i+2,j-2+2*s] + IMGEXP[i-2,j+2-2*s] - 2*IMGEXP[i,j] + 2*step)
|
||||
|
||||
EISO = (C1[i,j]*C1[i,j]*D2[i,j] - 2*C1[i,j]*C2[i,j]*D3[i,j] + C2[i,j]*C2[i,j]*D1[i,j])/(C1[i,j]*C1[i,j]+C2[i,j]*C2[i,j])
|
||||
|
||||
if np.abs(EISO) < 0.2:
|
||||
EISO = 0
|
||||
|
||||
if PF==1:
|
||||
EN = AL*(EN1 + EN2 + EN3 + EN4) + BT*(EN5 + EN6)
|
||||
EA = AL*(EA1 + EA2 + EA3 + EA4) + BT*(EA5 + EA6)
|
||||
ES = AL*(ES1 + ES2 + ES3 + ES4) + BT*(ES5 + ES6)
|
||||
|
||||
elif PF==2:
|
||||
EN = AL*(EN1 + EN2 + EN3 + EN4)
|
||||
EA = AL*(EA1 + EA2 + EA3 + EA4) - GM*np.sign(EISO)
|
||||
ES = AL*(ES1 + ES2 + ES3 + ES4) - GM*np.sign(EISO)
|
||||
|
||||
else:
|
||||
EN = AL*(EN1 + EN2 + EN3 + EN4) + BT*(EN5 + EN6)
|
||||
EA = AL*(EA1 + EA2 + EA3 + EA4) + BT*(EA5 + EA6) - GM*np.sign(EISO)
|
||||
ES = AL*(ES1 + ES2 + ES3 + ES4) + BT*(ES5 + ES6) + GM*np.sign(EISO)
|
||||
|
||||
if (EN>EA) and (ES>EA):
|
||||
IMGEXP[i,j] = IMGEXP[i,j] + step
|
||||
diff = diff + step
|
||||
|
||||
elif (EN>ES) and (EA>ES):
|
||||
IMGEXP[i,j] = IMGEXP[i,j] - step
|
||||
diff = diff + step
|
||||
|
||||
if (SC==1) and (diff<TS):
|
||||
break
|
||||
|
||||
#assign the expanded image to the current image
|
||||
IMG = IMGEXP
|
||||
|
||||
EI[:,:,CID] = np.round(IMG)
|
||||
|
||||
#back to 2D array if gray
|
||||
if CL ==1:
|
||||
EI = np.reshape(EI,(fm,fn))
|
||||
|
||||
return EI
|
||||
@@ -336,6 +336,7 @@ class ScriptRunner:
|
||||
self.alwayson_scripts = []
|
||||
self.auto_processing_scripts = []
|
||||
self.titles = []
|
||||
self.alwayson_titles = []
|
||||
self.infotext_fields = []
|
||||
self.paste_field_names = []
|
||||
self.script_load_ctr = 0
|
||||
@@ -376,6 +377,7 @@ class ScriptRunner:
|
||||
self.selectable_scripts.clear()
|
||||
self.alwayson_scripts.clear()
|
||||
self.titles.clear()
|
||||
self.alwayson_titles.clear()
|
||||
self.infotext_fields.clear()
|
||||
self.paste_field_names.clear()
|
||||
self.script_load_ctr = 0
|
||||
@@ -405,6 +407,7 @@ class ScriptRunner:
|
||||
def setup_ui(self, parent='unknown', accordion=True):
|
||||
import modules.api.models as api_models
|
||||
self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts]
|
||||
self.alwayson_titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.alwayson_scripts]
|
||||
|
||||
inputs = []
|
||||
inputs_alwayson = [True]
|
||||
@@ -501,7 +504,7 @@ class ScriptRunner:
|
||||
if title == 'None': # called when an initial value is set from ui-config.json to show script's UI components
|
||||
return
|
||||
if title not in self.titles:
|
||||
errors.log.error(f'Script not found: {title}')
|
||||
errors.log.error(f'Script: title="{title}" op=init not found')
|
||||
return
|
||||
script_index = self.titles.index(title)
|
||||
self.selectable_scripts[script_index].group.visible = True
|
||||
@@ -511,12 +514,18 @@ class ScriptRunner:
|
||||
|
||||
def onload_script_visibility(params):
|
||||
title = params.get('Script', None)
|
||||
if title:
|
||||
if title and title in self.titles:
|
||||
title_index = self.titles.index(title)
|
||||
visibility = title_index == self.script_load_ctr
|
||||
self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles)
|
||||
return gr.update(visible=visibility)
|
||||
elif title and title in self.alwayson_titles:
|
||||
title_index = self.alwayson_titles.index(title)
|
||||
visibility = title_index == self.script_load_ctr
|
||||
self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles)
|
||||
return gr.update(visible=visibility)
|
||||
else:
|
||||
errors.log.warning(f'Script: title="{title}" op=visibility not found')
|
||||
return gr.update(visible=False)
|
||||
|
||||
self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None'))))
|
||||
@@ -526,9 +535,11 @@ class ScriptRunner:
|
||||
def run(self, p, *args):
|
||||
s = ScriptSummary('run')
|
||||
script_index = args[0] if len(args) > 0 else 0
|
||||
if script_index == 0:
|
||||
if (script_index is None) or (script_index == 0):
|
||||
return None
|
||||
script = self.selectable_scripts[script_index-1]
|
||||
script = self.selectable_scripts[script_index - 1]
|
||||
if script is None:
|
||||
script = self.alwayson_scripts[script_index - 1]
|
||||
if script is None:
|
||||
return None
|
||||
if 'upscale' in script.title():
|
||||
@@ -549,9 +560,9 @@ class ScriptRunner:
|
||||
def after(self, p, processed, *args):
|
||||
s = ScriptSummary('after')
|
||||
script_index = args[0] if len(args) > 0 else 0
|
||||
if script_index == 0:
|
||||
if (script_index is None) or (script_index == 0):
|
||||
return processed
|
||||
script = self.selectable_scripts[script_index-1]
|
||||
script = self.selectable_scripts[script_index - 1]
|
||||
if script is None or not hasattr(script, 'after'):
|
||||
return processed
|
||||
parsed = []
|
||||
|
||||
@@ -103,7 +103,7 @@ def guess_by_name(fn, current_guess):
|
||||
new_guess = 'FLUX'
|
||||
elif 'flex.2' in fn.lower():
|
||||
new_guess = 'FLEX'
|
||||
elif 'anima' in fn.lower() and 'animat' not in fn.lower():
|
||||
elif fn.lower().endswith('anima') or 'anima-' in fn.lower():
|
||||
new_guess = 'Anima'
|
||||
elif 'cosmos-predict2' in fn.lower():
|
||||
new_guess = 'Cosmos'
|
||||
|
||||
+2
-1
@@ -535,6 +535,7 @@ options_templates.update(options_section(('saving-images', "Image Options"), {
|
||||
"image_sep_browser": OptionInfo("<h2>Image Gallery</h2>", "", gr.HTML),
|
||||
"browser_cache": OptionInfo(True, "Use image gallery cache"),
|
||||
"browser_folders": OptionInfo("", "Additional image browser folders"),
|
||||
"browser_gallery_autoupdate": OptionInfo(False, "Automatically update when switching to the gallery"),
|
||||
"browser_fixed_width": OptionInfo(False, "Use fixed width thumbnails"),
|
||||
"viewer_show_metadata": OptionInfo(True, "Show metadata in full screen image browser"),
|
||||
|
||||
@@ -575,7 +576,7 @@ options_templates.update(options_section(('saving-paths', "Image Paths"), {
|
||||
"outdir_init_images": OptionInfo("outputs/inputs", "Folder for init images", component_args=hide_dirs, folder=True),
|
||||
|
||||
"outdir_sep_grids": OptionInfo("<h2>Grids</h2>", "", gr.HTML),
|
||||
"outdir_grids": OptionInfo("", "Base grids folde", component_args=hide_dirs, folder=True),
|
||||
"outdir_grids": OptionInfo("", "Base grids folder", component_args=hide_dirs, folder=True),
|
||||
"outdir_txt2img_grids": OptionInfo("outputs/grids", 'Folder for txt2img grids', component_args=hide_dirs, folder=True),
|
||||
"outdir_img2img_grids": OptionInfo("outputs/grids", 'Folder for img2img grids', component_args=hide_dirs, folder=True),
|
||||
"outdir_control_grids": OptionInfo("outputs/grids", 'Folder for control grids', component_args=hide_dirs, folder=True),
|
||||
|
||||
+13
-15
@@ -65,7 +65,7 @@ def select_from_weighted_list(inner: str) -> str:
|
||||
w = float(wstr.strip())
|
||||
except Exception:
|
||||
w = 0.0
|
||||
w = max(0.0, min(1.0, w))
|
||||
w = max(0.0, w)
|
||||
weighted[name] = weighted.get(name, 0.0) + w
|
||||
else:
|
||||
unweighted.append(p)
|
||||
@@ -78,34 +78,32 @@ def select_from_weighted_list(inner: str) -> str:
|
||||
if not keys:
|
||||
return ''
|
||||
if W == 0.0:
|
||||
return random.choice(keys)
|
||||
return ''
|
||||
if abs(W - 1.0) > 1e-12:
|
||||
for k in weighted:
|
||||
weighted[k] = weighted[k] / W
|
||||
weighted = {k: v / W for k, v in weighted.items()}
|
||||
else: # mix of weighted and unweighted
|
||||
if W >= 1.0: # weighted probabilities consume whole mass -> normalize them, unweighted get 0
|
||||
for k in weighted:
|
||||
weighted[k] = weighted[k] / W
|
||||
if W > 1.0: # weighted probabilities consume whole mass -> normalize them, unweighted get 0
|
||||
for name in unweighted:
|
||||
weighted[name] = weighted.get(name, 0.0) + 1.0
|
||||
total_before = sum(weighted.values())
|
||||
if total_before > 0.0:
|
||||
weighted = {k: v / total_before for k, v in weighted.items()}
|
||||
else:
|
||||
remaining = 1.0 - W
|
||||
per = remaining / U
|
||||
per = remaining / U if U > 0 else 0.0
|
||||
for name in unweighted:
|
||||
weighted[name] = weighted.get(name, 0.0) + per
|
||||
|
||||
items = list(weighted.items())
|
||||
if not items:
|
||||
return ''
|
||||
|
||||
total = sum(v for _, v in items)
|
||||
if total <= 0.0:
|
||||
return items[0][0]
|
||||
|
||||
r = random.random() * total
|
||||
cum = 0.0
|
||||
for name, prob in items:
|
||||
cum += prob
|
||||
if r <= cum:
|
||||
return name
|
||||
return items[-1][0]
|
||||
names, weights = zip(*items)
|
||||
return random.choices(names, weights=weights, k=1)[0]
|
||||
|
||||
|
||||
def apply_curly_braces_to_prompt(prompt, seed=-1):
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import time
|
||||
from PIL import Image
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
from modules.shared import log
|
||||
|
||||
|
||||
class UpscalerDCC(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "DCC Interpolation"
|
||||
self.vae = None
|
||||
self.scalers = [
|
||||
UpscalerData("DCC Interpolation", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
import math
|
||||
import numpy as np
|
||||
from modules.postprocess.dcc import DCC
|
||||
t0 = time.time()
|
||||
normalized = np.array(img).astype(np.float32) / 255.0
|
||||
scale = math.ceil(self.scale)
|
||||
upscaled = DCC(normalized, scale)
|
||||
upscaled = (upscaled - upscaled.min()) / (upscaled.max() - upscaled.min())
|
||||
upscaled = (255.0 * upscaled).astype(np.uint8)
|
||||
upscaled = Image.fromarray(upscaled)
|
||||
t1 = time.time()
|
||||
log.debug(f"Upscale: name=DCC input={img.size} output={upscaled.size} time={t1 - t0:.2f}")
|
||||
return upscaled
|
||||
|
||||
|
||||
class UpscalerVIPS(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "VIPS"
|
||||
self.scalers = [
|
||||
UpscalerData("VIPS Lanczos 2", None, self),
|
||||
UpscalerData("VIPS Lanczos 3", None, self),
|
||||
UpscalerData("VIPS Mitchell", None, self),
|
||||
UpscalerData("VIPS MagicKernelSharp 2013", None, self),
|
||||
UpscalerData("VIPS MagicKernelSharp 2021", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
if selected_model is None:
|
||||
return img
|
||||
from installer import install
|
||||
install('pyvips')
|
||||
try:
|
||||
import pyvips
|
||||
except Exception as e:
|
||||
log.error(f"Upscaler: vips {e}")
|
||||
return img
|
||||
t0 = time.time()
|
||||
vips_image = pyvips.Image.new_from_array(img)
|
||||
try:
|
||||
if selected_model is None:
|
||||
return img
|
||||
elif selected_model == "VIPS Lanczos 2":
|
||||
vips_image = vips_image.resize(2, kernel='lanczos2')
|
||||
elif selected_model == "VIPS Lanczos 3":
|
||||
vips_image = vips_image.resize(2, kernel='lanczos3')
|
||||
elif selected_model == "VIPS Mitchell":
|
||||
vips_image = vips_image.resize(2, kernel='mitchell')
|
||||
elif selected_model == "VIPS MagicKernelSharp 2013":
|
||||
vips_image = vips_image.resize(2, kernel='mks2013')
|
||||
elif selected_model == "VIPS MagicKernelSharp 2021":
|
||||
vips_image = vips_image.resize(2, kernel='mks2021')
|
||||
else:
|
||||
return img
|
||||
except Exception as e:
|
||||
log.error(f"Upscaler: vips {e}")
|
||||
return img
|
||||
upscaled = Image.fromarray(vips_image.numpy())
|
||||
t1 = time.time()
|
||||
log.debug(f"Upscale: name=VIPS input={img.size} output={upscaled.size} time={t1 - t0:.2f}")
|
||||
return upscaled
|
||||
|
||||
class UpscalerHQX(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "HQX"
|
||||
self.scalers = [
|
||||
UpscalerData("HQX Interpolation", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
import numpy as np
|
||||
from modules.postprocess.hqx import hqx
|
||||
t0 = time.time()
|
||||
np_img = np.array(img).astype(np.uint32)
|
||||
upscaled = hqx(np_img, 2)
|
||||
upscaled = (upscaled).astype(np.uint8)
|
||||
upscaled = Image.fromarray(upscaled)
|
||||
t1 = time.time()
|
||||
log.debug(f"Upscale: name=HQX input={img.size} output={upscaled.size} time={t1 - t0:.2f}")
|
||||
return upscaled
|
||||
|
||||
class UpscalerICBI(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "ICB"
|
||||
self.scalers = [
|
||||
UpscalerData("ICB Interpolation", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
import numpy as np
|
||||
from modules.postprocess.icbi import icbi
|
||||
t0 = time.time()
|
||||
np_img = np.array(img)
|
||||
upscaled = icbi(np_img)
|
||||
upscaled = Image.fromarray(upscaled)
|
||||
t1 = time.time()
|
||||
log.debug(f"Upscale: name=ICB input={img.size} output={upscaled.size} time={t1 - t0:.2f}")
|
||||
return upscaled
|
||||
@@ -93,160 +93,3 @@ class UpscalerLatent(Upscaler):
|
||||
else:
|
||||
raise log.error(f"Upscale: type=latent model={selected_model} unknown")
|
||||
return F.interpolate(img, size=(h, w), mode=mode, antialias=antialias)
|
||||
|
||||
|
||||
class UpscalerAsymmetricVAE(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "Asymmetric VAE"
|
||||
self.vae = None
|
||||
self.selected = None
|
||||
self.scalers = [
|
||||
UpscalerData("Asymmetric VAE v1", None, self),
|
||||
UpscalerData("Asymmetric VAE v2", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
if selected_model is None:
|
||||
return img
|
||||
import torchvision.transforms.functional as F
|
||||
import diffusers
|
||||
from modules import shared, devices
|
||||
if self.vae is None or (selected_model != self.selected):
|
||||
if 'v1' in selected_model:
|
||||
repo_id = 'Heasterian/AsymmetricAutoencoderKLUpscaler'
|
||||
else:
|
||||
repo_id = 'Heasterian/AsymmetricAutoencoderKLUpscaler_v2'
|
||||
self.vae = diffusers.AsymmetricAutoencoderKL.from_pretrained(repo_id, cache_dir=shared.opts.hfcache_dir)
|
||||
self.vae.requires_grad_(False)
|
||||
self.vae = self.vae.to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae.eval()
|
||||
self.selected = selected_model
|
||||
shared.log.debug(f'Upscaler load: selected="{self.selected}" vae="{repo_id}"')
|
||||
img = img.resize((8 * (img.width // 8), 8 * (img.height // 8)), resample=Image.Resampling.LANCZOS).convert('RGB')
|
||||
tensor = (F.pil_to_tensor(img).unsqueeze(0) / 255.0).to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae = self.vae.to(device=devices.device)
|
||||
tensor = self.vae(tensor).sample
|
||||
upscaled = F.to_pil_image(tensor.squeeze().clamp(0.0, 1.0).float().cpu())
|
||||
self.vae = self.vae.to(device=devices.cpu)
|
||||
return upscaled
|
||||
|
||||
|
||||
class UpscalerWanUpscale(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "WAN Upscale"
|
||||
self.vae_encode = None
|
||||
self.vae_decode = None
|
||||
self.selected = None
|
||||
self.scalers = [
|
||||
UpscalerData("WAN Asymmetric Upscale", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
if selected_model is None:
|
||||
return img
|
||||
import torchvision.transforms.functional as F
|
||||
import torch.nn.functional as FN
|
||||
import diffusers
|
||||
from modules import shared, devices
|
||||
if (self.vae_encode is None) or (self.vae_decode is None) or (selected_model != self.selected):
|
||||
repo_encode = 'Qwen/Qwen-Image-Edit-2509'
|
||||
subfolder_encode = 'vae'
|
||||
self.vae_encode = diffusers.AutoencoderKLWan.from_pretrained(repo_encode, subfolder=subfolder_encode, cache_dir=shared.opts.hfcache_dir)
|
||||
self.vae_encode.requires_grad_(False)
|
||||
self.vae_encode = self.vae_encode.to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae_encode.eval()
|
||||
repo_decode = 'spacepxl/Wan2.1-VAE-upscale2x'
|
||||
subfolder_decode = "diffusers/Wan2.1_VAE_upscale2x_imageonly_real_v1"
|
||||
self.vae_decode = diffusers.AutoencoderKLWan.from_pretrained(repo_decode, subfolder=subfolder_decode, cache_dir=shared.opts.hfcache_dir)
|
||||
self.vae_decode.requires_grad_(False)
|
||||
self.vae_decode = self.vae_decode.to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae_decode.eval()
|
||||
self.selected = selected_model
|
||||
shared.log.debug(f'Upscaler load: selected="{self.selected}" encode="{repo_encode}" decode="{repo_decode}"')
|
||||
|
||||
self.vae_encode = self.vae_encode.to(device=devices.device)
|
||||
tensor = (F.pil_to_tensor(img).unsqueeze(0).unsqueeze(2) / 255.0).to(device=devices.device, dtype=devices.dtype)
|
||||
tensor = self.vae_encode.encode(tensor).latent_dist.mode()
|
||||
self.vae_encode.to(device=devices.cpu)
|
||||
|
||||
self.vae_decode = self.vae_decode.to(device=devices.device)
|
||||
tensor = self.vae_decode.decode(tensor).sample
|
||||
tensor = FN.pixel_shuffle(tensor.movedim(2, 1), upscale_factor=2).movedim(1, 2) # pixel shuffle needs [..., C, H, W] format
|
||||
self.vae_decode.to(device=devices.cpu)
|
||||
|
||||
upscaled = F.to_pil_image(tensor.squeeze().clamp(0.0, 1.0).float().cpu())
|
||||
return upscaled
|
||||
|
||||
|
||||
class UpscalerDCC(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "DCC Interpolation"
|
||||
self.vae = None
|
||||
self.scalers = [
|
||||
UpscalerData("DCC Interpolation", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
import math
|
||||
import numpy as np
|
||||
from modules.postprocess.dcc import DCC
|
||||
normalized = np.array(img).astype(np.float32) / 255.0
|
||||
scale = math.ceil(self.scale)
|
||||
upscaled = DCC(normalized, scale)
|
||||
upscaled = (upscaled - upscaled.min()) / (upscaled.max() - upscaled.min())
|
||||
upscaled = (255.0 * upscaled).astype(np.uint8)
|
||||
upscaled = Image.fromarray(upscaled)
|
||||
return upscaled
|
||||
|
||||
|
||||
class UpscalerVIPS(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "VIPS"
|
||||
self.scalers = [
|
||||
UpscalerData("VIPS Lanczos 2", None, self),
|
||||
UpscalerData("VIPS Lanczos 3", None, self),
|
||||
UpscalerData("VIPS Mitchell", None, self),
|
||||
UpscalerData("VIPS MagicKernelSharp 2013", None, self),
|
||||
UpscalerData("VIPS MagicKernelSharp 2021", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
if selected_model is None:
|
||||
return img
|
||||
from installer import install
|
||||
install('pyvips')
|
||||
try:
|
||||
import pyvips
|
||||
except Exception as e:
|
||||
log.error(f"Upscaler: vips {e}")
|
||||
return img
|
||||
vips_image = pyvips.Image.new_from_array(img)
|
||||
# import numpy as np
|
||||
# np_image = np.array(img)
|
||||
# h, w, c = np_image.shape
|
||||
# np_linear = np_image.reshape(w * h * c)
|
||||
# vips_image = pyvips.Image.new_from_memory(np_linear.data, w, h, c, 'uchar')
|
||||
try:
|
||||
if selected_model is None:
|
||||
return img
|
||||
elif selected_model == "VIPS Lanczos 2":
|
||||
vips_image = vips_image.resize(2, kernel='lanczos2')
|
||||
elif selected_model == "VIPS Lanczos 3":
|
||||
vips_image = vips_image.resize(2, kernel='lanczos3')
|
||||
elif selected_model == "VIPS Mitchell":
|
||||
vips_image = vips_image.resize(2, kernel='mitchell')
|
||||
elif selected_model == "VIPS MagicKernelSharp 2013":
|
||||
vips_image = vips_image.resize(2, kernel='mks2013')
|
||||
elif selected_model == "VIPS MagicKernelSharp 2021":
|
||||
vips_image = vips_image.resize(2, kernel='mks2021')
|
||||
else:
|
||||
return img
|
||||
except Exception as e:
|
||||
log.error(f"Upscaler: vips {e}")
|
||||
return img
|
||||
upscaled = Image.fromarray(vips_image.numpy())
|
||||
return upscaled
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
import time
|
||||
from PIL import Image
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
from modules import devices, paths
|
||||
from modules.shared import log
|
||||
|
||||
|
||||
MODELS = {
|
||||
"Spandrel 4x RealPLKSR NomosWebPhoto": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/4xNomosWebPhoto_RealPLKSR.safetensors",
|
||||
"Spandrel 2x RealPLKSR AnimeSharpV2": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/2x-AnimeSharpV2_RPLKSR_Sharp.pth",
|
||||
}
|
||||
|
||||
class UpscalerSpandrel(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "Spandrel"
|
||||
self.model_path = os.path.join(paths.models_path, 'Spandrel')
|
||||
self.user_path = os.path.join(paths.models_path, 'Spandrel')
|
||||
self.selected = None
|
||||
self.model = None
|
||||
self.scalers = []
|
||||
for model_name, model_path in MODELS.items():
|
||||
scaler = UpscalerData(name=model_name, path=model_path, upscaler=self)
|
||||
self.scalers.append(scaler)
|
||||
|
||||
def process(self, img: Image.Image) -> Image.Image:
|
||||
import torchvision.transforms.functional as TF
|
||||
tensor = TF.to_tensor(img).unsqueeze(0).to(devices.device)
|
||||
img = img.convert('RGB')
|
||||
t0 = time.time()
|
||||
with devices.inference_context():
|
||||
tensor = self.model(tensor)
|
||||
tensor = tensor.clamp(0, 1).squeeze(0).cpu()
|
||||
t1 = time.time()
|
||||
upscaled = TF.to_pil_image(tensor)
|
||||
log.debug(f'Upscale: name="{self.selected}" input={img.size} output={upscaled.size} time={t1 - t0:.2f}')
|
||||
return upscaled
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
from installer import install
|
||||
if selected_model is None:
|
||||
return img
|
||||
install('spandrel')
|
||||
try:
|
||||
import spandrel
|
||||
if (self.model is None) or (self.selected != selected_model):
|
||||
self.selected = selected_model
|
||||
model = self.find_model(selected_model)
|
||||
self.model = spandrel.ModelLoader().load_from_file(model.local_data_path)
|
||||
self.model.to(devices.device).eval()
|
||||
return self.process(img)
|
||||
except Exception as e:
|
||||
log.error(f'Spandrel: {e}')
|
||||
return img
|
||||
@@ -0,0 +1,94 @@
|
||||
import time
|
||||
from PIL import Image
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
|
||||
|
||||
class UpscalerAsymmetricVAE(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "Asymmetric VAE"
|
||||
self.vae = None
|
||||
self.selected = None
|
||||
self.scalers = [
|
||||
UpscalerData("Asymmetric VAE v1", None, self),
|
||||
UpscalerData("Asymmetric VAE v2", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
if selected_model is None:
|
||||
return img
|
||||
import torchvision.transforms.functional as F
|
||||
import diffusers
|
||||
from modules import shared, devices
|
||||
if self.vae is None or (selected_model != self.selected):
|
||||
if 'v1' in selected_model:
|
||||
repo_id = 'Heasterian/AsymmetricAutoencoderKLUpscaler'
|
||||
else:
|
||||
repo_id = 'Heasterian/AsymmetricAutoencoderKLUpscaler_v2'
|
||||
self.vae = diffusers.AsymmetricAutoencoderKL.from_pretrained(repo_id, cache_dir=shared.opts.hfcache_dir)
|
||||
self.vae.requires_grad_(False)
|
||||
self.vae = self.vae.to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae.eval()
|
||||
self.selected = selected_model
|
||||
shared.log.debug(f'Upscaler load: selected="{self.selected}" vae="{repo_id}"')
|
||||
t0 = time.time()
|
||||
img = img.resize((8 * (img.width // 8), 8 * (img.height // 8)), resample=Image.Resampling.LANCZOS).convert('RGB')
|
||||
tensor = (F.pil_to_tensor(img).unsqueeze(0) / 255.0).to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae = self.vae.to(device=devices.device)
|
||||
tensor = self.vae(tensor).sample
|
||||
upscaled = F.to_pil_image(tensor.squeeze().clamp(0.0, 1.0).float().cpu())
|
||||
self.vae = self.vae.to(device=devices.cpu)
|
||||
t1 = time.time()
|
||||
shared.log.debug(f'Upscale: name="{self.selected}" input={img.size} output={upscaled.size} time={t1 - t0:.2f}')
|
||||
return upscaled
|
||||
|
||||
|
||||
class UpscalerWanUpscale(Upscaler):
|
||||
def __init__(self, dirname=None): # pylint: disable=unused-argument
|
||||
super().__init__(False)
|
||||
self.name = "WAN Upscale"
|
||||
self.vae_encode = None
|
||||
self.vae_decode = None
|
||||
self.selected = None
|
||||
self.scalers = [
|
||||
UpscalerData("WAN Asymmetric Upscale", None, self),
|
||||
]
|
||||
|
||||
def do_upscale(self, img: Image, selected_model=None):
|
||||
if selected_model is None:
|
||||
return img
|
||||
import torchvision.transforms.functional as F
|
||||
import torch.nn.functional as FN
|
||||
import diffusers
|
||||
from modules import shared, devices
|
||||
if (self.vae_encode is None) or (self.vae_decode is None) or (selected_model != self.selected):
|
||||
repo_encode = 'Qwen/Qwen-Image-Edit-2509'
|
||||
subfolder_encode = 'vae'
|
||||
self.vae_encode = diffusers.AutoencoderKLWan.from_pretrained(repo_encode, subfolder=subfolder_encode, cache_dir=shared.opts.hfcache_dir)
|
||||
self.vae_encode.requires_grad_(False)
|
||||
self.vae_encode = self.vae_encode.to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae_encode.eval()
|
||||
repo_decode = 'spacepxl/Wan2.1-VAE-upscale2x'
|
||||
subfolder_decode = "diffusers/Wan2.1_VAE_upscale2x_imageonly_real_v1"
|
||||
self.vae_decode = diffusers.AutoencoderKLWan.from_pretrained(repo_decode, subfolder=subfolder_decode, cache_dir=shared.opts.hfcache_dir)
|
||||
self.vae_decode.requires_grad_(False)
|
||||
self.vae_decode = self.vae_decode.to(device=devices.device, dtype=devices.dtype)
|
||||
self.vae_decode.eval()
|
||||
self.selected = selected_model
|
||||
shared.log.debug(f'Upscaler load: selected="{self.selected}" encode="{repo_encode}" decode="{repo_decode}"')
|
||||
|
||||
t0 = time.time()
|
||||
self.vae_encode = self.vae_encode.to(device=devices.device)
|
||||
tensor = (F.pil_to_tensor(img).unsqueeze(0).unsqueeze(2) / 255.0).to(device=devices.device, dtype=devices.dtype)
|
||||
tensor = self.vae_encode.encode(tensor).latent_dist.mode()
|
||||
self.vae_encode.to(device=devices.cpu)
|
||||
|
||||
self.vae_decode = self.vae_decode.to(device=devices.device)
|
||||
tensor = self.vae_decode.decode(tensor).sample
|
||||
tensor = FN.pixel_shuffle(tensor.movedim(2, 1), upscale_factor=2).movedim(1, 2) # pixel shuffle needs [..., C, H, W] format
|
||||
self.vae_decode.to(device=devices.cpu)
|
||||
|
||||
upscaled = F.to_pil_image(tensor.squeeze().clamp(0.0, 1.0).float().cpu())
|
||||
t1 = time.time()
|
||||
shared.log.debug(f'Upscale: name="{self.selected}" input={img.size} output={upscaled.size} time={t1 - t0:.2f}')
|
||||
return upscaled
|
||||
+15
-15
@@ -25,7 +25,7 @@ class Script(scripts_manager.Script):
|
||||
current_axis_options = []
|
||||
|
||||
def title(self):
|
||||
return "XYZ Grid"
|
||||
return "XYZ Grid Script"
|
||||
|
||||
def ui(self, is_img2img):
|
||||
self.current_axis_options = [x for x in axis_options if type(x) == AxisOption or x.is_img2img == is_img2img]
|
||||
@@ -135,14 +135,14 @@ class Script(scripts_manager.Script):
|
||||
return gr.update(value = valslist)
|
||||
|
||||
self.infotext_fields = (
|
||||
(x_type, "X Type"),
|
||||
(x_values, "X Values"),
|
||||
(x_type, "X Script Type"),
|
||||
(x_values, "X Script Values"),
|
||||
(x_values_dropdown, lambda params:get_dropdown_update_from_params("X",params)),
|
||||
(y_type, "Y Type"),
|
||||
(y_values, "Y Values"),
|
||||
(y_type, "Y Script Type"),
|
||||
(y_values, "Y Script Values"),
|
||||
(y_values_dropdown, lambda params:get_dropdown_update_from_params("Y",params)),
|
||||
(z_type, "Z Type"),
|
||||
(z_values, "Z Values"),
|
||||
(z_type, "Z Script Type"),
|
||||
(z_values, "Z Script Values"),
|
||||
(z_values_dropdown, lambda params:get_dropdown_update_from_params("Z",params)),
|
||||
)
|
||||
|
||||
@@ -334,21 +334,21 @@ class Script(scripts_manager.Script):
|
||||
pc.extra_generation_params = copy(pc.extra_generation_params)
|
||||
pc.extra_generation_params['Script'] = self.title()
|
||||
if x_opt.label != 'Nothing':
|
||||
pc.extra_generation_params["X Type"] = x_opt.label
|
||||
pc.extra_generation_params["X Values"] = x_values
|
||||
pc.extra_generation_params["X Script Type"] = x_opt.label
|
||||
pc.extra_generation_params["X Script Values"] = x_values
|
||||
if x_opt.label in ["[Param] Seed", "[Param] Variation seed"] and not no_fixed_seeds:
|
||||
pc.extra_generation_params["Fixed X Values"] = ", ".join([str(x) for x in xs])
|
||||
pc.extra_generation_params["Fixed X Script Values"] = ", ".join([str(x) for x in xs])
|
||||
if y_opt.label != 'Nothing':
|
||||
pc.extra_generation_params["Y Type"] = y_opt.label
|
||||
pc.extra_generation_params["Y Values"] = y_values
|
||||
pc.extra_generation_params["Y Script Type"] = y_opt.label
|
||||
pc.extra_generation_params["Y Script Values"] = y_values
|
||||
if y_opt.label in ["[Param] Seed", "[Param] Variation seed"] and not no_fixed_seeds:
|
||||
pc.extra_generation_params["Fixed Y Values"] = ", ".join([str(y) for y in ys])
|
||||
pc.extra_generation_params["Fixed Y Script Values"] = ", ".join([str(y) for y in ys])
|
||||
grid_infotext[subgrid_index] = processing.create_infotext(pc, pc.all_prompts, pc.all_seeds, pc.all_subseeds, grid=f'{len(xs)}x{len(ys)}')
|
||||
if grid_infotext[0] is None and ix == 0 and iy == 0 and iz == 0: # Sets main grid infotext
|
||||
pc.extra_generation_params = copy(pc.extra_generation_params)
|
||||
if z_opt.label != 'Nothing':
|
||||
pc.extra_generation_params["Z Type"] = z_opt.label
|
||||
pc.extra_generation_params["Z Values"] = z_values
|
||||
pc.extra_generation_params["Z Script Type"] = z_opt.label
|
||||
pc.extra_generation_params["Z Script Values"] = z_values
|
||||
if z_opt.label in ["[Param] Seed", "[Param] Variation seed"] and not no_fixed_seeds:
|
||||
pc.extra_generation_params["Fixed Z Values"] = ", ".join([str(z) for z in zs])
|
||||
grid_text = f'{len(zs)}x{len(xs)}x{len(ys)}' if len(zs) > 0 else f'{len(xs)}x{len(ys)}'
|
||||
|
||||
@@ -141,6 +141,7 @@ class Script(scripts_manager.Script):
|
||||
return gr.update(value = valslist)
|
||||
|
||||
self.infotext_fields = (
|
||||
(enabled, "XYZ Grid Enabled"),
|
||||
(x_type, "X Type"),
|
||||
(x_values, "X Values"),
|
||||
(x_values_dropdown, lambda params:get_dropdown_update_from_params("X",params)),
|
||||
@@ -357,6 +358,7 @@ class Script(scripts_manager.Script):
|
||||
if ix == 0 and iy == 0: # create subgrid info text
|
||||
pc.extra_generation_params = copy(pc.extra_generation_params)
|
||||
pc.extra_generation_params['Script'] = self.title()
|
||||
pc.extra_generation_params['XYZ Grid Enabled'] = enabled
|
||||
if x_opt.label != 'Nothing':
|
||||
pc.extra_generation_params["X Type"] = x_opt.label
|
||||
pc.extra_generation_params["X Values"] = x_values
|
||||
|
||||
@@ -37,6 +37,9 @@ import modules.txt2img
|
||||
import modules.img2img
|
||||
import modules.upscaler
|
||||
import modules.upscaler_simple
|
||||
import modules.upscaler_vae
|
||||
import modules.upscaler_algo
|
||||
import modules.upscaler_spandrel
|
||||
import modules.extra_networks
|
||||
import modules.ui_extra_networks
|
||||
import modules.textual_inversion
|
||||
|
||||
Reference in New Issue
Block a user