mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 17:24:32 +02:00
gallery improve sort and separators
This commit is contained in:
+5
-5
@@ -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
@@ -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`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user