Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2693bec08 | ||
|
|
b28cf84c36 | ||
|
|
dfb2e830d9 | ||
|
|
4a626f6ea6 | ||
|
|
6a4db7b9a5 | ||
|
|
d51847c184 | ||
|
|
91c56c51c7 | ||
|
|
3030dcfefd | ||
|
|
5dde56afe3 | ||
|
|
16fb8d24d4 | ||
|
|
f3de9bf7d9 | ||
|
|
9f267af3f7 | ||
|
|
6153d9d9e9 | ||
|
|
d5520d43fd | ||
|
|
94aeb5dec9 | ||
|
|
e16d762800 | ||
|
|
35229d9488 | ||
|
|
3daf9cac46 | ||
|
|
26f733a026 | ||
|
|
bb2732c1c7 | ||
|
|
c4e90bf689 | ||
|
|
c9430e53f6 | ||
|
|
c253d6bdab | ||
|
|
f6aa0cdb0b | ||
|
|
4c51752464 | ||
|
|
493032a7af | ||
|
|
df81de0d2f | ||
|
|
74c5f7974c | ||
|
|
0cfbd59d6d | ||
|
|
928b246c9e | ||
|
|
a0e819de90 | ||
|
|
d62fbcc5aa | ||
|
|
7bf76af40a | ||
|
|
cf150757b5 | ||
|
|
efc8ed13e1 | ||
|
|
6bea45d495 | ||
|
|
21f2a706bb | ||
|
|
f9f9d04b5f | ||
|
|
e73e2ce2fd | ||
|
|
859fff3700 | ||
|
|
f07d789b79 | ||
|
|
66b09bbfec |
@@ -14,4 +14,5 @@ __pycache__
|
||||
/styles.csv
|
||||
/styles.csv.bak
|
||||
/webui-user.bat
|
||||
/webui-user.sh
|
||||
/interrogate
|
||||
|
||||
@@ -70,6 +70,39 @@ RealESRGAN into the directory with ESRGAN models. Thank you.
|
||||
- _*(optional)*_ place `GFPGANv1.3.pth` into webui directory, next to `webui.bat`.
|
||||
- run `webui-user.bat` from Windows Explorer. Run it as a normal user, ***not*** as administrator.
|
||||
|
||||
### Running on AMD GPUs
|
||||
See the [wiki article](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Running-using-AMD-GPUs) by [cryzed](https://github.com/cryzed).
|
||||
|
||||
### Linux Automatic installation/launch
|
||||
|
||||
Prequisites:
|
||||
- For Debian-based:
|
||||
```commandline
|
||||
sudo apt install wget git python3 python3-venv
|
||||
```
|
||||
- For Red Hat-based:
|
||||
```commandline
|
||||
sudo dnf install wget git python3
|
||||
```
|
||||
|
||||
|
||||
- If you want to install to default directory `/home/$(whoami)/stable-diffusion-webui/`, you can launch directly:
|
||||
```commandline
|
||||
bash <(wget -qO- https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh)
|
||||
```
|
||||
|
||||
|
||||
- If you want to customize the installation just `git clone` the repo where you want it,
|
||||
change the variables in `webui-user.sh` and launch in console `bash webui.sh`.
|
||||
|
||||
|
||||
|
||||
- place `model.ckpt` into webui directory, next to `webui.py`.
|
||||
- _*(optional)*_ place `GFPGANv1.3.pth` into webui directory, next to `webui.py`.
|
||||
- run `bash webui.sh`. Run it as a normal user, ***not*** as root.
|
||||
|
||||
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
- if your version of Python is not in PATH (or if another version is), edit `webui-user.bat`, and modify the
|
||||
@@ -79,7 +112,7 @@ You can do this for python, but not for git.
|
||||
to enable appropriate optimization according to low VRAM guide below (for example, `set COMMANDLINE_ARGS=--medvram --opt-split-attention`).
|
||||
- to prevent the creation of virtual environment and use your system python, use custom parameter replacing `set VENV_DIR=-` (see below).
|
||||
- webui.bat installs requirements from files `requirements_versions.txt`, which lists versions for modules specifically compatible with
|
||||
Python 3.10.6. If you choose to install for a different version of python, using custom parameter `set REQS_FILE=requirements.txt`
|
||||
Python 3.10.6. If you choose to install for a different version of python, using custom parameter `set REQS_FILE=requirements.txt`
|
||||
may help (but I still recommend you to just use the recommended version of python).
|
||||
- if you feel you broke something and want to reinstall from scratch, delete directories: `venv`, `repositories`.
|
||||
- if you get a green or black screen instead of generated pictures, you have a card that doesn't support half precision
|
||||
@@ -133,7 +166,7 @@ Here's a list of optimization arguments:
|
||||
- If you have 4GB VRAM and want to make 512x512 (or maybe up to 640x640) images, use `--medvram`.
|
||||
- If you have 4GB VRAM and want to make 512x512 images, but you get an out of memory error with `--medvram`, use `--medvram --opt-split-attention` instead.
|
||||
- If you have 4GB VRAM and want to make 512x512 images, and you still get an out of memory error, use `--lowvram --always-batch-cond-uncond --opt-split-attention` instead.
|
||||
- If you have 4GB VRAM and want to make images larger than you can with `--medvram`, use `--lowvram --opt-split-attention`.
|
||||
- If you have 4GB VRAM and want to make images larger than you can with `--medvram`, use `--lowvram --opt-split-attention`.
|
||||
- If you have more VRAM and want to make larger images than you can usually make (for example 1024x1024 instead of 512x512), use `--medvram --opt-split-attention`. You can use `--lowvram`
|
||||
also but the effect will likely be barely noticeable.
|
||||
- Otherwise, do not use any of those.
|
||||
@@ -141,7 +174,7 @@ also but the effect will likely be barely noticeable.
|
||||
### Running online
|
||||
|
||||
Use the `--share` option to run online. You will get a xxx.app.gradio link. This is the intended way to use the
|
||||
program in collabs. You may set up authentication for said gradio shared instance with the flag `--gradio-auth username:password`, optionally providing multiple sets of usernames and passwords separated by commas.
|
||||
program in Colab. You may set up authentication for said gradio shared instance with the flag `--gradio-auth username:password`, optionally providing multiple sets of usernames and passwords separated by commas.
|
||||
|
||||
Use `--listen` to make the server listen to network connections. This will allow computers on the local network
|
||||
to access the UI, and if you configure port forwarding, also computers on the internet.
|
||||
@@ -150,9 +183,9 @@ Use `--port xxxx` to make the server listen on a specific port, xxxx being the w
|
||||
all ports below 1024 need root/admin rights, for this reason it is advised to use a port above 1024.
|
||||
Defaults to port 7860 if available.
|
||||
|
||||
### Google collab
|
||||
### Google Colab
|
||||
|
||||
If you don't want or can't run locally, here is a Google colab that allows you to run the webui:
|
||||
If you don't want or can't run locally, here is a Google Colab that allows you to run the webui:
|
||||
|
||||
https://colab.research.google.com/drive/1Iy-xW9t1-OQWhb0hNxueGij8phCyluOh
|
||||
|
||||
@@ -309,6 +342,8 @@ After that follow the instructions in the `Manual instructions` section starting
|
||||
|
||||
[A list of custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-scripts-from-users), along with installation instructions.
|
||||
|
||||
|
||||
|
||||
### img2img alternative test
|
||||
- see [this post](https://www.reddit.com/r/StableDiffusion/comments/xboy90/a_better_way_of_doing_img2img_by_finding_the/) on ebaumsworld.com for context.
|
||||
- find it in scripts section
|
||||
@@ -330,4 +365,4 @@ After that follow the instructions in the `Manual instructions` section starting
|
||||
- Idea for SD upscale - https://github.com/jquesnelle/txt2imghd
|
||||
- CLIP interrogator idea and borrowing some code - https://github.com/pharmapsychotic/clip-interrogator
|
||||
- Initial Gradio script - posted on 4chan by an Anonymous user. Thank you Anonymous user.
|
||||
- (You)
|
||||
- (You)
|
||||
@@ -47,13 +47,11 @@ def setup_codeformer():
|
||||
def __init__(self):
|
||||
self.net = None
|
||||
self.face_helper = None
|
||||
if shared.device.type == 'mps': # CodeFormer currently does not support mps backend
|
||||
shared.device_codeformer = torch.device('cpu')
|
||||
|
||||
def create_models(self):
|
||||
|
||||
if self.net is not None and self.face_helper is not None:
|
||||
self.net.to(shared.device)
|
||||
self.net.to(devices.device_codeformer)
|
||||
return self.net, self.face_helper
|
||||
|
||||
net = net_class(dim_embd=512, codebook_size=1024, n_head=8, n_layers=9, connect_list=['32', '64', '128', '256']).to(devices.device_codeformer)
|
||||
@@ -66,7 +64,7 @@ def setup_codeformer():
|
||||
|
||||
self.net = net
|
||||
self.face_helper = face_helper
|
||||
self.net.to(shared.device)
|
||||
self.net.to(devices.device_codeformer)
|
||||
|
||||
return net, face_helper
|
||||
|
||||
|
||||
+8
-2
@@ -7,6 +7,7 @@ import modules.gfpgan_model
|
||||
from modules.ui import plaintext_to_html
|
||||
import modules.codeformer_model
|
||||
import piexif
|
||||
import piexif.helper
|
||||
|
||||
|
||||
cached_images = {}
|
||||
@@ -69,7 +70,7 @@ def run_extras(image, gfpgan_visibility, codeformer_visibility, codeformer_weigh
|
||||
while len(cached_images) > 2:
|
||||
del cached_images[next(iter(cached_images.keys()))]
|
||||
|
||||
images.save_image(image, outpath, "", None, info=info, extension=opts.samples_format, short_filename=True, no_prompt=True, pnginfo_section_name="extras", existing_info=existing_pnginfo)
|
||||
images.save_image(image, path=outpath, basename="", seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo)
|
||||
|
||||
return image, plaintext_to_html(info), ''
|
||||
|
||||
@@ -80,7 +81,12 @@ def run_pnginfo(image):
|
||||
if "exif" in image.info:
|
||||
exif = piexif.load(image.info["exif"])
|
||||
exif_comment = (exif or {}).get("Exif", {}).get(piexif.ExifIFD.UserComment, b'')
|
||||
exif_comment = exif_comment.decode("utf8", 'ignore')
|
||||
try:
|
||||
exif_comment = piexif.helper.UserComment.load(exif_comment)
|
||||
except ValueError:
|
||||
exif_comment = exif_comment.decode('utf8', errors="ignore")
|
||||
|
||||
|
||||
items['exif comment'] = exif_comment
|
||||
|
||||
for field in ['jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif']:
|
||||
|
||||
+30
-10
@@ -13,7 +13,7 @@ import string
|
||||
|
||||
import modules.shared
|
||||
from modules import sd_samplers, shared
|
||||
from modules.shared import opts
|
||||
from modules.shared import opts, cmd_opts
|
||||
|
||||
LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
|
||||
|
||||
@@ -277,13 +277,33 @@ def apply_filename_pattern(x, p, seed, prompt):
|
||||
x = x.replace("[model_hash]", shared.sd_model_hash)
|
||||
x = x.replace("[date]", datetime.date.today().isoformat())
|
||||
|
||||
if cmd_opts.hide_ui_dir_config:
|
||||
x = re.sub(r'^[\\/]+|\.{2,}[\\/]+|[\\/]+\.{2,}', '', x)
|
||||
|
||||
return x
|
||||
|
||||
def get_next_sequence_number(path, basename):
|
||||
"""
|
||||
Determines and returns the next sequence number to use when saving an image in the specified directory.
|
||||
|
||||
def save_image(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, pnginfo_section_name='parameters', p=None, existing_info=None):
|
||||
# would be better to add this as an argument in future, but will do for now
|
||||
is_a_grid = basename != ""
|
||||
The sequence starts at 0.
|
||||
"""
|
||||
result = -1
|
||||
if basename != '':
|
||||
basename = basename + "-"
|
||||
|
||||
prefix_length = len(basename)
|
||||
for p in os.listdir(path):
|
||||
if p.startswith(basename):
|
||||
l = os.path.splitext(p[prefix_length:])[0].split('-') #splits the filename (removing the basename first if one is defined, so the sequence number is always the first element)
|
||||
try:
|
||||
result = max(int(l[0]), result)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return result + 1
|
||||
|
||||
def save_image(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None):
|
||||
if short_filename or prompt is None or seed is None:
|
||||
file_decoration = ""
|
||||
elif opts.save_to_dirs:
|
||||
@@ -307,7 +327,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i
|
||||
else:
|
||||
pnginfo = None
|
||||
|
||||
save_to_dirs = (is_a_grid and opts.grid_save_to_dirs) or (not is_a_grid and opts.save_to_dirs)
|
||||
save_to_dirs = (grid and opts.grid_save_to_dirs) or (not grid and opts.save_to_dirs and not no_prompt)
|
||||
|
||||
if save_to_dirs:
|
||||
dirname = apply_filename_pattern(opts.directories_filename_pattern or "[prompt_words]", p, seed, prompt)
|
||||
@@ -315,21 +335,21 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i
|
||||
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
filecount = len([x for x in os.listdir(path) if os.path.splitext(x)[1] == '.' + extension])
|
||||
basecount = get_next_sequence_number(path, basename)
|
||||
fullfn = "a.png"
|
||||
fullfn_without_extension = "a"
|
||||
for i in range(500):
|
||||
fn = f"{filecount+i:05}" if basename == '' else f"{basename}-{filecount+i:04}"
|
||||
fn = f"{basecount+i:05}" if basename == '' else f"{basename}-{basecount+i:04}"
|
||||
fullfn = os.path.join(path, f"{fn}{file_decoration}.{extension}")
|
||||
fullfn_without_extension = os.path.join(path, f"{fn}{file_decoration}")
|
||||
if not os.path.exists(fullfn):
|
||||
break
|
||||
|
||||
if extension.lower() in ("jpg", "jpeg"):
|
||||
if extension.lower() in ("jpg", "jpeg", "webp"):
|
||||
exif_bytes = piexif.dump({
|
||||
"Exif": {
|
||||
piexif.ExifIFD.UserComment: info.encode("utf8"),
|
||||
}
|
||||
piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(info, encoding="unicode")
|
||||
},
|
||||
})
|
||||
else:
|
||||
exif_bytes = None
|
||||
|
||||
+4
-41
@@ -11,10 +11,9 @@ from modules.ui import plaintext_to_html
|
||||
import modules.images as images
|
||||
import modules.scripts
|
||||
|
||||
def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init_img_with_mask, init_mask, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, mode: int, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, denoising_strength_change_factor: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, resize_mode: int, upscaler_index: str, upscale_overlap: int, inpaint_full_res: bool, inpainting_mask_invert: int, *args):
|
||||
def img2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_mask, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, mode: int, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, resize_mode: int, upscaler_index: str, upscale_overlap: int, inpaint_full_res: bool, inpainting_mask_invert: int, *args):
|
||||
is_inpaint = mode == 1
|
||||
is_loopback = mode == 2
|
||||
is_upscale = mode == 3
|
||||
is_upscale = mode == 2
|
||||
|
||||
if is_inpaint:
|
||||
if mask_mode == 0:
|
||||
@@ -38,7 +37,7 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init
|
||||
outpath_grids=opts.outdir_grids or opts.outdir_img2img_grids,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
prompt_style=prompt_style,
|
||||
styles=[prompt_style, prompt_style2],
|
||||
seed=seed,
|
||||
subseed=subseed,
|
||||
subseed_strength=subseed_strength,
|
||||
@@ -61,46 +60,10 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init
|
||||
denoising_strength=denoising_strength,
|
||||
inpaint_full_res=inpaint_full_res,
|
||||
inpainting_mask_invert=inpainting_mask_invert,
|
||||
extra_generation_params={
|
||||
"Denoising strength change factor": (denoising_strength_change_factor if is_loopback else None)
|
||||
}
|
||||
)
|
||||
print(f"\nimg2img: {prompt}", file=shared.progress_print_out)
|
||||
|
||||
if is_loopback:
|
||||
output_images, info = None, None
|
||||
history = []
|
||||
initial_seed = None
|
||||
initial_info = None
|
||||
|
||||
state.job_count = n_iter
|
||||
|
||||
for i in range(n_iter):
|
||||
p.n_iter = 1
|
||||
p.batch_size = 1
|
||||
p.do_not_save_grid = True
|
||||
|
||||
state.job = f"Batch {i + 1} out of {n_iter}"
|
||||
processed = process_images(p)
|
||||
|
||||
if initial_seed is None:
|
||||
initial_seed = processed.seed
|
||||
initial_info = processed.info
|
||||
|
||||
init_img = processed.images[0]
|
||||
|
||||
p.init_images = [init_img]
|
||||
p.seed = processed.seed + 1
|
||||
p.denoising_strength = min(max(p.denoising_strength * denoising_strength_change_factor, 0.1), 1)
|
||||
history.append(processed.images[0])
|
||||
|
||||
grid = images.image_grid(history, batch_size, rows=1)
|
||||
|
||||
images.save_image(grid, p.outpath_grids, "grid", initial_seed, prompt, opts.grid_format, info=info, short_filename=not opts.grid_extended_filename, p=p)
|
||||
|
||||
processed = Processed(p, history, initial_seed, initial_info)
|
||||
|
||||
elif is_upscale:
|
||||
if is_upscale:
|
||||
initial_info = None
|
||||
|
||||
processing.fix_seed(p)
|
||||
|
||||
+13
-12
@@ -12,7 +12,7 @@ import cv2
|
||||
from skimage import exposure
|
||||
|
||||
import modules.sd_hijack
|
||||
from modules import devices
|
||||
from modules import devices, prompt_parser
|
||||
from modules.sd_hijack import model_hijack
|
||||
from modules.sd_samplers import samplers, samplers_for_img2img
|
||||
from modules.shared import opts, cmd_opts, state
|
||||
@@ -46,14 +46,14 @@ def apply_color_correction(correction, image):
|
||||
|
||||
|
||||
class StableDiffusionProcessing:
|
||||
def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", prompt_style="None", seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None):
|
||||
def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None):
|
||||
self.sd_model = sd_model
|
||||
self.outpath_samples: str = outpath_samples
|
||||
self.outpath_grids: str = outpath_grids
|
||||
self.prompt: str = prompt
|
||||
self.prompt_for_display: str = None
|
||||
self.negative_prompt: str = (negative_prompt or "")
|
||||
self.prompt_style: str = prompt_style
|
||||
self.styles: str = styles
|
||||
self.seed: int = seed
|
||||
self.subseed: int = subseed
|
||||
self.subseed_strength: float = subseed_strength
|
||||
@@ -180,9 +180,9 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
|
||||
modules.sd_hijack.model_hijack.apply_circular(p.tiling)
|
||||
|
||||
comments = []
|
||||
comments = {}
|
||||
|
||||
modules.styles.apply_style(p, shared.prompt_styles[p.prompt_style])
|
||||
shared.prompt_styles.apply_styles(p)
|
||||
|
||||
if type(p.prompt) == list:
|
||||
all_prompts = p.prompt
|
||||
@@ -247,11 +247,14 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
seeds = all_seeds[n * p.batch_size:(n + 1) * p.batch_size]
|
||||
subseeds = all_subseeds[n * p.batch_size:(n + 1) * p.batch_size]
|
||||
|
||||
uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt])
|
||||
c = p.sd_model.get_learned_conditioning(prompts)
|
||||
#uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt])
|
||||
#c = p.sd_model.get_learned_conditioning(prompts)
|
||||
uc = prompt_parser.get_learned_conditioning(len(prompts) * [p.negative_prompt], p.steps)
|
||||
c = prompt_parser.get_learned_conditioning(prompts, p.steps)
|
||||
|
||||
if len(model_hijack.comments) > 0:
|
||||
comments += model_hijack.comments
|
||||
for comment in model_hijack.comments:
|
||||
comments[comment] = 1
|
||||
|
||||
# we manually generate all input noises because each one should have a specific seed
|
||||
x = create_random_tensors([opt_C, p.height // opt_f, p.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w)
|
||||
@@ -312,12 +315,10 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
state.nextjob()
|
||||
|
||||
unwanted_grid_because_of_img_count = len(output_images) < 2 and opts.grid_only_if_multiple
|
||||
if not p.do_not_save_grid and not unwanted_grid_because_of_img_count:
|
||||
return_grid = opts.return_grid
|
||||
|
||||
if (opts.return_grid or opts.grid_save) and not p.do_not_save_grid and not unwanted_grid_because_of_img_count:
|
||||
grid = images.image_grid(output_images, p.batch_size)
|
||||
|
||||
if return_grid:
|
||||
if opts.return_grid:
|
||||
output_images.insert(0, grid)
|
||||
|
||||
if opts.grid_save:
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import re
|
||||
from collections import namedtuple
|
||||
import torch
|
||||
|
||||
import modules.shared as shared
|
||||
|
||||
re_prompt = re.compile(r'''
|
||||
(.*?)
|
||||
\[
|
||||
([^]:]+):
|
||||
(?:([^]:]*):)?
|
||||
([0-9]*\.?[0-9]+)
|
||||
]
|
||||
|
|
||||
(.+)
|
||||
''', re.X)
|
||||
|
||||
# a prompt like this: "fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]"
|
||||
# will be represented with prompt_schedule like this (assuming steps=100):
|
||||
# [25, 'fantasy landscape with a mountain and an oak in foreground shoddy']
|
||||
# [50, 'fantasy landscape with a lake and an oak in foreground in background shoddy']
|
||||
# [60, 'fantasy landscape with a lake and an oak in foreground in background masterful']
|
||||
# [75, 'fantasy landscape with a lake and an oak in background masterful']
|
||||
# [100, 'fantasy landscape with a lake and a christmas tree in background masterful']
|
||||
|
||||
|
||||
def get_learned_conditioning_prompt_schedules(prompts, steps):
|
||||
res = []
|
||||
cache = {}
|
||||
|
||||
for prompt in prompts:
|
||||
prompt_schedule: list[list[str | int]] = [[steps, ""]]
|
||||
|
||||
cached = cache.get(prompt, None)
|
||||
if cached is not None:
|
||||
res.append(cached)
|
||||
|
||||
for m in re_prompt.finditer(prompt):
|
||||
plaintext = m.group(1) if m.group(5) is None else m.group(5)
|
||||
concept_from = m.group(2)
|
||||
concept_to = m.group(3)
|
||||
if concept_to is None:
|
||||
concept_to = concept_from
|
||||
concept_from = ""
|
||||
swap_position = float(m.group(4)) if m.group(4) is not None else None
|
||||
|
||||
if swap_position is not None:
|
||||
if swap_position < 1:
|
||||
swap_position = swap_position * steps
|
||||
swap_position = int(min(swap_position, steps))
|
||||
|
||||
swap_index = None
|
||||
found_exact_index = False
|
||||
for i in range(len(prompt_schedule)):
|
||||
end_step = prompt_schedule[i][0]
|
||||
prompt_schedule[i][1] += plaintext
|
||||
|
||||
if swap_position is not None and swap_index is None:
|
||||
if swap_position == end_step:
|
||||
swap_index = i
|
||||
found_exact_index = True
|
||||
|
||||
if swap_position < end_step:
|
||||
swap_index = i
|
||||
|
||||
if swap_index is not None:
|
||||
if not found_exact_index:
|
||||
prompt_schedule.insert(swap_index, [swap_position, prompt_schedule[swap_index][1]])
|
||||
|
||||
for i in range(len(prompt_schedule)):
|
||||
end_step = prompt_schedule[i][0]
|
||||
must_replace = swap_position < end_step
|
||||
|
||||
prompt_schedule[i][1] += concept_to if must_replace else concept_from
|
||||
|
||||
res.append(prompt_schedule)
|
||||
cache[prompt] = prompt_schedule
|
||||
#for t in prompt_schedule:
|
||||
# print(t)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
ScheduledPromptConditioning = namedtuple("ScheduledPromptConditioning", ["end_at_step", "cond"])
|
||||
ScheduledPromptBatch = namedtuple("ScheduledPromptBatch", ["shape", "schedules"])
|
||||
|
||||
|
||||
def get_learned_conditioning(prompts, steps):
|
||||
|
||||
res = []
|
||||
|
||||
prompt_schedules = get_learned_conditioning_prompt_schedules(prompts, steps)
|
||||
cache = {}
|
||||
|
||||
for prompt, prompt_schedule in zip(prompts, prompt_schedules):
|
||||
|
||||
cached = cache.get(prompt, None)
|
||||
if cached is not None:
|
||||
res.append(cached)
|
||||
|
||||
texts = [x[1] for x in prompt_schedule]
|
||||
conds = shared.sd_model.get_learned_conditioning(texts)
|
||||
|
||||
cond_schedule = []
|
||||
for i, (end_at_step, text) in enumerate(prompt_schedule):
|
||||
cond_schedule.append(ScheduledPromptConditioning(end_at_step, conds[i]))
|
||||
|
||||
cache[prompt] = cond_schedule
|
||||
res.append(cond_schedule)
|
||||
|
||||
return ScheduledPromptBatch((len(prompts),) + res[0][0].cond.shape, res)
|
||||
|
||||
|
||||
def reconstruct_cond_batch(c: ScheduledPromptBatch, current_step):
|
||||
res = torch.zeros(c.shape)
|
||||
for i, cond_schedule in enumerate(c.schedules):
|
||||
target_index = 0
|
||||
for curret_index, (end_at, cond) in enumerate(cond_schedule):
|
||||
if current_step <= end_at:
|
||||
target_index = curret_index
|
||||
break
|
||||
res[i] = cond_schedule[target_index].cond
|
||||
|
||||
return res.to(shared.device)
|
||||
|
||||
|
||||
|
||||
#get_learned_conditioning_prompt_schedules(["fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]"], 100)
|
||||
+3
-3
@@ -42,10 +42,10 @@ def load_scripts(basedir):
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
|
||||
with open(path, "r", encoding="utf8") as file:
|
||||
text = file.read()
|
||||
|
||||
try:
|
||||
with open(path, "r", encoding="utf8") as file:
|
||||
text = file.read()
|
||||
|
||||
from types import ModuleType
|
||||
compiled = compile(text, path, 'exec')
|
||||
module = ModuleType(filename)
|
||||
|
||||
+28
-16
@@ -7,6 +7,7 @@ from PIL import Image
|
||||
import k_diffusion.sampling
|
||||
import ldm.models.diffusion.ddim
|
||||
import ldm.models.diffusion.plms
|
||||
from modules import prompt_parser
|
||||
|
||||
from modules.shared import opts, cmd_opts, state
|
||||
import modules.shared as shared
|
||||
@@ -53,20 +54,6 @@ def store_latent(decoded):
|
||||
shared.state.current_image = sample_to_image(decoded)
|
||||
|
||||
|
||||
def p_sample_ddim_hook(sampler_wrapper, x_dec, cond, ts, *args, **kwargs):
|
||||
if sampler_wrapper.mask is not None:
|
||||
img_orig = sampler_wrapper.sampler.model.q_sample(sampler_wrapper.init_latent, ts)
|
||||
x_dec = img_orig * sampler_wrapper.mask + sampler_wrapper.nmask * x_dec
|
||||
|
||||
res = sampler_wrapper.orig_p_sample_ddim(x_dec, cond, ts, *args, **kwargs)
|
||||
|
||||
if sampler_wrapper.mask is not None:
|
||||
store_latent(sampler_wrapper.init_latent * sampler_wrapper.mask + sampler_wrapper.nmask * res[1])
|
||||
else:
|
||||
store_latent(res[1])
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def extended_tdqm(sequence, *args, desc=None, **kwargs):
|
||||
state.sampling_steps = len(sequence)
|
||||
@@ -93,6 +80,25 @@ class VanillaStableDiffusionSampler:
|
||||
self.mask = None
|
||||
self.nmask = None
|
||||
self.init_latent = None
|
||||
self.step = 0
|
||||
|
||||
def p_sample_ddim_hook(self, x_dec, cond, ts, unconditional_conditioning, *args, **kwargs):
|
||||
cond = prompt_parser.reconstruct_cond_batch(cond, self.step)
|
||||
unconditional_conditioning = prompt_parser.reconstruct_cond_batch(unconditional_conditioning, self.step)
|
||||
|
||||
if self.mask is not None:
|
||||
img_orig = self.sampler.model.q_sample(self.init_latent, ts)
|
||||
x_dec = img_orig * self.mask + self.nmask * x_dec
|
||||
|
||||
res = self.orig_p_sample_ddim(x_dec, cond, ts, unconditional_conditioning=unconditional_conditioning, *args, **kwargs)
|
||||
|
||||
if self.mask is not None:
|
||||
store_latent(self.init_latent * self.mask + self.nmask * res[1])
|
||||
else:
|
||||
store_latent(res[1])
|
||||
|
||||
self.step += 1
|
||||
return res
|
||||
|
||||
def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning):
|
||||
t_enc = int(min(p.denoising_strength, 0.999) * p.steps)
|
||||
@@ -105,7 +111,7 @@ class VanillaStableDiffusionSampler:
|
||||
|
||||
x1 = self.sampler.stochastic_encode(x, torch.tensor([t_enc] * int(x.shape[0])).to(shared.device), noise=noise)
|
||||
|
||||
self.sampler.p_sample_ddim = lambda x_dec, cond, ts, *args, **kwargs: p_sample_ddim_hook(self, x_dec, cond, ts, *args, **kwargs)
|
||||
self.sampler.p_sample_ddim = self.p_sample_ddim_hook
|
||||
self.mask = p.mask
|
||||
self.nmask = p.nmask
|
||||
self.init_latent = p.init_latent
|
||||
@@ -117,7 +123,7 @@ class VanillaStableDiffusionSampler:
|
||||
def sample(self, p, x, conditioning, unconditional_conditioning):
|
||||
for fieldname in ['p_sample_ddim', 'p_sample_plms']:
|
||||
if hasattr(self.sampler, fieldname):
|
||||
setattr(self.sampler, fieldname, lambda x_dec, cond, ts, *args, **kwargs: p_sample_ddim_hook(self, x_dec, cond, ts, *args, **kwargs))
|
||||
setattr(self.sampler, fieldname, self.p_sample_ddim_hook)
|
||||
self.mask = None
|
||||
self.nmask = None
|
||||
self.init_latent = None
|
||||
@@ -138,8 +144,12 @@ class CFGDenoiser(torch.nn.Module):
|
||||
self.mask = None
|
||||
self.nmask = None
|
||||
self.init_latent = None
|
||||
self.step = 0
|
||||
|
||||
def forward(self, x, sigma, uncond, cond, cond_scale):
|
||||
cond = prompt_parser.reconstruct_cond_batch(cond, self.step)
|
||||
uncond = prompt_parser.reconstruct_cond_batch(uncond, self.step)
|
||||
|
||||
if shared.batch_cond_uncond:
|
||||
x_in = torch.cat([x] * 2)
|
||||
sigma_in = torch.cat([sigma] * 2)
|
||||
@@ -154,6 +164,8 @@ class CFGDenoiser(torch.nn.Module):
|
||||
if self.mask is not None:
|
||||
denoised = self.init_latent * self.mask + self.nmask * denoised
|
||||
|
||||
self.step += 1
|
||||
|
||||
return denoised
|
||||
|
||||
|
||||
|
||||
+10
-8
@@ -23,7 +23,7 @@ parser.add_argument("--ckpt", type=str, default=os.path.join(sd_path, sd_model_f
|
||||
parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=('./src/gfpgan' if os.path.exists('./src/gfpgan') else './GFPGAN'))
|
||||
parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", default='GFPGANv1.3.pth')
|
||||
parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats")
|
||||
parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware accleration in browser)")
|
||||
parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware acceleration in browser)")
|
||||
parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI")
|
||||
parser.add_argument("--embeddings-dir", type=str, default=os.path.join(script_path, 'embeddings'), help="embeddings directory for textual inversion (default: embeddings)")
|
||||
parser.add_argument("--allow-code", action='store_true', help="allow custom script execution from webui")
|
||||
@@ -45,6 +45,7 @@ parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui
|
||||
parser.add_argument("--gradio-debug", action='store_true', help="launch gradio with --debug option")
|
||||
parser.add_argument("--gradio-auth", type=str, help='set gradio authentication like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None)
|
||||
parser.add_argument("--opt-channelslast", action='store_true', help="change memory type for stable diffusion to channels last")
|
||||
parser.add_argument("--styles-file", type=str, help="filename to use for styles", default=os.path.join(script_path, 'styles.csv'))
|
||||
|
||||
cmd_opts = parser.parse_args()
|
||||
|
||||
@@ -79,8 +80,8 @@ state = State()
|
||||
|
||||
artist_db = modules.artists.ArtistsDatabase(os.path.join(script_path, 'artists.csv'))
|
||||
|
||||
styles_filename = os.path.join(script_path, 'styles.csv')
|
||||
prompt_styles = modules.styles.load_styles(styles_filename)
|
||||
styles_filename = cmd_opts.styles_file
|
||||
prompt_styles = modules.styles.StyleDatabase(styles_filename)
|
||||
|
||||
interrogator = modules.interrogate.InterrogateModels("interrogate")
|
||||
|
||||
@@ -109,10 +110,11 @@ class Options:
|
||||
"outdir_txt2img_grids": OptionInfo("outputs/txt2img-grids", 'Output directory for txt2img grids', component_args=hide_dirs),
|
||||
"outdir_img2img_grids": OptionInfo("outputs/img2img-grids", 'Output directory for img2img grids', component_args=hide_dirs),
|
||||
"outdir_save": OptionInfo("log/images", "Directory for saving images using the Save button", component_args=hide_dirs),
|
||||
"samples_save": OptionInfo(True, "Save indiviual samples"),
|
||||
"samples_save": OptionInfo(True, "Always save all generated images"),
|
||||
"save_selected_only": OptionInfo(False, "When using 'Save' button, only save a single selected image"),
|
||||
"samples_format": OptionInfo('png', 'File format for individual samples'),
|
||||
"filter_nsfw": OptionInfo(False, "Filter NSFW content"),
|
||||
"grid_save": OptionInfo(True, "Save image grids"),
|
||||
"grid_save": OptionInfo(True, "Always save all generated image grids"),
|
||||
"return_grid": OptionInfo(True, "Show grid in results for web"),
|
||||
"grid_format": OptionInfo('png', 'File format for grids'),
|
||||
"grid_extended_filename": OptionInfo(False, "Add extended info (seed, prompt) to filename when saving grid"),
|
||||
@@ -124,7 +126,7 @@ class Options:
|
||||
"add_model_hash_to_info": OptionInfo(False, "Add model hash to generation information"),
|
||||
"img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."),
|
||||
"font": OptionInfo("", "Font for image grids that have text"),
|
||||
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text text and [text] to make it pay less attention"),
|
||||
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text and [text] to make it pay less attention"),
|
||||
"save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."),
|
||||
"ESRGAN_tile": OptionInfo(192, "Tile size for upscaling. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
||||
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for upscaling. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
|
||||
@@ -140,8 +142,8 @@ class Options:
|
||||
"interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"),
|
||||
"interrogate_use_builtin_artists": OptionInfo(True, "Interrogate: use artists from artists.csv"),
|
||||
"interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}),
|
||||
"interrogate_clip_min_length": OptionInfo(24, "Interrogate: minimum descripton length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
|
||||
"interrogate_clip_max_length": OptionInfo(48, "Interrogate: maximum descripton length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
|
||||
"interrogate_clip_min_length": OptionInfo(24, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
|
||||
"interrogate_clip_max_length": OptionInfo(48, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
|
||||
"interrogate_clip_dict_limit": OptionInfo(1500, "Interrogate: maximum number of lines in text file (0 = No limit)"),
|
||||
}
|
||||
|
||||
|
||||
+51
-33
@@ -20,49 +20,67 @@ class PromptStyle(typing.NamedTuple):
|
||||
negative_prompt: str
|
||||
|
||||
|
||||
def load_styles(path: str) -> dict[str, PromptStyle]:
|
||||
styles = {"None": PromptStyle("None", "", "")}
|
||||
def merge_prompts(style_prompt: str, prompt: str) -> str:
|
||||
if "{prompt}" in style_prompt:
|
||||
res = style_prompt.replace("{prompt}", prompt)
|
||||
else:
|
||||
parts = filter(None, (prompt.strip(), style_prompt.strip()))
|
||||
res = ", ".join(parts)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def apply_styles_to_prompt(prompt, styles):
|
||||
for style in styles:
|
||||
prompt = merge_prompts(style, prompt)
|
||||
|
||||
return prompt
|
||||
|
||||
|
||||
class StyleDatabase:
|
||||
def __init__(self, path: str):
|
||||
self.no_style = PromptStyle("None", "", "")
|
||||
self.styles = {"None": self.no_style}
|
||||
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
|
||||
if os.path.exists(path):
|
||||
with open(path, "r", encoding="utf8", newline='') as file:
|
||||
reader = csv.DictReader(file)
|
||||
for row in reader:
|
||||
# Support loading old CSV format with "name, text"-columns
|
||||
prompt = row["prompt"] if "prompt" in row else row["text"]
|
||||
negative_prompt = row.get("negative_prompt", "")
|
||||
styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt)
|
||||
self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt)
|
||||
|
||||
return styles
|
||||
def apply_styles_to_prompt(self, prompt, styles):
|
||||
return apply_styles_to_prompt(prompt, [self.styles.get(x, self.no_style).prompt for x in styles])
|
||||
|
||||
def apply_negative_styles_to_prompt(self, prompt, styles):
|
||||
return apply_styles_to_prompt(prompt, [self.styles.get(x, self.no_style).negative_prompt for x in styles])
|
||||
|
||||
def merge_prompts(style_prompt: str, prompt: str) -> str:
|
||||
parts = filter(None, (prompt.strip(), style_prompt.strip()))
|
||||
return ", ".join(parts)
|
||||
def apply_styles(self, p: StableDiffusionProcessing) -> None:
|
||||
if isinstance(p.prompt, list):
|
||||
p.prompt = [self.apply_styles_to_prompt(prompt, p.styles) for prompt in p.prompt]
|
||||
else:
|
||||
p.prompt = self.apply_styles_to_prompt(p.prompt, p.styles)
|
||||
|
||||
if isinstance(p.negative_prompt, list):
|
||||
p.negative_prompt = [self.apply_negative_styles_to_prompt(prompt, p.styles) for prompt in p.negative_prompt]
|
||||
else:
|
||||
p.negative_prompt = self.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)
|
||||
|
||||
def apply_style(processing: StableDiffusionProcessing, style: PromptStyle) -> None:
|
||||
if isinstance(processing.prompt, list):
|
||||
processing.prompt = [merge_prompts(style.prompt, p) for p in processing.prompt]
|
||||
else:
|
||||
processing.prompt = merge_prompts(style.prompt, processing.prompt)
|
||||
def save_styles(self, path: str) -> None:
|
||||
# Write to temporary file first, so we don't nuke the file if something goes wrong
|
||||
fd, temp_path = tempfile.mkstemp(".csv")
|
||||
with os.fdopen(fd, "w", encoding="utf8", newline='') as file:
|
||||
# _fields is actually part of the public API: typing.NamedTuple is a replacement for collections.NamedTuple,
|
||||
# and collections.NamedTuple has explicit documentation for accessing _fields. Same goes for _asdict()
|
||||
writer = csv.DictWriter(file, fieldnames=PromptStyle._fields)
|
||||
writer.writeheader()
|
||||
writer.writerows(style._asdict() for k, style in self.styles.items())
|
||||
|
||||
if isinstance(processing.negative_prompt, list):
|
||||
processing.negative_prompt = [merge_prompts(style.negative_prompt, p) for p in processing.negative_prompt]
|
||||
else:
|
||||
processing.negative_prompt = merge_prompts(style.negative_prompt, processing.negative_prompt)
|
||||
|
||||
|
||||
def save_styles(path: str, styles: abc.Iterable[PromptStyle]) -> None:
|
||||
# Write to temporary file first, so we don't nuke the file if something goes wrong
|
||||
fd, temp_path = tempfile.mkstemp(".csv")
|
||||
with os.fdopen(fd, "w", encoding="utf8", newline='') as file:
|
||||
# _fields is actually part of the public API: typing.NamedTuple is a replacement for collections.NamedTuple,
|
||||
# and collections.NamedTuple has explicit documentation for accessing _fields. Same goes for _asdict()
|
||||
writer = csv.DictWriter(file, fieldnames=PromptStyle._fields)
|
||||
writer.writeheader()
|
||||
writer.writerows(style._asdict() for style in styles)
|
||||
|
||||
# Always keep a backup file around
|
||||
if os.path.exists(path):
|
||||
shutil.move(path, path + ".bak")
|
||||
shutil.move(temp_path, path)
|
||||
# Always keep a backup file around
|
||||
if os.path.exists(path):
|
||||
shutil.move(path, path + ".bak")
|
||||
shutil.move(temp_path, path)
|
||||
|
||||
+2
-2
@@ -6,13 +6,13 @@ import modules.processing as processing
|
||||
from modules.ui import plaintext_to_html
|
||||
|
||||
|
||||
def txt2img(prompt: str, negative_prompt: str, prompt_style: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, *args):
|
||||
def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, *args):
|
||||
p = StableDiffusionProcessingTxt2Img(
|
||||
sd_model=shared.sd_model,
|
||||
outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples,
|
||||
outpath_grids=opts.outdir_grids or opts.outdir_txt2img_grids,
|
||||
prompt=prompt,
|
||||
prompt_style=prompt_style,
|
||||
styles=[prompt_style, prompt_style2],
|
||||
negative_prompt=negative_prompt,
|
||||
seed=seed,
|
||||
subseed=subseed,
|
||||
|
||||
+85
-34
@@ -80,7 +80,7 @@ def send_gradio_gallery_to_image(x):
|
||||
return image_from_url_text(x[0])
|
||||
|
||||
|
||||
def save_files(js_data, images):
|
||||
def save_files(js_data, images, index):
|
||||
import csv
|
||||
|
||||
os.makedirs(opts.outdir_save, exist_ok=True)
|
||||
@@ -88,6 +88,10 @@ def save_files(js_data, images):
|
||||
filenames = []
|
||||
|
||||
data = json.loads(js_data)
|
||||
|
||||
if index > -1 and opts.save_selected_only and (index > 0 or not opts.return_grid): # ensures we are looking at a specific non-grid picture, and we have save_selected_only
|
||||
images = [images[index]]
|
||||
data["seed"] += (index - 1 if opts.return_grid else index)
|
||||
|
||||
with open(os.path.join(opts.outdir_save, "log.csv"), "a", encoding="utf8", newline='') as file:
|
||||
at_start = file.tell() == 0
|
||||
@@ -233,13 +237,20 @@ def add_style(name: str, prompt: str, negative_prompt: str):
|
||||
return [gr_show(), gr_show()]
|
||||
|
||||
style = modules.styles.PromptStyle(name, prompt, negative_prompt)
|
||||
shared.prompt_styles[style.name] = style
|
||||
shared.prompt_styles.styles[style.name] = style
|
||||
# Save all loaded prompt styles: this allows us to update the storage format in the future more easily, because we
|
||||
# reserialize all styles every time we save them
|
||||
modules.styles.save_styles(shared.styles_filename, shared.prompt_styles.values())
|
||||
shared.prompt_styles.save_styles(shared.styles_filename)
|
||||
|
||||
update = {"visible": True, "choices": list(shared.prompt_styles), "__type__": "update"}
|
||||
return [update, update]
|
||||
update = {"visible": True, "choices": list(shared.prompt_styles.styles), "__type__": "update"}
|
||||
return [update, update, update, update]
|
||||
|
||||
|
||||
def apply_styles(prompt, prompt_neg, style1_name, style2_name):
|
||||
prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, [style1_name, style2_name])
|
||||
prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, [style1_name, style2_name])
|
||||
|
||||
return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value="None"), gr.Dropdown.update(value="None")]
|
||||
|
||||
|
||||
def interrogate(image):
|
||||
@@ -247,15 +258,46 @@ def interrogate(image):
|
||||
|
||||
return gr_show(True) if prompt is None else prompt
|
||||
|
||||
|
||||
def create_toprow(is_img2img):
|
||||
with gr.Row(elem_id="toprow"):
|
||||
with gr.Column(scale=4):
|
||||
with gr.Row():
|
||||
with gr.Column(scale=8):
|
||||
with gr.Row():
|
||||
prompt = gr.Textbox(label="Prompt", elem_id="prompt", show_label=False, placeholder="Prompt", lines=2)
|
||||
roll = gr.Button('Roll', elem_id="roll", visible=len(shared.artist_db.artists) > 0)
|
||||
|
||||
with gr.Column(scale=1, elem_id="style_pos_col"):
|
||||
prompt_style = gr.Dropdown(label="Style 1", elem_id="style_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
|
||||
|
||||
with gr.Row():
|
||||
with gr.Column(scale=8):
|
||||
negative_prompt = gr.Textbox(label="Negative prompt", elem_id="negative_prompt", show_label=False, placeholder="Negative prompt", lines=2)
|
||||
|
||||
with gr.Column(scale=1, elem_id="style_neg_col"):
|
||||
prompt_style2 = gr.Dropdown(label="Style 2", elem_id="style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
|
||||
|
||||
with gr.Column(scale=1):
|
||||
with gr.Row():
|
||||
submit = gr.Button('Generate', elem_id="generate", variant='primary')
|
||||
|
||||
with gr.Row():
|
||||
if is_img2img:
|
||||
interrogate = gr.Button('Interrogate', elem_id="interrogate")
|
||||
else:
|
||||
interrogate = None
|
||||
prompt_style_apply = gr.Button('Apply style', elem_id="style_apply")
|
||||
save_style = gr.Button('Create style', elem_id="style_create")
|
||||
|
||||
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
|
||||
|
||||
return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, interrogate, prompt_style_apply, save_style, check_progress
|
||||
|
||||
|
||||
def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
|
||||
with gr.Row(elem_id="toprow"):
|
||||
txt2img_prompt = gr.Textbox(label="Prompt", elem_id="txt2img_prompt", show_label=False, placeholder="Prompt", lines=1)
|
||||
txt2img_negative_prompt = gr.Textbox(label="Negative prompt", elem_id="txt2img_negative_prompt", show_label=False, placeholder="Negative prompt", lines=1)
|
||||
txt2img_prompt_style = gr.Dropdown(label="Style", show_label=False, elem_id="style_index", choices=[k for k, v in shared.prompt_styles.items()], value=next(iter(shared.prompt_styles.keys())), visible=len(shared.prompt_styles) > 1)
|
||||
roll = gr.Button('Roll', elem_id="txt2img_roll", visible=len(shared.artist_db.artists) > 0)
|
||||
submit = gr.Button('Generate', elem_id="txt2img_generate", variant='primary')
|
||||
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
|
||||
txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, txt2img_prompt_style_apply, txt2img_save_style, check_progress = create_toprow(is_img2img=False)
|
||||
|
||||
with gr.Row().style(equal_height=False):
|
||||
with gr.Column(variant='panel'):
|
||||
@@ -286,7 +328,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
txt2img_preview = gr.Image(elem_id='txt2img_preview', visible=False)
|
||||
txt2img_gallery = gr.Gallery(label='Output', elem_id='txt2img_gallery').style(grid=4)
|
||||
|
||||
|
||||
with gr.Group():
|
||||
with gr.Row():
|
||||
save = gr.Button('Save')
|
||||
@@ -294,7 +335,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
send_to_inpaint = gr.Button('Send to inpaint')
|
||||
send_to_extras = gr.Button('Send to extras')
|
||||
interrupt = gr.Button('Interrupt')
|
||||
txt2img_save_style = gr.Button('Save prompt as style')
|
||||
|
||||
progressbar = gr.HTML(elem_id="progressbar")
|
||||
|
||||
@@ -302,7 +342,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
html_info = gr.HTML()
|
||||
generation_info = gr.Textbox(visible=False)
|
||||
|
||||
|
||||
txt2img_args = dict(
|
||||
fn=txt2img,
|
||||
_js="submit",
|
||||
@@ -310,6 +349,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
txt2img_prompt,
|
||||
txt2img_negative_prompt,
|
||||
txt2img_prompt_style,
|
||||
txt2img_prompt_style2,
|
||||
steps,
|
||||
sampler_index,
|
||||
restore_faces,
|
||||
@@ -339,7 +379,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
outputs=[progressbar, txt2img_preview, txt2img_preview],
|
||||
)
|
||||
|
||||
|
||||
interrupt.click(
|
||||
fn=lambda: shared.state.interrupt(),
|
||||
inputs=[],
|
||||
@@ -348,9 +387,11 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
|
||||
save.click(
|
||||
fn=wrap_gradio_call(save_files),
|
||||
_js = "(x, y, z) => [x, y, selected_gallery_index()]",
|
||||
inputs=[
|
||||
generation_info,
|
||||
txt2img_gallery,
|
||||
html_info
|
||||
],
|
||||
outputs=[
|
||||
html_info,
|
||||
@@ -370,18 +411,12 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
)
|
||||
|
||||
with gr.Blocks(analytics_enabled=False) as img2img_interface:
|
||||
with gr.Row(elem_id="toprow"):
|
||||
img2img_prompt = gr.Textbox(label="Prompt", elem_id="img2img_prompt", show_label=False, placeholder="Prompt", lines=1)
|
||||
img2img_negative_prompt = gr.Textbox(label="Negative prompt", elem_id="img2img_negative_prompt", show_label=False, placeholder="Negative prompt", lines=1)
|
||||
img2img_prompt_style = gr.Dropdown(label="Style", show_label=False, elem_id="style_index", choices=[k for k, v in shared.prompt_styles.items()], value=next(iter(shared.prompt_styles.keys())), visible=len(shared.prompt_styles) > 1)
|
||||
img2img_interrogate = gr.Button('Interrogate', elem_id="img2img_interrogate", variant='primary')
|
||||
submit = gr.Button('Generate', elem_id="img2img_generate", variant='primary')
|
||||
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
|
||||
img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_prompt_style_apply, img2img_save_style, check_progress = create_toprow(is_img2img=True)
|
||||
|
||||
with gr.Row().style(equal_height=False):
|
||||
with gr.Column(variant='panel'):
|
||||
with gr.Group():
|
||||
switch_mode = gr.Radio(label='Mode', elem_id="img2img_mode", choices=['Redraw whole image', 'Inpaint a part of image', 'Loopback', 'SD upscale'], value='Redraw whole image', type="index", show_label=False)
|
||||
switch_mode = gr.Radio(label='Mode', elem_id="img2img_mode", choices=['Redraw whole image', 'Inpaint a part of image', 'SD upscale'], value='Redraw whole image', type="index", show_label=False)
|
||||
init_img = gr.Image(label="Image for img2img", source="upload", interactive=True, type="pil")
|
||||
init_img_with_mask = gr.Image(label="Image for inpainting with mask", elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", visible=False, image_mode="RGBA")
|
||||
init_mask = gr.Image(label="Mask", source="upload", interactive=True, type="pil", visible=False)
|
||||
@@ -415,7 +450,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
with gr.Group():
|
||||
cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0)
|
||||
denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75)
|
||||
denoising_strength_change_factor = gr.Slider(minimum=0.9, maximum=1.1, step=0.01, label='Denoising strength change factor', value=1, visible=False)
|
||||
|
||||
with gr.Group():
|
||||
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
|
||||
@@ -449,8 +483,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
def apply_mode(mode, uploadmask):
|
||||
is_classic = mode == 0
|
||||
is_inpaint = mode == 1
|
||||
is_loopback = mode == 2
|
||||
is_upscale = mode == 3
|
||||
is_upscale = mode == 2
|
||||
|
||||
return {
|
||||
init_img: gr_show(not is_inpaint or (is_inpaint and uploadmask == 1)),
|
||||
@@ -460,12 +493,10 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
mask_mode: gr_show(is_inpaint),
|
||||
mask_blur: gr_show(is_inpaint),
|
||||
inpainting_fill: gr_show(is_inpaint),
|
||||
batch_size: gr_show(not is_loopback),
|
||||
sd_upscale_upscaler_name: gr_show(is_upscale),
|
||||
sd_upscale_overlap: gr_show(is_upscale),
|
||||
inpaint_full_res: gr_show(is_inpaint),
|
||||
inpainting_mask_invert: gr_show(is_inpaint),
|
||||
denoising_strength_change_factor: gr_show(is_loopback),
|
||||
img2img_interrogate: gr_show(not is_inpaint),
|
||||
}
|
||||
|
||||
@@ -480,12 +511,10 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
mask_mode,
|
||||
mask_blur,
|
||||
inpainting_fill,
|
||||
batch_size,
|
||||
sd_upscale_upscaler_name,
|
||||
sd_upscale_overlap,
|
||||
inpaint_full_res,
|
||||
inpainting_mask_invert,
|
||||
denoising_strength_change_factor,
|
||||
img2img_interrogate,
|
||||
]
|
||||
)
|
||||
@@ -511,6 +540,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
img2img_prompt,
|
||||
img2img_negative_prompt,
|
||||
img2img_prompt_style,
|
||||
img2img_prompt_style2,
|
||||
init_img,
|
||||
init_img_with_mask,
|
||||
init_mask,
|
||||
@@ -526,7 +556,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
batch_size,
|
||||
cfg_scale,
|
||||
denoising_strength,
|
||||
denoising_strength_change_factor,
|
||||
seed,
|
||||
subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w,
|
||||
height,
|
||||
@@ -568,9 +597,11 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
|
||||
save.click(
|
||||
fn=wrap_gradio_call(save_files),
|
||||
_js = "(x, y, z) => [x, y, selected_gallery_index()]",
|
||||
inputs=[
|
||||
generation_info,
|
||||
img2img_gallery,
|
||||
html_info
|
||||
],
|
||||
outputs=[
|
||||
html_info,
|
||||
@@ -579,15 +610,35 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
]
|
||||
)
|
||||
|
||||
roll.click(
|
||||
fn=roll_artist,
|
||||
inputs=[
|
||||
img2img_prompt,
|
||||
],
|
||||
outputs=[
|
||||
img2img_prompt,
|
||||
]
|
||||
)
|
||||
|
||||
prompts = [(txt2img_prompt, txt2img_negative_prompt), (img2img_prompt, img2img_negative_prompt)]
|
||||
style_dropdowns = [(txt2img_prompt_style, txt2img_prompt_style2), (img2img_prompt_style, img2img_prompt_style2)]
|
||||
|
||||
dummy_component = gr.Label(visible=False)
|
||||
for button, (prompt, negative_prompt) in zip([txt2img_save_style, img2img_save_style], [(txt2img_prompt, txt2img_negative_prompt), (img2img_prompt, img2img_negative_prompt)]):
|
||||
for button, (prompt, negative_prompt) in zip([txt2img_save_style, img2img_save_style], prompts):
|
||||
button.click(
|
||||
fn=add_style,
|
||||
_js="ask_for_style_name",
|
||||
# Have to pass empty dummy component here, because the JavaScript and Python function have to accept
|
||||
# the same number of parameters, but we only know the style-name after the JavaScript prompt
|
||||
inputs=[dummy_component, prompt, negative_prompt],
|
||||
outputs=[txt2img_prompt_style, img2img_prompt_style],
|
||||
outputs=[txt2img_prompt_style, img2img_prompt_style, txt2img_prompt_style2, img2img_prompt_style2],
|
||||
)
|
||||
|
||||
for button, (prompt, negative_prompt), (style1, style2) in zip([txt2img_prompt_style_apply, img2img_prompt_style_apply], prompts, style_dropdowns):
|
||||
button.click(
|
||||
fn=apply_styles,
|
||||
inputs=[prompt, negative_prompt, style1, style2],
|
||||
outputs=[prompt, negative_prompt, style1, style2],
|
||||
)
|
||||
|
||||
with gr.Blocks(analytics_enabled=False) as extras_interface:
|
||||
|
||||
@@ -13,7 +13,6 @@ titles = {
|
||||
"Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result",
|
||||
|
||||
"Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt",
|
||||
"Loopback": "Process an image, use it as an input, repeat. Batch count determins number of iterations.",
|
||||
"SD upscale": "Upscale image normally, split result into tiles, improve each tile using img2img, merge whole image back",
|
||||
|
||||
"Just resize": "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.",
|
||||
@@ -54,10 +53,19 @@ titles = {
|
||||
"Resize seed from height": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution",
|
||||
"Resize seed from width": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution",
|
||||
|
||||
"Interrogate": "Reconstruct frompt from existing image and put it into the prompt field.",
|
||||
"Interrogate": "Reconstruct prompt from existing image and put it into the prompt field.",
|
||||
|
||||
"Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [sampler], [seed], [model_hash], [prompt_words], [date]; leave empty for default.",
|
||||
"Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [sampler], [seed], [model_hash], [prompt_words], [date]; leave empty for default.",
|
||||
|
||||
"Loopback": "Process an image, use it as an input, repeat.",
|
||||
"Loops": "How many times to repeat processing an image and using it as input for the next iteration",
|
||||
|
||||
|
||||
"Style 1": "Style to apply; styles have components for both positive and negative prompts and apply to both",
|
||||
"Style 2": "Style to apply; styles have components for both positive and negative prompts and apply to both",
|
||||
"Apply style": "Insert selected styles into prompt fields",
|
||||
"Create style": "Save current prompts as a style. If you add the token {prompt} to the text, the style use that as placeholder for your prompt when you use the style in the future.",
|
||||
}
|
||||
|
||||
function gradioApp(){
|
||||
|
||||
+22
-10
@@ -1,3 +1,5 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
from tqdm import trange
|
||||
|
||||
@@ -56,9 +58,14 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps):
|
||||
|
||||
return x / x.std()
|
||||
|
||||
cache = [None, None, None, None, None]
|
||||
|
||||
Cached = namedtuple("Cached", ["noise", "cfg_scale", "steps", "latent", "original_prompt"])
|
||||
|
||||
|
||||
class Script(scripts.Script):
|
||||
def __init__(self):
|
||||
self.cache = None
|
||||
|
||||
def title(self):
|
||||
return "img2img alternative test"
|
||||
|
||||
@@ -67,7 +74,7 @@ class Script(scripts.Script):
|
||||
|
||||
def ui(self, is_img2img):
|
||||
original_prompt = gr.Textbox(label="Original prompt", lines=1)
|
||||
cfg = gr.Slider(label="Decode CFG scale", minimum=0.1, maximum=3.0, step=0.1, value=1.0)
|
||||
cfg = gr.Slider(label="Decode CFG scale", minimum=0.0, maximum=15.0, step=0.1, value=1.0)
|
||||
st = gr.Slider(label="Decode steps", minimum=1, maximum=150, step=1, value=50)
|
||||
|
||||
return [original_prompt, cfg, st]
|
||||
@@ -77,19 +84,18 @@ class Script(scripts.Script):
|
||||
p.batch_count = 1
|
||||
|
||||
def sample_extra(x, conditioning, unconditional_conditioning):
|
||||
lat = tuple([int(x*10) for x in p.init_latent.cpu().numpy().flatten().tolist()])
|
||||
lat = (p.init_latent.cpu().numpy() * 10).astype(int)
|
||||
|
||||
if cache[0] is not None and cache[1] == cfg and cache[2] == st and len(cache[3]) == len(lat) and sum(np.array(cache[3])-np.array(lat)) < 100 and cache[4] == original_prompt:
|
||||
noise = cache[0]
|
||||
same_params = self.cache is not None and self.cache.cfg_scale == cfg and self.cache.steps == st and self.cache.original_prompt == original_prompt
|
||||
same_everything = same_params and self.cache.latent.shape == lat.shape and np.abs(self.cache.latent-lat).sum() < 100
|
||||
|
||||
if same_everything:
|
||||
noise = self.cache.noise
|
||||
else:
|
||||
shared.state.job_count += 1
|
||||
cond = p.sd_model.get_learned_conditioning(p.batch_size * [original_prompt])
|
||||
noise = find_noise_for_image(p, cond, unconditional_conditioning, cfg, st)
|
||||
cache[0] = noise
|
||||
cache[1] = cfg
|
||||
cache[2] = st
|
||||
cache[3] = lat
|
||||
cache[4] = original_prompt
|
||||
self.cache = Cached(noise, cfg, st, lat, original_prompt)
|
||||
|
||||
sampler = samplers[p.sampler_index].constructor(p.sd_model)
|
||||
|
||||
@@ -98,6 +104,12 @@ class Script(scripts.Script):
|
||||
|
||||
p.sample = sample_extra
|
||||
|
||||
p.extra_generation_params = {
|
||||
"Decode prompt": original_prompt,
|
||||
"Decode CFG scale": cfg,
|
||||
"Decode steps": st,
|
||||
}
|
||||
|
||||
processed = processing.process_images(p)
|
||||
|
||||
return processed
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import numpy as np
|
||||
from tqdm import trange
|
||||
|
||||
import modules.scripts as scripts
|
||||
import gradio as gr
|
||||
|
||||
from modules import processing, shared, sd_samplers, images
|
||||
from modules.processing import Processed
|
||||
from modules.sd_samplers import samplers
|
||||
from modules.shared import opts, cmd_opts, state
|
||||
|
||||
class Script(scripts.Script):
|
||||
def title(self):
|
||||
return "Loopback"
|
||||
|
||||
def show(self, is_img2img):
|
||||
return is_img2img
|
||||
|
||||
def ui(self, is_img2img):
|
||||
loops = gr.Slider(minimum=1, maximum=32, step=1, label='Loops', value=4)
|
||||
denoising_strength_change_factor = gr.Slider(minimum=0.9, maximum=1.1, step=0.01, label='Denoising strength change factor', value=1)
|
||||
|
||||
return [loops, denoising_strength_change_factor]
|
||||
|
||||
def run(self, p, loops, denoising_strength_change_factor):
|
||||
processing.fix_seed(p)
|
||||
batch_count = p.n_iter
|
||||
p.extra_generation_params = {
|
||||
"Denoising strength change factor": denoising_strength_change_factor,
|
||||
}
|
||||
|
||||
p.batch_size = 1
|
||||
p.n_iter = 1
|
||||
|
||||
output_images, info = None, None
|
||||
initial_seed = None
|
||||
initial_info = None
|
||||
|
||||
grids = []
|
||||
all_images = []
|
||||
state.job_count = loops * batch_count
|
||||
|
||||
for n in range(batch_count):
|
||||
history = []
|
||||
|
||||
for i in range(loops):
|
||||
p.n_iter = 1
|
||||
p.batch_size = 1
|
||||
p.do_not_save_grid = True
|
||||
|
||||
state.job = f"Iteration {i + 1}/{loops}, batch {n + 1}/{batch_count}"
|
||||
|
||||
processed = processing.process_images(p)
|
||||
|
||||
if initial_seed is None:
|
||||
initial_seed = processed.seed
|
||||
initial_info = processed.info
|
||||
|
||||
init_img = processed.images[0]
|
||||
|
||||
p.init_images = [init_img]
|
||||
p.seed = processed.seed + 1
|
||||
p.denoising_strength = min(max(p.denoising_strength * denoising_strength_change_factor, 0.1), 1)
|
||||
history.append(processed.images[0])
|
||||
|
||||
grid = images.image_grid(history, rows=1)
|
||||
if opts.grid_save:
|
||||
images.save_image(grid, p.outpath_grids, "grid", initial_seed, p.prompt, opts.grid_format, info=info, short_filename=not opts.grid_extended_filename, grid=True, p=p)
|
||||
|
||||
grids.append(grid)
|
||||
all_images += history
|
||||
|
||||
if opts.return_grid:
|
||||
all_images = grids + all_images
|
||||
|
||||
processed = Processed(p, all_images, initial_seed, initial_info)
|
||||
|
||||
return processed
|
||||
@@ -82,6 +82,6 @@ class Script(scripts.Script):
|
||||
processed.images.insert(0, grid)
|
||||
|
||||
if opts.grid_save:
|
||||
images.save_image(processed.images[0], p.outpath_grids, "prompt_matrix", prompt=original_prompt, seed=processed.seed, p=p)
|
||||
images.save_image(processed.images[0], p.outpath_grids, "prompt_matrix", prompt=original_prompt, seed=processed.seed, grid=True, p=p)
|
||||
|
||||
return processed
|
||||
|
||||
+30
-9
@@ -78,7 +78,7 @@ axis_options = [
|
||||
]
|
||||
|
||||
|
||||
def draw_xy_grid(xs, ys, x_label, y_label, cell):
|
||||
def draw_xy_grid(p, xs, ys, x_label, y_label, cell, draw_legend):
|
||||
res = []
|
||||
|
||||
ver_texts = [[images.GridAnnotation(y_label(y))] for y in ys]
|
||||
@@ -86,7 +86,7 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell):
|
||||
|
||||
first_pocessed = None
|
||||
|
||||
state.job_count = len(xs) * len(ys)
|
||||
state.job_count = len(xs) * len(ys) * p.n_iter
|
||||
|
||||
for iy, y in enumerate(ys):
|
||||
for ix, x in enumerate(xs):
|
||||
@@ -99,7 +99,8 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell):
|
||||
res.append(processed.images[0])
|
||||
|
||||
grid = images.image_grid(res, rows=len(ys))
|
||||
grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts)
|
||||
if draw_legend:
|
||||
grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts)
|
||||
|
||||
first_pocessed.images = [grid]
|
||||
|
||||
@@ -109,6 +110,9 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell):
|
||||
re_range = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\(([+-]\d+)\s*\))?\s*")
|
||||
re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\(([+-]\d+(?:.\d*)?)\s*\))?\s*")
|
||||
|
||||
re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*\])?\s*")
|
||||
re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*\])?\s*")
|
||||
|
||||
class Script(scripts.Script):
|
||||
def title(self):
|
||||
return "X/Y plot"
|
||||
@@ -123,13 +127,14 @@ class Script(scripts.Script):
|
||||
with gr.Row():
|
||||
y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[4].label, visible=False, type="index", elem_id="y_type")
|
||||
y_values = gr.Textbox(label="Y values", visible=False, lines=1)
|
||||
|
||||
draw_legend = gr.Checkbox(label='Draw legend', value=True)
|
||||
|
||||
return [x_type, x_values, y_type, y_values, draw_legend]
|
||||
|
||||
return [x_type, x_values, y_type, y_values]
|
||||
|
||||
def run(self, p, x_type, x_values, y_type, y_values):
|
||||
def run(self, p, x_type, x_values, y_type, y_values, draw_legend):
|
||||
modules.processing.fix_seed(p)
|
||||
p.batch_size = 1
|
||||
p.batch_count = 1
|
||||
|
||||
def process_axis(opt, vals):
|
||||
valslist = [x.strip() for x in vals.split(",")]
|
||||
@@ -139,6 +144,7 @@ class Script(scripts.Script):
|
||||
|
||||
for val in valslist:
|
||||
m = re_range.fullmatch(val)
|
||||
mc = re_range_count.fullmatch(val)
|
||||
if m is not None:
|
||||
|
||||
start = int(m.group(1))
|
||||
@@ -146,6 +152,12 @@ class Script(scripts.Script):
|
||||
step = int(m.group(3)) if m.group(3) is not None else 1
|
||||
|
||||
valslist_ext += list(range(start, end, step))
|
||||
elif mc is not None:
|
||||
start = int(mc.group(1))
|
||||
end = int(mc.group(2))
|
||||
num = int(mc.group(3)) if mc.group(3) is not None else 1
|
||||
|
||||
valslist_ext += [int(x) for x in np.linspace(start = start, stop = end, num = num).tolist()]
|
||||
else:
|
||||
valslist_ext.append(val)
|
||||
|
||||
@@ -155,12 +167,19 @@ class Script(scripts.Script):
|
||||
|
||||
for val in valslist:
|
||||
m = re_range_float.fullmatch(val)
|
||||
mc = re_range_count_float.fullmatch(val)
|
||||
if m is not None:
|
||||
start = float(m.group(1))
|
||||
end = float(m.group(2))
|
||||
step = float(m.group(3)) if m.group(3) is not None else 1
|
||||
|
||||
valslist_ext += np.arange(start, end + step, step).tolist()
|
||||
elif mc is not None:
|
||||
start = float(mc.group(1))
|
||||
end = float(mc.group(2))
|
||||
num = int(mc.group(3)) if mc.group(3) is not None else 1
|
||||
|
||||
valslist_ext += np.linspace(start = start, stop = end, num = num).tolist()
|
||||
else:
|
||||
valslist_ext.append(val)
|
||||
|
||||
@@ -184,14 +203,16 @@ class Script(scripts.Script):
|
||||
return process_images(pc)
|
||||
|
||||
processed = draw_xy_grid(
|
||||
p,
|
||||
xs=xs,
|
||||
ys=ys,
|
||||
x_label=lambda x: x_opt.format_value(p, x_opt, x),
|
||||
y_label=lambda y: y_opt.format_value(p, y_opt, y),
|
||||
cell=cell
|
||||
cell=cell,
|
||||
draw_legend=draw_legend
|
||||
)
|
||||
|
||||
if opts.grid_save:
|
||||
images.save_image(processed.images[0], p.outpath_grids, "xy_grid", prompt=p.prompt, seed=processed.seed, p=p)
|
||||
images.save_image(processed.images[0], p.outpath_grids, "xy_grid", prompt=p.prompt, seed=processed.seed, grid=True, p=p)
|
||||
|
||||
return processed
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
.output-html p {margin: 0 0.5em;}
|
||||
.performance { font-size: 0.85em; color: #444; }
|
||||
|
||||
#txt2img_generate, #img2img_generate{
|
||||
max-width: 13em;
|
||||
#generate{
|
||||
min-height: 4.5em;
|
||||
}
|
||||
|
||||
#img2img_interrogate{
|
||||
max-width: 10em;
|
||||
#txt2img_gallery, #img2img_gallery{
|
||||
min-height: 768px;
|
||||
}
|
||||
#txt2img_gallery img, #img2img_gallery img{
|
||||
object-fit: scale-down;
|
||||
}
|
||||
|
||||
#subseed_show{
|
||||
@@ -18,21 +21,33 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#txt2img_roll{
|
||||
#roll{
|
||||
min-width: 1em;
|
||||
max-width: 4em;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
#style_index{
|
||||
min-width: 9em;
|
||||
max-width: 9em;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
#style_apply, #style_create, #interrogate{
|
||||
margin: 0.75em 0.25em 0.25em 0.25em;
|
||||
min-width: 3em;
|
||||
}
|
||||
|
||||
#style_pos_col, #style_neg_col{
|
||||
min-width: 4em !important;
|
||||
}
|
||||
|
||||
#style_index, #style2_index{
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.gr-form{
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#toprow div{
|
||||
border: none;
|
||||
gap: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#resize_mode{
|
||||
@@ -43,10 +58,10 @@ button{
|
||||
align-self: stretch !important;
|
||||
}
|
||||
|
||||
#img2img_prompt, #txt2img_prompt, #img2img_negative_prompt, #txt2img_negative_prompt{
|
||||
#prompt, #negative_prompt{
|
||||
border: none !important;
|
||||
}
|
||||
#img2img_prompt textarea, #txt2img_prompt textarea, #img2img_negative_prompt textarea, #txt2img_negative_prompt textarea{
|
||||
#prompt textarea, #negative_prompt textarea{
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@ -134,8 +149,6 @@ input[type="range"]{
|
||||
}
|
||||
|
||||
#txt2img_negative_prompt, #img2img_negative_prompt{
|
||||
flex: 0.3;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.progressDiv{
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
###########################################
|
||||
# Change the variables below to your need:#
|
||||
###########################################
|
||||
|
||||
# Install directory without trailing slash
|
||||
install_dir="/home/$(whoami)"
|
||||
|
||||
# Name of the subdirectory (defaults to stable-diffusion-webui)
|
||||
clone_dir="stable-diffusion-webui"
|
||||
|
||||
# Commandline arguments for webui.py, for example: export COMMANDLINE_ARGS=(--medvram --opt-split-attention)
|
||||
export COMMANDLINE_ARGS=()
|
||||
|
||||
# python3 executable
|
||||
python_cmd="python3"
|
||||
|
||||
# git executable
|
||||
export GIT=""
|
||||
|
||||
# python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv)
|
||||
venv_dir="venv"
|
||||
|
||||
# install command for torch
|
||||
export TORCH_COMMAND=(python3 -m pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113)
|
||||
|
||||
# Requirements file to use for stable-diffusion-webui
|
||||
export REQS_FILE=""
|
||||
|
||||
# Fixed git repos
|
||||
export K_DIFFUSION_PACKAGE=""
|
||||
export GFPGAN_PACKAGE=""
|
||||
|
||||
# Fixed git commits
|
||||
export STABLE_DIFFUSION_COMMIT_HASH=""
|
||||
export TAMING_TRANSFORMERS_COMMIT_HASH=""
|
||||
export CODEFORMER_COMMIT_HASH=""
|
||||
export BLIP_COMMIT_HASH=""
|
||||
|
||||
###########################################
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
#################################################
|
||||
# Please do not make any changes to this file, #
|
||||
# change the variables in webui-user.sh instead #
|
||||
#################################################
|
||||
# Read variables from webui-user.sh
|
||||
# shellcheck source=/dev/null
|
||||
if [[ -f webui-user.sh ]]
|
||||
then
|
||||
source ./webui-user.sh
|
||||
fi
|
||||
|
||||
# Set defaults
|
||||
# Install directory without trailing slash
|
||||
if [[ -z "${install_dir}" ]]
|
||||
then
|
||||
install_dir="/home/$(whoami)"
|
||||
fi
|
||||
|
||||
# Name of the subdirectory (defaults to stable-diffusion-webui)
|
||||
if [[ -z "${clone_dir}" ]]
|
||||
then
|
||||
clone_dir="stable-diffusion-webui"
|
||||
fi
|
||||
|
||||
# python3 executable
|
||||
if [[ -z "${python_cmd}" ]]
|
||||
then
|
||||
python_cmd="python3"
|
||||
fi
|
||||
|
||||
# git executable
|
||||
if [[ -z "${GIT}" ]]
|
||||
then
|
||||
export GIT="git"
|
||||
fi
|
||||
|
||||
# python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv)
|
||||
if [[ -z "${venv_dir}" ]]
|
||||
then
|
||||
venv_dir="venv"
|
||||
fi
|
||||
|
||||
# install command for torch
|
||||
if [[ -z "${TORCH_COMMAND}" ]]
|
||||
then
|
||||
export TORCH_COMMAND=(python3 -m pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113)
|
||||
fi
|
||||
|
||||
# Do not reinstall existing pip packages on Debian/Ubuntu
|
||||
export PIP_IGNORE_INSTALLED=0
|
||||
|
||||
# Pretty print
|
||||
delimiter="################################################################"
|
||||
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "\e[1m\e[32mInstall script for stable-diffusion + Web UI\n"
|
||||
printf "\e[1m\e[34mTested on Debian 11 (Bullseye)\e[0m"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
|
||||
# Do not run as root
|
||||
if [[ $(id -u) -eq 0 ]]
|
||||
then
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "\e[1m\e[31mERROR: This script must not be launched as root, aborting...\e[0m"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
exit 1
|
||||
else
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "Running on \e[1m\e[32m%s\e[0m user" "$(whoami)"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
fi
|
||||
|
||||
if [[ -d .git ]]
|
||||
then
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "Repo already cloned, using it as install directory"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
install_dir="${PWD}/../"
|
||||
clone_dir="${PWD##*/}"
|
||||
fi
|
||||
|
||||
# Check prequisites
|
||||
for preq in git python3
|
||||
do
|
||||
if ! hash "${preq}" &>/dev/null
|
||||
then
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "\e[1m\e[31mERROR: %s is not installed, aborting...\e[0m" "${preq}"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if ! "${python_cmd}" -c "import venv" &>/dev/null
|
||||
then
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "\e[1m\e[31mERROR: python3-venv is not installed, aborting...\e[0m"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "Clone or update stable-diffusion-webui"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
cd "${install_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/, aborting...\e[0m" "${install_dir}"; exit 1; }
|
||||
if [[ -d "${clone_dir}" ]]
|
||||
then
|
||||
cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; }
|
||||
"${GIT}" pull
|
||||
else
|
||||
"${GIT}" clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git "${clone_dir}"
|
||||
cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; }
|
||||
fi
|
||||
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "Create and activate python venv"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
cd "${install_dir}"/"${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; }
|
||||
if [[ ! -d "${venv_dir}" ]]
|
||||
then
|
||||
"${python_cmd}" -m venv "${venv_dir}"
|
||||
first_launch=1
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
if [[ -f "${venv_dir}"/bin/activate ]]
|
||||
then
|
||||
source "${venv_dir}"/bin/activate
|
||||
else
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "\e[1m\e[31mERROR: Cannot activate python venv, aborting...\e[0m"
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
printf "Launching launch.py..."
|
||||
printf "\n%s\n" "${delimiter}"
|
||||
"${python_cmd}" launch.py
|
||||
Reference in New Issue
Block a user