gallery improve sort and separators

This commit is contained in:
Vladimir Mandic
2024-03-24 11:11:23 -04:00
parent 6fa0315682
commit 0862b80377
10 changed files with 70 additions and 108 deletions
+5 -5
View File
@@ -6,17 +6,15 @@
- Quick apply style
- Add refine workflow in img2img
- Control API/CLI
- Model load from dropdown select variant
- VAE preview
- SC LoRA
## Update for 2024-03-23
- **Features**:
- **Gallery**:
implemented as infinite-scroll with client-side-caching and lazy-loading while being fully async and non-blocking
search or sort by path, name, size, width, height, mtime or any image metadata item, also with extended syntax like *width > 1000*
*settings*: optional additional user-defined folders, thumbnails in fixed or variable aspect-ratio
implemented as infinite-scroll with client-side-caching and lazy-loading while being fully async and non-blocking
search or sort by path, name, size, width, height, mtime or any image metadata item, also with extended syntax like *width > 1000*
*settings*: optional additional user-defined folders, thumbnails in fixed or variable aspect-ratio
- **Changes**:
- Removed built-in extensions: *ControlNet* and *Image-Browser*
as both *image-browser* and *controlnet* have native equivalents
@@ -24,11 +22,13 @@
- **Improvements**:
- Styles apply wildcards to params
- Make metadata in full screen viewer optional
- Add VAE civitai scan metadata/preview
- **Fixes**:
- Prompt params parser
- Fix image save without metadata
- fix ROCm compatibility, thanks @Disty0
- Fix API generate save metadata
- Enumerate diffusers model with multiple variants
## Update for 2024-03-19
+24 -10
View File
@@ -68,6 +68,27 @@ async function createThumb(img) {
return dataURL;
}
async function addSeparators() {
document.querySelectorAll('.gallery-separator').forEach((node) => el.files.removeChild(node));
const all = Array.from(el.files.children);
let lastDir;
for (const f of all) {
let dir = f.name.match(/(.*)[\/\\]/);
if (!dir) dir = '';
else dir = dir[1];
if (dir !== lastDir) {
lastDir = dir;
if (dir.length > 0) {
const sep = document.createElement('div');
sep.className = 'gallery-separator';
sep.innerText = dir;
sep.title = dir;
el.files.insertBefore(sep, f);
}
}
}
}
async function delayFetchThumb(fn) {
while (outstanding > 16) await new Promise((resolve) => setTimeout(resolve, 50)); // eslint-disable-line no-promise-executor-return
outstanding++;
@@ -102,6 +123,7 @@ class GalleryFile extends HTMLElement {
}
async connectedCallback() {
if (this.shadow.children.length > 0) return;
const ext = this.name.split('.').pop().toLowerCase();
if (!['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(ext)) return;
this.hash = await getHash(`${this.folder}/${this.name}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define
@@ -243,7 +265,6 @@ async function gallerySearch(evt) {
async function gallerySort(btn) {
const t0 = performance.now();
document.querySelectorAll('.gallery-separator').forEach((node) => el.files.removeChild(node)); // cannot sort separators
const arr = Array.from(el.files.children);
const fragment = document.createDocumentFragment();
el.files.innerHTML = '';
@@ -292,6 +313,7 @@ async function gallerySort(btn) {
break;
}
el.files.appendChild(fragment);
addSeparators();
const t1 = performance.now();
el.status.innerText = `Sort | ${arr.length.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`;
}
@@ -315,21 +337,13 @@ async function fetchFiles(evt) { // fetch file-by-file list over websockets
ws.close();
} else {
const json = JSON.parse(event.data);
const dir = json.file.match(/(.*)[\/\\]/) || '';
if (dir?.[1] !== lastDir) { // create separator
lastDir = dir[1];
const sep = document.createElement('div');
sep.className = 'gallery-separator';
sep.innerText = lastDir;
sep.title = lastDir;
el.files.appendChild(sep);
}
const file = new GalleryFile(json);
fragment.appendChild(file);
if (numFiles % 100 === 0) {
el.files.appendChild(fragment);
fragment = document.createDocumentFragment();
}
addSeparators();
el.status.innerText = `Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`;
}
};
+2 -9
View File
@@ -436,15 +436,8 @@ class ControlNetXSModel(ModelMixin, ConfigMixin):
norm_num_groups = unet.config.norm_num_groups
else:
norm_num_groups = min(block_out_channels)
if group_norms_match_channel_sizes(norm_num_groups, block_out_channels):
print(
f"`norm_num_groups` was set to `min(block_out_channels)` (={norm_num_groups}) so it divides all block_out_channels` ({block_out_channels}). Set it explicitly to remove this information."
)
else:
raise ValueError(
f"`block_out_channels` ({block_out_channels}) don't match the base models `norm_num_groups` ({unet.config.norm_num_groups}). Setting `norm_num_groups` to `min(block_out_channels)` ({norm_num_groups}) didn't fix this. Pass `norm_num_groups` explicitly so it divides all block_out_channels."
)
if not group_norms_match_channel_sizes(norm_num_groups, block_out_channels):
raise ValueError(f'ControlNetXSModel mismatch: block_out_channels={block_out_channels} norm_num_groups={unet.config.norm_num_groups}')
def get_time_emb_input_dim(unet: UNet2DConditionModel):
return unet.time_embedding.linear_1.in_features
+5 -4
View File
@@ -1,6 +1,7 @@
import math
import torch
from modules.postprocess.realesrgan_model_arch import RealESRGANer
from modules.shared import log
# DML Solution: Some of contents of output tensor turn to 0 after Extended Slices. Move it to cpu.
@@ -36,16 +37,15 @@ def tile_process(self):
# input tile dimensions
input_tile_width = input_end_x - input_start_x
input_tile_height = input_end_y - input_start_y
tile_idx = y * tiles_x + x + 1
_tile_idx = y * tiles_x + x + 1
input_tile = self.img[0:self.img.shape[0], 0:self.img.shape[1], input_start_y_pad:input_end_y_pad, input_start_x_pad:input_end_x_pad]
# upscale tile
try:
with torch.no_grad():
output_tile = self.model(input_tile)
except RuntimeError as error:
print('Error', error)
print(f'\tTile {tile_idx}/{tiles_x * tiles_y}')
except Exception as e:
log.error(f'Upscale error: type=R-ESRGAN {e}')
# output tile area on total image
output_start_x = input_start_x * self.scale
@@ -63,4 +63,5 @@ def tile_process(self):
# put tile into output image
self.output[0:self.output.shape[0], 0:self.output.shape[1], output_start_y:output_end_y, output_start_x:output_end_x] = output_tile.cpu()[0:output_tile.shape[0], 0:output_tile.shape[1], output_start_y_tile:output_end_y_tile, output_start_x_tile:output_end_x_tile]
self.output = self.output.to(output_tile.device)
RealESRGANer.tile_process = tile_process
-15
View File
@@ -229,16 +229,6 @@ class Hypernetwork:
# Dropout structure should have same length as layer structure, Every digits should be in [0,1), and last digit must be 0.
if self.dropout_structure is None:
self.dropout_structure = parse_dropout_structure(self.layer_structure, self.use_dropout, self.last_layer_dropout)
if shared.opts.print_hypernet_extra:
if self.optional_info is not None:
print(f" INFO:\n {self.optional_info}\n")
print(f" Layer structure: {self.layer_structure}")
print(f" Activation function: {self.activation_func}")
print(f" Weight initialization: {self.weight_init}")
print(f" Layer norm: {self.add_layer_norm}")
print(f" Dropout usage: {self.use_dropout}" )
print(f" Activate last layer: {self.activate_output}")
print(f" Dropout structure: {self.dropout_structure}")
optimizer_saved_dict = torch.load(self.filename + '.optim', map_location='cpu') if os.path.exists(self.filename + '.optim') else {}
if self.shorthash() == optimizer_saved_dict.get('hash', None):
self.optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None)
@@ -246,13 +236,8 @@ class Hypernetwork:
self.optimizer_state_dict = None
if self.optimizer_state_dict:
self.optimizer_name = optimizer_saved_dict.get('optimizer_name', 'AdamW')
if shared.opts.print_hypernet_extra:
print("Load existing optimizer from checkpoint")
print(f"Optimizer name is {self.optimizer_name}")
else:
self.optimizer_name = "AdamW"
if shared.opts.print_hypernet_extra:
print("No saved optimizer exists in checkpoint")
for size, sd in state_dict.items():
if type(size) == int:
self.layers[size] = (
+12 -13
View File
@@ -235,7 +235,6 @@ def load_diffusers_models(clear=True):
place = os.path.join(models_path, 'Diffusers')
if clear:
diffuser_repos.clear()
output = []
try:
for folder in os.listdir(place):
try:
@@ -253,20 +252,20 @@ def load_diffusers_models(clear=True):
if len(snapshots) == 0:
shared.log.warning(f"Diffusers folder has no snapshots: location={place} folder={folder} name={name}")
continue
commit = os.path.join(folder, 'snapshots', snapshots[-1])
mtime = os.path.getmtime(commit)
info = os.path.join(commit, "model_info.json")
diffuser_repos.append({ 'name': name, 'filename': name, 'friendly': friendly, 'folder': folder, 'path': commit, 'hash': commit, 'mtime': mtime, 'model_info': info })
if os.path.exists(os.path.join(folder, 'hidden')):
continue
output.append(name)
except Exception:
# shared.log.error(f"Error analyzing diffusers model: {folder} {e}")
pass
for snapshot in snapshots:
commit = os.path.join(folder, 'snapshots', snapshot)
mtime = os.path.getmtime(commit)
info = os.path.join(commit, "model_info.json")
repo = { 'name': name, 'filename': name, 'friendly': friendly, 'folder': folder, 'path': commit, 'hash': snapshot, 'mtime': mtime, 'model_info': info }
diffuser_repos.append(repo)
if os.path.exists(os.path.join(folder, 'hidden')):
continue
except Exception as e:
debug(f"Error analyzing diffusers model: {folder} {e}")
except Exception as e:
shared.log.error(f"Error listing diffusers: {place} {e}")
shared.log.debug(f'Scanning diffusers cache: folder={place} items={len(output)} time={time.time()-t0:.2f}')
return output
shared.log.debug(f'Scanning diffusers cache: folder={place} items={len(list(diffuser_repos))} time={time.time()-t0:.2f}')
return diffuser_repos
def find_diffuser(name: str):
-16
View File
@@ -848,19 +848,3 @@ class SwinIR(nn.Module):
flops += H * W * 3 * self.embed_dim * self.embed_dim
flops += self.upsample.flops()
return flops
if __name__ == '__main__':
upscale = 4
window_size = 8
height = (1024 // upscale // window_size + 1) * window_size
width = (720 // upscale // window_size + 1) * window_size
model = SwinIR(upscale=2, img_size=(height, width),
window_size=window_size, img_range=1., depths=[6, 6, 6, 6],
embed_dim=60, num_heads=[6, 6, 6, 6], mlp_ratio=2, upsampler='pixelshuffledirect')
print(model)
print(height, width, model.flops() / 1e9)
x = torch.randn((1, 3, height, width))
x = model(x)
print(x.shape)
@@ -997,19 +997,3 @@ class Swin2SR(nn.Module):
flops += H * W * 3 * self.embed_dim * self.embed_dim
flops += self.upsample.flops()
return flops
if __name__ == '__main__':
upscale = 4
window_size = 8
height = (1024 // upscale // window_size + 1) * window_size
width = (720 // upscale // window_size + 1) * window_size
model = Swin2SR(upscale=2, img_size=(height, width),
window_size=window_size, img_range=1., depths=[6, 6, 6, 6],
embed_dim=60, num_heads=[6, 6, 6, 6], mlp_ratio=2, upsampler='pixelshuffledirect')
print(model)
print(height, width, model.flops() / 1e9)
x = torch.randn((1, 3, height, width))
x = model(x)
print(x.shape)
+21 -19
View File
@@ -41,9 +41,9 @@ debug_load = os.environ.get('SD_LOAD_DEBUG', None)
class CheckpointInfo:
def __init__(self, filename):
def __init__(self, filename, sha=None):
self.name = None
self.hash = None
self.hash = sha
self.filename = filename
self.type = ''
relname = filename
@@ -77,9 +77,11 @@ class CheckpointInfo:
self.filename = filename
self.sha256 = hashes.sha256_from_cache(self.filename, f"checkpoint/{relname}")
self.type = ext
# self.model_name = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]
else: # maybe a diffuser
repo = [r for r in modelloader.diffuser_repos if filename == r['name']]
if self.hash is None:
repo = [r for r in modelloader.diffuser_repos if self.filename == r['name']]
else:
repo = [r for r in modelloader.diffuser_repos if self.hash == r['hash']]
if len(repo) == 0:
self.name = relname
self.filename = filename
@@ -140,17 +142,17 @@ def list_models():
global checkpoints_list # pylint: disable=global-statement
checkpoints_list.clear()
checkpoint_aliases.clear()
if shared.opts.sd_disable_ckpt or shared.backend == shared.Backend.DIFFUSERS:
ext_filter = [".safetensors"]
else:
ext_filter = [".ckpt", ".safetensors"]
ext_filter = [".safetensors"] if shared.opts.sd_disable_ckpt or shared.backend == shared.Backend.DIFFUSERS else [".ckpt", ".safetensors"]
model_list = list(modelloader.load_models(model_path=model_path, model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=ext_filter, download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]))
if shared.backend == shared.Backend.DIFFUSERS:
model_list += modelloader.load_diffusers_models(clear=True)
for filename in sorted(model_list, key=str.lower):
checkpoint_info = CheckpointInfo(filename)
if checkpoint_info.name is not None:
checkpoint_info.register()
if shared.backend == shared.Backend.DIFFUSERS:
for repo in modelloader.load_diffusers_models(clear=True):
checkpoint_info = CheckpointInfo(repo['name'], sha=repo['hash'])
if checkpoint_info.name is not None:
checkpoint_info.register()
if shared.cmd_opts.ckpt is not None:
if not os.path.exists(shared.cmd_opts.ckpt) and shared.backend == shared.Backend.ORIGINAL:
if shared.cmd_opts.ckpt.lower() != "none":
@@ -920,18 +922,18 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
try: # this is horrible special-case handling for stable-cascade multi-stage pipeline with variants and non-standard revision
diffusers_load_config.pop("vae", None)
diffusers_load_config["variant"] = 'bf16'
if 'lite' in checkpoint_info.name:
decoder_unet = diffusers.models.StableCascadeUNet.from_pretrained("stabilityai/stable-cascade", subfolder="decoder_lite", cache_dir=shared.opts.diffusers_dir, revision="refs/pr/44", **diffusers_load_config)
decoder = diffusers.StableCascadeDecoderPipeline.from_pretrained("stabilityai/stable-cascade", cache_dir=shared.opts.diffusers_dir, revision="refs/pr/44", decoder=decoder_unet, **diffusers_load_config)
if 'lite' in checkpoint_info.name or 'abc818bb0d' in checkpoint_info.hash:
decoder_unet = diffusers.models.StableCascadeUNet.from_pretrained("stabilityai/stable-cascade", subfolder="decoder_lite", cache_dir=shared.opts.diffusers_dir, **diffusers_load_config)
decoder = diffusers.StableCascadeDecoderPipeline.from_pretrained("stabilityai/stable-cascade", cache_dir=shared.opts.diffusers_dir, decoder=decoder_unet, **diffusers_load_config)
shared.log.debug(f'StableCascade lite decoder: scale={decoder.latent_dim_scale}')
prior_unet = diffusers.models.StableCascadeUNet.from_pretrained("stabilityai/stable-cascade-prior", subfolder="prior_lite", cache_dir=shared.opts.diffusers_dir, revision="refs/pr/2", **diffusers_load_config)
prior = diffusers.StableCascadePriorPipeline.from_pretrained("stabilityai/stable-cascade-prior", cache_dir=shared.opts.diffusers_dir, revision="refs/pr/2", prior=prior_unet, **diffusers_load_config)
prior_unet = diffusers.models.StableCascadeUNet.from_pretrained("stabilityai/stable-cascade-prior", subfolder="prior_lite", cache_dir=shared.opts.diffusers_dir, **diffusers_load_config)
prior = diffusers.StableCascadePriorPipeline.from_pretrained("stabilityai/stable-cascade-prior", cache_dir=shared.opts.diffusers_dir, prior=prior_unet, **diffusers_load_config)
shared.log.debug(f'StableCascade lite prior: scale={prior.resolution_multiple}')
else:
decoder = diffusers.StableCascadeDecoderPipeline.from_pretrained("stabilityai/stable-cascade", cache_dir=shared.opts.diffusers_dir, revision="refs/pr/44", **diffusers_load_config)
shared.log.debug(f'StableCascade decoder: scale={decoder.latent_dim_scale}')
prior = diffusers.StableCascadePriorPipeline.from_pretrained("stabilityai/stable-cascade-prior", cache_dir=shared.opts.diffusers_dir, revision="refs/pr/2", **diffusers_load_config)
shared.log.debug(f'StableCascade prior: scale={prior.resolution_multiple}')
decoder = diffusers.StableCascadeDecoderPipeline.from_pretrained("stabilityai/stable-cascade", cache_dir=shared.opts.diffusers_dir, **diffusers_load_config)
shared.log.debug(f'StableCascade full decoder: scale={decoder.latent_dim_scale}')
prior = diffusers.StableCascadePriorPipeline.from_pretrained("stabilityai/stable-cascade-prior", cache_dir=shared.opts.diffusers_dir, **diffusers_load_config)
shared.log.debug(f'StableCascade full prior: scale={prior.resolution_multiple}')
sd_model = diffusers.StableCascadeCombinedPipeline(
tokenizer=decoder.tokenizer,
text_encoder=decoder.text_encoder,
+1 -1
View File
@@ -150,7 +150,7 @@ def create_ui():
for i, elem in enumerate(img2img_tabs):
elem.select(fn=lambda tab=i: select_img2img_tab(tab), inputs=[], outputs=[inpaint_controls, mask_alpha]) # pylint: disable=cell-var-from-loop
override_settings = ui_common.create_override_inputs('img2img')
override_settings = ui_common.create_override_inputs('img2img')
with gr.Group(elem_id="img2img_script_container"):
img2img_script_inputs = modules.scripts.scripts_img2img.setup_ui(parent='img2img', accordion=True)