diff --git a/TODO.md b/TODO.md
index fe3633b34..17d64f355 100644
--- a/TODO.md
+++ b/TODO.md
@@ -55,6 +55,7 @@ Tech that can be integrated as part of the core workflow...
- [Null-text inversion](https://github.com/ouhenio/null-text-inversion-colab)
- [Custom diffusion](https://github.com/guaneec/custom-diffusion-webui)
-
+- [Dream artist](https://github.com/7eu7d7/DreamArtist-sd-webui-extension)
## Video Generation
@@ -80,3 +81,22 @@ Cool stuff that is not integrated anywhere...
- Bunch of stuff:
- Prevalent colors to interrogate
- Auto-Sort inputs by face recognition
+
+
+core library updates:
+- run `./automatic.sh install`
+- note, this is quite a big one so some testing is reccomended after upgrade
+ui updates
+new script:
+- `cli/watermark.py` to a) strip exif from images, b) add invisible watermark to images which persists even if user modifies image so we can always track it
+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
+tested `aesthetic gradients` training, not worth it
+updated `image browser`
+initial work on **queue management** allowing to submit multiple requests to server
+initial work on `lora` integration (hidden)
+initial work on `custom diffusion` integration (hidden)
+spent quite some time making stable-diffusion compatible with upcomming `pytorch` 2.0 release
diff --git a/automatic.sh b/automatic.sh
index 151fc235a..0300d0daa 100755
--- a/automatic.sh
+++ b/automatic.sh
@@ -8,7 +8,7 @@ PYTORCH_CUDA_ALLOC_CONF=garbage_collection_threshold:0.9,max_split_size_mb:512
CUDA_LAUNCH_BLOCKING=0
CUDA_CACHE_DISABLE=0
CUDA_AUTO_BOOST=1
-CUDA_DEVICE_DEFAULT_PERSISTING_L2_CACHE_PERCENTAGE_LIMIT=0
+CUDA_DEVICE_DEFAULT_PERSISTING_L2_CACHE_PERCENTAGE_LIMIT=50
if [ "$PYTHON" == "" ]; then
PYTHON=`which python`
diff --git a/cli/palette.py b/cli/palette.py
new file mode 100644
index 000000000..54ffb9cdd
--- /dev/null
+++ b/cli/palette.py
@@ -0,0 +1,148 @@
+# based on
+
+import numpy as np
+import pandas as pd
+import matplotlib.pyplot as plt
+import matplotlib.patches as patches
+import matplotlib.image as mpimg
+
+from PIL import Image
+from matplotlib.offsetbox import OffsetImage, AnnotationBbox
+
+#!pip install easydev #version 0.12.0
+#!pip install colormap #version 1.0.4
+#!pip install opencv-python #version 4.5.5.64
+#!pip install colorgram.py #version 1.2.0
+#!pip install extcolors #version 1.0.0
+
+import cv2
+import extcolors
+
+from colormap import rgb2hex
+
+
+input_name = ''
+output_width = 900 #set the output size
+img = Image.open(input_name)
+wpercent = (output_width/float(img.size[0]))
+hsize = int((float(img.size[1])*float(wpercent)))
+img = img.resize((output_width,hsize), Image.ANTIALIAS)
+
+#save
+resize_name = 'resize_' + input_name #the resized image name
+img.save(resize_name) #output location can be specified before resize_name
+
+#read
+plt.figure(figsize=(9, 9))
+img_url = resize_name
+img = plt.imread(img_url)
+plt.imshow(img)
+plt.axis('off')
+plt.show()
+
+colors_x = extcolors.extract_from_path(img_url, tolerance = 12, limit = 12)
+colors_x
+
+
+def color_to_df(input):
+ colors_pre_list = str(input).replace('([(','').split(', (')[0:-1]
+ df_rgb = [i.split('), ')[0] + ')' for i in colors_pre_list]
+ df_percent = [i.split('), ')[1].replace(')','') for i in colors_pre_list]
+
+ #convert RGB to HEX code
+ df_color_up = [rgb2hex(int(i.split(", ")[0].replace("(","")),
+ int(i.split(", ")[1]),
+ int(i.split(", ")[2].replace(")",""))) for i in df_rgb]
+
+ df = pd.DataFrame(zip(df_color_up, df_percent), columns = ['c_code','occurence'])
+ return df
+
+df_color = color_to_df(colors_x)
+df_color
+
+
+list_color = list(df_color['c_code'])
+list_precent = [int(i) for i in list(df_color['occurence'])]
+text_c = [c + ' ' + str(round(p*100/sum(list_precent),1)) +'%' for c, p in zip(list_color,
+ list_precent)]
+fig, ax = plt.subplots(figsize=(90,90),dpi=10)
+wedges, text = ax.pie(list_precent,
+ labels= text_c,
+ labeldistance= 1.05,
+ colors = list_color,
+ textprops={'fontsize': 120, 'color':'black'}
+ )
+plt.setp(wedges, width=0.3)
+
+#create space in the center
+plt.setp(wedges, width=0.36)
+
+ax.set_aspect("equal")
+fig.set_facecolor('white')
+plt.show()
+
+
+#create background color
+fig, ax = plt.subplots(figsize=(192,108),dpi=10)
+fig.set_facecolor('white')
+plt.savefig('bg.png')
+plt.close(fig)
+
+#create color palette
+bg = plt.imread('bg.png')
+fig = plt.figure(figsize=(90, 90), dpi = 10)
+ax = fig.add_subplot(1,1,1)
+
+x_posi, y_posi, y_posi2 = 320, 25, 25
+for c in list_color:
+ if list_color.index(c) <= 5:
+ y_posi += 125
+ rect = patches.Rectangle((x_posi, y_posi), 290, 115, facecolor = c)
+ ax.add_patch(rect)
+ ax.text(x = x_posi+360, y = y_posi+80, s = c, fontdict={'fontsize': 150})
+ else:
+ y_posi2 += 125
+ rect = patches.Rectangle((x_posi + 800, y_posi2), 290, 115, facecolor = c)
+ ax.add_artist(rect)
+ ax.text(x = x_posi+1160, y = y_posi2+80, s = c, fontdict={'fontsize': 150})
+
+ax.axis('off')
+plt.imshow(bg)
+plt.tight_layout()
+
+img = mpimg.imread('')
+bg = plt.imread('bg.png')
+
+fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(160,120), dpi = 10)
+
+#donut plot
+wedges, text = ax1.pie(list_precent,
+ labels= text_c,
+ labeldistance= 1.05,
+ colors = list_color,
+ textprops={'fontsize': 160, 'color':'black'})
+plt.setp(wedges, width=0.3)
+
+#add image in the center of donut plot
+imagebox = OffsetImage(img, zoom=2.3)
+ab = AnnotationBbox(imagebox, (0, 0))
+ax1.add_artist(ab)
+
+#color palette
+x_posi, y_posi, y_posi2 = 160, -170, -170
+for c in list_color:
+ if list_color.index(c) <= 5:
+ y_posi += 180
+ rect = patches.Rectangle((x_posi, y_posi), 360, 160, facecolor = c)
+ ax2.add_patch(rect)
+ ax2.text(x = x_posi+400, y = y_posi+100, s = c, fontdict={'fontsize': 190})
+ else:
+ y_posi2 += 180
+ rect = patches.Rectangle((x_posi + 1000, y_posi2), 360, 160, facecolor = c)
+ ax2.add_artist(rect)
+ ax2.text(x = x_posi+1400, y = y_posi2+100, s = c, fontdict={'fontsize': 190})
+
+ax2.axis('off')
+fig.set_facecolor('white')
+plt.imshow(bg)
+plt.tight_layout()
diff --git a/cli/watermark.py b/cli/watermark.py
new file mode 100755
index 000000000..50f3553c4
--- /dev/null
+++ b/cli/watermark.py
@@ -0,0 +1,128 @@
+#!/bin/env python
+import os
+import io
+import pathlib
+import argparse
+import filetype
+import numpy as np
+from imwatermark import WatermarkEncoder, WatermarkDecoder
+from PIL import Image
+from PIL.ExifTags import TAGS
+from PIL.TiffImagePlugin import ImageFileDirectory_v2
+from modules.util import log, Map
+import piexif
+import piexif.helper
+
+
+options = Map({ 'method': 'dwtDctSvd', 'type': 'bytes' })
+
+
+def get_exif(image):
+ # using piexif
+ res1 = {}
+ try:
+ exif = piexif.load(image.info["exif"])
+ exif = exif.get("Exif", {})
+ for k, v in exif.items():
+ key = list(vars(piexif.ExifIFD).keys())[list(vars(piexif.ExifIFD).values()).index(k)]
+ res1[key] = piexif.helper.UserComment.load(v)
+ except:
+ pass
+ # using pillow
+ res2 = {}
+ try:
+ res2 = { TAGS[k]: v for k, v in image.getexif().items() if k in TAGS }
+ except:
+ pass
+ return {**res1, **res2}
+
+
+def set_exif(d: dict):
+ ifd = ImageFileDirectory_v2()
+ _TAGS = dict(((v, k) for k, v in TAGS.items())) # enumerate possible exif tags
+ for k, v in d.items():
+ ifd[_TAGS[k]] = v
+ exif_stream = io.BytesIO()
+ ifd.save(exif_stream)
+ bytes = b'Exif\x00\x00' + exif_stream.getvalue()
+ return bytes
+
+
+def get_watermark(image, args):
+ data = np.asarray(image)
+ decoder = WatermarkDecoder(options.type, args.length)
+ bytes = decoder.decode(data, options.method)
+ try:
+ watermark = str(bytes, 'UTF-8').replace('\x00', '')
+ except:
+ watermark = ''
+ return watermark
+
+
+def set_watermark(image, args):
+ data = np.asarray(image)
+ encoder = WatermarkEncoder()
+ encoder.set_watermark(options.type, args.wm.encode('utf-8'))
+ encoded = encoder.encode(data, options.method)
+ image = Image.fromarray(encoded)
+ return image
+
+
+def watermark(args, file):
+ if not os.path.exists(file):
+ log.error({ 'watermark': 'file not found' })
+ return
+ if not filetype.is_image(file):
+ log.error({ 'watermark': 'file is not an image' })
+ return
+ image = Image.open(file)
+ if image.width * image.height < 256 * 256:
+ log.error({ 'watermark': 'image too small' })
+ return
+
+ exif = get_exif(image)
+
+ if args.command == 'read':
+ watermark = get_watermark(image, args)
+ log.info({ 'file': file, 'watermark': watermark, 'exif': exif, 'resolution': f'{image.width}x{image.height}' })
+
+ elif args.command == 'write':
+ metadata = b'' if args.strip else set_exif(exif)
+ if args.output != '':
+ pathlib.Path(args.output).mkdir(parents = True, exist_ok = True)
+ image=set_watermark(image, args)
+ fn = os.path.join(args.output, file)
+ image.save(fn, exif=metadata)
+
+ if args.verify:
+ data = np.asarray(image)
+ decoder = WatermarkDecoder(options.type, args.length)
+ bytes = decoder.decode(data, options.method)
+ if bytes.startswith(b'\xff'):
+ watermark = ''
+ else:
+ watermark = str(bytes, 'UTF-8').replace('\x00', '')
+ else:
+ watermark = args.wm
+
+ log.info({ 'file': fn, 'watermark': watermark, 'exif': None if args.strip else exif, 'resolution': f'{image.width}x{image.height}' })
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description = 'image watermarking')
+ parser.add_argument('command', choices = ['read', 'write'])
+ parser.add_argument('--wm', type=str, required=False, default='mm', help='watermark string')
+ parser.add_argument('--strip', default=False, action='store_true', help = "strip existing exif data")
+ parser.add_argument('--verify', default=False, action='store_true', help = "verify watermark during write")
+ parser.add_argument('--length', type=int, default=16, help="watermark length in bits")
+ parser.add_argument('--output', type=str, required=False, default='', help='folder to store images, default is overwrite in-place')
+ parser.add_argument('input', type=str, nargs='*')
+ args = parser.parse_args()
+ log.info({ 'watermark args': vars(args), 'options': options })
+ for arg in args.input:
+ if os.path.isfile(arg):
+ watermark(args, arg)
+ elif os.path.isdir(arg):
+ for root, _dirs, files in os.walk(arg):
+ for f in files:
+ watermark(args, os.path.join(root, f))
diff --git a/config.json b/config.json
index 5eaa11e06..451bf785b 100644
--- a/config.json
+++ b/config.json
@@ -20,9 +20,6 @@
"directories_max_prompt_words": 8,
"disable_weights_auto_swap": false,
"disabled_extensions": [
- "embedding-inspector",
- "sd-webui-additional-networks",
- "stable-diffusion-webui-instruct-pix2pix",
"ScuNET"
],
"do_not_add_watermark": true,
@@ -134,7 +131,7 @@
"sd_checkpoint_hash": "cc6cb27103417325ff94f52b7a5d2dde45a7515b25c255d8e396c90014281516",
"sd_hypernetwork_strength": 1.0,
"sd_hypernetwork": "None",
- "sd_lora": "None",
+ "sd_lora": "",
"sd_model_checkpoint": "sd-v15-runwayml.ckpt [cc6cb27103]",
"sd_vae_as_default": false,
"sd_vae_checkpoint_cache": 0,
@@ -185,5 +182,12 @@
"DPM2 a Karras",
"LMS Karras"
],
- "images_logger_warning": false
+ "images_logger_warning": false,
+ "images_logger_debug": false,
+ "images_scan_exif": false,
+ "additional_networks_extra_lora_path": "",
+ "additional_networks_sort_models_by": "name",
+ "additional_networks_model_name_filter": "",
+ "additional_networks_xy_grid_model_metadata": "",
+ "additional_networks_hash_thread_count": 1.0
}
\ No newline at end of file
diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser
index 1f325cdc4..818d59a66 160000
--- a/extensions-builtin/stable-diffusion-webui-images-browser
+++ b/extensions-builtin/stable-diffusion-webui-images-browser
@@ -1 +1 @@
-Subproject commit 1f325cdc49f1c6a567a2b63cda02ce217560a538
+Subproject commit 818d59a6629ed70d13890ccf5d0299939a550596
diff --git a/requirements_versions.txt b/requirements_versions.txt
index 5931d2ccc..88f4fcadd 100644
--- a/requirements_versions.txt
+++ b/requirements_versions.txt
@@ -23,4 +23,4 @@ scikit-image==0.19.3
timm==0.6.12
torchdiffeq==0.2.3
torchsde==0.2.5
-transformers==4.25.1
+transformers==4.26.0
diff --git a/ui-config.json b/ui-config.json
index 32b2b1889..fa980858f 100644
--- a/ui-config.json
+++ b/ui-config.json
@@ -1012,5 +1012,105 @@
"train/Low-rank approximation sum threshold (lower value means smaller file size, 1 to disable)/value": 0.5,
"train/Low-rank approximation sum threshold (lower value means smaller file size, 1 to disable)/minimum": 0,
"train/Low-rank approximation sum threshold (lower value means smaller file size, 1 to disable)/maximum": 1,
- "train/Low-rank approximation sum threshold (lower value means smaller file size, 1 to disable)/step": 0.01
+ "train/Low-rank approximation sum threshold (lower value means smaller file size, 1 to disable)/step": 0.01,
+ "customscript/prompt_matrix.py/txt2img/Select joining char/visible": true,
+ "customscript/prompt_matrix.py/txt2img/Select joining char/value": "comma",
+ "customscript/prompt_matrix.py/txt2img/Grid margins (px)/visible": true,
+ "customscript/prompt_matrix.py/txt2img/Grid margins (px)/value": 0,
+ "customscript/prompt_matrix.py/txt2img/Grid margins (px)/minimum": 0,
+ "customscript/prompt_matrix.py/txt2img/Grid margins (px)/maximum": 100,
+ "customscript/prompt_matrix.py/txt2img/Grid margins (px)/step": 2,
+ "customscript/xyz_grid.py/txt2img/Grid margins (px)/visible": true,
+ "customscript/xyz_grid.py/txt2img/Grid margins (px)/value": 0,
+ "customscript/xyz_grid.py/txt2img/Grid margins (px)/minimum": 0,
+ "customscript/xyz_grid.py/txt2img/Grid margins (px)/maximum": 100,
+ "customscript/xyz_grid.py/txt2img/Grid margins (px)/step": 2,
+ "customscript/prompt_matrix.py/img2img/Select joining char/visible": true,
+ "customscript/prompt_matrix.py/img2img/Select joining char/value": "comma",
+ "customscript/prompt_matrix.py/img2img/Grid margins (px)/visible": true,
+ "customscript/prompt_matrix.py/img2img/Grid margins (px)/value": 0,
+ "customscript/prompt_matrix.py/img2img/Grid margins (px)/minimum": 0,
+ "customscript/prompt_matrix.py/img2img/Grid margins (px)/maximum": 100,
+ "customscript/prompt_matrix.py/img2img/Grid margins (px)/step": 2,
+ "customscript/xyz_grid.py/img2img/Grid margins (px)/visible": true,
+ "customscript/xyz_grid.py/img2img/Grid margins (px)/value": 0,
+ "customscript/xyz_grid.py/img2img/Grid margins (px)/minimum": 0,
+ "customscript/xyz_grid.py/img2img/Grid margins (px)/maximum": 100,
+ "customscript/xyz_grid.py/img2img/Grid margins (px)/step": 2,
+ "customscript/aesthetic.py/txt2img/Aesthetic weight/visible": true,
+ "customscript/aesthetic.py/txt2img/Aesthetic weight/value": 0.9,
+ "customscript/aesthetic.py/txt2img/Aesthetic weight/minimum": 0,
+ "customscript/aesthetic.py/txt2img/Aesthetic weight/maximum": 1,
+ "customscript/aesthetic.py/txt2img/Aesthetic weight/step": 0.01,
+ "customscript/aesthetic.py/txt2img/Aesthetic steps/visible": true,
+ "customscript/aesthetic.py/txt2img/Aesthetic steps/value": 5,
+ "customscript/aesthetic.py/txt2img/Aesthetic steps/minimum": 0,
+ "customscript/aesthetic.py/txt2img/Aesthetic steps/maximum": 50,
+ "customscript/aesthetic.py/txt2img/Aesthetic steps/step": 1,
+ "customscript/aesthetic.py/txt2img/Aesthetic learning rate/visible": true,
+ "customscript/aesthetic.py/txt2img/Aesthetic learning rate/value": "0.0001",
+ "customscript/aesthetic.py/txt2img/Slerp interpolation/visible": true,
+ "customscript/aesthetic.py/txt2img/Slerp interpolation/value": false,
+ "customscript/aesthetic.py/txt2img/Aesthetic imgs embedding/visible": true,
+ "customscript/aesthetic.py/txt2img/Aesthetic imgs embedding/value": "None",
+ "customscript/aesthetic.py/txt2img/Aesthetic text for imgs/visible": true,
+ "customscript/aesthetic.py/txt2img/Aesthetic text for imgs/value": "",
+ "customscript/aesthetic.py/txt2img/Slerp angle/visible": true,
+ "customscript/aesthetic.py/txt2img/Slerp angle/value": 0.1,
+ "customscript/aesthetic.py/txt2img/Slerp angle/minimum": 0,
+ "customscript/aesthetic.py/txt2img/Slerp angle/maximum": 1,
+ "customscript/aesthetic.py/txt2img/Slerp angle/step": 0.01,
+ "customscript/aesthetic.py/txt2img/Is negative text/visible": true,
+ "customscript/aesthetic.py/txt2img/Is negative text/value": false,
+ "customscript/aesthetic.py/img2img/Aesthetic weight/visible": true,
+ "customscript/aesthetic.py/img2img/Aesthetic weight/value": 0.9,
+ "customscript/aesthetic.py/img2img/Aesthetic weight/minimum": 0,
+ "customscript/aesthetic.py/img2img/Aesthetic weight/maximum": 1,
+ "customscript/aesthetic.py/img2img/Aesthetic weight/step": 0.01,
+ "customscript/aesthetic.py/img2img/Aesthetic steps/visible": true,
+ "customscript/aesthetic.py/img2img/Aesthetic steps/value": 5,
+ "customscript/aesthetic.py/img2img/Aesthetic steps/minimum": 0,
+ "customscript/aesthetic.py/img2img/Aesthetic steps/maximum": 50,
+ "customscript/aesthetic.py/img2img/Aesthetic steps/step": 1,
+ "customscript/aesthetic.py/img2img/Aesthetic learning rate/visible": true,
+ "customscript/aesthetic.py/img2img/Aesthetic learning rate/value": "0.0001",
+ "customscript/aesthetic.py/img2img/Slerp interpolation/visible": true,
+ "customscript/aesthetic.py/img2img/Slerp interpolation/value": false,
+ "customscript/aesthetic.py/img2img/Aesthetic imgs embedding/visible": true,
+ "customscript/aesthetic.py/img2img/Aesthetic imgs embedding/value": "None",
+ "customscript/aesthetic.py/img2img/Aesthetic text for imgs/visible": true,
+ "customscript/aesthetic.py/img2img/Aesthetic text for imgs/value": "",
+ "customscript/aesthetic.py/img2img/Slerp angle/visible": true,
+ "customscript/aesthetic.py/img2img/Slerp angle/value": 0.1,
+ "customscript/aesthetic.py/img2img/Slerp angle/minimum": 0,
+ "customscript/aesthetic.py/img2img/Slerp angle/maximum": 1,
+ "customscript/aesthetic.py/img2img/Slerp angle/step": 0.01,
+ "customscript/aesthetic.py/img2img/Is negative text/visible": true,
+ "customscript/aesthetic.py/img2img/Is negative text/value": false,
+ "train/Regularization dataset directory (optional)/visible": true,
+ "train/Regularization dataset directory (optional)/value": "",
+ "train/Prior-preservation loss weight/visible": true,
+ "train/Prior-preservation loss weight/value": 1.0,
+ "train/Prior-preservation loss weight/minimum": 0.0,
+ "train/Prior-preservation loss weight/maximum": 10.0,
+ "train/Prior-preservation loss weight/step": 0.1,
+ "train/Batch size/minimum": 1,
+ "train/Batch size/maximum": 1024,
+ "train/Batch size/step": 1,
+ "customscript/aesthetic.py/txt2img/Learning rate/visible": true,
+ "customscript/aesthetic.py/txt2img/Learning rate/value": "0.0001",
+ "customscript/aesthetic.py/txt2img/Embedding/visible": true,
+ "customscript/aesthetic.py/txt2img/Embedding/value": "None",
+ "customscript/aesthetic.py/txt2img/Aesthetic text/visible": true,
+ "customscript/aesthetic.py/txt2img/Aesthetic text/value": "",
+ "customscript/aesthetic.py/txt2img/Negative/visible": true,
+ "customscript/aesthetic.py/txt2img/Negative/value": false,
+ "customscript/aesthetic.py/img2img/Learning rate/visible": true,
+ "customscript/aesthetic.py/img2img/Learning rate/value": "0.0001",
+ "customscript/aesthetic.py/img2img/Embedding/visible": true,
+ "customscript/aesthetic.py/img2img/Embedding/value": "None",
+ "customscript/aesthetic.py/img2img/Aesthetic text/visible": true,
+ "customscript/aesthetic.py/img2img/Aesthetic text/value": "",
+ "customscript/aesthetic.py/img2img/Negative/visible": true,
+ "customscript/aesthetic.py/img2img/Negative/value": false
}
\ No newline at end of file
diff --git a/wiki b/wiki
index de9e86009..ccf8e5af0 160000
--- a/wiki
+++ b/wiki
@@ -1 +1 @@
-Subproject commit de9e8600904ee70e83a9d2cf72e0cf0e667ce68e
+Subproject commit ccf8e5af0cfe33016c55256c875947a67b6ad4eb