+_Note: This fork is rapidly evolving. Please use the
+[Issues](https://github.com/invoke-ai/InvokeAI/issues) tab to
+report bugs and make feature requests. Be sure to use the provided
+templates. They will help aid diagnose issues faster._
+
+_This repository was formally known as lstein/stable-diffusion_
+
+# **Table of Contents**
+
+![project logo](docs/assets/logo.png)
+
+[![discord badge]][discord link]
+
+[![latest release badge]][latest release link] [![github stars badge]][github stars link] [![github forks badge]][github forks link]
+
+[![CI checks on main badge]][CI checks on main link] [![CI checks on dev badge]][CI checks on dev link] [![latest commit to dev badge]][latest commit to dev link]
+
+[![github open issues badge]][github open issues link] [![github open prs badge]][github open prs link]
+
+[CI checks on dev badge]: https://flat.badgen.net/github/checks/invoke-ai/InvokeAI/development?label=CI%20status%20on%20dev&cache=900&icon=github
+[CI checks on dev link]: https://github.com/invoke-ai/InvokeAI/actions?query=branch%3Adevelopment
+[CI checks on main badge]: https://flat.badgen.net/github/checks/invoke-ai/InvokeAI/main?label=CI%20status%20on%20main&cache=900&icon=github
+[CI checks on main link]: https://github.com/invoke-ai/InvokeAI/actions/workflows/test-dream-conda.yml
+[discord badge]: https://flat.badgen.net/discord/members/htRgbc7e?icon=discord
+[discord link]: https://discord.gg/ZmtBAhwWhy
+[github forks badge]: https://flat.badgen.net/github/forks/invoke-ai/InvokeAI?icon=github
+[github forks link]: https://useful-forks.github.io/?repo=invoke-ai%2FInvokeAI
+[github open issues badge]: https://flat.badgen.net/github/open-issues/invoke-ai/InvokeAI?icon=github
+[github open issues link]: https://github.com/invoke-ai/InvokeAI/issues?q=is%3Aissue+is%3Aopen
+[github open prs badge]: https://flat.badgen.net/github/open-prs/invoke-ai/InvokeAI?icon=github
+[github open prs link]: https://github.com/invoke-ai/InvokeAI/pulls?q=is%3Apr+is%3Aopen
+[github stars badge]: https://flat.badgen.net/github/stars/invoke-ai/InvokeAI?icon=github
+[github stars link]: https://github.com/invoke-ai/InvokeAI/stargazers
+[latest commit to dev badge]: https://flat.badgen.net/github/last-commit/invoke-ai/InvokeAI/development?icon=github&color=yellow&label=last%20dev%20commit&cache=900
+[latest commit to dev link]: https://github.com/invoke-ai/InvokeAI/commits/development
+[latest release badge]: https://flat.badgen.net/github/release/invoke-ai/InvokeAI/development?icon=github
+[latest release link]: https://github.com/invoke-ai/InvokeAI/releases
+
This is a fork of [CompVis/stable-diffusion](https://github.com/CompVis/stable-diffusion), the open
source text-to-image generator. It provides a streamlined process with various new features and
@@ -18,10 +47,10 @@ options to aid the image generation process. It runs on Windows, Mac and Linux m
GPU cards with as little as 4 GB or RAM.
_Note: This fork is rapidly evolving. Please use the
-[Issues](https://github.com/lstein/stable-diffusion/issues) tab to report bugs and make feature
+[Issues](https://github.com/invoke-ai/InvokeAI/issues) tab to report bugs and make feature
requests. Be sure to use the provided templates. They will help aid diagnose issues faster._
-**Table of Contents**
+## Table of Contents
1. [Installation](#installation)
2. [Hardware Requirements](#hardware-requirements)
@@ -33,84 +62,75 @@ requests. Be sure to use the provided templates. They will help aid diagnose iss
8. [Support](#support)
9. [Further Reading](#further-reading)
-## Installation
+### Installation
This fork is supported across multiple platforms. You can find individual installation instructions
below.
-- ### [Linux](docs/installation/INSTALL_LINUX.md)
+- #### [Linux](docs/installation/INSTALL_LINUX.md)
-- ### [Windows](docs/installation/INSTALL_WINDOWS.md)
+- #### [Windows](docs/installation/INSTALL_WINDOWS.md)
-- ### [Macintosh](docs/installation/INSTALL_MAC.md)
+- #### [Macintosh](docs/installation/INSTALL_MAC.md)
-## Hardware Requirements
+### Hardware Requirements
-**System**
+#### System
You wil need one of the following:
- An NVIDIA-based graphics card with 4 GB or more VRAM memory.
- An Apple computer with an M1 chip.
-**Memory**
+#### Memory
- At least 12 GB Main Memory RAM.
-**Disk**
+#### Disk
- At least 6 GB of free disk space for the machine learning model, Python, and all its dependencies.
-**Note**
+#### Note
-If you are have a Nvidia 10xx series card (e.g. the 1080ti), please run the dream script in
-full-precision mode as shown below.
-
-Similarly, specify full-precision mode on Apple M1 hardware.
-
-To run in full-precision mode, start `dream.py` with the `--full_precision` flag:
+Precision is auto configured based on the device. If however you encounter
+errors like 'expected type Float but found Half' or 'not implemented for Half'
+you can try starting `dream.py` with the `--precision=float32` flag:
```bash
-(ldm) ~/stable-diffusion$ python scripts/dream.py --full_precision
+(ldm) ~/stable-diffusion$ python scripts/dream.py --precision=float32
```
-## Features
+### Features
-### Major Features
+#### Major Features
-- #### [Interactive Command Line Interface](docs/features/CLI.md)
+- [Interactive Command Line Interface](docs/features/CLI.md)
+- [Image To Image](docs/features/IMG2IMG.md)
+- [Inpainting Support](docs/features/INPAINTING.md)
+- [Outpainting Support](docs/features/OUTPAINTING.md)
+- [GFPGAN and Real-ESRGAN Support](docs/features/UPSCALE.md)
+- [Seamless Tiling](docs/features/OTHER.md#seamless-tiling)
+- [Google Colab](docs/features/OTHER.md#google-colab)
+- [Web Server](docs/features/WEB.md)
+- [Reading Prompts From File](docs/features/PROMPTS.md#reading-prompts-from-a-file)
+- [Shortcut: Reusing Seeds](docs/features/OTHER.md#shortcuts-reusing-seeds)
+- [Weighted Prompts](docs/features/PROMPTS.md#weighted-prompts)
+- [Negative/Unconditioned Prompts](docs/features/PROMPTS.md#negative-and-unconditioned-prompts)
+- [Variations](docs/features/VARIATIONS.md)
+- [Personalizing Text-to-Image Generation](docs/features/TEXTUAL_INVERSION.md)
+- [Simplified API for text to image generation](docs/features/OTHER.md#simplified-api)
-- #### [Image To Image](docs/features/IMG2IMG.md)
+#### Other Features
-- #### [Inpainting Support](docs/features/INPAINTING.md)
+- [Creating Transparent Regions for Inpainting](docs/features/INPAINTING.md#creating-transparent-regions-for-inpainting)
+- [Preload Models](docs/features/OTHER.md#preload-models)
-- #### [GFPGAN and Real-ESRGAN Support](docs/features/UPSCALE.md)
+### Latest Changes
-- #### [Seamless Tiling](docs/features/OTHER.md#seamless-tiling)
+- vNEXT (TODO 2022)
-- #### [Google Colab](docs/features/OTHER.md#google-colab)
-
-- #### [Web Server](docs/features/WEB.md)
-
-- #### [Reading Prompts From File](docs/features/OTHER.md#reading-prompts-from-a-file)
-
-- #### [Shortcut: Reusing Seeds](docs/features/OTHER.md#shortcuts-reusing-seeds)
-
-- #### [Weighted Prompts](docs/features/OTHER.md#weighted-prompts)
-
-- #### [Variations](docs/features/VARIATIONS.md)
-
-- #### [Personalizing Text-to-Image Generation](docs/features/TEXTUAL_INVERSION.md)
-
-- #### [Simplified API for text to image generation](docs/features/OTHER.md#simplified-api)
-
-### Other Features
-
-- #### [Creating Transparent Regions for Inpainting](docs/features/INPAINTING.md#creating-transparent-regions-for-inpainting)
-
-- #### [Preload Models](docs/features/OTHER.md#preload-models)
-
-## Latest Changes
+ - Deprecated `--full_precision` / `-F`. Simply omit it and `dream.py` will auto
+ configure. To switch away from auto use the new flag like `--precision=float32`.
- v1.14 (11 September 2022)
@@ -142,12 +162,12 @@ To run in full-precision mode, start `dream.py` with the `--full_precision` flag
For older changelogs, please visit the **[CHANGELOG](docs/features/CHANGELOG.md)**.
-## Troubleshooting
+### Troubleshooting
Please check out our **[Q&A](docs/help/TROUBLESHOOT.md)** to get solutions for common installation
problems and other issues.
-## Contributing
+# Contributing
Anyone who wishes to contribute to this project, whether documentation, features, bug fixes, code
cleanup, testing, or code reviews, is very much encouraged to do so. If you are unfamiliar with how
@@ -159,13 +179,13 @@ important thing is to **make your pull request against the "development" branch*
"main". This will help keep public breakage to a minimum and will allow you to propose more radical
changes.
-## Contributors
+### Contributors
This fork is a combined effort of various people from across the world.
[Check out the list of all these amazing people](docs/other/CONTRIBUTORS.md). We thank them for
their time, hard work and effort.
-## Support
+### Support
For support, please use this repository's GitHub Issues tracking service. Feel free to send me an
email if you use and like the script.
@@ -173,7 +193,7 @@ email if you use and like the script.
Original portions of the software are Copyright (c) 2020
[Lincoln D. Stein](https://github.com/lstein)
-## Further Reading
+### Further Reading
Please see the original README for more information on this software and underlying algorithm,
located in the file [README-CompViz.md](docs/other/README-CompViz.md).
diff --git a/backend/modules/create_cmd_parser.py b/backend/modules/create_cmd_parser.py
new file mode 100644
index 0000000000..06f2f4025c
--- /dev/null
+++ b/backend/modules/create_cmd_parser.py
@@ -0,0 +1,49 @@
+import argparse
+import os
+from ldm.dream.args import PRECISION_CHOICES
+
+
+def create_cmd_parser():
+ parser = argparse.ArgumentParser(description="InvokeAI web UI")
+ parser.add_argument(
+ "--host",
+ type=str,
+ help="The host to serve on",
+ default="localhost",
+ )
+ parser.add_argument("--port", type=int, help="The port to serve on", default=9090)
+ parser.add_argument(
+ "--cors",
+ nargs="*",
+ type=str,
+ help="Additional allowed origins, comma-separated",
+ )
+ parser.add_argument(
+ "--embedding_path",
+ type=str,
+ help="Path to a pre-trained embedding manager checkpoint - can only be set on command line",
+ )
+ # TODO: Can't get flask to serve images from any dir (saving to the dir does work when specified)
+ # parser.add_argument(
+ # "--output_dir",
+ # default="outputs/",
+ # type=str,
+ # help="Directory for output images",
+ # )
+ parser.add_argument(
+ "-v",
+ "--verbose",
+ action="store_true",
+ help="Enables verbose logging",
+ )
+ parser.add_argument(
+ "--precision",
+ dest="precision",
+ type=str,
+ choices=PRECISION_CHOICES,
+ metavar="PRECISION",
+ help=f'Set model precision. Defaults to auto selected based on device. Options: {", ".join(PRECISION_CHOICES)}',
+ default="auto",
+ )
+
+ return parser
diff --git a/backend/modules/parameters.py b/backend/modules/parameters.py
index c5a14f7ed0..ec0cfe8272 100644
--- a/backend/modules/parameters.py
+++ b/backend/modules/parameters.py
@@ -2,14 +2,14 @@ from modules.parse_seed_weights import parse_seed_weights
import argparse
SAMPLER_CHOICES = [
- 'ddim',
- 'k_dpm_2_a',
- 'k_dpm_2',
- 'k_euler_a',
- 'k_euler',
- 'k_heun',
- 'k_lms',
- 'plms',
+ "ddim",
+ "k_dpm_2_a",
+ "k_dpm_2",
+ "k_euler_a",
+ "k_euler",
+ "k_heun",
+ "k_lms",
+ "plms",
]
@@ -20,187 +20,42 @@ def parameters_to_command(params):
switches = list()
- if 'prompt' in params:
+ if "prompt" in params:
switches.append(f'"{params["prompt"]}"')
- if 'steps' in params:
+ if "steps" in params:
switches.append(f'-s {params["steps"]}')
- if 'seed' in params:
+ if "seed" in params:
switches.append(f'-S {params["seed"]}')
- if 'width' in params:
+ if "width" in params:
switches.append(f'-W {params["width"]}')
- if 'height' in params:
+ if "height" in params:
switches.append(f'-H {params["height"]}')
- if 'cfg_scale' in params:
+ if "cfg_scale" in params:
switches.append(f'-C {params["cfg_scale"]}')
- if 'sampler_name' in params:
+ if "sampler_name" in params:
switches.append(f'-A {params["sampler_name"]}')
- if 'seamless' in params and params["seamless"] == True:
- switches.append(f'--seamless')
- if 'init_img' in params and len(params['init_img']) > 0:
+ if "seamless" in params and params["seamless"] == True:
+ switches.append(f"--seamless")
+ if "init_img" in params and len(params["init_img"]) > 0:
switches.append(f'-I {params["init_img"]}')
- if 'init_mask' in params and len(params['init_mask']) > 0:
+ if "init_mask" in params and len(params["init_mask"]) > 0:
switches.append(f'-M {params["init_mask"]}')
- if 'strength' in params and 'init_img' in params:
+ if "init_color" in params and len(params["init_color"]) > 0:
+ switches.append(f'--init_color {params["init_color"]}')
+ if "strength" in params and "init_img" in params:
switches.append(f'-f {params["strength"]}')
- if 'fit' in params and params["fit"] == True:
- switches.append(f'--fit')
- if 'gfpgan_strength' in params and params["gfpgan_strength"]:
+ if "fit" in params and params["fit"] == True:
+ switches.append(f"--fit")
+ if "gfpgan_strength" in params and params["gfpgan_strength"]:
switches.append(f'-G {params["gfpgan_strength"]}')
- if 'upscale' in params and params["upscale"]:
+ if "upscale" in params and params["upscale"]:
switches.append(f'-U {params["upscale"][0]} {params["upscale"][1]}')
- if 'variation_amount' in params and params['variation_amount'] > 0:
+ if "variation_amount" in params and params["variation_amount"] > 0:
switches.append(f'-v {params["variation_amount"]}')
- if 'with_variations' in params:
- seed_weight_pairs = ','.join(f'{seed}:{weight}' for seed, weight in params["with_variations"])
- switches.append(f'-V {seed_weight_pairs}')
+ if "with_variations" in params:
+ seed_weight_pairs = ",".join(
+ f"{seed}:{weight}" for seed, weight in params["with_variations"]
+ )
+ switches.append(f"-V {seed_weight_pairs}")
- return ' '.join(switches)
-
-
-
-def create_cmd_parser():
- """
- This is simply a copy of the parser from `dream.py` with a change to give
- prompt a default value. This is a temporary hack pending merge of #587 which
- provides a better way to do this.
- """
- parser = argparse.ArgumentParser(
- description='Example: dream> a fantastic alien landscape -W1024 -H960 -s100 -n12',
- exit_on_error=True,
- )
- parser.add_argument('prompt', nargs='?', default='')
- parser.add_argument('-s', '--steps', type=int, help='Number of steps')
- parser.add_argument(
- '-S',
- '--seed',
- type=int,
- help='Image seed; a +ve integer, or use -1 for the previous seed, -2 for the one before that, etc',
- )
- parser.add_argument(
- '-n',
- '--iterations',
- type=int,
- default=1,
- help='Number of samplings to perform (slower, but will provide seeds for individual images)',
- )
- parser.add_argument(
- '-W', '--width', type=int, help='Image width, multiple of 64'
- )
- parser.add_argument(
- '-H', '--height', type=int, help='Image height, multiple of 64'
- )
- parser.add_argument(
- '-C',
- '--cfg_scale',
- default=7.5,
- type=float,
- help='Classifier free guidance (CFG) scale - higher numbers cause generator to "try" harder.',
- )
- parser.add_argument(
- '-g', '--grid', action='store_true', help='generate a grid'
- )
- parser.add_argument(
- '--outdir',
- '-o',
- type=str,
- default=None,
- help='Directory to save generated images and a log of prompts and seeds',
- )
- parser.add_argument(
- '--seamless',
- action='store_true',
- help='Change the model to seamless tiling (circular) mode',
- )
- parser.add_argument(
- '-i',
- '--individual',
- action='store_true',
- help='Generate individual files (default)',
- )
- parser.add_argument(
- '-I',
- '--init_img',
- type=str,
- help='Path to input image for img2img mode (supersedes width and height)',
- )
- parser.add_argument(
- '-M',
- '--init_mask',
- type=str,
- help='Path to input mask for inpainting mode (supersedes width and height)',
- )
- parser.add_argument(
- '-T',
- '-fit',
- '--fit',
- action='store_true',
- help='If specified, will resize the input image to fit within the dimensions of width x height (512x512 default)',
- )
- parser.add_argument(
- '-f',
- '--strength',
- default=0.75,
- type=float,
- help='Strength for noising/unnoising. 0.0 preserves image exactly, 1.0 replaces it completely',
- )
- parser.add_argument(
- '-G',
- '--gfpgan_strength',
- default=0,
- type=float,
- help='The strength at which to apply the GFPGAN model to the result, in order to improve faces.',
- )
- parser.add_argument(
- '-U',
- '--upscale',
- nargs='+',
- default=None,
- type=float,
- help='Scale factor (2, 4) for upscaling followed by upscaling strength (0-1.0). If strength not specified, defaults to 0.75'
- )
- parser.add_argument(
- '-save_orig',
- '--save_original',
- action='store_true',
- help='Save original. Use it when upscaling to save both versions.',
- )
- # variants is going to be superseded by a generalized "prompt-morph" function
- # 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")
- parser.add_argument(
- '-x',
- '--skip_normalize',
- action='store_true',
- help='Skip subprompt weight normalization',
- )
- parser.add_argument(
- '-A',
- '-m',
- '--sampler',
- dest='sampler_name',
- default=None,
- type=str,
- choices=SAMPLER_CHOICES,
- metavar='SAMPLER_NAME',
- help=f'Switch to a different sampler. Supported samplers: {", ".join(SAMPLER_CHOICES)}',
- )
- parser.add_argument(
- '-t',
- '--log_tokenization',
- action='store_true',
- help='shows how the prompt is split into tokens'
- )
- parser.add_argument(
- '-v',
- '--variation_amount',
- default=0.0,
- type=float,
- 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.'
- )
- parser.add_argument(
- '-V',
- '--with_variations',
- default=None,
- type=str,
- help='list of variations to apply, in the format `seed:weight,seed:weight,...'
- )
- return parser
+ return " ".join(switches)
diff --git a/backend/server.py b/backend/server.py
index ef93c5b0d7..5ee28f20f3 100644
--- a/backend/server.py
+++ b/backend/server.py
@@ -6,7 +6,18 @@ import traceback
import eventlet
import glob
import shlex
-import argparse
+import math
+import shutil
+import sys
+
+sys.path.append(".")
+
+from argparse import ArgumentTypeError
+from modules.create_cmd_parser import create_cmd_parser
+
+parser = create_cmd_parser()
+opt = parser.parse_args()
+
from flask_socketio import SocketIO
from flask import Flask, send_from_directory, url_for, jsonify
@@ -15,58 +26,67 @@ from PIL import Image
from pytorch_lightning import logging
from threading import Event
from uuid import uuid4
+from send2trash import send2trash
+
-from ldm.gfpgan.gfpgan_tools import real_esrgan_upscale
-from ldm.gfpgan.gfpgan_tools import run_gfpgan
from ldm.generate import Generate
+from ldm.dream.restoration import Restoration
from ldm.dream.pngwriter import PngWriter, retrieve_metadata
+from ldm.dream.args import APP_ID, APP_VERSION, calculate_init_img_hash
+from ldm.dream.conditioning import split_weighted_subprompts
-from modules.parameters import parameters_to_command, create_cmd_parser
+from modules.parameters import parameters_to_command
"""
USER CONFIG
"""
+if opt.cors and "*" in opt.cors:
+ raise ArgumentTypeError('"*" is not an allowed CORS origin')
+
output_dir = "outputs/" # Base output directory for images
-#host = 'localhost' # Web & socket.io host
-host = '0.0.0.0' # Web & socket.io host
-port = 9090 # Web & socket.io port
-verbose = False # enables copious socket.io logging
-additional_allowed_origins = ['http://localhost:9090'] # additional CORS allowed origins
-
+host = opt.host # Web & socket.io host
+port = opt.port # Web & socket.io port
+verbose = opt.verbose # enables copious socket.io logging
+precision = opt.precision
+embedding_path = opt.embedding_path
+additional_allowed_origins = (
+ opt.cors if opt.cors else []
+) # additional CORS allowed origins
+model = "stable-diffusion-1.4"
"""
END USER CONFIG
"""
+print("* Initializing, be patient...\n")
+
+
"""
SERVER SETUP
"""
# fix missing mimetypes on windows due to registry wonkiness
-mimetypes.add_type('application/javascript', '.js')
-mimetypes.add_type('text/css', '.css')
+mimetypes.add_type("application/javascript", ".js")
+mimetypes.add_type("text/css", ".css")
-app = Flask(__name__, static_url_path='', static_folder='../frontend/dist/')
+app = Flask(__name__, static_url_path="", static_folder="../frontend/dist/")
-app.config['OUTPUTS_FOLDER'] = "../outputs"
+app.config["OUTPUTS_FOLDER"] = "../outputs"
-@app.route('/outputs/')
+@app.route("/outputs/")
def outputs(filename):
- return send_from_directory(
- app.config['OUTPUTS_FOLDER'],
- filename
- )
+ return send_from_directory(app.config["OUTPUTS_FOLDER"], filename)
-@app.route("/", defaults={'path': ''})
+@app.route("/", defaults={"path": ""})
def serve(path):
- return send_from_directory(app.static_folder, 'index.html')
+ return send_from_directory(app.static_folder, "index.html")
logger = True if verbose else False
@@ -78,12 +98,14 @@ max_http_buffer_size = 10000000
cors_allowed_origins = [f"http://{host}:{port}"] + additional_allowed_origins
socketio = SocketIO(
- app,
- logger=logger,
- engineio_logger=engineio_logger,
- max_http_buffer_size=max_http_buffer_size,
- cors_allowed_origins=cors_allowed_origins,
- )
+ app,
+ logger=logger,
+ engineio_logger=engineio_logger,
+ max_http_buffer_size=max_http_buffer_size,
+ cors_allowed_origins=cors_allowed_origins,
+ ping_interval=(50, 50),
+ ping_timeout=60,
+)
"""
@@ -100,33 +122,53 @@ class CanceledException(Exception):
pass
+try:
+ gfpgan, codeformer, esrgan = None, None, None
+ from ldm.dream.restoration.base import Restoration
+
+ restoration = Restoration()
+ gfpgan, codeformer = restoration.load_face_restore_models()
+ esrgan = restoration.load_esrgan()
+
+ # coreformer.process(self, image, strength, device, seed=None, fidelity=0.75)
+
+except (ModuleNotFoundError, ImportError):
+ print(traceback.format_exc(), file=sys.stderr)
+ print(">> You may need to install the ESRGAN and/or GFPGAN modules")
+
canceled = Event()
# reduce logging outputs to error
transformers.logging.set_verbosity_error()
-logging.getLogger('pytorch_lightning').setLevel(logging.ERROR)
+logging.getLogger("pytorch_lightning").setLevel(logging.ERROR)
# Initialize and load model
-model = Generate()
-model.load_model()
+generate = Generate(
+ model,
+ precision=precision,
+ embedding_path=embedding_path,
+)
+generate.load_model()
# location for "finished" images
-result_path = os.path.join(output_dir, 'img-samples/')
+result_path = os.path.join(output_dir, "img-samples/")
# temporary path for intermediates
-intermediate_path = os.path.join(result_path, 'intermediates/')
+intermediate_path = os.path.join(result_path, "intermediates/")
# path for user-uploaded init images and masks
-init_path = os.path.join(result_path, 'init-images/')
-mask_path = os.path.join(result_path, 'mask-images/')
+init_image_path = os.path.join(result_path, "init-images/")
+mask_image_path = os.path.join(result_path, "mask-images/")
# txt log
-log_path = os.path.join(result_path, 'dream_log.txt')
+log_path = os.path.join(result_path, "dream_log.txt")
# make all output paths
-[os.makedirs(path, exist_ok=True)
- for path in [result_path, intermediate_path, init_path, mask_path]]
+[
+ os.makedirs(path, exist_ok=True)
+ for path in [result_path, intermediate_path, init_image_path, mask_image_path]
+]
"""
@@ -139,126 +181,251 @@ SOCKET.IO LISTENERS
"""
-@socketio.on('requestAllImages')
-def handle_request_all_images():
- print(f'>> All images requested')
- parser = create_cmd_parser()
- paths = list(filter(os.path.isfile, glob.glob(result_path + "*.png")))
- paths.sort(key=lambda x: os.path.getmtime(x))
+@socketio.on("requestSystemConfig")
+def handle_request_capabilities():
+ print(f">> System config requested")
+ config = get_system_config()
+ socketio.emit("systemConfig", config)
+
+
+@socketio.on("requestImages")
+def handle_request_images(page=1, offset=0, last_mtime=None):
+ chunk_size = 50
+
+ if last_mtime:
+ print(f">> Latest images requested")
+ else:
+ print(
+ f">> Page {page} of images requested (page size {chunk_size} offset {offset})"
+ )
+
+ paths = glob.glob(os.path.join(result_path, "*.png"))
+ sorted_paths = sorted(paths, key=lambda x: os.path.getmtime(x), reverse=True)
+
+ if last_mtime:
+ image_paths = filter(lambda x: os.path.getmtime(x) > last_mtime, sorted_paths)
+ else:
+
+ image_paths = sorted_paths[
+ slice(chunk_size * (page - 1) + offset, chunk_size * page + offset)
+ ]
+ page = page + 1
+
image_array = []
- for path in paths:
- # image = Image.open(path)
- all_metadata = retrieve_metadata(path)
- if 'Dream' in all_metadata and not all_metadata['sd-metadata']:
- metadata = vars(parser.parse_args(shlex.split(all_metadata['Dream'])))
- else:
- metadata = all_metadata['sd-metadata']
- image_array.append({'path': path, 'metadata': metadata})
- return make_response("OK", data=image_array)
+ for path in image_paths:
+ metadata = retrieve_metadata(path)
+ image_array.append(
+ {
+ "url": path,
+ "mtime": os.path.getmtime(path),
+ "metadata": metadata["sd-metadata"],
+ }
+ )
-@socketio.on('generateImage')
-def handle_generate_image_event(generation_parameters, esrgan_parameters, gfpgan_parameters):
- print(f'>> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}')
- generate_images(
- generation_parameters,
- esrgan_parameters,
- gfpgan_parameters
+ socketio.emit(
+ "galleryImages",
+ {
+ "images": image_array,
+ "nextPage": page,
+ "offset": offset,
+ "onlyNewImages": True if last_mtime else False,
+ },
)
- return make_response("OK")
-@socketio.on('runESRGAN')
+@socketio.on("generateImage")
+def handle_generate_image_event(
+ generation_parameters, esrgan_parameters, gfpgan_parameters
+):
+ print(
+ f">> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}"
+ )
+ generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters)
+
+
+@socketio.on("runESRGAN")
def handle_run_esrgan_event(original_image, esrgan_parameters):
- print(f'>> ESRGAN upscale requested for "{original_image["url"]}": {esrgan_parameters}')
+ print(
+ f'>> ESRGAN upscale requested for "{original_image["url"]}": {esrgan_parameters}'
+ )
+ progress = {
+ "currentStep": 1,
+ "totalSteps": 1,
+ "currentIteration": 1,
+ "totalIterations": 1,
+ "currentStatus": "Preparing",
+ "isProcessing": True,
+ "currentStatusHasSteps": False,
+ }
+
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
image = Image.open(original_image["url"])
- seed = original_image['metadata']['seed'] if 'seed' in original_image['metadata'] else 'unknown_seed'
-
- image = real_esrgan_upscale(
- image=image,
- upsampler_scale=esrgan_parameters['upscale'][0],
- strength=esrgan_parameters['upscale'][1],
- seed=seed
+ seed = (
+ original_image["metadata"]["seed"]
+ if "seed" in original_image["metadata"]
+ else "unknown_seed"
)
- esrgan_parameters['seed'] = seed
- path = save_image(image, esrgan_parameters, result_path, postprocessing='esrgan')
+ progress["currentStatus"] = "Upscaling"
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
+ image = esrgan.process(
+ image=image,
+ upsampler_scale=esrgan_parameters["upscale"][0],
+ strength=esrgan_parameters["upscale"][1],
+ seed=seed,
+ )
+
+ progress["currentStatus"] = "Saving image"
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
+ esrgan_parameters["seed"] = seed
+ metadata = parameters_to_post_processed_image_metadata(
+ parameters=esrgan_parameters,
+ original_image_path=original_image["url"],
+ type="esrgan",
+ )
command = parameters_to_command(esrgan_parameters)
+ path = save_image(image, command, metadata, result_path, postprocessing="esrgan")
+
write_log_message(f'[Upscaled] "{original_image["url"]}" > "{path}": {command}')
+ progress["currentStatus"] = "Finished"
+ progress["currentStep"] = 0
+ progress["totalSteps"] = 0
+ progress["currentIteration"] = 0
+ progress["totalIterations"] = 0
+ progress["isProcessing"] = False
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
socketio.emit(
- 'result', {'url': os.path.relpath(path), 'type': 'esrgan', 'uuid': original_image['uuid'],'metadata': esrgan_parameters})
-
-
-
-@socketio.on('runGFPGAN')
-def handle_run_gfpgan_event(original_image, gfpgan_parameters):
- print(f'>> GFPGAN face fix requested for "{original_image["url"]}": {gfpgan_parameters}')
- image = Image.open(original_image["url"])
-
- seed = original_image['metadata']['seed'] if 'seed' in original_image['metadata'] else 'unknown_seed'
-
- image = run_gfpgan(
- image=image,
- strength=gfpgan_parameters['gfpgan_strength'],
- seed=seed,
- upsampler_scale=1
+ "esrganResult",
+ {
+ "url": os.path.relpath(path),
+ "mtime": os.path.getmtime(path),
+ "metadata": metadata,
+ },
)
- gfpgan_parameters['seed'] = seed
- path = save_image(image, gfpgan_parameters, result_path, postprocessing='gfpgan')
+
+@socketio.on("runGFPGAN")
+def handle_run_gfpgan_event(original_image, gfpgan_parameters):
+ print(
+ f'>> GFPGAN face fix requested for "{original_image["url"]}": {gfpgan_parameters}'
+ )
+ progress = {
+ "currentStep": 1,
+ "totalSteps": 1,
+ "currentIteration": 1,
+ "totalIterations": 1,
+ "currentStatus": "Preparing",
+ "isProcessing": True,
+ "currentStatusHasSteps": False,
+ }
+
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
+ image = Image.open(original_image["url"])
+
+ seed = (
+ original_image["metadata"]["seed"]
+ if "seed" in original_image["metadata"]
+ else "unknown_seed"
+ )
+
+ progress["currentStatus"] = "Fixing faces"
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
+ image = gfpgan.process(
+ image=image, strength=gfpgan_parameters["gfpgan_strength"], seed=seed
+ )
+
+ progress["currentStatus"] = "Saving image"
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
+ gfpgan_parameters["seed"] = seed
+ metadata = parameters_to_post_processed_image_metadata(
+ parameters=gfpgan_parameters,
+ original_image_path=original_image["url"],
+ type="gfpgan",
+ )
command = parameters_to_command(gfpgan_parameters)
+ path = save_image(image, command, metadata, result_path, postprocessing="gfpgan")
+
write_log_message(f'[Fixed faces] "{original_image["url"]}" > "{path}": {command}')
+ progress["currentStatus"] = "Finished"
+ progress["currentStep"] = 0
+ progress["totalSteps"] = 0
+ progress["currentIteration"] = 0
+ progress["totalIterations"] = 0
+ progress["isProcessing"] = False
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
socketio.emit(
- 'result', {'url': os.path.relpath(path), 'type': 'gfpgan', 'uuid': original_image['uuid'],'metadata': gfpgan_parameters})
+ "gfpganResult",
+ {
+ "url": os.path.relpath(path),
+ "mtime": os.path.mtime(path),
+ "metadata": metadata,
+ },
+ )
-@socketio.on('cancel')
+@socketio.on("cancel")
def handle_cancel():
- print(f'>> Cancel processing requested')
+ print(f">> Cancel processing requested")
canceled.set()
- return make_response("OK")
+ socketio.emit("processingCanceled")
# TODO: I think this needs a safety mechanism.
-@socketio.on('deleteImage')
-def handle_delete_image(path):
+@socketio.on("deleteImage")
+def handle_delete_image(path, uuid):
print(f'>> Delete requested "{path}"')
- Path(path).unlink()
- return make_response("OK")
+ send2trash(path)
+ socketio.emit("imageDeleted", {"url": path, "uuid": uuid})
# TODO: I think this needs a safety mechanism.
-@socketio.on('uploadInitialImage')
+@socketio.on("uploadInitialImage")
def handle_upload_initial_image(bytes, name):
print(f'>> Init image upload requested "{name}"')
uuid = uuid4().hex
split = os.path.splitext(name)
- name = f'{split[0]}.{uuid}{split[1]}'
- file_path = os.path.join(init_path, name)
+ name = f"{split[0]}.{uuid}{split[1]}"
+ file_path = os.path.join(init_image_path, name)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
newFile = open(file_path, "wb")
newFile.write(bytes)
- return make_response("OK", data=file_path)
+ socketio.emit("initialImageUploaded", {"url": file_path, "uuid": ""})
# TODO: I think this needs a safety mechanism.
-@socketio.on('uploadMaskImage')
+@socketio.on("uploadMaskImage")
def handle_upload_mask_image(bytes, name):
print(f'>> Mask image upload requested "{name}"')
uuid = uuid4().hex
split = os.path.splitext(name)
- name = f'{split[0]}.{uuid}{split[1]}'
- file_path = os.path.join(mask_path, name)
+ name = f"{split[0]}.{uuid}{split[1]}"
+ file_path = os.path.join(mask_image_path, name)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
newFile = open(file_path, "wb")
newFile.write(bytes)
- return make_response("OK", data=file_path)
-
+ socketio.emit("maskImageUploaded", {"url": file_path, "uuid": ""})
"""
@@ -266,114 +433,366 @@ END SOCKET.IO LISTENERS
"""
-
"""
ADDITIONAL FUNCTIONS
"""
+def get_system_config():
+ return {
+ "model": "stable diffusion",
+ "model_id": model,
+ "model_hash": generate.model_hash,
+ "app_id": APP_ID,
+ "app_version": APP_VERSION,
+ }
+
+
+def parameters_to_post_processed_image_metadata(parameters, original_image_path, type):
+ # top-level metadata minus `image` or `images`
+ metadata = get_system_config()
+
+ orig_hash = calculate_init_img_hash(original_image_path)
+
+ image = {"orig_path": original_image_path, "orig_hash": orig_hash}
+
+ if type == "esrgan":
+ image["type"] = "esrgan"
+ image["scale"] = parameters["upscale"][0]
+ image["strength"] = parameters["upscale"][1]
+ elif type == "gfpgan":
+ image["type"] = "gfpgan"
+ image["strength"] = parameters["gfpgan_strength"]
+ else:
+ raise TypeError(f"Invalid type: {type}")
+
+ metadata["image"] = image
+ return metadata
+
+
+def parameters_to_generated_image_metadata(parameters):
+ # top-level metadata minus `image` or `images`
+
+ metadata = get_system_config()
+ # remove any image keys not mentioned in RFC #266
+ rfc266_img_fields = [
+ "type",
+ "postprocessing",
+ "sampler",
+ "prompt",
+ "seed",
+ "variations",
+ "steps",
+ "cfg_scale",
+ "step_number",
+ "width",
+ "height",
+ "extra",
+ "seamless",
+ ]
+
+ rfc_dict = {}
+
+ for item in parameters.items():
+ key, value = item
+ if key in rfc266_img_fields:
+ rfc_dict[key] = value
+
+ postprocessing = []
+
+ # 'postprocessing' is either null or an
+ if "gfpgan_strength" in parameters:
+
+ postprocessing.append(
+ {"type": "gfpgan", "strength": float(parameters["gfpgan_strength"])}
+ )
+
+ if "upscale" in parameters:
+ postprocessing.append(
+ {
+ "type": "esrgan",
+ "scale": int(parameters["upscale"][0]),
+ "strength": float(parameters["upscale"][1]),
+ }
+ )
+
+ rfc_dict["postprocessing"] = postprocessing if len(postprocessing) > 0 else None
+
+ # semantic drift
+ rfc_dict["sampler"] = parameters["sampler_name"]
+
+ # display weighted subprompts (liable to change)
+ subprompts = split_weighted_subprompts(parameters["prompt"])
+ subprompts = [{"prompt": x[0], "weight": x[1]} for x in subprompts]
+ rfc_dict["prompt"] = subprompts
+
+ # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs
+ variations = []
+
+ if "with_variations" in parameters:
+ variations = [
+ {"seed": x[0], "weight": x[1]} for x in parameters["with_variations"]
+ ]
+
+ rfc_dict["variations"] = variations
+
+ if "init_img" in parameters:
+ rfc_dict["type"] = "img2img"
+ rfc_dict["strength"] = parameters["strength"]
+ rfc_dict["fit"] = parameters["fit"] # TODO: Noncompliant
+ rfc_dict["orig_hash"] = calculate_init_img_hash(parameters["init_img"])
+ rfc_dict["init_image_path"] = parameters["init_img"] # TODO: Noncompliant
+ rfc_dict["sampler"] = "ddim" # TODO: FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS
+ if "init_mask" in parameters:
+ rfc_dict["mask_hash"] = calculate_init_img_hash(
+ parameters["init_mask"]
+ ) # TODO: Noncompliant
+ rfc_dict["mask_image_path"] = parameters["init_mask"] # TODO: Noncompliant
+ else:
+ rfc_dict["type"] = "txt2img"
+
+ metadata["image"] = rfc_dict
+
+ return metadata
+
+
+def make_unique_init_image_filename(name):
+ uuid = uuid4().hex
+ split = os.path.splitext(name)
+ name = f"{split[0]}.{uuid}{split[1]}"
+ return name
+
+
def write_log_message(message, log_path=log_path):
"""Logs the filename and parameters used to generate or process that image to log file"""
- message = f'{message}\n'
- with open(log_path, 'a', encoding='utf-8') as file:
+ message = f"{message}\n"
+ with open(log_path, "a", encoding="utf-8") as file:
file.writelines(message)
-def make_response(status, message=None, data=None):
- response = {'status': status}
- if message is not None:
- response['message'] = message
- if data is not None:
- response['data'] = data
- return response
-
-
-def save_image(image, parameters, output_dir, step_index=None, postprocessing=False):
- seed = parameters['seed'] if 'seed' in parameters else 'unknown_seed'
-
+def save_image(
+ image, command, metadata, output_dir, step_index=None, postprocessing=False
+):
pngwriter = PngWriter(output_dir)
prefix = pngwriter.unique_prefix()
- filename = f'{prefix}.{seed}'
+ seed = "unknown_seed"
+
+ if "image" in metadata:
+ if "seed" in metadata["image"]:
+ seed = metadata["image"]["seed"]
+
+ filename = f"{prefix}.{seed}"
if step_index:
- filename += f'.{step_index}'
+ filename += f".{step_index}"
if postprocessing:
- filename += f'.postprocessed'
+ filename += f".postprocessed"
- filename += '.png'
+ filename += ".png"
- command = parameters_to_command(parameters)
-
- path = pngwriter.save_image_and_prompt_to_png(image, command, metadata=parameters, name=filename)
+ path = pngwriter.save_image_and_prompt_to_png(
+ image=image, dream_prompt=command, metadata=metadata, name=filename
+ )
return path
+
+def calculate_real_steps(steps, strength, has_init_image):
+ return math.floor(strength * steps) if has_init_image else steps
+
+
def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters):
canceled.clear()
step_index = 1
+ prior_variations = (
+ generation_parameters["with_variations"]
+ if "with_variations" in generation_parameters
+ else []
+ )
+ """
+ If a result image is used as an init image, and then deleted, we will want to be
+ able to use it as an init image in the future. Need to copy it.
+
+ If the init/mask image doesn't exist in the init_image_path/mask_image_path,
+ make a unique filename for it and copy it there.
+ """
+ if "init_img" in generation_parameters:
+ filename = os.path.basename(generation_parameters["init_img"])
+ if not os.path.exists(os.path.join(init_image_path, filename)):
+ unique_filename = make_unique_init_image_filename(filename)
+ new_path = os.path.join(init_image_path, unique_filename)
+ shutil.copy(generation_parameters["init_img"], new_path)
+ generation_parameters["init_img"] = new_path
+ if "init_mask" in generation_parameters:
+ filename = os.path.basename(generation_parameters["init_mask"])
+ if not os.path.exists(os.path.join(mask_image_path, filename)):
+ unique_filename = make_unique_init_image_filename(filename)
+ new_path = os.path.join(init_image_path, unique_filename)
+ shutil.copy(generation_parameters["init_img"], new_path)
+ generation_parameters["init_mask"] = new_path
+
+ totalSteps = calculate_real_steps(
+ steps=generation_parameters["steps"],
+ strength=generation_parameters["strength"]
+ if "strength" in generation_parameters
+ else None,
+ has_init_image="init_img" in generation_parameters,
+ )
+
+ progress = {
+ "currentStep": 1,
+ "totalSteps": totalSteps,
+ "currentIteration": 1,
+ "totalIterations": generation_parameters["iterations"],
+ "currentStatus": "Preparing",
+ "isProcessing": True,
+ "currentStatusHasSteps": False,
+ }
+
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
def image_progress(sample, step):
if canceled.is_set():
raise CanceledException
+
nonlocal step_index
nonlocal generation_parameters
- if generation_parameters["progress_images"] and step % 5 == 0 and step < generation_parameters['steps'] - 1:
- image = model.sample_to_image(sample)
- path = save_image(image, generation_parameters, intermediate_path, step_index)
+ nonlocal progress
+
+ progress["currentStep"] = step + 1
+ progress["currentStatus"] = "Generating"
+ progress["currentStatusHasSteps"] = True
+
+ if (
+ generation_parameters["progress_images"]
+ and step % 5 == 0
+ and step < generation_parameters["steps"] - 1
+ ):
+ image = generate.sample_to_image(sample)
+ path = save_image(
+ image, generation_parameters, intermediate_path, step_index
+ )
step_index += 1
- socketio.emit('intermediateResult', {
- 'url': os.path.relpath(path), 'metadata': generation_parameters})
- socketio.emit('progress', {'step': step + 1})
+ socketio.emit(
+ "intermediateResult",
+ {
+ "url": os.path.relpath(path),
+ "mtime": os.path.getmtime(path),
+ "metadata": generation_parameters,
+ },
+ )
+ socketio.emit("progressUpdate", progress)
eventlet.sleep(0)
- def image_done(image, seed):
+ def image_done(image, seed, first_seed):
nonlocal generation_parameters
nonlocal esrgan_parameters
nonlocal gfpgan_parameters
+ nonlocal progress
+
+ step_index = 1
+ nonlocal prior_variations
+
+ progress["currentStatus"] = "Generation complete"
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
all_parameters = generation_parameters
postprocessing = False
+ if (
+ "variation_amount" in all_parameters
+ and all_parameters["variation_amount"] > 0
+ ):
+ first_seed = first_seed or seed
+ this_variation = [[seed, all_parameters["variation_amount"]]]
+ all_parameters["with_variations"] = prior_variations + this_variation
+ all_parameters["seed"] = first_seed
+ elif ("with_variations" in all_parameters):
+ all_parameters["seed"] = first_seed
+ else:
+ all_parameters["seed"] = seed
+
if esrgan_parameters:
- image = real_esrgan_upscale(
+ progress["currentStatus"] = "Upscaling"
+ progress["currentStatusHasSteps"] = False
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
+ image = esrgan.process(
image=image,
- strength=esrgan_parameters['strength'],
- upsampler_scale=esrgan_parameters['level'],
- seed=seed
+ upsampler_scale=esrgan_parameters["level"],
+ strength=esrgan_parameters["strength"],
+ seed=seed,
)
+
postprocessing = True
- all_parameters["upscale"] = [esrgan_parameters['level'], esrgan_parameters['strength']]
+ all_parameters["upscale"] = [
+ esrgan_parameters["level"],
+ esrgan_parameters["strength"],
+ ]
if gfpgan_parameters:
- image = run_gfpgan(
- image=image,
- strength=gfpgan_parameters['strength'],
- seed=seed,
- upsampler_scale=1,
+ progress["currentStatus"] = "Fixing faces"
+ progress["currentStatusHasSteps"] = False
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
+ image = gfpgan.process(
+ image=image, strength=gfpgan_parameters["strength"], seed=seed
)
postprocessing = True
- all_parameters["gfpgan_strength"] = gfpgan_parameters['strength']
+ all_parameters["gfpgan_strength"] = gfpgan_parameters["strength"]
- all_parameters['seed'] = seed
+ progress["currentStatus"] = "Saving image"
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
- path = save_image(image, all_parameters, result_path, postprocessing=postprocessing)
+ metadata = parameters_to_generated_image_metadata(all_parameters)
command = parameters_to_command(all_parameters)
- print(f'Image generated: "{path}"')
+ path = save_image(
+ image, command, metadata, result_path, postprocessing=postprocessing
+ )
+
+ print(f'>> Image generated: "{path}"')
write_log_message(f'[Generated] "{path}": {command}')
+ if progress["totalIterations"] > progress["currentIteration"]:
+ progress["currentStep"] = 1
+ progress["currentIteration"] += 1
+ progress["currentStatus"] = "Iteration finished"
+ progress["currentStatusHasSteps"] = False
+ else:
+ progress["currentStep"] = 0
+ progress["totalSteps"] = 0
+ progress["currentIteration"] = 0
+ progress["totalIterations"] = 0
+ progress["currentStatus"] = "Finished"
+ progress["isProcessing"] = False
+
+ socketio.emit("progressUpdate", progress)
+ eventlet.sleep(0)
+
socketio.emit(
- 'result', {'url': os.path.relpath(path), 'type': 'generation', 'metadata': all_parameters})
+ "generationResult",
+ {
+ "url": os.path.relpath(path),
+ "mtime": os.path.getmtime(path),
+ "metadata": metadata,
+ },
+ )
eventlet.sleep(0)
try:
- model.prompt2image(
+ generate.prompt2image(
**generation_parameters,
step_callback=image_progress,
- image_callback=image_done
+ image_callback=image_done,
)
except KeyboardInterrupt:
@@ -381,7 +800,7 @@ def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters)
except CanceledException:
pass
except Exception as e:
- socketio.emit('error', (str(e)))
+ socketio.emit("error", {"message": (str(e))})
print("\n")
traceback.print_exc()
print("\n")
@@ -392,6 +811,6 @@ END ADDITIONAL FUNCTIONS
"""
-if __name__ == '__main__':
- print(f'Starting server at http://{host}:{port}')
+if __name__ == "__main__":
+ print(f">> Starting server at http://{host}:{port}")
socketio.run(app, host=host, port=port)
diff --git a/docker-build/Dockerfile b/docker-build/Dockerfile
new file mode 100644
index 0000000000..f3d6834c93
--- /dev/null
+++ b/docker-build/Dockerfile
@@ -0,0 +1,57 @@
+FROM debian
+
+ARG gsd
+ENV GITHUB_STABLE_DIFFUSION $gsd
+
+ARG rsd
+ENV REQS $rsd
+
+ARG cs
+ENV CONDA_SUBDIR $cs
+
+ENV PIP_EXISTS_ACTION="w"
+
+# TODO: Optimize image size
+
+SHELL ["/bin/bash", "-c"]
+
+WORKDIR /
+RUN apt update && apt upgrade -y \
+ && apt install -y \
+ git \
+ libgl1-mesa-glx \
+ libglib2.0-0 \
+ pip \
+ python3 \
+ && git clone $GITHUB_STABLE_DIFFUSION
+
+# Install Anaconda or Miniconda
+COPY anaconda.sh .
+RUN bash anaconda.sh -b -u -p /anaconda && /anaconda/bin/conda init bash
+
+# SD
+WORKDIR /stable-diffusion
+RUN source ~/.bashrc \
+ && conda create -y --name ldm && conda activate ldm \
+ && conda config --env --set subdir $CONDA_SUBDIR \
+ && pip3 install -r $REQS \
+ && pip3 install basicsr facexlib realesrgan \
+ && mkdir models/ldm/stable-diffusion-v1 \
+ && ln -s "/data/sd-v1-4.ckpt" models/ldm/stable-diffusion-v1/model.ckpt
+
+# Face restoreation
+# by default expected in a sibling directory to stable-diffusion
+WORKDIR /
+RUN git clone https://github.com/TencentARC/GFPGAN.git
+
+WORKDIR /GFPGAN
+RUN pip3 install -r requirements.txt \
+ && python3 setup.py develop \
+ && ln -s "/data/GFPGANv1.4.pth" experiments/pretrained_models/GFPGANv1.4.pth
+
+WORKDIR /stable-diffusion
+RUN python3 scripts/preload_models.py
+
+WORKDIR /
+COPY entrypoint.sh .
+ENTRYPOINT ["/entrypoint.sh"]
\ No newline at end of file
diff --git a/docker-build/entrypoint.sh b/docker-build/entrypoint.sh
new file mode 100755
index 0000000000..f47e6669e0
--- /dev/null
+++ b/docker-build/entrypoint.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+cd /stable-diffusion
+
+if [ $# -eq 0 ]; then
+ python3 scripts/dream.py --full_precision -o /data
+ # bash
+else
+ python3 scripts/dream.py --full_precision -o /data "$@"
+fi
\ No newline at end of file
diff --git a/docs/assets/join-us-on-discord-image.png b/docs/assets/join-us-on-discord-image.png
new file mode 100644
index 0000000000..53e4ee0fe0
Binary files /dev/null and b/docs/assets/join-us-on-discord-image.png differ
diff --git a/docs/assets/logo.png b/docs/assets/logo.png
index fa0548ff78..b6eb33a6db 100644
Binary files a/docs/assets/logo.png and b/docs/assets/logo.png differ
diff --git a/docs/assets/negative_prompt_walkthru/step1.png b/docs/assets/negative_prompt_walkthru/step1.png
new file mode 100644
index 0000000000..6f94d7d035
Binary files /dev/null and b/docs/assets/negative_prompt_walkthru/step1.png differ
diff --git a/docs/assets/negative_prompt_walkthru/step2.png b/docs/assets/negative_prompt_walkthru/step2.png
new file mode 100644
index 0000000000..0ff90eca3c
Binary files /dev/null and b/docs/assets/negative_prompt_walkthru/step2.png differ
diff --git a/docs/assets/negative_prompt_walkthru/step3.png b/docs/assets/negative_prompt_walkthru/step3.png
new file mode 100644
index 0000000000..f6676de386
Binary files /dev/null and b/docs/assets/negative_prompt_walkthru/step3.png differ
diff --git a/docs/assets/negative_prompt_walkthru/step4.png b/docs/assets/negative_prompt_walkthru/step4.png
new file mode 100644
index 0000000000..2e73532629
Binary files /dev/null and b/docs/assets/negative_prompt_walkthru/step4.png differ
diff --git a/docs/assets/outpainting/elven_princess.outpainted.png b/docs/assets/outpainting/elven_princess.outpainted.png
new file mode 100644
index 0000000000..98f98564df
Binary files /dev/null and b/docs/assets/outpainting/elven_princess.outpainted.png differ
diff --git a/docs/assets/outpainting/elven_princess.png b/docs/assets/outpainting/elven_princess.png
new file mode 100644
index 0000000000..aa5f00ccf7
Binary files /dev/null and b/docs/assets/outpainting/elven_princess.png differ
diff --git a/assets/stable-samples/img2img/mountains-2.png b/docs/assets/stable-samples/img2img/mountains-2.png
similarity index 100%
rename from assets/stable-samples/img2img/mountains-2.png
rename to docs/assets/stable-samples/img2img/mountains-2.png
diff --git a/assets/stable-samples/img2img/mountains-3.png b/docs/assets/stable-samples/img2img/mountains-3.png
similarity index 100%
rename from assets/stable-samples/img2img/mountains-3.png
rename to docs/assets/stable-samples/img2img/mountains-3.png
diff --git a/assets/stable-samples/img2img/sketch-mountains-input.jpg b/docs/assets/stable-samples/img2img/sketch-mountains-input.jpg
similarity index 100%
rename from assets/stable-samples/img2img/sketch-mountains-input.jpg
rename to docs/assets/stable-samples/img2img/sketch-mountains-input.jpg
diff --git a/assets/stable-samples/txt2img/merged-0005.png b/docs/assets/stable-samples/txt2img/merged-0005.png
similarity index 100%
rename from assets/stable-samples/txt2img/merged-0005.png
rename to docs/assets/stable-samples/txt2img/merged-0005.png
diff --git a/assets/stable-samples/txt2img/merged-0006.png b/docs/assets/stable-samples/txt2img/merged-0006.png
similarity index 100%
rename from assets/stable-samples/txt2img/merged-0006.png
rename to docs/assets/stable-samples/txt2img/merged-0006.png
diff --git a/assets/stable-samples/txt2img/merged-0007.png b/docs/assets/stable-samples/txt2img/merged-0007.png
similarity index 100%
rename from assets/stable-samples/txt2img/merged-0007.png
rename to docs/assets/stable-samples/txt2img/merged-0007.png
diff --git a/docs/assets/step1.png b/docs/assets/step1.png
new file mode 100644
index 0000000000..6309f41f20
Binary files /dev/null and b/docs/assets/step1.png differ
diff --git a/docs/assets/step2.png b/docs/assets/step2.png
new file mode 100644
index 0000000000..06027289b2
Binary files /dev/null and b/docs/assets/step2.png differ
diff --git a/docs/assets/step4.png b/docs/assets/step4.png
new file mode 100644
index 0000000000..c24db6b470
Binary files /dev/null and b/docs/assets/step4.png differ
diff --git a/docs/assets/step5.png b/docs/assets/step5.png
new file mode 100644
index 0000000000..b4e9b50576
Binary files /dev/null and b/docs/assets/step5.png differ
diff --git a/docs/assets/step6.png b/docs/assets/step6.png
new file mode 100644
index 0000000000..c43140c1aa
Binary files /dev/null and b/docs/assets/step6.png differ
diff --git a/docs/assets/step7.png b/docs/assets/step7.png
new file mode 100644
index 0000000000..a575af28b2
Binary files /dev/null and b/docs/assets/step7.png differ
diff --git a/assets/v1-variants-scores.jpg b/docs/assets/v1-variants-scores.jpg
similarity index 100%
rename from assets/v1-variants-scores.jpg
rename to docs/assets/v1-variants-scores.jpg
diff --git a/docs/features/CHANGELOG.md b/docs/features/CHANGELOG.md
index f50c4f11b8..a6258f6a56 100644
--- a/docs/features/CHANGELOG.md
+++ b/docs/features/CHANGELOG.md
@@ -2,6 +2,8 @@
title: Changelog
---
+# :octicons-log-16: Changelog
+
## v1.13 (in process)
- Supports a Google Colab notebook for a standalone server running on Google
diff --git a/docs/features/CLI.md b/docs/features/CLI.md
index 081af3dbf4..89b872d46b 100644
--- a/docs/features/CLI.md
+++ b/docs/features/CLI.md
@@ -1,27 +1,37 @@
---
title: CLI
+hide:
+ - toc
---
+# :material-bash: CLI
+
## **Interactive Command Line Interface**
-The `dream.py` script, located in `scripts/dream.py`, provides an interactive interface to image
-generation similar to the "dream mothership" bot that Stable AI provided on its Discord server.
+The `dream.py` script, located in `scripts/dream.py`, provides an interactive
+interface to image generation similar to the "dream mothership" bot that Stable
+AI provided on its Discord server.
-Unlike the txt2img.py and img2img.py scripts provided in the original CompViz/stable-diffusion
-source code repository, the time-consuming initialization of the AI model initialization only
-happens once. After that image generation from the command-line interface is very fast.
+Unlike the `txt2img.py` and `img2img.py` scripts provided in the original
+[CompVis/stable-diffusion](https://github.com/CompVis/stable-diffusion) source
+code repository, the time-consuming initialization of the AI model
+initialization only happens once. After that image generation from the
+command-line interface is very fast.
-The script uses the readline library to allow for in-line editing, command history (up and down
-arrows), autocompletion, and more. To help keep track of which prompts generated which images, the
-script writes a log file of image names and prompts to the selected output directory.
+The script uses the readline library to allow for in-line editing, command
+history (++up++ and ++down++), autocompletion, and more. To help keep track of
+which prompts generated which images, the script writes a log file of image
+names and prompts to the selected output directory.
-In addition, as of version 1.02, it also writes the prompt into the PNG file's metadata where it can
-be retrieved using scripts/images2prompt.py
+In addition, as of version 1.02, it also writes the prompt into the PNG file's
+metadata where it can be retrieved using `scripts/images2prompt.py`
The script is confirmed to work on Linux, Windows and Mac systems.
-_Note:_ This script runs from the command-line or can be used as a Web application. The Web GUI is
-currently rudimentary, but a much better replacement is on its way.
+!!! note
+
+ This script runs from the command-line or can be used as a Web application. The Web GUI is
+ currently rudimentary, but a much better replacement is on its way.
```bash
(ldm) ~/stable-diffusion$ python3 ./scripts/dream.py
@@ -47,179 +57,191 @@ dream> q
00011.png: "there's a fly in my soup" -n6 -g -S 2685670268
```
-