Compare commits
66
Commits
codeformer
..
seeds
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d714ea4c41 | ||
|
|
5b6a585ae5 | ||
|
|
17a7477c72 | ||
|
|
efde17e839 | ||
|
|
b1707553cf | ||
|
|
ab623db52c | ||
|
|
16792691c7 | ||
|
|
003b60b94e | ||
|
|
41434ba3cd | ||
|
|
bcb8a5eb0a | ||
|
|
1fd2c22919 | ||
|
|
0c63aa95e1 | ||
|
|
116a2b89c0 | ||
|
|
93524bfb73 | ||
|
|
764a64b02e | ||
|
|
5d49003e0d | ||
|
|
02bcd51a5a | ||
|
|
ec33d6e842 | ||
|
|
7c8b6b2abb | ||
|
|
fe4e3c2673 | ||
|
|
ca3861e05f | ||
|
|
bb46ad9504 | ||
|
|
701f76b29a | ||
|
|
f211c498b9 | ||
|
|
20b86e81c3 | ||
|
|
ad02b249f5 | ||
|
|
62ce77e245 | ||
|
|
f5001246e2 | ||
|
|
6dc5cf558d | ||
|
|
ed01f69542 | ||
|
|
3eea3c4dab | ||
|
|
e817a28b8e | ||
|
|
3a4c6d9ef5 | ||
|
|
a196c45f15 | ||
|
|
cce6f1df41 | ||
|
|
27dfcf69da | ||
|
|
50178b7f5b | ||
|
|
567c1fbc1c | ||
|
|
48317a5176 | ||
|
|
9c510011ac | ||
|
|
db92896e30 | ||
|
|
4f3cebd51d | ||
|
|
5d087731a5 | ||
|
|
61785cef65 | ||
|
|
0fedd50886 | ||
|
|
9ddaf8269e | ||
|
|
21a375e6b2 | ||
|
|
bc12eddb40 | ||
|
|
0959fa2d02 | ||
|
|
782b819a55 | ||
|
|
02fecac1c7 | ||
|
|
1e7a36fd79 | ||
|
|
52e071da2a | ||
|
|
d03e9502b1 | ||
|
|
7045c84643 | ||
|
|
4d5a366f00 | ||
|
|
296d012423 | ||
|
|
ba1124b326 | ||
|
|
ee29bb77bf | ||
|
|
795d49aa24 | ||
|
|
b681a8f4f2 | ||
|
|
e92d4cf747 | ||
|
|
aaeeef82fa | ||
|
|
700c47a674 | ||
|
|
65fbefd033 | ||
|
|
2cbda50cdd |
+3
-1
@@ -8,4 +8,6 @@ __pycache__
|
||||
/ui-config.json
|
||||
/outputs
|
||||
/config.json
|
||||
/log
|
||||
/log
|
||||
/webui.settings.bat
|
||||
/embeddings
|
||||
|
||||
@@ -8,7 +8,7 @@ A browser interface based on Gradio library for Stable Diffusion.
|
||||
[Detailed feature showcase with images, art by Greg Rutkowski](https://github.com/AUTOMATIC1111/stable-diffusion-webui-feature-showcase)
|
||||
|
||||
- Original txt2img and img2img modes
|
||||
- One click install and run script (but you still must install python, git and CUDA)
|
||||
- One click install and run script (but you still must install python and git)
|
||||
- Outpainting
|
||||
- Inpainting
|
||||
- Prompt matrix
|
||||
@@ -42,9 +42,6 @@ A browser interface based on Gradio library for Stable Diffusion.
|
||||
You need [python](https://www.python.org/downloads/windows/) and [git](https://git-scm.com/download/win)
|
||||
installed to run this, and an NVidia videocard.
|
||||
|
||||
I tested the installation to work Windows with Python 3.8.10, and with Python 3.10.6. You may be able
|
||||
to have success with different versions.
|
||||
|
||||
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)
|
||||
@@ -60,40 +57,75 @@ as model if it has .pth extension. Grab models from the [Model Database](https:/
|
||||
|
||||
- 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)
|
||||
- install [CUDA 11.3](https://developer.nvidia.com/cuda-11.3.0-download-archive?target_os=Windows&target_arch=x86_64)
|
||||
- place `model.ckpt` into webui directory, next to `webui.bat`.
|
||||
- _*(optional)*_ place `GFPGANv1.3.pth` into webui directory, next to `webui.bat`.
|
||||
- run `webui.bat` from Windows Explorer.
|
||||
- run `webui-user.bat` from Windows Explorer. Run it as normal user, ***not*** as administrator.
|
||||
|
||||
#### Troublehooting:
|
||||
#### Troubleshooting
|
||||
|
||||
- According to reports, intallation currently does not work in a directory with spaces in filenames.
|
||||
- if your version of Python is not in PATH (or if another version is), edit `webui.bat`, change the line `set PYTHON=python` to say the full path to your python executable: `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 videocard has low amount of VRAM (4GB), edit `webui.bat`, change line 5 to from `set COMMANDLINE_ARGS=` to `set COMMANDLINE_ARGS=--medvram` (see below for other possible options)
|
||||
- installer creates python virtual environment, so none of installed modules will affect your system installation of python if you had one prior to installing this.
|
||||
- to prevent the creation of virtual environment and use your system python, edit `webui.bat` replacing `set VENV_DIR=venv` with `set VENV_DIR=`.
|
||||
- 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, editing `webui.bat` to have `set REQS_FILE=requirements.txt` instead of `set REQS_FILE=requirements_versions.txt` may help (but I still reccomend you to just use the recommended version of python).
|
||||
- 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`).
|
||||
- installer creates python virtual environment, so none of 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 this
|
||||
exact version for my sanity.
|
||||
|
||||
### Google collab
|
||||
#### How to run with custom parameters
|
||||
|
||||
If you don't want or can't run locally, here is google collab that allows you to run the webui:
|
||||
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.
|
||||
|
||||
https://colab.research.google.com/drive/1Iy-xW9t1-OQWhb0hNxueGij8phCyluOh
|
||||
The recommndended 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.
|
||||
|
||||
### What options to use for low VRAM videocards?
|
||||
Here is an example that runs the prgoram 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 custom python path, a different model named `a.ckpt` and without virtual environment:
|
||||
|
||||
```commandline
|
||||
@echo off
|
||||
|
||||
set PYTHON=b:/soft/Python310/Python.exe
|
||||
set VENV_DIR=-
|
||||
set COMMANDLINE_ARGS=--ckpt a.ckpt
|
||||
|
||||
call webui.bat
|
||||
```
|
||||
|
||||
### 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, use `--medvram --opt-split-attention`. You can use `--lowvram`
|
||||
- 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.
|
||||
|
||||
Extra: if you get a green screen instead of generated pictures, you have a card that doesn't support half
|
||||
precision floating point numbers. You must use `--precision full --no-half` in addition to other flags,
|
||||
and the model will take much more space in VRAM.
|
||||
|
||||
### Running online
|
||||
|
||||
Use `--share` option to run online. You will get a xxx.app.gradio link. This is the intended way to use the
|
||||
@@ -102,6 +134,16 @@ program in collabs.
|
||||
Use `--listen` to make the server listen to network connections. This will allow computers on local newtork
|
||||
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 needs 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 google collab 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 `embeddings` directory (in the same palce as `webui.py`)
|
||||
and put your embeddings into it. They must be .pt files, each with only one trained embedding,
|
||||
@@ -137,10 +179,6 @@ Alternatively, if you don't want to run webui.bat, here are instructions for ins
|
||||
everything by hand:
|
||||
|
||||
```commandline
|
||||
:: crate a directory somewhere for stable diffusion and open cmd in it;
|
||||
:: make sure you are in the right directory; the command must output the directory you chose
|
||||
echo %cd%
|
||||
|
||||
:: 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
|
||||
|
||||
@@ -148,53 +186,59 @@ pip install torch --extra-index-url https://download.pytorch.org/whl/cu113
|
||||
:: a different version, but this is what I tested.
|
||||
python -c "import torch; print(torch.cuda.is_available())"
|
||||
|
||||
:: clone Stable Diffusion repositories
|
||||
git clone https://github.com/CompVis/stable-diffusion.git
|
||||
git clone https://github.com/CompVis/taming-transformers
|
||||
:: 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
|
||||
|
||||
:: install requirements of Stable Diffusion
|
||||
pip install transformers==4.19.2 diffusers invisible-watermark
|
||||
pip install transformers==4.19.2 diffusers invisible-watermark --prefer-binary
|
||||
|
||||
:: install k-diffusion
|
||||
pip install git+https://github.com/crowsonkb/k-diffusion.git
|
||||
pip install git+https://github.com/crowsonkb/k-diffusion.git --prefer-binary
|
||||
|
||||
:: (optional) install GFPGAN to fix faces
|
||||
pip install git+https://github.com/TencentARC/GFPGAN.git
|
||||
:: (optional) install GFPGAN (face resoration)
|
||||
pip install git+https://github.com/TencentARC/GFPGAN.git --prefer-binary
|
||||
|
||||
:: go into stable diffusion's repo directory
|
||||
cd stable-diffusion
|
||||
|
||||
:: clone web ui
|
||||
git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
|
||||
:: (optional) install requirements for CodeFormer (face resoration)
|
||||
pip install -r repositories/CodeFormer/requirements.txt --prefer-binary
|
||||
|
||||
:: install requirements of web ui
|
||||
pip install -r stable-diffusion-webui/requirements.txt
|
||||
pip install -r stable-diffusion-webui/requirements.txt --prefer-binary
|
||||
|
||||
:: update numpy to latest version
|
||||
pip install -U numpy
|
||||
pip install -U numpy --prefer-binary
|
||||
|
||||
:: (outside of command line) put stable diffusion model into models/ldm/stable-diffusion-v1/model.ckpt; you'll have
|
||||
:: to create one missing directory;
|
||||
:: (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 models\ldm\stable-diffusion-v1\model.ckpt
|
||||
dir model.ckpt
|
||||
|
||||
:: (outside of command line) put the GFPGAN model into same directory as webui script
|
||||
:: (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 stable-diffusion-webui\GFPGANv1.3.pth
|
||||
dir GFPGANv1.3.pth
|
||||
```
|
||||
|
||||
> Note: the directory structure for manual instruction has been changed on 2022-09-09 to match automatic installation: previosuly
|
||||
> webui was in a subdirectory of stable diffusion, now it's the reverse. If you followed manual installation before the
|
||||
> chage, you can still use the program with you existing directory sctructure.
|
||||
|
||||
After that the installation is finished.
|
||||
|
||||
Run the command to start web ui:
|
||||
|
||||
```
|
||||
python stable-diffusion-webui/webui.py
|
||||
python webui.py
|
||||
```
|
||||
|
||||
If you have a 4GB video card, run the command with either `--lowvram` or `--medvram` argument:
|
||||
|
||||
```
|
||||
python stable-diffusion-webui/webui.py --medvram
|
||||
python webui.py --medvram
|
||||
```
|
||||
|
||||
After a while, you will get a message like this:
|
||||
|
||||
@@ -14,14 +14,20 @@ import modules.images
|
||||
|
||||
def load_model(filename):
|
||||
# this code is adapted from https://github.com/xinntao/ESRGAN
|
||||
|
||||
pretrained_net = torch.load(filename)
|
||||
pretrained_net = torch.load(filename, map_location='cpu' if torch.has_mps else None)
|
||||
crt_model = arch.RRDBNet(3, 3, 64, 23, gc=32)
|
||||
|
||||
if 'conv_first.weight' in pretrained_net:
|
||||
crt_model.load_state_dict(pretrained_net)
|
||||
return crt_model
|
||||
|
||||
if 'model.0.weight' not in pretrained_net:
|
||||
is_realesrgan = "params_ema" in pretrained_net and 'body.0.rdb1.conv1.weight' in pretrained_net["params_ema"]
|
||||
if is_realesrgan:
|
||||
raise Exception("The file is a RealESRGAN model, it can't be used as a ESRGAN model.")
|
||||
else:
|
||||
raise Exception("The file is not a ESRGAN model.")
|
||||
|
||||
crt_net = crt_model.state_dict()
|
||||
load_net_clean = {}
|
||||
for k, v in pretrained_net.items():
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts):
|
||||
color_active = (0, 0, 0)
|
||||
color_inactive = (153, 153, 153)
|
||||
|
||||
pad_left = width * 3 // 4 if len(ver_texts) > 0 else 0
|
||||
pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in ver_texts]) == 0 else width * 3 // 4
|
||||
|
||||
cols = im.width // width
|
||||
rows = im.height // height
|
||||
|
||||
+49
-7
@@ -1,5 +1,7 @@
|
||||
import math
|
||||
from PIL import Image
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image, ImageOps, ImageChops
|
||||
|
||||
from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images
|
||||
from modules.shared import opts, state
|
||||
@@ -9,14 +11,21 @@ from modules.ui import plaintext_to_html
|
||||
import modules.images as images
|
||||
import modules.scripts
|
||||
|
||||
def img2img(prompt: str, init_img, init_img_with_mask, 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, 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, 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):
|
||||
is_inpaint = mode == 1
|
||||
is_loopback = mode == 2
|
||||
is_upscale = mode == 3
|
||||
|
||||
if is_inpaint:
|
||||
image = init_img_with_mask['image']
|
||||
mask = init_img_with_mask['mask']
|
||||
if mask_mode == 0:
|
||||
image = init_img_with_mask['image']
|
||||
mask = init_img_with_mask['mask']
|
||||
alpha_mask = ImageOps.invert(image.split()[-1]).convert('L').point(lambda x: 255 if x > 0 else 0, mode='1')
|
||||
mask = ImageChops.lighter(alpha_mask, mask.convert('L')).convert('L')
|
||||
image = image.convert('RGB')
|
||||
else:
|
||||
image = init_img
|
||||
mask = init_mask
|
||||
else:
|
||||
image = init_img
|
||||
mask = None
|
||||
@@ -28,7 +37,12 @@ def img2img(prompt: str, init_img, init_img_with_mask, steps: int, sampler_index
|
||||
outpath_samples=opts.outdir_samples or opts.outdir_img2img_samples,
|
||||
outpath_grids=opts.outdir_grids or opts.outdir_img2img_grids,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
seed=seed,
|
||||
subseed=subseed,
|
||||
subseed_strength=subseed_strength,
|
||||
seed_resize_from_h=seed_resize_from_h,
|
||||
seed_resize_from_w=seed_resize_from_w,
|
||||
sampler_index=sampler_index,
|
||||
batch_size=batch_size,
|
||||
n_iter=n_iter,
|
||||
@@ -46,8 +60,12 @@ def img2img(prompt: str, init_img, init_img_with_mask, steps: int, sampler_index
|
||||
denoising_strength=denoising_strength,
|
||||
inpaint_full_res=inpaint_full_res,
|
||||
inpainting_mask_invert=inpainting_mask_invert,
|
||||
extra_generation_params={"Denoising Strength": denoising_strength}
|
||||
extra_generation_params={
|
||||
"Denoising strength": denoising_strength,
|
||||
"Denoising strength change factor": denoising_strength_change_factor
|
||||
}
|
||||
)
|
||||
print(f"\nimg2img: {prompt}", file=shared.progress_print_out)
|
||||
|
||||
if is_loopback:
|
||||
output_images, info = None, None
|
||||
@@ -57,8 +75,19 @@ def img2img(prompt: str, init_img, init_img_with_mask, steps: int, sampler_index
|
||||
|
||||
state.job_count = n_iter
|
||||
|
||||
do_color_correction = False
|
||||
try:
|
||||
from skimage import exposure
|
||||
do_color_correction = True
|
||||
except:
|
||||
print("Install scikit-image to perform color correction on loopback")
|
||||
|
||||
|
||||
for i in range(n_iter):
|
||||
|
||||
if do_color_correction and i == 0:
|
||||
correction_target = cv2.cvtColor(np.asarray(init_img.copy()), cv2.COLOR_RGB2LAB)
|
||||
|
||||
p.n_iter = 1
|
||||
p.batch_size = 1
|
||||
p.do_not_save_grid = True
|
||||
@@ -69,10 +98,22 @@ def img2img(prompt: str, init_img, init_img_with_mask, steps: int, sampler_index
|
||||
if initial_seed is None:
|
||||
initial_seed = processed.seed
|
||||
initial_info = processed.info
|
||||
|
||||
init_img = processed.images[0]
|
||||
|
||||
p.init_images = [processed.images[0]]
|
||||
if do_color_correction and correction_target is not None:
|
||||
init_img = Image.fromarray(cv2.cvtColor(exposure.match_histograms(
|
||||
cv2.cvtColor(
|
||||
np.asarray(init_img),
|
||||
cv2.COLOR_RGB2LAB
|
||||
),
|
||||
correction_target,
|
||||
channel_axis=2
|
||||
), cv2.COLOR_LAB2RGB).astype("uint8"))
|
||||
|
||||
p.init_images = [init_img]
|
||||
p.seed = processed.seed + 1
|
||||
p.denoising_strength = max(p.denoising_strength * 0.95, 0.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)
|
||||
@@ -141,5 +182,6 @@ def img2img(prompt: str, init_img, init_img_with_mask, steps: int, sampler_index
|
||||
if processed is None:
|
||||
processed = process_images(p)
|
||||
|
||||
shared.total_tqdm.clear()
|
||||
|
||||
return processed.images, processed.js(), plaintext_to_html(processed.info)
|
||||
|
||||
+6
-3
@@ -2,9 +2,12 @@ import torch
|
||||
|
||||
module_in_gpu = None
|
||||
cpu = torch.device("cpu")
|
||||
gpu = torch.device("cuda")
|
||||
device = gpu if torch.cuda.is_available() else cpu
|
||||
|
||||
if torch.has_cuda:
|
||||
device = gpu = torch.device("cuda")
|
||||
elif torch.has_mps:
|
||||
device = gpu = torch.device("mps")
|
||||
else:
|
||||
device = gpu = torch.device("cpu")
|
||||
|
||||
def setup_for_low_vram(sd_model, use_medvram):
|
||||
parents = {}
|
||||
|
||||
+93
-28
@@ -29,7 +29,7 @@ def torch_gc():
|
||||
|
||||
|
||||
class StableDiffusionProcessing:
|
||||
def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", seed=-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="", 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
|
||||
@@ -37,6 +37,10 @@ class StableDiffusionProcessing:
|
||||
self.prompt_for_display: str = None
|
||||
self.negative_prompt: str = (negative_prompt or "")
|
||||
self.seed: int = seed
|
||||
self.subseed: int = subseed
|
||||
self.subseed_strength: float = subseed_strength
|
||||
self.seed_resize_from_h: int = seed_resize_from_h
|
||||
self.seed_resize_from_w: int = seed_resize_from_w
|
||||
self.sampler_index: int = sampler_index
|
||||
self.batch_size: int = batch_size
|
||||
self.n_iter: int = n_iter
|
||||
@@ -52,7 +56,7 @@ class StableDiffusionProcessing:
|
||||
self.overlay_images = overlay_images
|
||||
self.paste_to = None
|
||||
|
||||
def init(self):
|
||||
def init(self, seed):
|
||||
pass
|
||||
|
||||
def sample(self, x, conditioning, unconditional_conditioning):
|
||||
@@ -84,23 +88,67 @@ class Processed:
|
||||
|
||||
return json.dumps(obj)
|
||||
|
||||
# from https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3
|
||||
def slerp(val, low, high):
|
||||
low_norm = low/torch.norm(low, dim=1, keepdim=True)
|
||||
high_norm = high/torch.norm(high, dim=1, keepdim=True)
|
||||
omega = torch.acos((low_norm*high_norm).sum(1))
|
||||
so = torch.sin(omega)
|
||||
res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high
|
||||
return res
|
||||
|
||||
def create_random_tensors(shape, seeds):
|
||||
|
||||
def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, seed_resize_from_h=0, seed_resize_from_w=0):
|
||||
xs = []
|
||||
for seed in seeds:
|
||||
torch.manual_seed(seed)
|
||||
for i, seed in enumerate(seeds):
|
||||
noise_shape = shape if seed_resize_from_h <= 0 or seed_resize_from_w <= 0 else (shape[0], seed_resize_from_h//8, seed_resize_from_w//8)
|
||||
|
||||
subnoise = None
|
||||
if subseeds is not None:
|
||||
subseed = 0 if i >= len(subseeds) else subseeds[i]
|
||||
torch.manual_seed(subseed)
|
||||
subnoise = torch.randn(noise_shape, device=shared.device)
|
||||
|
||||
# randn results depend on device; gpu and cpu get different results for same seed;
|
||||
# the way I see it, it's better to do this on CPU, so that everyone gets same result;
|
||||
# but the original script had it like this so I do not dare change it for now because
|
||||
# but the original script had it like this, so I do not dare change it for now because
|
||||
# it will break everyone's seeds.
|
||||
xs.append(torch.randn(shape, device=shared.device))
|
||||
x = torch.stack(xs)
|
||||
torch.manual_seed(seed)
|
||||
noise = torch.randn(noise_shape, device=shared.device)
|
||||
|
||||
if subnoise is not None:
|
||||
#noise = subnoise * subseed_strength + noise * (1 - subseed_strength)
|
||||
noise = slerp(subseed_strength, noise, subnoise)
|
||||
|
||||
if noise_shape != shape:
|
||||
#noise = torch.nn.functional.interpolate(noise.unsqueeze(1), size=shape[1:], mode="bilinear").squeeze()
|
||||
# noise_shape = (64, 80)
|
||||
# shape = (64, 72)
|
||||
|
||||
torch.manual_seed(seed)
|
||||
x = torch.randn(shape, device=shared.device)
|
||||
dx = (shape[2] - noise_shape[2]) // 2 # -4
|
||||
dy = (shape[1] - noise_shape[1]) // 2
|
||||
w = noise_shape[2] if dx >= 0 else noise_shape[2] + 2 * dx
|
||||
h = noise_shape[1] if dy >= 0 else noise_shape[1] + 2 * dy
|
||||
tx = 0 if dx < 0 else dx
|
||||
ty = 0 if dy < 0 else dy
|
||||
dx = max(-dx, 0)
|
||||
dy = max(-dy, 0)
|
||||
|
||||
x[:, ty:ty+h, tx:tx+w] = noise[:, dy:dy+h, dx:dx+w]
|
||||
noise = x
|
||||
|
||||
|
||||
|
||||
xs.append(noise)
|
||||
x = torch.stack(xs).to(shared.device)
|
||||
return x
|
||||
|
||||
|
||||
def set_seed(seed):
|
||||
return int(random.randrange(4294967294)) if seed is None or seed == -1 else seed
|
||||
def fix_seed(p):
|
||||
p.seed = int(random.randrange(4294967294)) if p.seed is None or p.seed == -1 else p.seed
|
||||
p.subseed = int(random.randrange(4294967294)) if p.subseed is None or p.subseed == -1 else p.subseed
|
||||
|
||||
|
||||
def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
@@ -111,7 +159,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
assert p.prompt is not None
|
||||
torch_gc()
|
||||
|
||||
seed = set_seed(p.seed)
|
||||
fix_seed(p)
|
||||
|
||||
os.makedirs(p.outpath_samples, exist_ok=True)
|
||||
os.makedirs(p.outpath_grids, exist_ok=True)
|
||||
@@ -125,28 +173,41 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
else:
|
||||
all_prompts = p.batch_size * p.n_iter * [prompt]
|
||||
|
||||
if type(seed) == list:
|
||||
all_seeds = seed
|
||||
if type(p.seed) == list:
|
||||
all_seeds = int(p.seed)
|
||||
else:
|
||||
all_seeds = [int(seed + x) for x in range(len(all_prompts))]
|
||||
all_seeds = [int(p.seed + x) for x in range(len(all_prompts))]
|
||||
|
||||
if type(p.subseed) == list:
|
||||
all_subseeds = p.subseed
|
||||
else:
|
||||
all_subseeds = [int(p.subseed + x) for x in range(len(all_prompts))]
|
||||
|
||||
def infotext(iteration=0, position_in_batch=0):
|
||||
index = position_in_batch + iteration * p.batch_size
|
||||
|
||||
generation_params = {
|
||||
"Steps": p.steps,
|
||||
"Sampler": samplers[p.sampler_index].name,
|
||||
"CFG scale": p.cfg_scale,
|
||||
"Seed": all_seeds[position_in_batch + iteration * p.batch_size],
|
||||
"Seed": all_seeds[index],
|
||||
"Face restoration": (opts.face_restoration_model if p.restore_faces else None),
|
||||
"Size": f"{p.width}x{p.height}",
|
||||
"Batch size": (None if p.batch_size < 2 else p.batch_size),
|
||||
"Batch pos": (None if p.batch_size < 2 else position_in_batch),
|
||||
"Variation seed": (None if p.subseed_strength == 0 else all_subseeds[index]),
|
||||
"Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength),
|
||||
"Seed resize from": (None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"),
|
||||
}
|
||||
|
||||
if p.extra_generation_params is not None:
|
||||
generation_params.update(p.extra_generation_params)
|
||||
|
||||
generation_params_text = ", ".join([k if k == v else f'{k}: {v}' for k, v in generation_params.items() if v is not None])
|
||||
|
||||
negative_prompt_text = "\nNegative prompt: " + p.negative_prompt if p.negative_prompt else ""
|
||||
|
||||
return f"{p.prompt_for_display or prompt}\n{generation_params_text}".strip() + "".join(["\n\n" + x for x in comments])
|
||||
return f"{p.prompt_for_display or prompt}{negative_prompt_text}\n{generation_params_text}".strip() + "".join(["\n\n" + x for x in comments])
|
||||
|
||||
if os.path.exists(cmd_opts.embeddings_dir):
|
||||
model_hijack.load_textual_inversion_embeddings(cmd_opts.embeddings_dir, p.sd_model)
|
||||
@@ -155,7 +216,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
precision_scope = torch.autocast if cmd_opts.precision == "autocast" else contextlib.nullcontext
|
||||
ema_scope = (contextlib.nullcontext if cmd_opts.lowvram else p.sd_model.ema_scope)
|
||||
with torch.no_grad(), precision_scope("cuda"), ema_scope():
|
||||
p.init()
|
||||
p.init(seed=all_seeds[0])
|
||||
|
||||
if state.job_count == -1:
|
||||
state.job_count = p.n_iter
|
||||
@@ -174,7 +235,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
comments += model_hijack.comments
|
||||
|
||||
# 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)
|
||||
x = create_random_tensors([opt_C, p.height // opt_f, p.width // opt_f], seeds=seeds, subseeds=all_subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w)
|
||||
|
||||
if p.n_iter > 1:
|
||||
shared.state.job = f"Batch {n+1} out of {p.n_iter}"
|
||||
@@ -231,16 +292,16 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
output_images.insert(0, grid)
|
||||
|
||||
if opts.grid_save:
|
||||
images.save_image(grid, p.outpath_grids, "grid", seed, all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename)
|
||||
images.save_image(grid, p.outpath_grids, "grid", all_seeds[0], all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename)
|
||||
|
||||
torch_gc()
|
||||
return Processed(p, output_images, seed, infotext())
|
||||
return Processed(p, output_images, all_seeds[0], infotext())
|
||||
|
||||
|
||||
class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
|
||||
sampler = None
|
||||
|
||||
def init(self):
|
||||
def init(self, seed):
|
||||
self.sampler = samplers[self.sampler_index].constructor(self.sd_model)
|
||||
|
||||
def sample(self, x, conditioning, unconditional_conditioning):
|
||||
@@ -320,7 +381,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
|
||||
self.mask = None
|
||||
self.nmask = None
|
||||
|
||||
def init(self):
|
||||
def init(self, seed):
|
||||
self.sampler = samplers_for_img2img[self.sampler_index].constructor(self.sd_model)
|
||||
crop_region = None
|
||||
|
||||
@@ -347,11 +408,13 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
|
||||
else:
|
||||
self.image_mask = images.resize_image(self.resize_mode, self.image_mask, self.width, self.height)
|
||||
np_mask = np.array(self.image_mask)
|
||||
np_mask = 255 - np.clip((255 - np_mask.astype(np.float)) * 2, 0, 255).astype(np.uint8)
|
||||
np_mask = np.clip((np_mask.astype(np.float)) * 2, 0, 255).astype(np.uint8)
|
||||
self.mask_for_overlay = Image.fromarray(np_mask)
|
||||
|
||||
self.overlay_images = []
|
||||
|
||||
latent_mask = self.latent_mask if self.latent_mask is not None else self.image_mask
|
||||
|
||||
imgs = []
|
||||
for img in self.init_images:
|
||||
image = img.convert("RGB")
|
||||
@@ -360,9 +423,6 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
|
||||
image = images.resize_image(self.resize_mode, image, self.width, self.height)
|
||||
|
||||
if self.image_mask is not None:
|
||||
if self.inpainting_fill != 1:
|
||||
image = fill(image, self.mask_for_overlay)
|
||||
|
||||
image_masked = Image.new('RGBa', (image.width, image.height))
|
||||
image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L')))
|
||||
|
||||
@@ -372,6 +432,10 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
|
||||
image = image.crop(crop_region)
|
||||
image = images.resize_image(2, image, self.width, self.height)
|
||||
|
||||
if self.image_mask is not None:
|
||||
if self.inpainting_fill != 1:
|
||||
image = fill(image, latent_mask)
|
||||
|
||||
image = np.array(image).astype(np.float32) / 255.0
|
||||
image = np.moveaxis(image, 2, 0)
|
||||
|
||||
@@ -394,17 +458,18 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
|
||||
self.init_latent = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(image))
|
||||
|
||||
if self.image_mask is not None:
|
||||
init_mask = self.latent_mask if self.latent_mask is not None else self.image_mask
|
||||
init_mask = latent_mask
|
||||
latmask = init_mask.convert('RGB').resize((self.init_latent.shape[3], self.init_latent.shape[2]))
|
||||
latmask = np.moveaxis(np.array(latmask, dtype=np.float64), 2, 0) / 255
|
||||
latmask = latmask[0]
|
||||
latmask = np.around(latmask)
|
||||
latmask = np.tile(latmask[None], (4, 1, 1))
|
||||
|
||||
self.mask = torch.asarray(1.0 - latmask).to(shared.device).type(self.sd_model.dtype)
|
||||
self.nmask = torch.asarray(latmask).to(shared.device).type(self.sd_model.dtype)
|
||||
|
||||
if self.inpainting_fill == 2:
|
||||
self.init_latent = self.init_latent * self.mask + create_random_tensors(self.init_latent.shape[1:], [self.seed + x + 1 for x in range(self.init_latent.shape[0])]) * self.nmask
|
||||
self.init_latent = self.init_latent * self.mask + create_random_tensors(self.init_latent.shape[1:], [seed + x + 1 for x in range(self.init_latent.shape[0])]) * self.nmask
|
||||
elif self.inpainting_fill == 3:
|
||||
self.init_latent = self.init_latent * self.mask
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
import modules.images
|
||||
from modules.shared import cmd_opts
|
||||
from modules.shared import cmd_opts, opts
|
||||
|
||||
RealesrganModelInfo = namedtuple("RealesrganModelInfo", ["name", "location", "model", "netscale"])
|
||||
|
||||
@@ -76,7 +76,9 @@ def upscale_with_realesrgan(image, RealESRGAN_upscaling, RealESRGAN_model_index)
|
||||
scale=info.netscale,
|
||||
model_path=info.location,
|
||||
model=model,
|
||||
half=not cmd_opts.no_half
|
||||
half=not cmd_opts.no_half,
|
||||
tile=opts.ESRGAN_tile,
|
||||
tile_pad=opts.ESRGAN_tile_overlap,
|
||||
)
|
||||
|
||||
upsampled = upsampler.enhance(np.array(image), outscale=RealESRGAN_upscaling)[0]
|
||||
|
||||
@@ -6,6 +6,7 @@ import modules.ui as ui
|
||||
import gradio as gr
|
||||
|
||||
from modules.processing import StableDiffusionProcessing
|
||||
from modules import shared
|
||||
|
||||
class Script:
|
||||
filename = None
|
||||
@@ -137,6 +138,8 @@ class ScriptRunner:
|
||||
script_args = args[script.args_from:script.args_to]
|
||||
processed = script.run(p, *script_args)
|
||||
|
||||
shared.total_tqdm.clear()
|
||||
|
||||
return processed
|
||||
|
||||
|
||||
|
||||
+16
-6
@@ -73,11 +73,21 @@ class StableDiffusionModelHijack:
|
||||
name = os.path.splitext(filename)[0]
|
||||
|
||||
data = torch.load(path)
|
||||
param_dict = data['string_to_param']
|
||||
if hasattr(param_dict, '_parameters'):
|
||||
param_dict = getattr(param_dict, '_parameters') # fix for torch 1.12.1 loading saved file from torch 1.11
|
||||
assert len(param_dict) == 1, 'embedding file has multiple terms in it'
|
||||
emb = next(iter(param_dict.items()))[1]
|
||||
|
||||
# textual inversion embeddings
|
||||
if 'string_to_param' in data:
|
||||
param_dict = data['string_to_param']
|
||||
if hasattr(param_dict, '_parameters'):
|
||||
param_dict = getattr(param_dict, '_parameters') # fix for torch 1.12.1 loading saved file from torch 1.11
|
||||
assert len(param_dict) == 1, 'embedding file has multiple terms in it'
|
||||
emb = next(iter(param_dict.items()))[1]
|
||||
elif type(data) == dict and type(next(iter(data.values()))) == torch.Tensor:
|
||||
assert len(data.keys()) == 1, 'embedding file has multiple terms in it'
|
||||
|
||||
emb = next(iter(data.values()))
|
||||
if len(emb.shape) == 1:
|
||||
emb = emb.unsqueeze(0)
|
||||
|
||||
self.word_embeddings[name] = emb.detach()
|
||||
self.word_embeddings_checksums[name] = f'{const_hash(emb.reshape(-1))&0xffff:04x}'
|
||||
|
||||
@@ -232,7 +242,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module):
|
||||
z = outputs.last_hidden_state
|
||||
|
||||
# restoring original mean is likely not correct, but it seems to work well to prevent artifacts that happen otherwise
|
||||
batch_multipliers = torch.asarray(np.array(batch_multipliers)).to(device)
|
||||
batch_multipliers = torch.asarray(batch_multipliers).to(device)
|
||||
original_mean = z.mean()
|
||||
z *= batch_multipliers.reshape(batch_multipliers.shape + (1,)).expand(z.shape)
|
||||
new_mean = z.mean()
|
||||
|
||||
+17
-4
@@ -58,7 +58,10 @@ def p_sample_ddim_hook(sampler_wrapper, x_dec, cond, ts, *args, **kwargs):
|
||||
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
|
||||
|
||||
store_latent(x_dec)
|
||||
store_latent(sampler_wrapper.init_latent * sampler_wrapper.mask + sampler_wrapper.nmask * x_dec)
|
||||
|
||||
else:
|
||||
store_latent(x_dec)
|
||||
|
||||
return sampler_wrapper.orig_p_sample_ddim(x_dec, cond, ts, *args, **kwargs)
|
||||
|
||||
@@ -67,13 +70,14 @@ def extended_tdqm(sequence, *args, desc=None, **kwargs):
|
||||
state.sampling_steps = len(sequence)
|
||||
state.sampling_step = 0
|
||||
|
||||
for x in tqdm.tqdm(sequence, *args, desc=state.job, **kwargs):
|
||||
for x in tqdm.tqdm(sequence, *args, desc=state.job, file=shared.progress_print_out, **kwargs):
|
||||
if state.interrupted:
|
||||
break
|
||||
|
||||
yield x
|
||||
|
||||
state.sampling_step += 1
|
||||
shared.total_tqdm.update()
|
||||
|
||||
|
||||
ldm.models.diffusion.ddim.tqdm = lambda *args, desc=None, **kwargs: extended_tdqm(*args, desc=desc, **kwargs)
|
||||
@@ -83,7 +87,7 @@ ldm.models.diffusion.plms.tqdm = lambda *args, desc=None, **kwargs: extended_tdq
|
||||
class VanillaStableDiffusionSampler:
|
||||
def __init__(self, constructor, sd_model):
|
||||
self.sampler = constructor(sd_model)
|
||||
self.orig_p_sample_ddim = self.sampler.p_sample_ddim if hasattr(self.sampler, 'p_sample_ddim') else None
|
||||
self.orig_p_sample_ddim = self.sampler.p_sample_ddim if hasattr(self.sampler, 'p_sample_ddim') else self.sampler.p_sample_plms
|
||||
self.mask = None
|
||||
self.nmask = None
|
||||
self.init_latent = None
|
||||
@@ -109,6 +113,13 @@ class VanillaStableDiffusionSampler:
|
||||
return samples
|
||||
|
||||
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))
|
||||
self.mask = None
|
||||
self.nmask = None
|
||||
self.init_latent = None
|
||||
|
||||
samples_ddim, _ = self.sampler.sample(S=p.steps, conditioning=conditioning, batch_size=int(x.shape[0]), shape=x[0].shape, verbose=False, unconditional_guidance_scale=p.cfg_scale, unconditional_conditioning=unconditional_conditioning, x_T=x)
|
||||
return samples_ddim
|
||||
|
||||
@@ -143,13 +154,14 @@ def extended_trange(count, *args, **kwargs):
|
||||
state.sampling_steps = count
|
||||
state.sampling_step = 0
|
||||
|
||||
for x in tqdm.trange(count, *args, desc=state.job, **kwargs):
|
||||
for x in tqdm.trange(count, *args, desc=state.job, file=shared.progress_print_out, **kwargs):
|
||||
if state.interrupted:
|
||||
break
|
||||
|
||||
yield x
|
||||
|
||||
state.sampling_step += 1
|
||||
shared.total_tqdm.update()
|
||||
|
||||
|
||||
class KDiffusionSampler:
|
||||
@@ -165,6 +177,7 @@ class KDiffusionSampler:
|
||||
def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning):
|
||||
t_enc = int(min(p.denoising_strength, 0.999) * p.steps)
|
||||
sigmas = self.model_wrap.get_sigmas(p.steps)
|
||||
|
||||
noise = noise * sigmas[p.steps - t_enc - 1]
|
||||
|
||||
xi = x + noise
|
||||
|
||||
+56
-19
@@ -1,9 +1,11 @@
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
|
||||
import gradio as gr
|
||||
import torch
|
||||
import tqdm
|
||||
|
||||
import modules.artists
|
||||
from modules.paths import script_path, sd_path
|
||||
@@ -23,22 +25,28 @@ parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", d
|
||||
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("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI")
|
||||
parser.add_argument("--embeddings-dir", type=str, default='embeddings', help="embeddings dirtectory for textual inversion (default: embeddings)")
|
||||
parser.add_argument("--embeddings-dir", type=str, default='embeddings', help="embeddings directory for textual inversion (default: embeddings)")
|
||||
parser.add_argument("--allow-code", action='store_true', help="allow custom script execution from webui")
|
||||
parser.add_argument("--medvram", action='store_true', help="enable stable diffusion model optimizations for sacrficing a little speed for low VRM usage")
|
||||
parser.add_argument("--lowvram", action='store_true', help="enable stable diffusion model optimizations for sacrficing a lot of speed for very low VRM usage")
|
||||
parser.add_argument("--always-batch-cond-uncond", action='store_true', help="a workaround test; may help with speed in you use --lowvram")
|
||||
parser.add_argument("--medvram", action='store_true', help="enable stable diffusion model optimizations for sacrificing a little speed for low VRM usage")
|
||||
parser.add_argument("--lowvram", action='store_true', help="enable stable diffusion model optimizations for sacrificing a lot of speed for very low VRM usage")
|
||||
parser.add_argument("--always-batch-cond-uncond", action='store_true', help="a workaround test; may help with speed if you use --lowvram")
|
||||
parser.add_argument("--unload-gfpgan", action='store_true', help="unload GFPGAN every time after processing images. Warning: seems to cause memory leaks")
|
||||
parser.add_argument("--precision", type=str, help="evaluate at this precision", choices=["full", "autocast"], default="autocast")
|
||||
parser.add_argument("--share", action='store_true', help="use share=True for gradio and make the UI accessible through their site (doesn't work for me but you might have better luck)")
|
||||
parser.add_argument("--esrgan-models-path", type=str, help="path to directory with ESRGAN models", default=os.path.join(script_path, 'ESRGAN'))
|
||||
parser.add_argument("--opt-split-attention", action='store_true', help="enable optimization that reduced vram usage by a lot for about 10%% decrease in performance")
|
||||
parser.add_argument("--opt-split-attention", action='store_true', help="enable optimization that reduce vram usage by a lot for about 10%% decrease in performance")
|
||||
parser.add_argument("--listen", action='store_true', help="launch gradio with 0.0.0.0 as server name, allowing to respond to network requests")
|
||||
parser.add_argument("--port", type=int, help="launch gradio with given server port, you need root/admin rights for ports < 1024, defaults to 7860 if available", default=None)
|
||||
parser.add_argument("--show-negative-prompt", action='store_true', help="enable the field that lets you input negative prompt", default=False)
|
||||
|
||||
cmd_opts = parser.parse_args()
|
||||
|
||||
cpu = torch.device("cpu")
|
||||
gpu = torch.device("cuda")
|
||||
device = gpu if torch.cuda.is_available() else cpu
|
||||
if torch.has_cuda:
|
||||
device = torch.device("cuda")
|
||||
elif torch.has_mps:
|
||||
device = torch.device("mps")
|
||||
else:
|
||||
device = torch.device("cpu")
|
||||
batch_cond_uncond = cmd_opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram)
|
||||
parallel_processing_allowed = not cmd_opts.lowvram and not cmd_opts.medvram
|
||||
|
||||
@@ -54,7 +62,6 @@ class State:
|
||||
current_image = None
|
||||
current_image_sampling_step = 0
|
||||
|
||||
|
||||
def interrupt(self):
|
||||
self.interrupted = True
|
||||
|
||||
@@ -70,6 +77,7 @@ artist_db = modules.artists.ArtistsDatabase(os.path.join(script_path, 'artists.c
|
||||
|
||||
face_restorers = []
|
||||
|
||||
|
||||
def find_any_font():
|
||||
fonts = ['/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf']
|
||||
|
||||
@@ -77,7 +85,7 @@ def find_any_font():
|
||||
if os.path.exists(font):
|
||||
return font
|
||||
|
||||
return "arial.ttf"
|
||||
return "Arial.TTF"
|
||||
|
||||
|
||||
class Options:
|
||||
@@ -90,13 +98,13 @@ class Options:
|
||||
|
||||
data = None
|
||||
data_labels = {
|
||||
"outdir_samples": OptionInfo("", "Output dictectory for images; if empty, defaults to two directories below"),
|
||||
"outdir_txt2img_samples": OptionInfo("outputs/txt2img-images", 'Output dictectory for txt2img images'),
|
||||
"outdir_img2img_samples": OptionInfo("outputs/img2img-images", 'Output dictectory for img2img images'),
|
||||
"outdir_extras_samples": OptionInfo("outputs/extras-images", 'Output dictectory for images from extras tab'),
|
||||
"outdir_grids": OptionInfo("", "Output dictectory for grids; if empty, defaults to two directories below"),
|
||||
"outdir_txt2img_grids": OptionInfo("outputs/txt2img-grids", 'Output dictectory for txt2img grids'),
|
||||
"outdir_img2img_grids": OptionInfo("outputs/img2img-grids", 'Output dictectory for img2img grids'),
|
||||
"outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to two directories below"),
|
||||
"outdir_txt2img_samples": OptionInfo("outputs/txt2img-images", 'Output directory for txt2img images'),
|
||||
"outdir_img2img_samples": OptionInfo("outputs/img2img-images", 'Output directory for img2img images'),
|
||||
"outdir_extras_samples": OptionInfo("outputs/extras-images", 'Output directory for images from extras tab'),
|
||||
"outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below"),
|
||||
"outdir_txt2img_grids": OptionInfo("outputs/txt2img-grids", 'Output directory for txt2img grids'),
|
||||
"outdir_img2img_grids": OptionInfo("outputs/img2img-grids", 'Output directory for img2img grids'),
|
||||
"save_to_dirs": OptionInfo(False, "When writing images/grids, create a directory with name derived from the prompt"),
|
||||
"save_to_dirs_prompt_len": OptionInfo(10, "When using above, how many words from prompt to put into directory name", gr.Slider, {"minimum": 1, "maximum": 32, "step": 1}),
|
||||
"outdir_save": OptionInfo("log/images", "Directory for saving images using the Save button"),
|
||||
@@ -114,12 +122,13 @@ class Options:
|
||||
"font": OptionInfo(find_any_font(), "Font for image grids that have text"),
|
||||
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text text and [text] to make it pay less attention"),
|
||||
"save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."),
|
||||
"ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscaling. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
||||
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for ESRGAN upscaling. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
|
||||
"ESRGAN_tile": OptionInfo(192, "Tile size for upscaling. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
||||
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for upscaling. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
|
||||
"random_artist_categories": OptionInfo([], "Allowed categories for random artists selection when using the Roll button", gr.CheckboxGroup, {"choices": artist_db.categories()}),
|
||||
"upscale_at_full_resolution_padding": OptionInfo(16, "Inpainting at full resolution: padding, in pixels, for the masked region.", gr.Slider, {"minimum": 0, "maximum": 128, "step": 4}),
|
||||
"show_progressbar": OptionInfo(True, "Show progressbar"),
|
||||
"show_progress_every_n_steps": OptionInfo(0, "Show show image creation progress every N sampling steps. Set 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 32, "step": 1}),
|
||||
"multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job. Broken in PyCharm console."),
|
||||
"face_restoration_model": OptionInfo(None, "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}),
|
||||
"code_former_weight": OptionInfo(0.5, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}),
|
||||
}
|
||||
@@ -161,4 +170,32 @@ sd_upscalers = []
|
||||
|
||||
sd_model = None
|
||||
|
||||
progress_print_out = sys.stdout
|
||||
|
||||
|
||||
class TotalTQDM:
|
||||
def __init__(self):
|
||||
self._tqdm = None
|
||||
|
||||
def reset(self):
|
||||
self._tqdm = tqdm.tqdm(
|
||||
desc="Total progress",
|
||||
total=state.job_count * state.sampling_steps,
|
||||
position=1,
|
||||
file=progress_print_out
|
||||
)
|
||||
|
||||
def update(self):
|
||||
if not opts.multiple_tqdm:
|
||||
return
|
||||
if self._tqdm is None:
|
||||
self.reset()
|
||||
self._tqdm.update()
|
||||
|
||||
def clear(self):
|
||||
if self._tqdm is not None:
|
||||
self._tqdm.close()
|
||||
self._tqdm = None
|
||||
|
||||
|
||||
total_tqdm = TotalTQDM()
|
||||
|
||||
+8
-1
@@ -6,7 +6,7 @@ import modules.processing as processing
|
||||
from modules.ui import plaintext_to_html
|
||||
|
||||
|
||||
def txt2img(prompt: str, negative_prompt: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, height: int, width: int, *args):
|
||||
def txt2img(prompt: str, negative_prompt: 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,
|
||||
@@ -14,6 +14,10 @@ def txt2img(prompt: str, negative_prompt: str, steps: int, sampler_index: int, r
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
seed=seed,
|
||||
subseed=subseed,
|
||||
subseed_strength=subseed_strength,
|
||||
seed_resize_from_h=seed_resize_from_h,
|
||||
seed_resize_from_w=seed_resize_from_w,
|
||||
sampler_index=sampler_index,
|
||||
batch_size=batch_size,
|
||||
n_iter=n_iter,
|
||||
@@ -25,6 +29,7 @@ def txt2img(prompt: str, negative_prompt: str, steps: int, sampler_index: int, r
|
||||
tiling=tiling,
|
||||
)
|
||||
|
||||
print(f"\ntxt2img: {prompt}", file=shared.progress_print_out)
|
||||
processed = modules.scripts.scripts_txt2img.run(p, *args)
|
||||
|
||||
if processed is not None:
|
||||
@@ -32,5 +37,7 @@ def txt2img(prompt: str, negative_prompt: str, steps: int, sampler_index: int, r
|
||||
else:
|
||||
processed = process_images(p)
|
||||
|
||||
shared.total_tqdm.clear()
|
||||
|
||||
return processed.images, processed.js(), plaintext_to_html(processed.info)
|
||||
|
||||
|
||||
+78
-12
@@ -192,11 +192,45 @@ def visit(x, func, path=""):
|
||||
func(path + "/" + str(x.label), x)
|
||||
|
||||
|
||||
def create_seed_inputs():
|
||||
with gr.Row():
|
||||
seed = gr.Number(label='Seed', value=-1)
|
||||
subseed = gr.Number(label='Variation seed', value=-1, visible=False)
|
||||
seed_checkbox = gr.Checkbox(label="Extra", elem_id="subseed_show", value=False)
|
||||
|
||||
with gr.Row():
|
||||
subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, visible=False)
|
||||
seed_resize_from_h = gr.Slider(minimum=0, maximum=2048, step=64, label="Resize seed from height", value=0, visible=False)
|
||||
seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=64, label="Resize seed from width", value=0, visible=False)
|
||||
|
||||
def change_visiblity(show):
|
||||
|
||||
return {
|
||||
subseed: gr_show(show),
|
||||
subseed_strength: gr_show(show),
|
||||
seed_resize_from_h: gr_show(show),
|
||||
seed_resize_from_w: gr_show(show),
|
||||
}
|
||||
|
||||
seed_checkbox.change(
|
||||
change_visiblity,
|
||||
inputs=[seed_checkbox],
|
||||
outputs=[
|
||||
subseed,
|
||||
subseed_strength,
|
||||
seed_resize_from_h,
|
||||
seed_resize_from_w
|
||||
]
|
||||
)
|
||||
|
||||
return seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w
|
||||
|
||||
|
||||
def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
|
||||
with gr.Row():
|
||||
prompt = gr.Textbox(label="Prompt", elem_id="txt2img_prompt", show_label=False, placeholder="Prompt", lines=1)
|
||||
negative_prompt = gr.Textbox(label="Negative prompt", elem_id="txt2img_negative_prompt", show_label=False, placeholder="Negative prompt", lines=1, visible=False)
|
||||
negative_prompt = gr.Textbox(label="Negative prompt", elem_id="txt2img_negative_prompt", show_label=False, placeholder="Negative prompt", lines=1, visible=cmd_opts.show_negative_prompt)
|
||||
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)
|
||||
@@ -220,7 +254,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512)
|
||||
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
|
||||
|
||||
seed = gr.Number(label='Seed', value=-1)
|
||||
seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs()
|
||||
|
||||
with gr.Group():
|
||||
custom_inputs = modules.scripts.scripts_txt2img.setup_ui(is_img2img=False)
|
||||
@@ -228,7 +262,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
with gr.Column(variant='panel'):
|
||||
with gr.Group():
|
||||
txt2img_preview = gr.Image(elem_id='txt2img_preview', visible=False)
|
||||
txt2img_gallery = gr.Gallery(label='Output', elem_id='txt2img_gallery')
|
||||
txt2img_gallery = gr.Gallery(label='Output', elem_id='txt2img_gallery').style(grid=4)
|
||||
|
||||
|
||||
with gr.Group():
|
||||
@@ -260,6 +294,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
batch_size,
|
||||
cfg_scale,
|
||||
seed,
|
||||
subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w,
|
||||
height,
|
||||
width,
|
||||
] + custom_inputs,
|
||||
@@ -314,6 +349,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
with gr.Blocks(analytics_enabled=False) as img2img_interface:
|
||||
with gr.Row():
|
||||
prompt = gr.Textbox(label="Prompt", elem_id="img2img_prompt", show_label=False, placeholder="Prompt", lines=1)
|
||||
negative_prompt = gr.Textbox(label="Negative prompt", elem_id="img2img_negative_prompt", show_label=False, placeholder="Negative prompt", lines=1, visible=cmd_opts.show_negative_prompt)
|
||||
submit = gr.Button('Generate', elem_id="img2img_generate", variant='primary')
|
||||
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
|
||||
|
||||
@@ -323,8 +359,12 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
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)
|
||||
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)
|
||||
resize_mode = gr.Radio(label="Resize mode", show_label=False, choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value="Just resize")
|
||||
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)
|
||||
|
||||
with gr.Row():
|
||||
resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", show_label=False, choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value="Just resize")
|
||||
mask_mode = gr.Radio(label="Mask mode", show_label=False, choices=["Draw mask", "Upload mask"], type="index", value="Draw mask")
|
||||
|
||||
steps = gr.Slider(minimum=1, maximum=150, step=1, label="Sampling Steps", value=20)
|
||||
sampler_index = gr.Radio(label='Sampling method', choices=[x.name for x in samplers_for_img2img], value=samplers_for_img2img[0].name, type="index")
|
||||
@@ -349,13 +389,14 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
|
||||
with gr.Group():
|
||||
cfg_scale = gr.Slider(minimum=1.0, maximum=15.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 = 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():
|
||||
height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512)
|
||||
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
|
||||
|
||||
seed = gr.Number(label='Seed', value=-1)
|
||||
seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs()
|
||||
|
||||
with gr.Group():
|
||||
custom_inputs = modules.scripts.scripts_img2img.setup_ui(is_img2img=True)
|
||||
@@ -363,7 +404,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
with gr.Column(variant='panel'):
|
||||
with gr.Group():
|
||||
img2img_preview = gr.Image(elem_id='img2img_preview', visible=False)
|
||||
img2img_gallery = gr.Gallery(label='Output', elem_id='img2img_gallery')
|
||||
img2img_gallery = gr.Gallery(label='Output', elem_id='img2img_gallery').style(grid=4)
|
||||
|
||||
with gr.Group():
|
||||
with gr.Row():
|
||||
@@ -379,15 +420,17 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
html_info = gr.HTML()
|
||||
generation_info = gr.Textbox(visible=False)
|
||||
|
||||
def apply_mode(mode):
|
||||
def apply_mode(mode, uploadmask):
|
||||
is_classic = mode == 0
|
||||
is_inpaint = mode == 1
|
||||
is_loopback = mode == 2
|
||||
is_upscale = mode == 3
|
||||
|
||||
return {
|
||||
init_img: gr_show(not is_inpaint),
|
||||
init_img_with_mask: gr_show(is_inpaint),
|
||||
init_img: gr_show(not is_inpaint or (is_inpaint and uploadmask == 1)),
|
||||
init_img_with_mask: gr_show(is_inpaint and uploadmask == 0),
|
||||
init_mask: gr_show(is_inpaint and uploadmask == 1),
|
||||
mask_mode: gr_show(is_inpaint),
|
||||
mask_blur: gr_show(is_inpaint),
|
||||
inpainting_fill: gr_show(is_inpaint),
|
||||
batch_count: gr_show(not is_upscale),
|
||||
@@ -396,14 +439,17 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
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),
|
||||
}
|
||||
|
||||
switch_mode.change(
|
||||
apply_mode,
|
||||
inputs=[switch_mode],
|
||||
inputs=[switch_mode, mask_mode],
|
||||
outputs=[
|
||||
init_img,
|
||||
init_img_with_mask,
|
||||
init_mask,
|
||||
mask_mode,
|
||||
mask_blur,
|
||||
inpainting_fill,
|
||||
batch_count,
|
||||
@@ -412,16 +458,34 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
|
||||
sd_upscale_overlap,
|
||||
inpaint_full_res,
|
||||
inpainting_mask_invert,
|
||||
denoising_strength_change_factor,
|
||||
]
|
||||
)
|
||||
|
||||
mask_mode.change(
|
||||
lambda mode: {
|
||||
init_img: gr_show(mode == 1),
|
||||
init_img_with_mask: gr_show(mode == 0),
|
||||
init_mask: gr_show(mode == 1),
|
||||
},
|
||||
inputs=[mask_mode],
|
||||
outputs=[
|
||||
init_img,
|
||||
init_img_with_mask,
|
||||
init_mask,
|
||||
],
|
||||
)
|
||||
|
||||
img2img_args = dict(
|
||||
fn=img2img,
|
||||
_js="submit",
|
||||
inputs=[
|
||||
prompt,
|
||||
negative_prompt,
|
||||
init_img,
|
||||
init_img_with_mask,
|
||||
init_mask,
|
||||
mask_mode,
|
||||
steps,
|
||||
sampler_index,
|
||||
mask_blur,
|
||||
@@ -433,7 +497,9 @@ 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,
|
||||
width,
|
||||
resize_mode,
|
||||
|
||||
@@ -10,5 +10,6 @@ omegaconf
|
||||
pytorch_lightning
|
||||
diffusers
|
||||
invisible-watermark
|
||||
scikit-image
|
||||
git+https://github.com/crowsonkb/k-diffusion.git
|
||||
git+https://github.com/TencentARC/GFPGAN.git
|
||||
|
||||
@@ -8,3 +8,4 @@ torch
|
||||
transformers==4.19.2
|
||||
omegaconf==2.1.1
|
||||
pytorch_lightning==1.7.2
|
||||
scikit-image==0.19.2
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
titles = {
|
||||
"Sampling steps": "How many times to imptove the generated image itratively; higher values take longer; very low values can produce bad results",
|
||||
"Sampling steps": "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results",
|
||||
"Sampling method": "Which algorithm to use to produce the image",
|
||||
"GFPGAN": "Restore low quality faces using GFPGAN neural network",
|
||||
"Euler a": "Euler Ancestral - very creative, each can get acompletely different pictures depending on step count, setting seps tohigher than 30-40 does not help",
|
||||
"Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help",
|
||||
"DDIM": "Denoising Diffusion Implicit Models - best at inpainting",
|
||||
|
||||
"Batch count": "How many batches of images to create",
|
||||
@@ -11,7 +11,7 @@ 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 determings number of iterations.",
|
||||
"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.",
|
||||
@@ -26,7 +26,8 @@ titles = {
|
||||
"latent nothing": "fill it with latent space zeroes",
|
||||
"Inpaint at full resolution": "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image",
|
||||
|
||||
"Denoising Strength": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image.",
|
||||
"Denoising strength": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image.",
|
||||
"Denoising strength change factor": "In loopback mode, on each loop the denoising strength is multiplied by this value. <1 means decreasing variety so your sequence will converge on a fixed picture. >1 means increasing variety so your sequence will become more and more chaotic.",
|
||||
|
||||
"Interrupt": "Stop processing images and return any results accumulated so far.",
|
||||
"Save": "Write image to a directory (default - log/images) and generation parameters into csv file.",
|
||||
@@ -36,15 +37,20 @@ titles = {
|
||||
|
||||
"None": "Do not do anything special",
|
||||
"Prompt matrix": "Separate prompts into parts using vertical pipe character (|) and the script will create a picture for every combination of them (except for the first part, which will be present in all combinations)",
|
||||
"X/Y plot": "Create a grid where images will have different parameters. Use inputs below to specify which parameterswill be shared by columns and rows",
|
||||
"Custom code": "Run python code. Advanced user only. Must run program with --allow-code for this to work",
|
||||
"X/Y plot": "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows",
|
||||
"Custom code": "Run Python code. Advanced user only. Must run program with --allow-code for this to work",
|
||||
|
||||
"Prompt S/R": "Separate a list of words with commas, and the first word will be used as a keyword: script will search for this word in the prompt, and replace it with others",
|
||||
|
||||
"Tiling": "Produce an image that can be tiled.",
|
||||
"Tile overlap": "For SD upscale, how much overlap in pixels should there be between tiles. Tils overlap so that when they are merged back into one oicture, there is no clearly visible seam.",
|
||||
"Tile overlap": "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.",
|
||||
|
||||
"Roll": "Add a random artist to the prompt.",
|
||||
|
||||
"Variation seed": "Seed of a different picture to be mixed into the generation.",
|
||||
"Variation strength": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).",
|
||||
"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",
|
||||
}
|
||||
|
||||
function gradioApp(){
|
||||
@@ -103,11 +109,31 @@ function addTitles(root){
|
||||
|
||||
}
|
||||
|
||||
tabNames = {"txt2img": 1, "img2img": 1, "Extras": 1, "PNG Info": 1, "Settings": 1}
|
||||
processedTabs = {}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var mutationObserver = new MutationObserver(function(m){
|
||||
addTitles(gradioApp());
|
||||
|
||||
// fix for gradio breaking when you switch away from tab with mask
|
||||
gradioApp().querySelectorAll('button').forEach(function(button){
|
||||
title = button.textContent.trim()
|
||||
if(processedTabs[title]) return
|
||||
if(tabNames[button.textContent.trim()]==null) return;
|
||||
processedTabs[title]=1
|
||||
|
||||
button.onclick = function(){
|
||||
mask_buttons = gradioApp().querySelectorAll('#img2maskimg button');
|
||||
if(mask_buttons.length == 2){
|
||||
mask_buttons[1].click();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
mutationObserver.observe( gradioApp(), { childList:true, subtree:true })
|
||||
|
||||
|
||||
});
|
||||
|
||||
function selected_gallery_index(){
|
||||
@@ -150,6 +176,21 @@ function submit(){
|
||||
for(var i=0;i<arguments.length;i++){
|
||||
res.push(arguments[i])
|
||||
}
|
||||
console.log(res)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('paste', e => {
|
||||
const files = e.clipboardData.files;
|
||||
if (!files || files.length !== 1) {
|
||||
return;
|
||||
}
|
||||
if (!['image/png', 'image/gif', 'image/jpeg'].includes(files[0].type)) {
|
||||
return;
|
||||
}
|
||||
[...gradioApp().querySelectorAll('input[type=file][accept="image/x-png,image/gif,image/jpeg"]')]
|
||||
.filter(input => !input.matches('.\\!hidden input[type=file]'))
|
||||
.forEach(input => {
|
||||
input.files = files;
|
||||
input.dispatchEvent(new Event('change'))
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ class Script(scripts.Script):
|
||||
if not is_img2img:
|
||||
return None
|
||||
|
||||
pixels = gr.Slider(label="Pixels to expand", minimum=8, maximum=128, step=8)
|
||||
pixels = gr.Slider(label="Pixels to expand", minimum=8, maximum=256, step=8, value=128)
|
||||
mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, visible=False)
|
||||
inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='fill', type="index", visible=False)
|
||||
direction = gr.CheckboxGroup(label="Outpainting direction", choices=['left', 'right', 'up', 'down'], value=['left', 'right', 'up', 'down'])
|
||||
@@ -32,7 +32,7 @@ class Script(scripts.Script):
|
||||
initial_seed = None
|
||||
initial_info = None
|
||||
|
||||
p.mask_blur = mask_blur
|
||||
p.mask_blur = mask_blur * 2
|
||||
p.inpainting_fill = inpainting_fill
|
||||
p.inpaint_full_res = False
|
||||
|
||||
@@ -47,11 +47,14 @@ class Script(scripts.Script):
|
||||
|
||||
if left > 0:
|
||||
left = left * (target_w - init_img.width) // (left + right)
|
||||
right = target_w - init_img.width - left
|
||||
if right > 0:
|
||||
right = target_w - init_img.width - left
|
||||
|
||||
if up > 0:
|
||||
up = up * (target_h - init_img.height) // (up + down)
|
||||
down = target_h - init_img.height - up
|
||||
|
||||
if down > 0:
|
||||
down = target_h - init_img.height - up
|
||||
|
||||
img = Image.new("RGB", (target_w, target_h))
|
||||
img.paste(init_img, (left, up))
|
||||
@@ -67,13 +70,18 @@ class Script(scripts.Script):
|
||||
|
||||
latent_mask = Image.new("L", (img.width, img.height), "white")
|
||||
latent_draw = ImageDraw.Draw(latent_mask)
|
||||
latent_draw.rectangle((left + left//2, up + up//2, mask.width - right - right//2, mask.height - down - down//2), fill="black")
|
||||
latent_draw.rectangle((
|
||||
left + (mask_blur//2 if left > 0 else 0),
|
||||
up + (mask_blur//2 if up > 0 else 0),
|
||||
mask.width - right - (mask_blur//2 if right > 0 else 0),
|
||||
mask.height - down - (mask_blur//2 if down > 0 else 0)
|
||||
), fill="black")
|
||||
|
||||
processing.torch_gc()
|
||||
|
||||
grid = images.split_grid(img, tile_w=p.width, tile_h=p.height, overlap=pixels)
|
||||
grid_mask = images.split_grid(mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
|
||||
grid_latent_mask = images.split_grid(mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
|
||||
grid_latent_mask = images.split_grid(latent_mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
|
||||
|
||||
p.n_iter = 1
|
||||
p.batch_size = 1
|
||||
@@ -85,8 +93,13 @@ class Script(scripts.Script):
|
||||
work_latent_mask = []
|
||||
work_results = []
|
||||
|
||||
for (_, _, row), (_, _, row_mask), (_, _, row_latent_mask) in zip(grid.tiles, grid_mask.tiles, grid_latent_mask.tiles):
|
||||
for (y, h, row), (_, _, row_mask), (_, _, row_latent_mask) in zip(grid.tiles, grid_mask.tiles, grid_latent_mask.tiles):
|
||||
for tiledata, tiledata_mask, tiledata_latent_mask in zip(row, row_mask, row_latent_mask):
|
||||
x, w = tiledata[0:2]
|
||||
|
||||
if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down:
|
||||
continue
|
||||
|
||||
work.append(tiledata[2])
|
||||
work_mask.append(tiledata_mask[2])
|
||||
work_latent_mask.append(tiledata_latent_mask[2])
|
||||
@@ -115,6 +128,11 @@ class Script(scripts.Script):
|
||||
image_index = 0
|
||||
for y, h, row in grid.tiles:
|
||||
for tiledata in row:
|
||||
x, w = tiledata[0:2]
|
||||
|
||||
if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down:
|
||||
continue
|
||||
|
||||
tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height))
|
||||
image_index += 1
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class Script(scripts.Script):
|
||||
return [put_at_start]
|
||||
|
||||
def run(self, p, put_at_start):
|
||||
seed = modules.processing.set_seed(p.seed)
|
||||
modules.processing.fix_seed(p)
|
||||
|
||||
original_prompt = p.prompt[0] if type(p.prompt) == list else p.prompt
|
||||
|
||||
|
||||
+38
-4
@@ -2,6 +2,8 @@ from collections import namedtuple
|
||||
from copy import copy
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
|
||||
import modules.scripts as scripts
|
||||
import gradio as gr
|
||||
|
||||
@@ -21,6 +23,7 @@ def apply_field(field):
|
||||
|
||||
def apply_prompt(p, x, xs):
|
||||
p.prompt = p.prompt.replace(xs[0], x)
|
||||
p.negative_prompt = p.negative_prompt.replace(xs[0], x)
|
||||
|
||||
|
||||
samplers_dict = {}
|
||||
@@ -39,24 +42,39 @@ def apply_sampler(p, x, xs):
|
||||
|
||||
|
||||
def format_value_add_label(p, opt, x):
|
||||
if type(x) == float:
|
||||
x = round(x, 8)
|
||||
|
||||
return f"{opt.label}: {x}"
|
||||
|
||||
|
||||
def format_value(p, opt, x):
|
||||
if type(x) == float:
|
||||
x = round(x, 8)
|
||||
|
||||
return x
|
||||
|
||||
def do_nothing(p, x, xs):
|
||||
pass
|
||||
|
||||
def format_nothing(p, opt, x):
|
||||
return ""
|
||||
|
||||
|
||||
AxisOption = namedtuple("AxisOption", ["label", "type", "apply", "format_value"])
|
||||
AxisOptionImg2Img = namedtuple("AxisOptionImg2Img", ["label", "type", "apply", "format_value"])
|
||||
|
||||
|
||||
axis_options = [
|
||||
AxisOption("Nothing", str, do_nothing, format_nothing),
|
||||
AxisOption("Seed", int, apply_field("seed"), format_value_add_label),
|
||||
AxisOption("Var. seed", int, apply_field("subseed"), format_value_add_label),
|
||||
AxisOption("Var. strength", float, apply_field("subseed_strength"), format_value_add_label),
|
||||
AxisOption("Steps", int, apply_field("steps"), format_value_add_label),
|
||||
AxisOption("CFG Scale", float, apply_field("cfg_scale"), format_value_add_label),
|
||||
AxisOption("Prompt S/R", str, apply_prompt, format_value),
|
||||
AxisOption("Sampler", str, apply_sampler, format_value),
|
||||
AxisOptionImg2Img("Denoising", float, apply_field("denoising_strength"), format_value_add_label) # as it is now all AxisOptionImg2Img items must go after AxisOption ones
|
||||
AxisOptionImg2Img("Denoising", float, apply_field("denoising_strength"), format_value_add_label), # as it is now all AxisOptionImg2Img items must go after AxisOption ones
|
||||
]
|
||||
|
||||
|
||||
@@ -89,6 +107,7 @@ 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*")
|
||||
|
||||
class Script(scripts.Script):
|
||||
def title(self):
|
||||
@@ -98,17 +117,17 @@ class Script(scripts.Script):
|
||||
current_axis_options = [x for x in axis_options if type(x) == AxisOption or type(x) == AxisOptionImg2Img and is_img2img]
|
||||
|
||||
with gr.Row():
|
||||
x_type = gr.Dropdown(label="X type", choices=[x.label for x in current_axis_options], value=current_axis_options[0].label, visible=False, type="index", elem_id="x_type")
|
||||
x_type = gr.Dropdown(label="X type", choices=[x.label for x in current_axis_options], value=current_axis_options[1].label, visible=False, type="index", elem_id="x_type")
|
||||
x_values = gr.Textbox(label="X values", visible=False, lines=1)
|
||||
|
||||
with gr.Row():
|
||||
y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[1].label, visible=False, type="index", elem_id="y_type")
|
||||
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)
|
||||
|
||||
return [x_type, x_values, y_type, y_values]
|
||||
|
||||
def run(self, p, x_type, x_values, y_type, y_values):
|
||||
p.seed = modules.processing.set_seed(p.seed)
|
||||
modules.processing.fix_seed(p)
|
||||
p.batch_size = 1
|
||||
p.batch_count = 1
|
||||
|
||||
@@ -131,6 +150,21 @@ class Script(scripts.Script):
|
||||
valslist_ext.append(val)
|
||||
|
||||
valslist = valslist_ext
|
||||
elif opt.type == float:
|
||||
valslist_ext = []
|
||||
|
||||
for val in valslist:
|
||||
m = re_range_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()
|
||||
else:
|
||||
valslist_ext.append(val)
|
||||
|
||||
valslist = valslist_ext
|
||||
|
||||
valslist = [opt.type(x) for x in valslist]
|
||||
|
||||
|
||||
@@ -5,16 +5,29 @@
|
||||
max-width: 13em;
|
||||
}
|
||||
|
||||
#subseed_show{
|
||||
min-width: 6em;
|
||||
max-width: 6em;
|
||||
}
|
||||
|
||||
#subseed_show label{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#txt2img_roll{
|
||||
min-width: 1em;
|
||||
max-width: 4em;
|
||||
}
|
||||
|
||||
#resize_mode{
|
||||
flex: 1.5;
|
||||
}
|
||||
|
||||
button{
|
||||
align-self: stretch !important;
|
||||
}
|
||||
|
||||
#img2img_prompt, #txt2img_prompt{
|
||||
#img2img_prompt, #txt2img_prompt, #img2img_negative_prompt, #txt2img_negative_prompt{
|
||||
padding: 0;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
|
||||
set PYTHON=
|
||||
set GIT=
|
||||
set VENV_DIR=
|
||||
set COMMANDLINE_ARGS=
|
||||
|
||||
call webui.bat
|
||||
@@ -1,15 +1,14 @@
|
||||
@echo off
|
||||
|
||||
set PYTHON=python
|
||||
set GIT=git
|
||||
set COMMANDLINE_ARGS=
|
||||
set VENV_DIR=venv
|
||||
if not defined PYTHON (set PYTHON=python)
|
||||
if not defined GIT (set GIT=git)
|
||||
if not defined COMMANDLINE_ARGS (set COMMANDLINE_ARGS=%*)
|
||||
if not defined VENV_DIR (set VENV_DIR=venv)
|
||||
if not defined TORCH_COMMAND (set TORCH_COMMAND=pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113)
|
||||
if not defined REQS_FILE (set REQS_FILE=requirements_versions.txt)
|
||||
|
||||
mkdir tmp 2>NUL
|
||||
|
||||
set TORCH_COMMAND=pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113
|
||||
set REQS_FILE=requirements_versions.txt
|
||||
|
||||
%PYTHON% -c "" >tmp/stdout.txt 2>tmp/stderr.txt
|
||||
if %ERRORLEVEL% == 0 goto :check_git
|
||||
echo Couldn't launch python
|
||||
@@ -22,7 +21,7 @@ echo Couldn't launch git
|
||||
goto :show_stdout_stderr
|
||||
|
||||
:setup_venv
|
||||
if [%VENV_DIR%] == [] goto :skip_venv
|
||||
if [%VENV_DIR%] == [-] goto :skip_venv
|
||||
|
||||
dir %VENV_DIR%\Scripts\Python.exe >tmp/stdout.txt 2>tmp/stderr.txt
|
||||
if %ERRORLEVEL% == 0 goto :activate_venv
|
||||
@@ -35,7 +34,7 @@ echo Unable to create venv in directory %VENV_DIR%
|
||||
goto :show_stdout_stderr
|
||||
|
||||
:activate_venv
|
||||
set PYTHON=%~dp0%VENV_DIR%\Scripts\Python.exe
|
||||
set PYTHON="%~dp0%VENV_DIR%\Scripts\Python.exe"
|
||||
%PYTHON% --version
|
||||
echo venv %PYTHON%
|
||||
goto :install_torch
|
||||
|
||||
@@ -43,6 +43,7 @@ def load_model_from_config(config, ckpt, verbose=False):
|
||||
if "global_step" in pl_sd:
|
||||
print(f"Global Step: {pl_sd['global_step']}")
|
||||
sd = pl_sd["state_dict"]
|
||||
|
||||
model = instantiate_from_config(config.model)
|
||||
m, u = model.load_state_dict(sd, strict=False)
|
||||
if len(m) > 0 and verbose:
|
||||
@@ -87,7 +88,7 @@ def run_extras(image, gfpgan_visibility, codeformer_visibility, codeformer_weigh
|
||||
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) + pixels
|
||||
key = (resize, scaler_index, image.width, image.height, gfpgan_visibility, codeformer_visibility, codeformer_weight) + pixels
|
||||
|
||||
c = cached_images.get(key)
|
||||
if c is None:
|
||||
@@ -152,6 +153,7 @@ def wrap_gradio_gpu_call(func):
|
||||
|
||||
return modules.ui.wrap_gradio_call(f)
|
||||
|
||||
modules.scripts.load_scripts(os.path.join(script_path, "scripts"))
|
||||
|
||||
try:
|
||||
# this silences the annoying "Some weights of the model checkpoint were not used when initializing..." message at start.
|
||||
@@ -173,15 +175,13 @@ else:
|
||||
|
||||
modules.sd_hijack.model_hijack.hijack(shared.sd_model)
|
||||
|
||||
modules.scripts.load_scripts(os.path.join(script_path, "scripts"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
def webui():
|
||||
# make the program just exit at ctrl+c without waiting for anything
|
||||
def sigint_handler(sig, frame):
|
||||
print(f'Interrupted with signal {sig} in {frame}')
|
||||
os._exit(0)
|
||||
|
||||
|
||||
signal.signal(signal.SIGINT, sigint_handler)
|
||||
|
||||
demo = modules.ui.create_ui(
|
||||
@@ -191,4 +191,7 @@ if __name__ == "__main__":
|
||||
run_pnginfo=run_pnginfo
|
||||
)
|
||||
|
||||
demo.launch(share=cmd_opts.share, server_name="0.0.0.0" if cmd_opts.listen else None)
|
||||
demo.launch(share=cmd_opts.share, server_name="0.0.0.0" if cmd_opts.listen else None, server_port=cmd_opts.port)
|
||||
|
||||
if __name__ == "__main__":
|
||||
webui()
|
||||
|
||||
Reference in New Issue
Block a user