refactor api auth

This commit is contained in:
Vladimir Mandic
2023-05-23 14:31:22 -04:00
parent beff89bad3
commit d36b16d03f
24 changed files with 152 additions and 282 deletions
+15 -7
View File
@@ -59,11 +59,19 @@ Tech that can be integrated as part of the core workflow...
### Pending Code Updates
- tested with **torch 2.1** and **cuda 12.1**
(production remains on torch2.0.1+cuda11.8)
(production remains on torch2.0.1+cuda11.8)
- fully extend support of `--data-dir`
allows multiple installations to share pretty much everything, not just models
- add dark/light theme mode toggle
- redo some `clip-skip` functionality
- better matching for vae vs model
- update to `xyz grid` to allow creation of large number of images without
- fixes...amazing how many issues were introduced by porting new a1111 code without adding almost no new functionality
allows multiple installations to share pretty much everything, not just models
- redo api authentication
now api authentication will use same user/pwd (if specified) for ui and strictly enforce it using httpbasicauth
new authentication is also fully supported in combination with ssl for both sync and async calls
if you want to use api programatically, see examples in `cli/sdapi.py`
- add dark/light theme mode toggle
- redo some `clip-skip` functionality
- better matching for vae vs model
- update to `xyz grid` to allow creation of large number of images without
- update `gradio` (again)
- more prompt parser optimizations
- better error handling when importing image settings which are not compatible with current install
for example, when upscaler or sampler originally used is not available
- fixes...amazing how many issues were introduced by porting new a1111 code without adding almost no new functionality
+1 -1
View File
@@ -23,7 +23,7 @@ console = Console(log_time=True, log_time_format='%H:%M:%S-%f')
pretty_install(console=console)
traceback_install(console=console, extra_lines=1, width=console.width, word_wrap=False, indent_guides=False)
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'modules', 'lora'))
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'modules', 'lora'))
import library.model_util as model_util
import library.train_util as train_util
+47 -7
View File
@@ -5,19 +5,55 @@ helper methods that creates HTTP session with managed connection pool
provides async HTTP get/post methods and several helper methods
"""
import os
import sys
import ssl
import asyncio
import logging
import aiohttp
import requests
import urllib3
from util import Map, log
sd_url = "http://127.0.0.1:7860" # automatic1111 api url root
sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860") # automatic1111 api url root
use_session = True
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
ssl.create_default_context = ssl._create_unverified_context # pylint: disable=protected-access
timeout = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training
sess = None
quiet = False
BaseThreadPolicy = asyncio.WindowsSelectorEventLoopPolicy if sys.platform == "win32" and hasattr(asyncio, "WindowsSelectorEventLoopPolicy") else asyncio.DefaultEventLoopPolicy
class AnyThreadEventLoopPolicy(BaseThreadPolicy):
def get_event_loop(self) -> asyncio.AbstractEventLoop:
try:
return super().get_event_loop()
except (RuntimeError, AssertionError):
loop = self.new_event_loop()
self.set_event_loop(loop)
return loop
asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
def authsync():
sd_username = os.environ.get('SDAPI_USR', None)
sd_password = os.environ.get('SDAPI_PWD', None)
if sd_username is not None and sd_password is not None:
return requests.auth.HTTPBasicAuth(sd_username, sd_password)
return None
def auth():
sd_username = os.environ.get('SDAPI_USR', None)
sd_password = os.environ.get('SDAPI_PWD', None)
if sd_username is not None and sd_password is not None:
return aiohttp.BasicAuth(sd_username, sd_password)
return None
async def result(req):
@@ -60,7 +96,7 @@ async def get(endpoint: str, json: dict = None):
global sess # pylint: disable=global-statement
sess = sess if sess is not None else await session()
try:
async with sess.get(url = endpoint, json = json) as req:
async with sess.get(url=endpoint, json=json, verify_ssl=False) as req:
res = await result(req)
return res
except Exception as err:
@@ -70,7 +106,7 @@ async def get(endpoint: str, json: dict = None):
def getsync(endpoint: str, json: dict = None):
try:
req = requests.get(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout
req = requests.get(f'{sd_url}{endpoint}', json=json, verify=False, auth=authsync()) # pylint: disable=missing-timeout
res = resultsync(req)
return res
except Exception as err:
@@ -85,7 +121,7 @@ async def post(endpoint: str, json: dict = None):
await sess.close()
sess = await session()
try:
async with sess.post(url = endpoint, json = json) as req:
async with sess.post(url=endpoint, json=json, verify_ssl=False) as req:
res = await result(req)
return res
except Exception as err:
@@ -94,7 +130,7 @@ async def post(endpoint: str, json: dict = None):
def postsync(endpoint: str, json: dict = None):
req = requests.post(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout
req = requests.post(f'{sd_url}{endpoint}', json=json, verify=False, auth=authsync()) # pylint: disable=missing-timeout
res = resultsync(req)
return res
@@ -150,7 +186,7 @@ def shutdown():
async def session():
global sess # pylint: disable=global-statement
time = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training
sess = aiohttp.ClientSession(timeout = time, base_url = sd_url)
sess = aiohttp.ClientSession(timeout = time, base_url = sd_url, auth=auth())
log.debug({ 'sdapi': 'session created', 'endpoint': sd_url })
"""
sess = await aiohttp.ClientSession(timeout = timeout).__aenter__()
@@ -170,6 +206,7 @@ async def session():
async def close():
if sess is not None:
await asyncio.sleep(0)
await sess.close()
await sess.__aexit__(None, None, None)
log.debug({ 'sdapi': 'session closed', 'endpoint': sd_url })
@@ -180,6 +217,8 @@ if __name__ == "__main__":
asyncio.run(interrupt())
if 'progress' in sys.argv:
asyncio.run(progress())
if 'progresssync' in sys.argv:
progresssync()
if 'options' in sys.argv:
opt = options()
log.debug({ 'options' })
@@ -189,4 +228,5 @@ if __name__ == "__main__":
print(json.dumps(opt['flags'], indent = 2))
if 'shutdown' in sys.argv:
shutdown()
asyncio.run(close())
asyncio.run(close(), debug=True)
asyncio.run(asyncio.sleep(0.5))
+4 -5
View File
@@ -17,7 +17,6 @@ import warnings
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
warnings.filterwarnings(action="ignore", category=UserWarning)
warnings.filterwarnings(action="ignore", category=FutureWarning)
sys.path.append('.')
# 3rd party imports
import filetype
@@ -27,9 +26,9 @@ from tqdm.rich import tqdm
# local imports
import util
import sdapi
import options
import process
import latents
import options
# globals
@@ -79,7 +78,7 @@ def mem_stats():
def parse_args():
global args # pylint: disable=global-statement
parser = argparse.ArgumentParser(description = 'Train')
parser = argparse.ArgumentParser(description = 'SD.Next Train')
group_main = parser.add_argument_group('Main')
group_main.add_argument('--type', type=str, choices=['embedding', 'ti', 'lora', 'lyco', 'dreambooth', 'hypernetwork'], default=None, required=True, help='training type')
@@ -240,9 +239,9 @@ def train_lora():
log.info(f'{args.type} options: {options.lora}')
# lora imports
lora_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'modules', 'lora'))
lycoris_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'modules', 'lycoris'))
sys.path.append(lora_path)
if args.type == 'lyco':
lycoris_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'modules', 'lycoris'))
sys.path.append(lycoris_path)
log.debug('importing lora lib')
import train_network
@@ -368,7 +367,7 @@ def process_inputs():
if __name__ == '__main__':
log.info('train script for stable diffusion')
log.info('SD.Next train script')
parse_args()
setup_logging()
prepare_server()
-109
View File
@@ -1,109 +0,0 @@
import asyncio
import aiohttp
import requests
from util import Map
sd_url = "http://127.0.0.1:7860" # automatic1111 api url root
use_session = True
timeout = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training
sess = None
quiet = False
async def result(req):
if req.status != 200:
if not use_session and sess is not None:
await sess.close()
return Map({ 'error': req.status, 'reason': req.reason, 'url': req.url })
else:
json = await req.json()
if type(json) == list:
res = json
elif json is None:
res = {}
else:
res = Map(json)
return res
def resultsync(req: requests.Response):
if req.status_code != 200:
return Map({ 'error': req.status_code, 'reason': req.reason, 'url': req.url })
else:
json = req.json()
if type(json) == list:
res = json
elif json is None:
res = {}
else:
res = Map(json)
return res
async def get(endpoint: str, json: dict = None):
global sess # pylint: disable=global-statement
sess = sess if sess is not None else await session()
async with sess.get(url = endpoint, json = json) as req:
res = await result(req)
return res
def getsync(endpoint: str, json: dict = None):
req = requests.get(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout
res = resultsync(req)
return res
async def post(endpoint: str, json: dict = None):
global sess # pylint: disable=global-statement
# sess = sess if sess is not None else await session()
if sess and not sess.closed:
await sess.close()
sess = await session()
async with sess.post(url = endpoint, json = json) as req:
res = await result(req)
return res
def postsync(endpoint: str, json: dict = None):
req = requests.post(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout
res = resultsync(req)
return res
def interrupt():
res = getsync('/sdapi/v1/progress?skip_current_image=true')
if 'state' in res and res.state.job_count > 0:
res = postsync('/sdapi/v1/interrupt')
return res
else:
return { 'interrupt': 'idle' }
def progress():
res = getsync('/sdapi/v1/progress?skip_current_image=true')
return res
def options():
opt = getsync('/sdapi/v1/options')
flags = getsync('/sdapi/v1/cmd-flags')
return { 'options': opt, 'flags': flags }
def shutdown():
postsync('/sdapi/v1/shutdown')
async def session():
global sess # pylint: disable=global-statement
time = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training
sess = aiohttp.ClientSession(timeout = time, base_url = sd_url)
return sess
async def close():
if sess is not None:
await asyncio.sleep(0)
await sess.__aexit__(None, None, None)
-85
View File
@@ -1,85 +0,0 @@
#!/usr/bin/env python
import os
import transformers
transformers.logging.set_verbosity_error()
def get_memory():
def gb(val: float):
return round(val / 1024 / 1024 / 1024, 2)
mem = {}
try:
import psutil
process = psutil.Process(os.getpid())
res = process.memory_info()
ram_total = 100 * res.rss / process.memory_percent()
ram = { 'free': gb(ram_total - res.rss), 'used': gb(res.rss), 'total': gb(ram_total) }
mem.update({ 'ram': ram })
except Exception as e:
mem.update({ 'ram': e })
try:
import torch
if torch.cuda.is_available():
s = torch.cuda.mem_get_info()
gpu = { 'free': gb(s[0]), 'used': gb(s[1] - s[0]), 'total': gb(s[1]) }
s = dict(torch.cuda.memory_stats('cuda'))
allocated = { 'current': gb(s['allocated_bytes.all.current']), 'peak': gb(s['allocated_bytes.all.peak']) }
reserved = { 'current': gb(s['reserved_bytes.all.current']), 'peak': gb(s['reserved_bytes.all.peak']) }
active = { 'current': gb(s['active_bytes.all.current']), 'peak': gb(s['active_bytes.all.peak']) }
inactive = { 'current': gb(s['inactive_split_bytes.all.current']), 'peak': gb(s['inactive_split_bytes.all.peak']) }
warnings = { 'retries': s['num_alloc_retries'], 'oom': s['num_ooms'] }
mem.update({
'gpu': gpu,
'gpu-active': active,
'gpu-allocated': allocated,
'gpu-reserved': reserved,
'gpu-inactive': inactive,
'events': warnings,
})
except:
pass
return Map(mem)
class Map(dict): # pylint: disable=C0205
__slots__ = ('__dict__') # pylint: disable=C0325
def __init__(self, *args, **kwargs):
super(Map, self).__init__(*args, **kwargs)
for arg in args:
if isinstance(arg, dict):
for k, v in arg.items():
if isinstance(v, dict):
v = Map(v)
if isinstance(v, list):
self.__convert(v)
self[k] = v
if kwargs:
for k, v in kwargs.items():
if isinstance(v, dict):
v = Map(v)
elif isinstance(v, list):
self.__convert(v)
self[k] = v
def __convert(self, v):
for elem in range(0, len(v)): # pylint: disable=consider-using-enumerate
if isinstance(v[elem], dict):
v[elem] = Map(v[elem])
elif isinstance(v[elem], list):
self.__convert(v[elem])
def __getattr__(self, attr):
return self.get(attr)
def __setattr__(self, key, value):
self.__setitem__(key, value)
def __setitem__(self, key, value):
super(Map, self).__setitem__(key, value)
self.__dict__.update({key: value})
def __delattr__(self, item):
self.__delitem__(item)
def __delitem__(self, key):
super(Map, self).__delitem__(key)
del self.__dict__[key]
if __name__ == "__main__":
pass
+6 -2
View File
@@ -6,9 +6,13 @@ generic helper methods
import os
import string
import logging
import warnings
log_format = '%(asctime)s %(levelname)s: %(message)s'
logging.basicConfig(level = logging.INFO, format = log_format)
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
warnings.filterwarnings(action="ignore", category=FutureWarning)
warnings.filterwarnings(action="ignore", category=UserWarning)
log = logging.getLogger("sd")
@@ -52,14 +56,14 @@ def get_memory():
reserved = { 'current': gb(s['reserved_bytes.all.current']), 'peak': gb(s['reserved_bytes.all.peak']) }
active = { 'current': gb(s['active_bytes.all.current']), 'peak': gb(s['active_bytes.all.peak']) }
inactive = { 'current': gb(s['inactive_split_bytes.all.current']), 'peak': gb(s['inactive_split_bytes.all.peak']) }
warnings = { 'retries': s['num_alloc_retries'], 'oom': s['num_ooms'] }
events = { 'retries': s['num_alloc_retries'], 'oom': s['num_ooms'] }
mem.update({
'gpu': gpu,
'gpu-active': active,
'gpu-allocated': allocated,
'gpu-reserved': reserved,
'gpu-inactive': inactive,
'events': warnings,
'events': events,
})
except:
pass
+22 -12
View File
@@ -68,6 +68,8 @@ def setup_logging(clean=False):
traceback_install(console=console, extra_lines=1, width=console.width, word_wrap=False, indent_guides=False, suppress=[])
rh = RichHandler(show_time=True, omit_repeated_times=False, show_level=True, show_path=False, markup=False, rich_tracebacks=True, log_time_format='%H:%M:%S-%f', level=logging.DEBUG if args.debug else logging.INFO, console=console)
rh.set_name(logging.DEBUG if args.debug else logging.INFO)
while log.hasHandlers() and len(log.handlers) > 0:
log.removeHandler(log.handlers[0])
log.addHandler(rh)
@@ -186,6 +188,7 @@ def clone(url, folder, commithash=None):
git(f'checkout {commithash}', folder)
return
else:
log.info(f'Cloning repository: {url}')
git(f'clone "{url}" "{folder}"')
if commithash is not None:
git(f'-C "{folder}" checkout {commithash}')
@@ -309,7 +312,7 @@ def install_packages():
# openclip_package = os.environ.get('OPENCLIP_PACKAGE', "git+https://github.com/mlfoundations/open_clip.git@bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b")
# install(gfpgan_package, 'gfpgan')
# install(openclip_package, 'open-clip-torch')
clip_package = os.environ.get('CLIP_PACKAGE', "git+https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1")
clip_package = os.environ.get('CLIP_PACKAGE', "git+https://github.com/openai/CLIP.git")
install(clip_package, 'clip')
install('onnxruntime==1.14.0', 'onnxruntime', ignore=True)
@@ -321,19 +324,24 @@ def install_repositories():
log.info('Installing repositories')
os.makedirs(os.path.join(os.path.dirname(__file__), 'repositories'), exist_ok=True)
stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git")
stable_diffusion_commit = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf")
# stable_diffusion_commit = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf")
stable_diffusion_commit = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', None)
clone(stable_diffusion_repo, d('stable-diffusion-stability-ai'), stable_diffusion_commit)
taming_transformers_repo = os.environ.get('TAMING_TRANSFORMERS_REPO', "https://github.com/CompVis/taming-transformers.git")
taming_transformers_commit = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "3ba01b241669f5ade541ce990f7650a3b8f65318")
# taming_transformers_commit = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "3ba01b241669f5ade541ce990f7650a3b8f65318")
taming_transformers_commit = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', None)
clone(taming_transformers_repo, d('taming-transformers'), taming_transformers_commit)
k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git')
k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', "b43db16749d51055f813255eea2fdf1def801919")
# k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', "b43db16749d51055f813255eea2fdf1def801919")
k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', None)
clone(k_diffusion_repo, d('k-diffusion'), k_diffusion_commit)
codeformer_repo = os.environ.get('CODEFORMER_REPO', 'https://github.com/sczhou/CodeFormer.git')
codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af")
# codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af")
codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "7a584fd")
clone(codeformer_repo, d('CodeFormer'), codeformer_commit)
blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git')
blip_commit = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
# blip_commit = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
blip_commit = os.environ.get('BLIP_COMMIT_HASH', None)
clone(blip_repo, d('BLIP'), blip_commit)
@@ -635,12 +643,14 @@ def extensions_preload(force = False):
log.info('Running extension preloading')
if args.safe:
log.info('Running in safe mode without user extensions')
from modules.script_loading import preload_extensions
from modules.paths_internal import extensions_builtin_dir, extensions_dir
extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir]
for ext_dir in extension_folders:
preload_extensions(ext_dir, parser)
try:
from modules.script_loading import preload_extensions
from modules.paths_internal import extensions_builtin_dir, extensions_dir
extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir]
for ext_dir in extension_folders:
preload_extensions(ext_dir, parser, args.debug)
except:
log.error('Error running extension preloading')
def git_reset():
log.warning('Running GIT reset')
+1
View File
@@ -9,6 +9,7 @@ commandline_args = os.environ.get('COMMANDLINE_ARGS', "")
sys.argv += shlex.split(commandline_args)
import installer
installer.setup_logging(False)
installer.add_args()
installer.ensure_base_requirements()
installer.parse_args()
+12 -22
View File
@@ -88,25 +88,16 @@ def encode_pil_to_base64(image):
class Api:
def __init__(self, app: FastAPI, queue_lock: Lock):
if shared.cmd_opts.api_auth:
self.credentials = dict()
for auth in shared.cmd_opts.api_auth.split(","):
self.credentials = dict()
if shared.cmd_opts.auth:
for auth in shared.cmd_opts.auth.split(","):
user, password = auth.split(":")
self.credentials[user] = password
else:
if shared.cmd_opts.auth:
self.credentials = dict()
for auth in shared.cmd_opts.auth.split(","):
user, password = auth.split(":")
self.credentials[user] = password
user, password = [x.strip() for x in shared.cmd_opts.auth.strip('"').replace('\n', '').split(',') if x.strip()].split(':')
self.credentials[user] = password
if shared.cmd_opts.authfile:
self.credentials = dict()
with open(shared.cmd_opts.authfile, 'r', encoding="utf8") as file:
for line in file.readlines():
user, password = line.split(":")
self.credentials[user] = password
self.credentials[user.replace('"', '').strip()] = password.replace('"', '').strip()
if shared.cmd_opts.auth_file:
with open(shared.cmd_opts.auth_file, 'r', encoding="utf8") as file:
for line in file.readlines():
user, password = line.split(":")
self.credentials[user.replace('"', '').strip()] = password.replace('"', '').strip()
self.router = APIRouter()
self.app = app
@@ -146,7 +137,7 @@ class Api:
self.default_script_arg_img2img = []
def add_api_route(self, path: str, endpoint, **kwargs):
if shared.cmd_opts.api_auth:
if shared.cmd_opts.auth or shared.cmd_opts.auth_file:
return self.app.add_api_route(path, endpoint, dependencies=[Depends(self.auth)], **kwargs)
return self.app.add_api_route(path, endpoint, **kwargs)
@@ -154,7 +145,7 @@ class Api:
if credentials.username in self.credentials:
if compare_digest(credentials.password, self.credentials[credentials.username]):
return True
raise HTTPException(status_code=401, detail="Incorrect username or password", headers={"WWW-Authenticate": "Basic"})
raise HTTPException(status_code=401, detail="Unauthorized", headers={"WWW-Authenticate": "Basic"})
def get_selectable_script(self, script_name, script_runner):
if script_name is None or script_name == "":
@@ -630,6 +621,5 @@ class Api:
def launch(self, server_name, port):
self.app.include_router(self.router)
server_name = "0.0.0.0" if cmd_opts.listen else None
server_name = "0.0.0.0" if shared.cmd_opts.listen else None
uvicorn.run(self.app, host=server_name, port=port)
+2 -2
View File
@@ -23,10 +23,9 @@ group.add_argument("--listen", action='store_true', help="Launch web server usin
group.add_argument("--port", type=int, default=7860, help="Launch web server with given server port, default: %(default)s")
group.add_argument("--freeze", action='store_true', help="Disable editing settings", default=False)
group.add_argument("--auth", type=str, help='Set access authentication like "user:pwd,user:pwd""', default=None)
group.add_argument("--authfile", type=str, help='Set access authentication using file, default: %(default)s', default=None)
group.add_argument("--auth-file", type=str, help='Set access authentication using file, default: %(default)s', default=None)
group.add_argument("--autolaunch", action='store_true', help="Open the UI URL in the system's default browser upon launch", default=False)
group.add_argument('--api-only', default = False, action='store_true', help = "Run in API only mode without starting UI")
group.add_argument("--api-auth", type=str, help='Set API authentication, default: %(default)s', default=None)
group.add_argument("--api-log", default=False, action='store_true', help="Enable logging of all API requests, default: %(default)s")
group.add_argument("--device-id", type=str, help="Select the default CUDA device to use, default: %(default)s", default=None)
group.add_argument("--cors-origins", type=str, help="Allowed CORS origins as comma-separated list, default: %(default)s", default=None)
@@ -57,6 +56,7 @@ group.add_argument("--disable-safe-unpickle", action='store_true', help=argparse
group.add_argument("--lowram", action='store_true', help=argparse.SUPPRESS)
group.add_argument("--disable-extension-access", default = False, action='store_true', help=argparse.SUPPRESS)
group.add_argument("--api", help=argparse.SUPPRESS, default=True)
group.add_argument("--api-auth", type=str, help=argparse.SUPPRESS, default=None)
def compatibility_args(opts, args):
+5 -7
View File
@@ -20,10 +20,8 @@ codeformer = None
def setup_model(dirname):
global model_path
if not os.path.exists(model_path):
os.makedirs(model_path)
path = modules.paths.paths.get("CodeFormer", None)
if path is None:
return
@@ -31,7 +29,7 @@ def setup_model(dirname):
try:
from torchvision.transforms.functional import normalize
from modules.codeformer.codeformer_arch import CodeFormer
from basicsr.utils import imwrite, img2tensor, tensor2img
from basicsr.utils import img2tensor, tensor2img
from facelib.utils.face_restoration_helper import FaceRestoreHelper
from facelib.detection.retinaface import retinaface
from modules.shared import cmd_opts
@@ -74,7 +72,7 @@ def setup_model(dirname):
def send_model_to(self, device):
self.net.to(device)
self.face_helper.face_det.to(device)
self.face_helper.face_det.to(device) # pylint: disable=no-member
self.face_helper.face_parse.to(device)
def restore(self, np_image, w=None):
@@ -93,7 +91,7 @@ def setup_model(dirname):
self.face_helper.get_face_landmarks_5(only_center_face=False, resize=640, eye_dist_threshold=5)
self.face_helper.align_warp_face()
for idx, cropped_face in enumerate(self.face_helper.cropped_faces):
for _idx, cropped_face in enumerate(self.face_helper.cropped_faces):
cropped_face_t = img2tensor(cropped_face / 255., bgr2rgb=True, float32=True)
normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True)
cropped_face_t = cropped_face_t.unsqueeze(0).to(devices.device_codeformer)
@@ -129,10 +127,10 @@ def setup_model(dirname):
return restored_img
global have_codeformer
global have_codeformer # pylint: disable=global-statement
have_codeformer = True
global codeformer
global codeformer # pylint: disable=global-statement
codeformer = FaceRestorerCodeFormer(dirname)
shared.face_restorers.append(codeformer)
+1 -1
View File
@@ -257,7 +257,7 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
res["Prompt"] = prompt
res["Negative prompt"] = negative_prompt
for k, v in re_param.findall(lastline):
v = v[1:-1] if v[0] == '"' and v[-1] == '"' else v
v = v[1:-1] if len(v) > 0 and v[0] == '"' and v[-1] == '"' else v
m = re_imagesize.match(v)
if m is not None:
res[f"{k}-1"] = m.group(1)
+1 -1
View File
@@ -236,7 +236,7 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None):
upscalers = [x for x in shared.sd_upscalers if x.name == upscaler_name]
if len(upscalers) == 0:
upscaler = shared.sd_upscalers[0]
shared.log.warning(f"could not find upscaler named {upscaler_name or '<empty string>'}, using {upscaler.name} as a fallback")
shared.log.warning(f"Could not find upscaler named {upscaler_name or '<empty string>'}, using {upscaler.name} as a fallback")
else:
upscaler = upscalers[0]
im = upscaler.scaler.upscale(im, scale, upscaler.data_path)
+9 -6
View File
@@ -3,7 +3,6 @@ import math
import os
import hashlib
import random
import logging
from typing import Any, Dict, List
import torch
import numpy as np
@@ -33,13 +32,13 @@ opt_f = 8
def setup_color_correction(image):
logging.info("Calibrating color correction.")
log.debug("Calibrating color correction.")
correction_target = cv2.cvtColor(np.asarray(image.copy()), cv2.COLOR_RGB2LAB)
return correction_target
def apply_color_correction(correction, original_image):
logging.info("Applying color correction.")
log.debug("Applying color correction.")
image = Image.fromarray(cv2.cvtColor(exposure.match_histograms(
cv2.cvtColor(np.asarray(original_image), cv2.COLOR_RGB2LAB),
correction,
@@ -575,7 +574,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
for n in range(p.n_iter):
p.iteration = n
if state.skipped:
shared.log.debug(f'Process skipped: {n}/{p.n_iter}')
state.skipped = False
continue
if state.interrupted:
shared.log.debug(f'Process interrupted: {n}/{p.n_iter}')
break
@@ -710,7 +711,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
index_of_first_image=index_of_first_image,
infotexts=infotexts,
)
if p.scripts is not None:
if p.scripts is not None and not state.interrupted:
p.scripts.postprocess(p, res)
return res
@@ -803,10 +804,12 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model)
latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest")
if self.enable_hr and latent_scale_mode is None:
assert len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) > 0, f"could not find upscaler named {self.hr_upscaler}"
if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0:
log.warning("Could not find upscaler to use with hrfix")
self.enable_hr = False
x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self)
samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x))
if not self.enable_hr:
if not self.enable_hr or state.interrupted or state.skipped:
return samples
self.is_hr_pass = True
target_width = self.hr_upscale_to_x
+14 -5
View File
@@ -3,18 +3,24 @@ import importlib.util
import modules.errors as errors
def load_module(path):
preloaded = []
def load_module(path, detailed=False):
module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path)
module = importlib.util.module_from_spec(module_spec)
try:
module_spec.loader.exec_module(module)
except Exception as e:
errors.display(e, f'Module load: {path}')
if detailed:
errors.display(e, f'Module load: {path}')
else:
errors.log.error(f'Module load: {path}')
return module
preloaded = []
def preload_extensions(extensions_dir, parser):
def preload_extensions(extensions_dir, parser, detailed=False):
if not os.path.isdir(extensions_dir):
return
for dirname in sorted(os.listdir(extensions_dir)):
@@ -29,4 +35,7 @@ def preload_extensions(extensions_dir, parser):
if hasattr(module, 'preload'):
module.preload(parser)
except Exception as e:
errors.display(e, f'Extension preload: {preload_script}')
if detailed:
errors.display(e, f'Extension preload: {preload_script}')
else:
errors.log.error(f'Extension preload: {preload_script}')
+1 -1
View File
@@ -11,7 +11,7 @@ class ScriptPostprocessingCodeFormer(scripts_postprocessing.ScriptPostprocessing
def ui(self):
with FormRow():
codeformer_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="CodeFormer visibility", value=1.0, elem_id="extras_codeformer_visibility")
codeformer_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="CodeFormer visibility", value=0.0, elem_id="extras_codeformer_visibility")
codeformer_weight = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="CodeFormer weight (0 = max), 1 = min)", value=0.2, elem_id="extras_codeformer_weight")
return {
+6 -4
View File
@@ -83,16 +83,17 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
upscaler_1_name = None
upscaler1 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_1_name]), None)
assert upscaler1 or (upscaler_1_name is None), f'could not find upscaler named {upscaler_1_name}'
if not upscaler1:
shared.log.warning(f"Could not find upscaler named {upscaler_1_name or '<empty string>'}")
return
if upscaler_2_name == "None":
upscaler_2_name = None
upscaler2 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_2_name and x.name != "None"]), None)
assert upscaler2 or (upscaler_2_name is None), f'could not find upscaler named {upscaler_2_name}'
if not upscaler2 and (upscaler_2_name is not None):
shared.log.warning(f"Could not find upscaler named {upscaler_1_name or '<empty string>'}")
return
upscaled_image = self.upscale(pp.image, pp.info, upscaler1, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop)
pp.info["Postprocess upscaler"] = upscaler1.name
@@ -128,7 +129,8 @@ class ScriptPostprocessingUpscaleSimple(ScriptPostprocessingUpscale):
return
upscaler1 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_name]), None)
assert upscaler1, f'could not find upscaler named {upscaler_name}'
if upscaler1 is None:
shared.log.warning(f"Could not find upscaler named {upscaler_name or '<empty string>'}")
pp.image = self.upscale(pp.image, pp.info, upscaler1, 0, upscale_by, 0, 0, False)
pp.info["Postprocess upscaler"] = upscaler1.name
+2 -2
View File
@@ -223,8 +223,8 @@ def start_ui():
gradio_auth_creds = []
if cmd_opts.auth:
gradio_auth_creds += [x.strip() for x in cmd_opts.auth.strip('"').replace('\n', '').split(',') if x.strip()]
if cmd_opts.authfile:
with open(cmd_opts.authfile, 'r', encoding="utf8") as file:
if cmd_opts.auth_file:
with open(cmd_opts.auth_file, 'r', encoding="utf8") as file:
for line in file.readlines():
gradio_auth_creds += [x.strip() for x in line.split(',') if x.strip()]