Skip to content

Commit d1a2c4c

Browse files
psychedeliciousKyle0654lstein
authored
React web UI with flask-socketio API (invoke-ai#429)
* Implements rudimentary api * Fixes blocking in API * Adds UI to monorepo > src/frontend/ * Updates frontend/README * Reverts conda env name to `ldm` * Fixes environment yamls * CORS config for testing * Fixes LogViewer position * API WID * Adds actions to image viewer * Increases vite chunkSizeWarningLimit to 1500 * Implements init image * Implements state persistence in localStorage * Improve progress data handling * Final build * Fixes mimetypes error on windows * Adds error logging * Fixes bugged img2img strength component * Adds sourcemaps to dev build * Fixes missing key * Changes connection status indicator to text * Adds ability to serve other hosts than localhost * Adding Flask API server * Removes source maps from config * Fixes prop transfer * Add missing packages and add CORS support * Adding API doc * Remove defaults from openapi doc * Adds basic error handling for server config query * Mostly working socket.io implementation. * Fixes bug preventing mask upload * Fixes bug with sampler name not written to metadata * UI Overhaul, numerous fixes Co-authored-by: Kyle Schouviller <kyle0654@hotmail.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
1 parent 403d02d commit d1a2c4c

89 files changed

Lines changed: 9648 additions & 122 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ db.sqlite3-journal
7777
instance/
7878
.webassets-cache
7979

80-
# WebUI temp files:
81-
img2img-tmp.png
82-
8380
# Scrapy stuff:
8481
.scrapy
8582

@@ -186,3 +183,11 @@ testtube
186183
checkpoints
187184
# If it's a Mac
188185
.DS_Store
186+
187+
# Let the frontend manage its own gitignore
188+
!frontend/*
189+
190+
# Scratch folder
191+
.scratch/
192+
.vscode/
193+
gfpgan/

backend/modules/parameters.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
from modules.parse_seed_weights import parse_seed_weights
2+
import argparse
3+
4+
SAMPLER_CHOICES = [
5+
'ddim',
6+
'k_dpm_2_a',
7+
'k_dpm_2',
8+
'k_euler_a',
9+
'k_euler',
10+
'k_heun',
11+
'k_lms',
12+
'plms',
13+
]
14+
15+
16+
def parameters_to_command(params):
17+
"""
18+
Converts dict of parameters into a `dream.py` REPL command.
19+
"""
20+
21+
switches = list()
22+
23+
if 'prompt' in params:
24+
switches.append(f'"{params["prompt"]}"')
25+
if 'steps' in params:
26+
switches.append(f'-s {params["steps"]}')
27+
if 'seed' in params:
28+
switches.append(f'-S {params["seed"]}')
29+
if 'width' in params:
30+
switches.append(f'-W {params["width"]}')
31+
if 'height' in params:
32+
switches.append(f'-H {params["height"]}')
33+
if 'cfg_scale' in params:
34+
switches.append(f'-C {params["cfg_scale"]}')
35+
if 'sampler_name' in params:
36+
switches.append(f'-A {params["sampler_name"]}')
37+
if 'seamless' in params and params["seamless"] == True:
38+
switches.append(f'--seamless')
39+
if 'init_img' in params and len(params['init_img']) > 0:
40+
switches.append(f'-I {params["init_img"]}')
41+
if 'init_mask' in params and len(params['init_mask']) > 0:
42+
switches.append(f'-M {params["init_mask"]}')
43+
if 'strength' in params and 'init_img' in params:
44+
switches.append(f'-f {params["strength"]}')
45+
if 'fit' in params and params["fit"] == True:
46+
switches.append(f'--fit')
47+
if 'gfpgan_strength' in params and params["gfpgan_strength"]:
48+
switches.append(f'-G {params["gfpgan_strength"]}')
49+
if 'upscale' in params and params["upscale"]:
50+
switches.append(f'-U {params["upscale"][0]} {params["upscale"][1]}')
51+
if 'variation_amount' in params and params['variation_amount'] > 0:
52+
switches.append(f'-v {params["variation_amount"]}')
53+
if 'with_variations' in params:
54+
seed_weight_pairs = ','.join(f'{seed}:{weight}' for seed, weight in params["with_variations"])
55+
switches.append(f'-V {seed_weight_pairs}')
56+
57+
return ' '.join(switches)
58+
59+
60+
61+
def create_cmd_parser():
62+
"""
63+
This is simply a copy of the parser from `dream.py` with a change to give
64+
prompt a default value. This is a temporary hack pending merge of #587 which
65+
provides a better way to do this.
66+
"""
67+
parser = argparse.ArgumentParser(
68+
description='Example: dream> a fantastic alien landscape -W1024 -H960 -s100 -n12',
69+
exit_on_error=True,
70+
)
71+
parser.add_argument('prompt', nargs='?', default='')
72+
parser.add_argument('-s', '--steps', type=int, help='Number of steps')
73+
parser.add_argument(
74+
'-S',
75+
'--seed',
76+
type=int,
77+
help='Image seed; a +ve integer, or use -1 for the previous seed, -2 for the one before that, etc',
78+
)
79+
parser.add_argument(
80+
'-n',
81+
'--iterations',
82+
type=int,
83+
default=1,
84+
help='Number of samplings to perform (slower, but will provide seeds for individual images)',
85+
)
86+
parser.add_argument(
87+
'-W', '--width', type=int, help='Image width, multiple of 64'
88+
)
89+
parser.add_argument(
90+
'-H', '--height', type=int, help='Image height, multiple of 64'
91+
)
92+
parser.add_argument(
93+
'-C',
94+
'--cfg_scale',
95+
default=7.5,
96+
type=float,
97+
help='Classifier free guidance (CFG) scale - higher numbers cause generator to "try" harder.',
98+
)
99+
parser.add_argument(
100+
'-g', '--grid', action='store_true', help='generate a grid'
101+
)
102+
parser.add_argument(
103+
'--outdir',
104+
'-o',
105+
type=str,
106+
default=None,
107+
help='Directory to save generated images and a log of prompts and seeds',
108+
)
109+
parser.add_argument(
110+
'--seamless',
111+
action='store_true',
112+
help='Change the model to seamless tiling (circular) mode',
113+
)
114+
parser.add_argument(
115+
'-i',
116+
'--individual',
117+
action='store_true',
118+
help='Generate individual files (default)',
119+
)
120+
parser.add_argument(
121+
'-I',
122+
'--init_img',
123+
type=str,
124+
help='Path to input image for img2img mode (supersedes width and height)',
125+
)
126+
parser.add_argument(
127+
'-M',
128+
'--init_mask',
129+
type=str,
130+
help='Path to input mask for inpainting mode (supersedes width and height)',
131+
)
132+
parser.add_argument(
133+
'-T',
134+
'-fit',
135+
'--fit',
136+
action='store_true',
137+
help='If specified, will resize the input image to fit within the dimensions of width x height (512x512 default)',
138+
)
139+
parser.add_argument(
140+
'-f',
141+
'--strength',
142+
default=0.75,
143+
type=float,
144+
help='Strength for noising/unnoising. 0.0 preserves image exactly, 1.0 replaces it completely',
145+
)
146+
parser.add_argument(
147+
'-G',
148+
'--gfpgan_strength',
149+
default=0,
150+
type=float,
151+
help='The strength at which to apply the GFPGAN model to the result, in order to improve faces.',
152+
)
153+
parser.add_argument(
154+
'-U',
155+
'--upscale',
156+
nargs='+',
157+
default=None,
158+
type=float,
159+
help='Scale factor (2, 4) for upscaling followed by upscaling strength (0-1.0). If strength not specified, defaults to 0.75'
160+
)
161+
parser.add_argument(
162+
'-save_orig',
163+
'--save_original',
164+
action='store_true',
165+
help='Save original. Use it when upscaling to save both versions.',
166+
)
167+
# variants is going to be superseded by a generalized "prompt-morph" function
168+
# parser.add_argument('-v','--variants',type=int,help="in img2img mode, the first generated image will get passed back to img2img to generate the requested number of variants")
169+
parser.add_argument(
170+
'-x',
171+
'--skip_normalize',
172+
action='store_true',
173+
help='Skip subprompt weight normalization',
174+
)
175+
parser.add_argument(
176+
'-A',
177+
'-m',
178+
'--sampler',
179+
dest='sampler_name',
180+
default=None,
181+
type=str,
182+
choices=SAMPLER_CHOICES,
183+
metavar='SAMPLER_NAME',
184+
help=f'Switch to a different sampler. Supported samplers: {", ".join(SAMPLER_CHOICES)}',
185+
)
186+
parser.add_argument(
187+
'-t',
188+
'--log_tokenization',
189+
action='store_true',
190+
help='shows how the prompt is split into tokens'
191+
)
192+
parser.add_argument(
193+
'-v',
194+
'--variation_amount',
195+
default=0.0,
196+
type=float,
197+
help='If > 0, generates variations on the initial seed instead of random seeds per iteration. Must be between 0 and 1. Higher values will be more different.'
198+
)
199+
parser.add_argument(
200+
'-V',
201+
'--with_variations',
202+
default=None,
203+
type=str,
204+
help='list of variations to apply, in the format `seed:weight,seed:weight,...'
205+
)
206+
return parser
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
def parse_seed_weights(seed_weights):
2+
"""
3+
Accepts seed weights as string in "12345:0.1,23456:0.2,3456:0.3" format
4+
Validates them
5+
If valid: returns as [[12345, 0.1], [23456, 0.2], [3456, 0.3]]
6+
If invalid: returns False
7+
"""
8+
9+
# Must be a string
10+
if not isinstance(seed_weights, str):
11+
return False
12+
# String must not be empty
13+
if len(seed_weights) == 0:
14+
return False
15+
16+
pairs = []
17+
18+
for pair in seed_weights.split(","):
19+
split_values = pair.split(":")
20+
21+
# Seed and weight are required
22+
if len(split_values) != 2:
23+
return False
24+
25+
if len(split_values[0]) == 0 or len(split_values[1]) == 1:
26+
return False
27+
28+
# Try casting the seed to int and weight to float
29+
try:
30+
seed = int(split_values[0])
31+
weight = float(split_values[1])
32+
except ValueError:
33+
return False
34+
35+
# Seed must be 0 or above
36+
if not seed >= 0:
37+
return False
38+
39+
# Weight must be between 0 and 1
40+
if not (weight >= 0 and weight <= 1):
41+
return False
42+
43+
# This pair is valid
44+
pairs.append([seed, weight])
45+
46+
# All pairs are valid
47+
return pairs

0 commit comments

Comments
 (0)