diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README.md | 307 | ||||
-rw-r--r-- | modules/codeformer_model.py | 6 | ||||
-rw-r--r-- | modules/extras.py | 107 | ||||
-rw-r--r-- | modules/images.py | 54 | ||||
-rw-r--r-- | modules/img2img.py | 53 | ||||
-rw-r--r-- | modules/interrogate.py | 2 | ||||
-rw-r--r-- | modules/processing.py | 31 | ||||
-rw-r--r-- | modules/prompt_parser.py | 130 | ||||
-rw-r--r-- | modules/scripts.py | 7 | ||||
-rw-r--r-- | modules/sd_hijack.py | 2 | ||||
-rw-r--r-- | modules/sd_samplers.py | 51 | ||||
-rw-r--r-- | modules/shared.py | 17 | ||||
-rw-r--r-- | modules/styles.py | 96 | ||||
-rw-r--r-- | modules/txt2img.py | 4 | ||||
-rw-r--r-- | modules/ui.py | 132 | ||||
-rw-r--r-- | script.js | 12 | ||||
-rw-r--r-- | scripts/img2imgalt.py | 37 | ||||
-rw-r--r-- | scripts/loopback.py | 81 | ||||
-rw-r--r-- | scripts/prompt_matrix.py | 2 | ||||
-rw-r--r-- | scripts/prompts_from_file.py | 2 | ||||
-rw-r--r-- | scripts/xy_grid.py | 39 | ||||
-rw-r--r-- | style.css | 41 | ||||
-rw-r--r-- | webui-user.sh | 40 | ||||
-rw-r--r-- | webui.sh | 139 |
25 files changed, 851 insertions, 542 deletions
@@ -14,4 +14,5 @@ __pycache__ /styles.csv /styles.csv.bak /webui-user.bat +/webui-user.sh /interrogate @@ -3,10 +3,8 @@ A browser interface based on Gradio library for Stable Diffusion. 
-## Feature showcase
-
-[Detailed feature showcase with images, art by Greg Rutkowski](https://github.com/AUTOMATIC1111/stable-diffusion-webui-feature-showcase)
-
+## Features
+[Detailed feature showcase with images](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features):
- Original txt2img and img2img modes
- One click install and run script (but you still must install python and git)
- Outpainting
@@ -18,10 +16,10 @@ A browser interface based on Gradio library for Stable Diffusion. - X/Y plot
- Textual Inversion
- Extras tab with:
- - GFPGAN, neural network that fixes faces
- - CodeFormer, face restoration tool as an alternative to GFPGAN
- - RealESRGAN, neural network upscaler
- - ESRGAN, neural network with a lot of third party models
+ - GFPGAN, neural network that fixes faces
+ - CodeFormer, face restoration tool as an alternative to GFPGAN
+ - RealESRGAN, neural network upscaler
+ - ESRGAN, neural network with a lot of third party models
- Resizing aspect ratio options
- Sampling method selection
- Interrupt processing at any time
@@ -42,283 +40,38 @@ A browser interface based on Gradio library for Stable Diffusion. - Variations
- Seed resizing
- CLIP interrogator
+- Prompt Editing
-## Installing and running
-
-You need [python](https://www.python.org/downloads/windows/) and [git](https://git-scm.com/download/win)
-installed to run this, and an NVidia video card.
-
-You need `model.ckpt`, Stable Diffusion model checkpoint, a big file containing the neural network weights. You
-can obtain it from the following places:
- - [official download](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original)
- - [file storage](https://drive.yerf.org/wl/?id=EBfTrmcCCUAGaQBXVIj5lJmEhjoP1tgl)
- - magnet:?xt=urn:btih:3a4a612d75ed088ea542acac52f9f45987488d1c&dn=sd-v1-4.ckpt&tr=udp%3a%2f%2ftracker.openbittorrent.com%3a6969%2fannounce&tr=udp%3a%2f%2ftracker.opentrackr.org%3a1337
-
-You can optionally use GFPGAN to improve faces, to do so you'll need to download the model from [here](https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth) and place it in the same directory as `webui.bat`.
-
-To use ESRGAN models, put them into ESRGAN directory in the same location as webui.py. A file will be loaded
-as a model if it has .pth extension, and it will show up with its name in the UI. Grab models from the [Model Database](https://upscale.wiki/wiki/Model_Database).
-
-> Note: RealESRGAN models are not ESRGAN models, they are not compatible. Do not download RealESRGAN models. Do not place
-RealESRGAN into the directory with ESRGAN models. Thank you.
-
-### Automatic installation/launch
-
-- install [Python 3.10.6](https://www.python.org/downloads/windows/) and check "Add Python to PATH" during installation. You must install this exact version.
-- install [git](https://git-scm.com/download/win)
-- place `model.ckpt` into webui directory, next to `webui.bat`.
-- _*(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.
-
-#### Troubleshooting
-
-- if your version of Python is not in PATH (or if another version is), edit `webui-user.bat`, and modify the
-line `set PYTHON=python` to say the full path to your python executable, for example: `set PYTHON=B:\soft\Python310\python.exe`.
-You can do this for python, but not for git.
-- if you get out of memory errors and your video-card has a low amount of VRAM (4GB), use custom parameter `set COMMANDLINE_ARGS` (see section below)
-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`
-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
-floating point numbers (Known issue with 16xx cards). You must use `--precision full --no-half` in addition to command line
-arguments (set them using `set COMMANDLINE_ARGS`, see below), and the model will take much more space in VRAM (you will likely
-have to also use at least `--medvram`).
-- the installer creates a python virtual environment, so none of the installed modules will affect your system installation of python if
-you had one prior to installing this.
-- About _"You must install this exact version"_ from the instructions above: you can use any version of python you like,
-and it will likely work, but if you want to seek help about things not working, I will not offer help unless you use this
-exact version for my sanity.
-
-#### How to run with custom parameters
-
-It's possible to edit `set COMMANDLINE_ARGS=` line in `webui.bat` to run the program with different command line arguments, but that may lead
-to inconveniences when the file is updated in the repository.
-
-The recommended way is to use another .bat file named anything you like, set the parameters you want in it, and run webui.bat from it.
-A `webui-user.bat` file included into the repository does exactly this.
-
-Here is an example that runs the program with `--opt-split-attention` argument:
-
-```commandline
-@echo off
-
-set COMMANDLINE_ARGS=--opt-split-attention
-
-call webui.bat
-```
-
-Another example, this file will run the program with a custom python path, a different model named `a.ckpt` and without a virtual environment:
-
-```commandline
-@echo off
-
-set PYTHON=b:/soft/Python310/Python.exe
-set VENV_DIR=-
-set COMMANDLINE_ARGS=--ckpt a.ckpt
-
-call webui.bat
-```
-
-### How to create large images?
-Use `--opt-split-attention` parameter. It slows down sampling a tiny bit, but allows you to make gigantic images.
-
-### What options to use for low VRAM video-cards?
-You can, through command line arguments, enable the various optimizations which sacrifice some/a lot of speed in favor of
-using less VRAM. Those arguments are added to the `COMMANDLINE_ARGS` parameter, see section above.
-
-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 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.
-
-### 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.
-
-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.
-
-Use `--port xxxx` to make the server listen on a specific port, xxxx being the wanted port. Remember that
-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
-
-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
-
-### Textual Inversion
-To make use of pretrained embeddings, create an `embeddings` directory (in the same place as `webui.py`)
-and put your embeddings into it. They must be either .pt or .bin files, each with only one trained embedding,
-and the filename (without .pt/.bin) will be the term you'll use in the prompt to get that embedding.
-
-As an example, I trained one for about 5000 steps: https://files.catbox.moe/e2ui6r.pt; it does not produce
-very good results, but it does work. To try it out download the file, rename it to `Usada Pekora.pt`, put it into the `embeddings` dir
-and use `Usada Pekora` in the prompt.
-
-You may also try some from the growing library of embeddings at https://huggingface.co/sd-concepts-library, downloading one of the `learned_embeds.bin` files, renaming it to the term you want to use for it in the prompt (be sure to keep the .bin extension) and putting it in your `embeddings` directory.
-
-### How to change UI defaults?
-
-After running once, a `ui-config.json` file appears in webui directory:
-
-```json
-{
- "txt2img/Sampling Steps/value": 20,
- "txt2img/Sampling Steps/minimum": 1,
- "txt2img/Sampling Steps/maximum": 150,
- "txt2img/Sampling Steps/step": 1,
- "txt2img/Batch count/value": 1,
- "txt2img/Batch count/minimum": 1,
- "txt2img/Batch count/maximum": 32,
- "txt2img/Batch count/step": 1,
- "txt2img/Batch size/value": 1,
- "txt2img/Batch size/minimum": 1,
-```
-
-Edit values to your liking and the next time you launch the program they will be applied.
-
-### Almost automatic installation and launch
+## Installation and Running
+Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs.
-Install python and git, place `model.ckpt` and `GFPGANv1.3.pth` into webui directory, run:
+Alternatively, use [Google Colab](https://colab.research.google.com/drive/1Iy-xW9t1-OQWhb0hNxueGij8phCyluOh).
-```
-python launch.py
-```
-
-This installs packages via pip. If you need to use a virtual environment, you must set it up yourself. I will not
-provide support for using the web ui this way unless you are using the recommended version of python below.
-
-If you'd like to use command line parameters, use them right there:
-
-```
-python launch.py --opt-split-attention --ckpt ../secret/anime9999.ckpt
-```
-
-### Manual installation
-Alternatively, if you don't want to run the installer, here are instructions for installing
-everything by hand. This can run on both Windows and Linux (if you're on linux, use `ls`
-instead of `dir`).
+### Automatic Installation on Windows
+1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH"
+2. Install [git](https://git-scm.com/download/win).
+3. Download the stable-diffusion-webui repository, for example by running `git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git`.
+4. Place `model.ckpt` in the base directory, alongside `webui.py`.
+5. _*(Optional)*_ Place `GFPGANv1.3.pth` in the base directory, alongside `webui.py`.
+6. Run `webui-user.bat` from Windows Explorer as normal, non-administrate, user.
+### Automatic Installation on Linux
+1. Install the dependencies:
```bash
-# install torch with CUDA support. See https://pytorch.org/get-started/locally/ for more instructions if this fails.
-pip install torch --extra-index-url https://download.pytorch.org/whl/cu113
-
-# check if torch supports GPU; this must output "True". You need CUDA 11. installed for this. You might be able to use
-# a different version, but this is what I tested.
-python -c "import torch; print(torch.cuda.is_available())"
-
-# clone web ui and go into its directory
-git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
-cd stable-diffusion-webui
-
-# clone repositories for Stable Diffusion and (optionally) CodeFormer
-mkdir repositories
-git clone https://github.com/CompVis/stable-diffusion.git repositories/stable-diffusion
-git clone https://github.com/CompVis/taming-transformers.git repositories/taming-transformers
-git clone https://github.com/sczhou/CodeFormer.git repositories/CodeFormer
-git clone https://github.com/salesforce/BLIP.git repositories/BLIP
-
-# install requirements of Stable Diffusion
-pip install transformers==4.19.2 diffusers invisible-watermark --prefer-binary
-
-# install k-diffusion
-pip install git+https://github.com/crowsonkb/k-diffusion.git --prefer-binary
-
-# (optional) install GFPGAN (face restoration)
-pip install git+https://github.com/TencentARC/GFPGAN.git --prefer-binary
-
-# (optional) install requirements for CodeFormer (face restoration)
-pip install -r repositories/CodeFormer/requirements.txt --prefer-binary
-
-# install requirements of web ui
-pip install -r requirements.txt --prefer-binary
-
-# update numpy to latest version
-pip install -U numpy --prefer-binary
-
-# (outside of command line) put stable diffusion model into web ui directory
-# the command below must output something like: 1 File(s) 4,265,380,512 bytes
-dir model.ckpt
-
-# (outside of command line) put the GFPGAN model into web ui directory
-# the command below must output something like: 1 File(s) 348,632,874 bytes
-dir GFPGANv1.3.pth
+# Debian-based:
+sudo apt install wget git python3 python3-venv
+# Red Hat-based:
+sudo dnf install wget git python3
+# Arch-based:
+sudo pacman -S wget git python3
```
-
-> Note: the directory structure for manual instruction has been changed on 2022-09-09 to match automatic installation: previously
-> webui was in a subdirectory of stable diffusion, now it's the reverse. If you followed manual installation before the
-> change, you can still use the program with your existing directory structure.
-
-After that the installation is finished.
-
-Run the command to start web ui:
-
-```
-python webui.py
-```
-
-If you have a 4GB video card, run the command with either `--lowvram` or `--medvram` argument:
-
-```
-python webui.py --medvram
-```
-
-After a while, you will get a message like this:
-
-```
-Running on local URL: http://127.0.0.1:7860/
-```
-
-Open the URL in a browser, and you are good to go.
-
-
-### Windows 11 WSL2 instructions
-Alternatively, here are instructions for installing under Windows 11 WSL2 Linux distro, everything by hand:
-
+2. To install in `/home/$(whoami)/stable-diffusion-webui/`, run:
```bash
-# install conda (if not already done)
-wget https://repo.anaconda.com/archive/Anaconda3-2022.05-Linux-x86_64.sh
-chmod +x Anaconda3-2022.05-Linux-x86_64.sh
-./Anaconda3-2022.05-Linux-x86_64.sh
-
-# Clone webui repo
-git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
-cd stable-diffusion-webui
-
-# Create and activate conda env
-conda env create -f environment-wsl2.yaml
-conda activate automatic
-
-# (optional) install requirements for GFPGAN (upscaling)
-wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth
+bash <(wget -qO- https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh)
```
-After that follow the instructions in the `Manual instructions` section starting at step `:: clone repositories for Stable Diffusion and (optionally) CodeFormer`.
-
-### Custom scripts from users
-
-[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
-- put description of input image into the Original prompt field
-- use Euler only
-- recommended: 50 steps, low cfg scale between 1 and 2
-- denoising and seed don't matter
-- decode cfg scale between 0 and 1
-- decode steps 50
-- original blue haired woman close nearly reproduces with cfg scale=1.8
+## Documentation
+The documentation was moved from this README over to the project's [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki).
## Credits
- Stable Diffusion - https://github.com/CompVis/stable-diffusion, https://github.com/CompVis/taming-transformers
@@ -326,7 +79,7 @@ After that follow the instructions in the `Manual instructions` section starting - GFPGAN - https://github.com/TencentARC/GFPGAN.git
- ESRGAN - https://github.com/xinntao/ESRGAN
- Ideas for optimizations - https://github.com/basujindal/stable-diffusion
-- Cross Attention layer optimization - https://github.com/Doggettx/stable-diffusion
+- Doggettx - Cross Attention layer optimization - https://github.com/Doggettx/stable-diffusion, original idea for prompt editing.
- 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.
diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index 21c704f7..8fbdea24 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -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
diff --git a/modules/extras.py b/modules/extras.py index cb083544..ffae7d67 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -7,71 +7,91 @@ import modules.gfpgan_model from modules.ui import plaintext_to_html
import modules.codeformer_model
import piexif
+import piexif.helper
cached_images = {}
-def run_extras(image, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility):
+def run_extras(image, image_folder, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility):
devices.torch_gc()
- existing_pnginfo = image.info or {}
+ imageArr = []
- image = image.convert("RGB")
- info = ""
+ if image_folder != None:
+ if image != None:
+ print("Batch detected and single image detected, please only use one of the two. Aborting.")
+ return None
+ #convert file to pillow image
+ for img in image_folder:
+ image = Image.fromarray(np.array(Image.open(img)))
+ imageArr.append(image)
+
+ elif image != None:
+ if image_folder != None:
+ print("Batch detected and single image detected, please only use one of the two. Aborting.")
+ return None
+ else:
+ imageArr.append(image)
outpath = opts.outdir_samples or opts.outdir_extras_samples
- if gfpgan_visibility > 0:
- restored_img = modules.gfpgan_model.gfpgan_fix_faces(np.array(image, dtype=np.uint8))
- res = Image.fromarray(restored_img)
+ for image in imageArr:
+ existing_pnginfo = image.info or {}
+
+ image = image.convert("RGB")
+ info = ""
+
+ if gfpgan_visibility > 0:
+ restored_img = modules.gfpgan_model.gfpgan_fix_faces(np.array(image, dtype=np.uint8))
+ res = Image.fromarray(restored_img)
- if gfpgan_visibility < 1.0:
- res = Image.blend(image, res, gfpgan_visibility)
+ if gfpgan_visibility < 1.0:
+ res = Image.blend(image, res, gfpgan_visibility)
- info += f"GFPGAN visibility:{round(gfpgan_visibility, 2)}\n"
- image = res
+ info += f"GFPGAN visibility:{round(gfpgan_visibility, 2)}\n"
+ image = res
- if codeformer_visibility > 0:
- restored_img = modules.codeformer_model.codeformer.restore(np.array(image, dtype=np.uint8), w=codeformer_weight)
- res = Image.fromarray(restored_img)
+ if codeformer_visibility > 0:
+ restored_img = modules.codeformer_model.codeformer.restore(np.array(image, dtype=np.uint8), w=codeformer_weight)
+ res = Image.fromarray(restored_img)
- if codeformer_visibility < 1.0:
- res = Image.blend(image, res, codeformer_visibility)
+ if codeformer_visibility < 1.0:
+ res = Image.blend(image, res, codeformer_visibility)
- info += f"CodeFormer w: {round(codeformer_weight, 2)}, CodeFormer visibility:{round(codeformer_visibility)}\n"
- image = res
+ info += f"CodeFormer w: {round(codeformer_weight, 2)}, CodeFormer visibility:{round(codeformer_visibility)}\n"
+ image = res
- if upscaling_resize != 1.0:
- def upscale(image, scaler_index, resize):
- small = image.crop((image.width // 2, image.height // 2, image.width // 2 + 10, image.height // 2 + 10))
- pixels = tuple(np.array(small).flatten().tolist())
- key = (resize, scaler_index, image.width, image.height, gfpgan_visibility, codeformer_visibility, codeformer_weight) + pixels
+ if upscaling_resize != 1.0:
+ def upscale(image, scaler_index, resize):
+ small = image.crop((image.width // 2, image.height // 2, image.width // 2 + 10, image.height // 2 + 10))
+ pixels = tuple(np.array(small).flatten().tolist())
+ key = (resize, scaler_index, image.width, image.height, gfpgan_visibility, codeformer_visibility, codeformer_weight) + pixels
- c = cached_images.get(key)
- if c is None:
- upscaler = shared.sd_upscalers[scaler_index]
- c = upscaler.upscale(image, image.width * resize, image.height * resize)
- cached_images[key] = c
+ c = cached_images.get(key)
+ if c is None:
+ upscaler = shared.sd_upscalers[scaler_index]
+ c = upscaler.upscale(image, image.width * resize, image.height * resize)
+ cached_images[key] = c
- return c
+ return c
- info += f"Upscale: {round(upscaling_resize, 3)}, model:{shared.sd_upscalers[extras_upscaler_1].name}\n"
- res = upscale(image, extras_upscaler_1, upscaling_resize)
+ info += f"Upscale: {round(upscaling_resize, 3)}, model:{shared.sd_upscalers[extras_upscaler_1].name}\n"
+ res = upscale(image, extras_upscaler_1, upscaling_resize)
- if extras_upscaler_2 != 0 and extras_upscaler_2_visibility > 0:
- res2 = upscale(image, extras_upscaler_2, upscaling_resize)
- info += f"Upscale: {round(upscaling_resize, 3)}, visibility: {round(extras_upscaler_2_visibility, 3)}, model:{shared.sd_upscalers[extras_upscaler_2].name}\n"
- res = Image.blend(res, res2, extras_upscaler_2_visibility)
+ if extras_upscaler_2 != 0 and extras_upscaler_2_visibility > 0:
+ res2 = upscale(image, extras_upscaler_2, upscaling_resize)
+ info += f"Upscale: {round(upscaling_resize, 3)}, visibility: {round(extras_upscaler_2_visibility, 3)}, model:{shared.sd_upscalers[extras_upscaler_2].name}\n"
+ res = Image.blend(res, res2, extras_upscaler_2_visibility)
- image = res
+ image = res
- while len(cached_images) > 2:
- del cached_images[next(iter(cached_images.keys()))]
+ 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), ''
+ return imageArr, plaintext_to_html(info), ''
def run_pnginfo(image):
@@ -80,7 +100,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']:
diff --git a/modules/images.py b/modules/images.py index 50b0e099..f37f5f08 100644 --- a/modules/images.py +++ b/modules/images.py @@ -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)
@@ -252,7 +252,7 @@ def sanitize_filename_part(text, replace_spaces=True): if replace_spaces:
text = text.replace(' ', '_')
- return text.translate({ord(x): '' for x in invalid_filename_chars})[:128]
+ return text.translate({ord(x): '_' for x in invalid_filename_chars})[:128]
def apply_filename_pattern(x, p, seed, prompt):
@@ -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.
+
+ The sequence starts at 0.
+ """
+ result = -1
+ if basename != '':
+ basename = basename + "-"
-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 != ""
+ 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,26 +335,30 @@ 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"):
- exif_bytes = piexif.dump({
+ def exif_bytes():
+ return piexif.dump({
"Exif": {
- piexif.ExifIFD.UserComment: info.encode("utf8"),
- }
+ piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(info or "", encoding="unicode")
+ },
})
+
+ if extension.lower() in ("jpg", "jpeg", "webp"):
+ image.save(fullfn, quality=opts.jpeg_quality, exif_bytes=exif_bytes())
else:
- exif_bytes = None
+ image.save(fullfn, quality=opts.jpeg_quality, pnginfo=pnginfo)
- image.save(fullfn, quality=opts.jpeg_quality, pnginfo=pnginfo, exif=exif_bytes)
+ if extension.lower() == "webp":
+ piexif.insert(exif_bytes, fullfn)
target_side_length = 4000
oversize = image.width > target_side_length or image.height > target_side_length
@@ -346,7 +370,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i elif oversize:
image = image.resize((image.width * target_side_length // image.height, target_side_length), LANCZOS)
- image.save(fullfn, quality=opts.jpeg_quality, exif=exif_bytes)
+ image.save(fullfn_without_extension + ".jpg", quality=opts.jpeg_quality, exif_bytes=exif_bytes())
if opts.save_txt and info is not None:
with open(f"{fullfn_without_extension}.txt", "w", encoding="utf8") as file:
diff --git a/modules/img2img.py b/modules/img2img.py index 70c99e33..2dcabc6b 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -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)
@@ -113,6 +76,7 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init grid = images.split_grid(img, tile_w=width, tile_h=height, overlap=upscale_overlap)
+ batch_size = p.batch_size
upscale_count = p.n_iter
p.n_iter = 1
p.do_not_save_grid = True
@@ -124,7 +88,7 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init for tiledata in row:
work.append(tiledata[2])
- batch_count = math.ceil(len(work) / p.batch_size)
+ batch_count = math.ceil(len(work) / batch_size)
state.job_count = batch_count * upscale_count
print(f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.")
@@ -136,9 +100,10 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init work_results = []
for i in range(batch_count):
- p.init_images = work[i*p.batch_size:(i+1)*p.batch_size]
+ p.batch_size = batch_size
+ p.init_images = work[i*batch_size:(i+1)*batch_size]
- state.job = f"Batch {i + 1} out of {state.job_count}"
+ state.job = f"Batch {i + 1 + n * batch_count} out of {state.job_count}"
processed = process_images(p)
if initial_info is None:
diff --git a/modules/interrogate.py b/modules/interrogate.py index 06862fcc..f62a4745 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -98,7 +98,7 @@ class InterrogateModels: text_array = text_array[0:int(shared.opts.interrogate_clip_dict_limit)]
top_count = min(top_count, len(text_array))
- text_tokens = clip.tokenize([text for text in text_array]).to(shared.device)
+ text_tokens = clip.tokenize([text for text in text_array], truncate=True).to(shared.device)
text_features = self.clip_model.encode_text(text_tokens).type(self.dtype)
text_features /= text_features.norm(dim=-1, keepdim=True)
diff --git a/modules/processing.py b/modules/processing.py index 5abdfd7c..798313ee 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -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
@@ -194,9 +194,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
@@ -261,11 +261,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, p=p)
@@ -326,12 +329,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:
@@ -458,7 +459,9 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): latent_mask = self.latent_mask if self.latent_mask is not None else self.image_mask
- self.color_corrections = []
+ add_color_corrections = opts.img2img_color_correction and self.color_corrections is None
+ if add_color_corrections:
+ self.color_corrections = []
imgs = []
for img in self.init_images:
image = img.convert("RGB")
@@ -480,7 +483,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if self.inpainting_fill != 1:
image = fill(image, latent_mask)
- if opts.img2img_color_correction:
+ if add_color_corrections:
self.color_corrections.append(setup_color_correction(image))
image = np.array(image).astype(np.float32) / 255.0
diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py new file mode 100644 index 00000000..0835f692 --- /dev/null +++ b/modules/prompt_parser.py @@ -0,0 +1,130 @@ +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)
+ continue
+
+ 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)
+ continue
+
+ 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)
diff --git a/modules/scripts.py b/modules/scripts.py index 74591bab..9cc5a185 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -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)
@@ -92,6 +92,7 @@ class ScriptRunner: for script in self.scripts:
script.args_from = len(inputs)
+ script.args_to = len(inputs)
controls = wrap_call(script.ui, script.filename, "ui", is_img2img)
diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index ec7d14cb..65414518 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -57,7 +57,7 @@ def split_cross_attention_forward(self, x, context=None, mask=None): q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q_in, k_in, v_in))
del q_in, k_in, v_in
- r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device)
+ r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype)
stats = torch.cuda.memory_stats(q.device)
mem_active = stats['active_bytes.all.current']
diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index d478c5bc..02ffce0e 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -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)
@@ -94,10 +81,29 @@ class VanillaStableDiffusionSampler: self.nmask = None
self.init_latent = None
self.sampler_noises = None
+ self.step = 0
def number_of_needed_noises(self, p):
return 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)
@@ -109,10 +115,11 @@ 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
+ self.step = 0
samples = self.sampler.decode(x1, conditioning, t_enc, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning)
@@ -121,10 +128,11 @@ 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
+ self.step = 0
# existing code fails with cetin step counts, like 9
try:
@@ -142,8 +150,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)
@@ -158,6 +170,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
@@ -191,7 +205,7 @@ class TorchHijack: class KDiffusionSampler:
def __init__(self, funcname, sd_model):
- self.model_wrap = k_diffusion.external.CompVisDenoiser(sd_model)
+ self.model_wrap = k_diffusion.external.CompVisDenoiser(sd_model, quantize=shared.opts.enable_quantization)
self.funcname = funcname
self.func = getattr(k_diffusion.sampling, self.funcname)
self.model_wrap_cfg = CFGDenoiser(self.model_wrap)
@@ -228,6 +242,7 @@ class KDiffusionSampler: self.model_wrap_cfg.mask = p.mask
self.model_wrap_cfg.nmask = p.nmask
self.model_wrap_cfg.init_latent = p.init_latent
+ self.model_wrap.step = 0
if hasattr(k_diffusion.sampling, 'trange'):
k_diffusion.sampling.trange = lambda *args, **kwargs: extended_trange(*args, **kwargs)
@@ -241,6 +256,8 @@ class KDiffusionSampler: sigmas = self.model_wrap.get_sigmas(p.steps)
x = x * sigmas[0]
+ self.model_wrap_cfg.step = 0
+
if hasattr(k_diffusion.sampling, 'trange'):
k_diffusion.sampling.trange = lambda *args, **kwargs: extended_trange(*args, **kwargs)
diff --git a/modules/shared.py b/modules/shared.py index ac870ec4..fa6a0e99 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -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"),
@@ -123,6 +125,7 @@ class Options: "enable_pnginfo": OptionInfo(True, "Save text information about generation parameters as chunks to png files"),
"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."),
+ "enable_quantization": OptionInfo(False, "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply."),
"font": OptionInfo("", "Font for image grids that have text"),
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text and [text] to make it pay less attention"),
"enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"),
@@ -141,8 +144,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)"),
}
diff --git a/modules/styles.py b/modules/styles.py index bc7f070f..eeedcd08 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -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)
- 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)
+ return res
- return styles
+def apply_styles_to_prompt(prompt, styles):
+ for style in styles:
+ prompt = merge_prompts(style, prompt)
-def merge_prompts(style_prompt: str, prompt: str) -> str:
- parts = filter(None, (prompt.strip(), style_prompt.strip()))
- return ", ".join(parts)
+ return prompt
-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)
+class StyleDatabase:
+ def __init__(self, path: str):
+ self.no_style = PromptStyle("None", "", "")
+ self.styles = {"None": self.no_style}
- 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)
+ if not os.path.exists(path):
+ return
+
+ 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", "")
+ self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt)
+
+ 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 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 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())
+
+ # Always keep a backup file around
+ if os.path.exists(path):
+ shutil.move(path, path + ".bak")
+ shutil.move(temp_path, path)
diff --git a/modules/txt2img.py b/modules/txt2img.py index d60febfc..30d89849 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -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,
diff --git a/modules/ui.py b/modules/ui.py index d1aa7793..b6d5dcd8 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -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,22 +610,46 @@ 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:
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
- with gr.Group():
- image = gr.Image(label="Source", source="upload", interactive=True, type="pil")
+ with gr.Tabs():
+ with gr.TabItem('Single Image'):
+ image = gr.Image(label="Source", source="upload", interactive=True, type="pil")
+
+ with gr.TabItem('Batch Process'):
+ image_batch = gr.File(label="Batch Process", file_count="multiple", source="upload", interactive=True, type="file")
upscaling_resize = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Resize", value=2)
@@ -615,7 +670,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): submit = gr.Button('Generate', elem_id="extras_generate", variant='primary')
with gr.Column(variant='panel'):
- result_image = gr.Image(label="Result")
+ result_images = gr.Gallery(label="Result")
html_info_x = gr.HTML()
html_info = gr.HTML()
@@ -623,6 +678,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): fn=run_extras,
inputs=[
image,
+ image_batch,
gfpgan_visibility,
codeformer_visibility,
codeformer_weight,
@@ -632,7 +688,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo): extras_upscaler_2_visibility,
],
outputs=[
- result_image,
+ result_images,
html_info_x,
html_info,
]
@@ -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(){ diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index 16a2fdf6..7813bbcc 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -1,10 +1,12 @@ +from collections import namedtuple
+
import numpy as np
from tqdm import trange
import modules.scripts as scripts
import gradio as gr
-from modules import processing, shared, sd_samplers
+from modules import processing, shared, sd_samplers, prompt_parser
from modules.processing import Processed
from modules.sd_samplers import samplers
from modules.shared import opts, cmd_opts, state
@@ -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,19 @@ 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)
+
+ 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 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]
+ 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
+ uncond = p.sd_model.get_learned_conditioning(p.batch_size * [""])
+ noise = find_noise_for_image(p, cond, uncond, cfg, st)
+ self.cache = Cached(noise, cfg, st, lat, original_prompt)
sampler = samplers[p.sampler_index].constructor(p.sd_model)
@@ -98,6 +105,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
diff --git a/scripts/loopback.py b/scripts/loopback.py new file mode 100644 index 00000000..8aca61f3 --- /dev/null +++ b/scripts/loopback.py @@ -0,0 +1,81 @@ +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
+
+ if opts.img2img_color_correction:
+ p.color_corrections = [processing.setup_color_correction(p.init_images[0])]
+
+ 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
diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index aaece054..e49c9b20 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -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
diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index e0c7f1d2..d9b01c81 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -33,7 +33,7 @@ class Script(scripts.Script): images = []
for batch_no in range(batch_count):
- state.job = f"{batch_no} out of {batch_count * p.n_iter}"
+ state.job = f"{batch_no + 1} out of {batch_count * p.n_iter}"
p.prompt = lines[batch_no*p.batch_size:(batch_no+1)*p.batch_size] * p.n_iter
proc = process_images(p)
images += proc.images
diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py index dd6db81c..eccfda87 100644 --- a/scripts/xy_grid.py +++ b/scripts/xy_grid.py @@ -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_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;
}
-#style_index{
- min-width: 9em;
- max-width: 9em;
- padding-left: 0;
- padding-right: 0;
+.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{
diff --git a/webui-user.sh b/webui-user.sh new file mode 100644 index 00000000..b7a1b607 --- /dev/null +++ b/webui-user.sh @@ -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="" + +########################################### diff --git a/webui.sh b/webui.sh new file mode 100644 index 00000000..0f9fdbef --- /dev/null +++ b/webui.sh @@ -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 |