diff --git a/README.md b/README.md index 63687dfcd..ee0df19ee 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,9 @@ Simplified start script: `automatic.sh` > ./automatic.sh -- Start in default mode with optimizations enabled - -> ./automatic.sh env - -- Print env info and exit - Example: +- Start in default mode with optimizations enabled + Additionally print environment info during startup + Example: Version: c07487a Tue Jan 24 08:04:31 2023 -0500 Platform: Ubuntu 22.04.1 LTS 5.15.79.1-microsoft-standard-WSL2 x86_64 @@ -81,10 +78,20 @@ For some Torch optimizations notes, see Wiki Fork is compatible with regular **PyTorch 1.13** as well as pre-release of **PyTorch 2.0** See [Wiki](https://github.com/vladmandic/automatic/wiki) for **Torch** optimization notes +
+ +## Scripts + +This repository comes with a large collection of scripts that can be used to process inputs, train, generate, and benchmark models + +As well as number of auxiliary scripts that do not rely on **WebUI**, but can be used for end-to-end solutions such as extract frames from videos, etc. + +For full details see [Docs](cli/README.md)
## Docs -Everything is in [Wiki](https://github.com/vladmandic/automatic/wiki) -Except my current [TODO](TODO.md) +- Scripts are in [Scripts](cli/README.md) +- Everything else is in [Wiki](https://github.com/vladmandic/automatic/wiki) +- Except my current [TODO](TODO.md) diff --git a/TODO.md b/TODO.md index ed7ce678e..df260553f 100644 --- a/TODO.md +++ b/TODO.md @@ -72,6 +72,7 @@ Tech that can be integrated as part of the core workflow... - [Seed travel](https://github.com/yownas/seed_travel) - [Google frame interpolation](https://github.com/google-research/frame-interpolation) - [Prompt fusion](https://github.com/ljleb/prompt-fusion-extension) +- [ControlNet](https://github.com/lllyasviel/ControlNet) ## Experimental @@ -80,49 +81,4 @@ Cool stuff that is not integrated anywhere... - [TensorRT](https://www.photoroom.com/tech/stable-diffusion-25-percent-faster-and-save-seconds/) - [GIT](https://huggingface.co/microsoft/git-large-textcaps) - Bunch of stuff: -- Prevalent colors to interrogate - Auto-Sort inputs by face recognition - -## Updates - -- core library updates: - - must run `./automatic.sh install` - - note: this is quite a big one so some testing is reccomended after upgrade -- non-trivial ui updates -- renamed scripts in `cli/modules` to be more descriptive - if you're using old script names, update them - for example, `ffmpeg.py` is now `video-extract.py` - also possible that there are some bugs due to broken import paths, so testing is welcome -- updated script `process.py` - - new **brightness dynamic range** check - - new **preview** mode to run all checks but without saving images plus print a summary at the end -- updated scripts `models-preview.py` - - can generate **lora** previews, note that trigger keywords are inferred from model name so name models carefully - - can generate **hypernetwork** previews -- new script: `image-watermark.py` - - optionally strip exif from images - - add invisible watermark to images which persists even if user modifies image so we can always track it -- new script: `palette-extract.py` - - creates color palette wheel from image(s) -- new script: `extract-lora.py` - - extract lora from fine-tuned model -- updated `embedding-preview.py` - - skip existing previews or overwrite them -- expose **variation seed** in main ui -- integrated **seed travel** functionality into core -- integrated `pix2pix` functionality to standard `img2img` workflow - - note: requires **pix2pix** model to be loaded -- integrated large `cfg scale` values fix -- integrated **model converter** -- tested `aesthetic gradients` training, not worth it -- updated `image browser` - was broken for a while and maintainer is gone -- initial work on **queue management** allowing to submit multiple requests to server -- initial work on `lora` integration - can render loras without extensions - can extract lora from fine-tuned model - training prototype in place in `train-lora.py`, not optimized or integrated -- initial work on `custom diffusion` integration - no testing so far -- spent quite some time making stable-diffusion compatible with upcomming `pytorch` 2.0 release - and testing `dynamo` torch dynamic optimizer and `triton` script compiler diff --git a/automatic.sh b/automatic.sh index 151fc235a..48a05dea8 100755 --- a/automatic.sh +++ b/automatic.sh @@ -1,4 +1,4 @@ -#/bin/env bash +#!/bin/env bash TF_CPP_MIN_LOG_LEVEL=2 FORCE_CUDA="1" @@ -11,7 +11,7 @@ CUDA_AUTO_BOOST=1 CUDA_DEVICE_DEFAULT_PERSISTING_L2_CACHE_PERCENTAGE_LIMIT=0 if [ "$PYTHON" == "" ]; then - PYTHON=`which python` + PYTHON=$(which python) fi CMD="launch.py --api --xformers --disable-console-progressbars --gradio-queue --skip-version-check --cors-allow-origins=http://127.0.0.1:7860" @@ -43,27 +43,27 @@ done echo "SD server: $MODE" -VER=`git log -1 --pretty=format:"%h %ad"` -LSB=`lsb_release -ds 2>/dev/null` -UN=`uname -rm 2>/dev/null` +VER=$(git log -1 --pretty=format:"%h %ad") +LSB=$(lsb_release -ds 2>/dev/null) +UNAME=$(uname -rm 2>/dev/null) echo "Version: $VER" -echo "Platform: $LSB $UN" -$PYTHON -c 'import torch; import platform; print("Python:", platform.python_version(), "Torch:", torch.__version__, "CUDA:", torch.version.cuda, "cuDNN:", torch.backends.cudnn.version(), "GPU:", torch.cuda.get_device_name(torch.cuda.current_device()), "Arch:", torch.cuda.get_device_capability());' +echo "Platform: $LSB $UNAME" +"$PYTHON" -c 'import torch; import platform; print("Python:", platform.python_version(), "Torch:", torch.__version__, "CUDA:", torch.version.cuda, "cuDNN:", torch.backends.cudnn.version(), "GPU:", torch.cuda.get_device_name(torch.cuda.current_device()), "Arch:", torch.cuda.get_device_capability());' -if [ $MODE == install ]; then - $PYTHON -m pip --version +if [ "$MODE" == install ]; then + "$PYTHON" -m pip --version echo "Installing general requirements" - $PYTHON -m pip install --disable-pip-version-check --quiet --no-warn-conflicts --requirement requirements.txt + "$PYTHON" -m pip install --disable-pip-version-check --quiet --no-warn-conflicts --requirement requirements.txt echo "Installing versioned requirements" - $PYTHON -m pip install --disable-pip-version-check --quiet --no-warn-conflicts --requirement requirements_versions.txt + "$PYTHON" -m pip install --disable-pip-version-check --quiet --no-warn-conflicts --requirement requirements_versions.txt echo "Updating submodules" git submodule update --rebase --remote exit 0 fi -if [ $MODE == clean ]; then +if [ "$MODE" == clean ]; then CMD="--disable-opt-split-attention --disable-console-progressbars --api" - $PYTHON launch.py $CMD + "$PYTHON" launch.py $CMD exit 0 fi @@ -75,4 +75,4 @@ if [ $MODE == optimized ]; then CMD="$CMD" fi -exec accelerate launch --no_python --quiet --num_cpu_threads_per_process=6 $PYTHON $CMD +exec accelerate launch --no_python --quiet --num_cpu_threads_per_process=6 "$PYTHON" $CMD diff --git a/cli/modules/preview-models.py b/cli/modules/preview-models.py index 7be4fd6f8..7815097bc 100755 --- a/cli/modules/preview-models.py +++ b/cli/modules/preview-models.py @@ -15,7 +15,7 @@ from generate import sd, generate default = 'sd-v15-runwayml.ckpt [cc6cb27103]' -embeddings = ['blonde', 'bruntette', 'sexy', 'naked', 'mia', 'lin', 'kelly', 'hanna', 'rreid-random-v0'] +embeddings = ['blonde', 'bruntette', 'sexy', 'naked', 'ti-mia', 'ti-lin', 'ti-kelly', 'ti-hanna', 'ti-rreid-random'] exclude = ['sd-v20', 'sd-v21', 'inpainting', 'pix2pix'] prompt = "photo of , 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" options = Map({ @@ -32,6 +32,7 @@ options = Map({ 'width': 512, 'height': 512, }, + 'format': '.jpg', 'paths': { "root": "/mnt/c/Users/mandi/OneDrive/Generative/Generate", "generate": "image", @@ -43,11 +44,11 @@ options = Map({ "sd_vae": "vae-ft-mse-840000-ema-pruned.ckpt", }, 'lora': { - 'strength': 0.8, + 'strength': 0.9, }, 'hypernetwork': { 'keyword': 'beautiful sexy woman', - 'strength': 1.0, + 'strength': 0.9, }, }) @@ -91,7 +92,7 @@ async def models(params): log.info({ 'total jobs': len(models) * len(embeddings) * options.generate.batch_size, 'per-model': len(embeddings) * options.generate.batch_size }) log.info(json.dumps(options, indent=2)) for model in models: - fn = os.path.join(dir, model + '.png') + fn = os.path.join(dir, model + options.format) if os.path.exists(fn) and len(params.input) == 0: # if model preview exists and not manually included log.info({ 'model preview exists': model }) continue @@ -139,15 +140,18 @@ async def lora(params): models = [f.stem for f in models1 + models2] log.info({ 'loras': len(models) }) for model in models: - fn = os.path.join(dir, model + '.png') + fn = os.path.join(dir, model + options.format) if os.path.exists(fn) and len(params.input) == 0: # if model preview exists and not manually included log.info({ 'lora preview exists': model }) continue images = [] labels = [] t0 = time.time() - keyword = model.replace('-', ' ') - options.generate.prompt = prompt.replace('', f'\"{keyword}\"') + import re + keywords = re.sub('\d', '', model) + keywords = keywords.replace('-v', ' ').replace('-', ' ').strip().split(' ') + keyword = '\"' + '\" \"'.join(keywords) + '\"' + options.generate.prompt = prompt.replace('', keyword) options.generate.prompt = options.generate.prompt.replace('', '') options.generate.prompt += f' ' log.info({ 'lora generating': model, 'keyword': keyword, 'prompt': options.generate.prompt }) @@ -175,7 +179,7 @@ async def hypernetwork(params): models = [f.stem for f in Path(dir).glob('*.pt')] log.info({ 'loras': len(models) }) for model in models: - fn = os.path.join(dir, model + '.png') + fn = os.path.join(dir, model + options.format) if os.path.exists(fn) and len(params.input) == 0: # if model preview exists and not manually included log.info({ 'hypernetwork preview exists': model }) continue @@ -186,7 +190,7 @@ async def hypernetwork(params): options.generate.prompt = prompt.replace('', options.hypernetwork.keyword) options.generate.prompt = options.generate.prompt.replace('', '') options.generate.prompt = f' ' + options.generate.prompt - log.info({ 'lora generating': model, 'keyword': keyword, 'prompt': options.generate.prompt }) + log.info({ 'hypernetwork generating': model, 'keyword': keyword, 'prompt': options.generate.prompt }) data = await generate(options = options, quiet=True) if 'image' in data: for img in data['image']: diff --git a/cli/modules/process.py b/cli/modules/process.py index 7ea370628..817f97a98 100755 --- a/cli/modules/process.py +++ b/cli/modules/process.py @@ -7,6 +7,7 @@ process people images - visible: is face or body detected - in frame: for face based on box, for body based on number of visible keypoints - resolution: is cropped image still of sufficient resolution + - optionaly upsample and restore face quality - blur: is image sharp enough - dynamic range: is image bright enough - similarity: compares image to all previously processed images to see if its unique enough @@ -40,20 +41,29 @@ from sdapi import postsync params = Map({ + # general settings, do not modify 'src': '', # source folder 'dst': '', # destination folder - 'format': '.jpg', # image format - 'extract_face': True, # extract face from image - 'extract_body': True, # extract face from image 'clear_dst': True, # remove all files from destination at the start + 'format': '.jpg', # image format 'target_size': 512, # target resolution 'square_images': True, # should output images be squared + 'segmentation_model': 0, # segmentation model 0/general 1/landscape + 'segmentation_background': (192, 192, 192), # segmentation background color 'blur_samplesize': 60, # sample size to use for blur detection + 'similarity_size': 64, # base similarity detection on reduced images + # face processing settings + 'extract_face': True, # extract face from image 'face_score': 0.7, # min face detection score 'face_pad': 0.2, # pad face image percentage 'face_model': 1, # which face model to use 0/close-up 1/standard 'face_blur_score': 1.5, # max score for face blur detection 'face_range_score': 0.15, # min score for face dynamic range detection + 'face_restore': True, # attempt to restore face quality + 'face_upscale': True, # attempt to scale small faces + 'face_segmentation': False, # segmentation enabled + # body processing settings + 'extract_body': True, # extract face from image 'body_score': 0.9, # min body detection score 'body_visibility': 0.5, # min visibility score for each detected body part 'body_parts': 15, # min number of detected body parts with sufficient visibility @@ -61,16 +71,13 @@ params = Map({ 'body_model': 2, # body model to use 0/low 1/medium 2/high 'body_blur_score': 1.8, # max score for body blur detection 'body_range_score': 0.15, # min score for body dynamic range detection - 'segmentation_face': False, # segmentation enabled - 'segmentation_body': False, # segmentation enabled - 'segmentation_model': 0, # segmentation model 0/general 1/landscape - 'segmentation_background': (192, 192, 192), # segmentation background color + 'body_segmentation': False, # segmentation enabled + # similarity detection settings 'similarity_score': 0.8, # maximum similarity score before image is discarded - 'similarity_size': 64, # base similarity detection on reduced images - 'interrogate_model': ['clip', 'deepdanbooru'], # interrogate model + # interrogate settings + 'interrogate_model': ['clip', 'deepdanbooru'], # interrogate models + 'interrogate_captions': True, # write captions to file 'tag_limit': 5, # number of tags to extract - 'face_restore': True, # attempt to restore face quality - 'face_upscale': True, # attempt to scale small faces }) face_model = None body_model = None @@ -181,6 +188,9 @@ def extract_face(img): }) original = [cropped.size[0], cropped.size[1]] res = postsync('/sdapi/v1/extra-single-image', kwargs) + if 'image' not in res: + log.error({ 'process face': 'upscale failed' }) + raise ValueError('upscale failed') cropped = Image.open(io.BytesIO(base64.b64decode(res['image']))) kwargs.image = [cropped.size[0], cropped.size[1]] upscaled = [cropped.size[0], cropped.size[1]] @@ -195,7 +205,7 @@ def extract_face(img): if params.square_images: squared = Image.new('RGB', (params.target_size, params.target_size)) squared.paste(cropped, (0, 0)) - if params.segmentation_face: + if params.face_segmentation: squared = segmentation(squared) else: squared = cropped @@ -258,7 +268,7 @@ def extract_body(img): if params.square_images: squared = Image.new('RGB', (params.target_size, params.target_size)) squared.paste(cropped, (0, 0)) - if params.segmentation_body: + if params.body_segmentation: squared = segmentation(squared) else: squared = cropped @@ -293,7 +303,7 @@ def encode(img): return encoded -def interrogate(img, fn, txt): +def interrogate(img, fn): if len(params.interrogate_model) == 0: return caption = '' @@ -308,7 +318,7 @@ def interrogate(img, fn, txt): tag = res.caption if 'caption' in res else '' tags = tag.split(',') tags = [t.replace('(', '').replace(')', '').split(':')[0].strip() for t in tags] - if txt: + if params.interrogate_captions: file = fn.replace(params.format, '.txt') f = open(file, 'w') f.write(caption) @@ -323,7 +333,7 @@ def interrogate(img, fn, txt): i = {} metadata = Map({}) -def process_file(f: str, dst: str = None, preview: bool = False, offline: bool = False, txt: bool = True): +def process_file(f: str, dst: str = None, preview: bool = False, offline: bool = False, txt = None): def save(img, f, what): i[what] = i.get(what, 0) + 1 @@ -338,7 +348,7 @@ def process_file(f: str, dst: str = None, preview: bool = False, offline: bool = if not preview: img.save(fn) if not offline: - caption, tags = interrogate(img, fn, txt) + caption, tags = interrogate(img, fn) metadata[fn] = { 'caption': caption, 'tags': tags } return fn @@ -350,6 +360,8 @@ def process_file(f: str, dst: str = None, preview: bool = False, offline: bool = return 0, {} image = ImageOps.exif_transpose(image) # rotate image according to EXIF orientation + if txt is not None: + params.interrogate_captions = txt if image.width < 512 or image.height < 512: log.info({ 'process skip': 'low resolution', 'resolution': [image.width, image.height] }) diff --git a/cli/train-lora.py b/cli/train-lora.py index 2e8049e8e..7246a988a 100755 --- a/cli/train-lora.py +++ b/cli/train-lora.py @@ -182,12 +182,16 @@ if __name__ == '__main__': if not args.noprocess: # preprocess for f in files: - res, metadata = modules.process.process_file(f = f, dst = dir, preview = False, offline = args.offline, txt = False) + try: + res, metadata = modules.process.process_file(f = f, dst = dir, preview = False, offline = args.offline, txt = False) + except ValueError as e: + exit(1) modules.process.unload_models() mem_stats() if args.tag is not None: for name, item in metadata.items(): item['tags'].insert(0, args.tag) + item['tags'] = ', '.join(item['tags']) with open(json_file, "w") as outfile: outfile.write(json.dumps(metadata, indent=2)) log.info({ 'processed': res, 'inputs': len(files), 'metadata': json_file, 'path': dir }) diff --git a/config.json b/config.json index e444c4f4d..3acf810ab 100644 --- a/config.json +++ b/config.json @@ -34,7 +34,7 @@ "eta_ddim": 0.0, "eta_noise_seed_delta": 0, "export_for_4chan": false, - "extra_networks_default_multiplier": 1, + "extra_networks_default_multiplier": 0.9, "extra_networks_default_view": "cards", "face_restoration_model": "CodeFormer", "face_restoration_unload": false, diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 22cabcb0f..3f7c597b9 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -15,7 +15,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): def list_items(self): for name, lora_on_disk in lora.available_loras.items(): path, ext = os.path.splitext(lora_on_disk.filename) - previews = [path + ".png", path + ".preview.png"] + previews = [path + ".png", path + ".preview.png", path + ".jpg", path + ".preview.jpg", path + ".jpeg", path + ".preview.jpeg", path + ".webp", path + ".preview.webp"] preview = None for file in previews: @@ -28,10 +28,9 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): "filename": path, "preview": preview, "search_term": self.search_terms_from_path(lora_on_disk.filename), - "prompt": json.dumps(f""), - "local_preview": path + ".png", + "prompt": json.dumps(f""), + "local_preview": f"{path}.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): return [shared.cmd_opts.lora_dir] - diff --git a/extensions-builtin/sd-dynamic-thresholding b/extensions-builtin/sd-dynamic-thresholding index 681a5ffe7..6c76a48a1 160000 --- a/extensions-builtin/sd-dynamic-thresholding +++ b/extensions-builtin/sd-dynamic-thresholding @@ -1 +1 @@ -Subproject commit 681a5ffe7bcc4cd00890339a32a7e903f9cf6e7c +Subproject commit 6c76a48a1f802287cfceacd912149c5141b22031 diff --git a/extensions-builtin/seed_travel b/extensions-builtin/seed_travel index 8f818f18f..185ae54e9 160000 --- a/extensions-builtin/seed_travel +++ b/extensions-builtin/seed_travel @@ -1 +1 @@ -Subproject commit 8f818f18f258ac4e53a34c1219819da497e97be1 +Subproject commit 185ae54e93c560111a7053bfd31be187b9ed1246 diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index 0416810ad..c25e8a73f 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit 0416810adf4827aa36a032dc7beed94ba6dcb954 +Subproject commit c25e8a73f3676b197c2c0bd37f7d493d108e521d diff --git a/modules/extensions.py b/modules/extensions.py index 5e12b1aaa..1975fca1c 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -2,6 +2,7 @@ import os import sys import traceback +import time import git from modules import paths, shared @@ -25,6 +26,7 @@ class Extension: self.status = '' self.can_update = False self.is_builtin = is_builtin + self.version = '' repo = None try: @@ -40,6 +42,10 @@ class Extension: try: self.remote = next(repo.remote().urls, None) self.status = 'unknown' + head = repo.head.commit + ts = time.asctime(time.gmtime(repo.head.commit.committed_date)) + self.version = f'{head.hexsha[:7]} ({ts})' + except Exception: self.remote = None diff --git a/modules/ui.py b/modules/ui.py index cbe6b9310..32d49e10d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1012,48 +1012,6 @@ def create_ui(): } return interp_descriptions[value] - with gr.Blocks(analytics_enabled=False) as modelmerger_interface: - with gr.Row().style(equal_height=False): - with gr.Column(variant='compact'): - interp_description = gr.HTML(value=update_interp_description("Weighted sum"), elem_id="modelmerger_interp_description") - - with FormRow(elem_id="modelmerger_models"): - primary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_primary_model_name", label="Primary model (A)") - create_refresh_button(primary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_A") - - secondary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_secondary_model_name", label="Secondary model (B)") - create_refresh_button(secondary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_B") - - tertiary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_tertiary_model_name", label="Tertiary model (C)") - create_refresh_button(tertiary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_C") - - custom_name = gr.Textbox(label="Custom Name (Optional)", elem_id="modelmerger_custom_name") - interp_amount = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Multiplier (M) - set to 0 to get model A', value=0.3, elem_id="modelmerger_interp_amount") - interp_method = gr.Radio(choices=["No interpolation", "Weighted sum", "Add difference"], value="Weighted sum", label="Interpolation Method", elem_id="modelmerger_interp_method") - interp_method.change(fn=update_interp_description, inputs=[interp_method], outputs=[interp_description]) - - with FormRow(): - checkpoint_format = gr.Radio(choices=["ckpt", "safetensors"], value="ckpt", label="Checkpoint format", elem_id="modelmerger_checkpoint_format") - save_as_half = gr.Checkbox(value=False, label="Save as float16", elem_id="modelmerger_save_as_half") - - with FormRow(): - with gr.Column(): - config_source = gr.Radio(choices=["A, B or C", "B", "C", "Don't"], value="A, B or C", label="Copy config from", type="index", elem_id="modelmerger_config_method") - - with gr.Column(): - with FormRow(): - bake_in_vae = gr.Dropdown(choices=["None"] + list(sd_vae.vae_dict), value="None", label="Bake in VAE", elem_id="modelmerger_bake_in_vae") - create_refresh_button(bake_in_vae, sd_vae.refresh_vae_list, lambda: {"choices": ["None"] + list(sd_vae.vae_dict)}, "modelmerger_refresh_bake_in_vae") - - with FormRow(): - discard_weights = gr.Textbox(value="", label="Discard weights with matching name", elem_id="modelmerger_discard_weights") - - with gr.Row(): - modelmerger_merge = gr.Button(elem_id="modelmerger_merge", value="Merge", variant='primary') - - with gr.Column(variant='compact', elem_id="modelmerger_results_container"): - with gr.Group(elem_id="modelmerger_results_panel"): - modelmerger_result = gr.HTML(elem_id="modelmerger_result", show_label=False) with gr.Blocks(analytics_enabled=False) as train_interface: with gr.Row().style(equal_height=False): @@ -1062,6 +1020,49 @@ def create_ui(): with gr.Row(variant="compact").style(equal_height=False): with gr.Tabs(elem_id="train_tabs"): + with gr.Tab(label="Merge models") as modelmerger_interface: + with gr.Row().style(equal_height=False): + with gr.Column(variant='compact'): + interp_description = gr.HTML(value=update_interp_description("Weighted sum"), elem_id="modelmerger_interp_description") + + with FormRow(elem_id="modelmerger_models"): + primary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_primary_model_name", label="Primary model (A)") + create_refresh_button(primary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_A") + + secondary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_secondary_model_name", label="Secondary model (B)") + create_refresh_button(secondary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_B") + + tertiary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_tertiary_model_name", label="Tertiary model (C)") + create_refresh_button(tertiary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_C") + + custom_name = gr.Textbox(label="Custom Name (Optional)", elem_id="modelmerger_custom_name") + interp_amount = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Multiplier (M) - set to 0 to get model A', value=0.3, elem_id="modelmerger_interp_amount") + interp_method = gr.Radio(choices=["No interpolation", "Weighted sum", "Add difference"], value="Weighted sum", label="Interpolation Method", elem_id="modelmerger_interp_method") + interp_method.change(fn=update_interp_description, inputs=[interp_method], outputs=[interp_description]) + + with FormRow(): + checkpoint_format = gr.Radio(choices=["ckpt", "safetensors"], value="ckpt", label="Checkpoint format", elem_id="modelmerger_checkpoint_format") + save_as_half = gr.Checkbox(value=False, label="Save as float16", elem_id="modelmerger_save_as_half") + + with FormRow(): + with gr.Column(): + config_source = gr.Radio(choices=["A, B or C", "B", "C", "Don't"], value="A, B or C", label="Copy config from", type="index", elem_id="modelmerger_config_method") + + with gr.Column(): + with FormRow(): + bake_in_vae = gr.Dropdown(choices=["None"] + list(sd_vae.vae_dict), value="None", label="Bake in VAE", elem_id="modelmerger_bake_in_vae") + create_refresh_button(bake_in_vae, sd_vae.refresh_vae_list, lambda: {"choices": ["None"] + list(sd_vae.vae_dict)}, "modelmerger_refresh_bake_in_vae") + + with FormRow(): + discard_weights = gr.Textbox(value="", label="Discard weights with matching name", elem_id="modelmerger_discard_weights") + + with gr.Row(): + modelmerger_merge = gr.Button(elem_id="modelmerger_merge", value="Merge", variant='primary') + + with gr.Column(variant='compact', elem_id="modelmerger_results_container"): + with gr.Group(elem_id="modelmerger_results_panel"): + modelmerger_result = gr.HTML(elem_id="modelmerger_result", show_label=False) + with gr.Tab(label="Create embedding"): new_embedding_name = gr.Textbox(label="Name", elem_id="train_new_embedding_name") initialization_text = gr.Textbox(label="Initialization text", value="*", elem_id="train_initialization_text") @@ -1539,7 +1540,7 @@ def create_ui(): (img2img_interface, "From Image", "img2img"), (extras_interface, "Process", "extras"), (pnginfo_interface, "Image Info", "pnginfo"), - (modelmerger_interface, "Checkpoint Merger", "modelmerger"), + # (modelmerger_interface, "Checkpoint Merger", "modelmerger"), (train_interface, "Train", "ti"), ] diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 37d30e1f2..bd4308ef0 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -80,6 +80,7 @@ def extension_table(): Extension URL + Version Update @@ -87,11 +88,7 @@ def extension_table(): """ for ext in extensions.extensions: - remote = "" - if ext.is_builtin: - remote = "built-in" - elif ext.remote: - remote = f"""{html.escape("built-in" if ext.is_builtin else ext.remote or '')}""" + remote = f"""{html.escape("built-in" if ext.is_builtin else ext.remote or '')}""" if ext.can_update: ext_status = f"""""" @@ -102,6 +99,7 @@ def extension_table(): {remote} + {ext.version} {ext_status} """ diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 04097a794..d80950c72 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -17,7 +17,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): checkpoint: sd_models.CheckpointInfo for name, checkpoint in sd_models.checkpoints_list.items(): path, ext = os.path.splitext(checkpoint.filename) - previews = [path + ".png", path + ".preview.png"] + previews = [path + ".png", path + ".preview.png", path + ".jpg", path + ".preview.jpg", path + ".jpeg", path + ".preview.jpeg", path + ".webp", path + ".preview.webp"] preview = None for file in previews: @@ -31,7 +31,8 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): "preview": preview, "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', - "local_preview": path + ".png", + "local_preview": f"{path}.{shared.opts.samples_format}", + } def allowed_directories_for_previews(self): diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 578510887..f5541d13d 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -14,7 +14,7 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): def list_items(self): for name, path in shared.hypernetworks.items(): path, ext = os.path.splitext(path) - previews = [path + ".png", path + ".preview.png"] + previews = [path + ".png", path + ".preview.png", path + ".jpg", path + ".preview.jpg", path + ".jpeg", path + ".preview.jpeg", path + ".webp", path + ".preview.webp"] preview = None for file in previews: @@ -27,8 +27,8 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): "filename": path, "preview": preview, "search_term": self.search_terms_from_path(path), - "prompt": json.dumps(f""), - "local_preview": path + ".png", + "prompt": json.dumps(f""), + "local_preview": f"{path}.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index bb64eb81e..2b5fef55f 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -1,7 +1,7 @@ import json import os -from modules import ui_extra_networks, sd_hijack +from modules import ui_extra_networks, sd_hijack, shared class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): @@ -16,10 +16,13 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): for embedding in sd_hijack.model_hijack.embedding_db.word_embeddings.values(): path, ext = os.path.splitext(embedding.filename) preview_file = path + ".preview.png" + previews = [path + ".preview.png", path + ".preview.jpg", path + ".preview.jpeg", path + ".preview.webp"] preview = None - if os.path.isfile(preview_file): - preview = self.link_preview(preview_file) + for file in previews: + if os.path.isfile(file): + preview = self.link_preview(file) + break yield { "name": embedding.name, @@ -27,7 +30,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): "preview": preview, "search_term": self.search_terms_from_path(embedding.filename), "prompt": json.dumps(embedding.name), - "local_preview": path + ".preview.png", + "local_preview": f"{path}.preview.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): diff --git a/ui-config.json b/ui-config.json index fd75e30ce..ddcdc0915 100644 --- a/ui-config.json +++ b/ui-config.json @@ -1130,5 +1130,44 @@ "customscript/dynamic_thresholding.py/img2img/Power Scheduler Value/value": 4.0, "customscript/dynamic_thresholding.py/img2img/Power Scheduler Value/minimum": 0.0, "customscript/dynamic_thresholding.py/img2img/Power Scheduler Value/maximum": 15.0, - "customscript/dynamic_thresholding.py/img2img/Power Scheduler Value/step": 0.5 + "customscript/dynamic_thresholding.py/img2img/Power Scheduler Value/step": 0.5, + "customscript/seed_travel.py/txt2img/Interpolation rate/visible": true, + "customscript/seed_travel.py/txt2img/Interpolation rate/value": "Linear", + "customscript/seed_travel.py/txt2img/Rate strength/visible": true, + "customscript/seed_travel.py/txt2img/Rate strength/value": 3, + "customscript/seed_travel.py/txt2img/Rate strength/minimum": 0.0, + "customscript/seed_travel.py/txt2img/Rate strength/maximum": 10.0, + "customscript/seed_travel.py/txt2img/Rate strength/step": 0.1, + "customscript/seed_travel.py/img2img/Interpolation rate/visible": true, + "customscript/seed_travel.py/img2img/Interpolation rate/value": "Linear", + "customscript/seed_travel.py/img2img/Rate strength/visible": true, + "customscript/seed_travel.py/img2img/Rate strength/value": 3, + "customscript/seed_travel.py/img2img/Rate strength/minimum": 0.0, + "customscript/seed_travel.py/img2img/Rate strength/maximum": 10.0, + "customscript/seed_travel.py/img2img/Rate strength/step": 0.1, + "train/Primary model (A)/visible": true, + "train/Primary model (A)/value": null, + "train/Secondary model (B)/visible": true, + "train/Secondary model (B)/value": null, + "train/Tertiary model (C)/visible": true, + "train/Tertiary model (C)/value": null, + "train/Custom Name (Optional)/visible": true, + "train/Custom Name (Optional)/value": "", + "train/Multiplier (M) - set to 0 to get model A/visible": true, + "train/Multiplier (M) - set to 0 to get model A/value": 0.3, + "train/Multiplier (M) - set to 0 to get model A/minimum": 0.0, + "train/Multiplier (M) - set to 0 to get model A/maximum": 1.0, + "train/Multiplier (M) - set to 0 to get model A/step": 0.05, + "train/Interpolation Method/visible": true, + "train/Interpolation Method/value": "Weighted sum", + "train/Checkpoint format/visible": true, + "train/Checkpoint format/value": "ckpt", + "train/Save as float16/visible": true, + "train/Save as float16/value": true, + "train/Copy config from/visible": true, + "train/Copy config from/value": "A, B or C", + "train/Bake in VAE/visible": true, + "train/Bake in VAE/value": "None", + "train/Discard weights with matching name/visible": true, + "train/Discard weights with matching name/value": "" } \ No newline at end of file