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