diff --git a/.dev_scripts/test_regression_txt2img_dream_v1_4.sh b/.dev_scripts/test_regression_txt2img_dream_v1_4.sh index 11cbf8f14b..9326d3c311 100644 --- a/.dev_scripts/test_regression_txt2img_dream_v1_4.sh +++ b/.dev_scripts/test_regression_txt2img_dream_v1_4.sh @@ -5,8 +5,7 @@ SAMPLES_DIR=${OUT_DIR} python scripts/dream.py \ --from_file ${PROMPT_FILE} \ --outdir ${OUT_DIR} \ - --sampler plms \ - --full_precision + --sampler plms # original output by CompVis/stable-diffusion IMAGE1=".dev_scripts/images/v1_4_astronaut_rides_horse_plms_step50_seed42.png" diff --git a/.github/workflows/cache-model.yml b/.github/workflows/cache-model.yml deleted file mode 100644 index 2682943eef..0000000000 --- a/.github/workflows/cache-model.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Cache Model -on: - workflow_dispatch -jobs: - build: - strategy: - matrix: - os: [ macos-12 ] - name: Create Caches using ${{ matrix.os }} - runs-on: ${{ matrix.os }} - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - name: Cache model - id: cache-sd-v1-4 - uses: actions/cache@v3 - env: - cache-name: cache-sd-v1-4 - with: - path: models/ldm/stable-diffusion-v1/model.ckpt - key: ${{ env.cache-name }} - restore-keys: | - ${{ env.cache-name }} - - name: Download Stable Diffusion v1.4 model - if: ${{ steps.cache-sd-v1-4.outputs.cache-hit != 'true' }} - continue-on-error: true - run: | - if [ ! -e models/ldm/stable-diffusion-v1 ]; then - mkdir -p models/ldm/stable-diffusion-v1 - fi - if [ ! -e models/ldm/stable-diffusion-v1/model.ckpt ]; then - curl -o models/ldm/stable-diffusion-v1/model.ckpt ${{ secrets.SD_V1_4_URL }} - fi -# Uncomment this when we no longer make changes to environment-mac.yaml -# - name: Cache environment -# id: cache-conda-env-ldm -# uses: actions/cache@v3 -# env: -# cache-name: cache-conda-env-ldm -# with: -# path: ~/.conda/envs/ldm -# key: ${{ env.cache-name }} -# restore-keys: | -# ${{ env.cache-name }} - - name: Install dependencies -# if: ${{ steps.cache-conda-env-ldm.outputs.cache-hit != 'true' }} - run: | - conda env create -f environment-mac.yaml - - name: Cache hugginface and torch models - id: cache-hugginface-torch - uses: actions/cache@v3 - env: - cache-name: cache-hugginface-torch - with: - path: ~/.cache - key: ${{ env.cache-name }} - restore-keys: | - ${{ env.cache-name }} - - name: Download Huggingface and Torch models - if: ${{ steps.cache-hugginface-torch.outputs.cache-hit != 'true' }} - continue-on-error: true - run: | - export PYTHON_BIN=/usr/local/miniconda/envs/ldm/bin/python - $PYTHON_BIN scripts/preload_models.py \ No newline at end of file diff --git a/.github/workflows/create-caches.yml b/.github/workflows/create-caches.yml new file mode 100644 index 0000000000..951718af1b --- /dev/null +++ b/.github/workflows/create-caches.yml @@ -0,0 +1,70 @@ +name: Create Caches +on: + workflow_dispatch +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, macos-12 ] + name: Create Caches on ${{ matrix.os }} conda + runs-on: ${{ matrix.os }} + steps: + - name: Set platform variables + id: vars + run: | + if [ "$RUNNER_OS" = "macOS" ]; then + echo "::set-output name=ENV_FILE::environment-mac.yaml" + echo "::set-output name=PYTHON_BIN::/usr/local/miniconda/envs/ldm/bin/python" + elif [ "$RUNNER_OS" = "Linux" ]; then + echo "::set-output name=ENV_FILE::environment.yaml" + echo "::set-output name=PYTHON_BIN::/usr/share/miniconda/envs/ldm/bin/python" + fi + - name: Checkout sources + uses: actions/checkout@v3 + - name: Use Cached Stable Diffusion v1.4 Model + id: cache-sd-v1-4 + uses: actions/cache@v3 + env: + cache-name: cache-sd-v1-4 + with: + path: models/ldm/stable-diffusion-v1/model.ckpt + key: ${{ env.cache-name }} + restore-keys: | + ${{ env.cache-name }} + - name: Download Stable Diffusion v1.4 Model + if: ${{ steps.cache-sd-v1-4.outputs.cache-hit != 'true' }} + run: | + if [ ! -e models/ldm/stable-diffusion-v1 ]; then + mkdir -p models/ldm/stable-diffusion-v1 + fi + if [ ! -e models/ldm/stable-diffusion-v1/model.ckpt ]; then + curl -o models/ldm/stable-diffusion-v1/model.ckpt ${{ secrets.SD_V1_4_URL }} + fi + - name: Use Cached Dependencies + id: cache-conda-env-ldm + uses: actions/cache@v3 + env: + cache-name: cache-conda-env-ldm + with: + path: ~/.conda/envs/ldm + key: ${{ env.cache-name }} + restore-keys: | + ${{ env.cache-name }}-${{ runner.os }}-${{ hashFiles(steps.vars.outputs.ENV_FILE) }} + - name: Install Dependencies + if: ${{ steps.cache-conda-env-ldm.outputs.cache-hit != 'true' }} + run: | + conda env create -f ${{ steps.vars.outputs.ENV_FILE }} + - name: Use Cached Huggingface and Torch models + id: cache-huggingface-torch + uses: actions/cache@v3 + env: + cache-name: cache-huggingface-torch + with: + path: ~/.cache + key: ${{ env.cache-name }} + restore-keys: | + ${{ env.cache-name }}-${{ hashFiles('scripts/preload_models.py') }} + - name: Download Huggingface and Torch models + if: ${{ steps.cache-huggingface-torch.outputs.cache-hit != 'true' }} + run: | + ${{ steps.vars.outputs.PYTHON_BIN }} scripts/preload_models.py diff --git a/.github/workflows/macos12-miniconda.yml b/.github/workflows/macos12-miniconda.yml deleted file mode 100644 index 18f21277c0..0000000000 --- a/.github/workflows/macos12-miniconda.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Build -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] -jobs: - build: - strategy: - matrix: - os: [ macos-12 ] - name: Build on ${{ matrix.os }} miniconda - runs-on: ${{ matrix.os }} - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - name: Cache model - id: cache-sd-v1-4 - uses: actions/cache@v3 - env: - cache-name: cache-sd-v1-4 - with: - path: models/ldm/stable-diffusion-v1/model.ckpt - key: ${{ env.cache-name }} - restore-keys: | - ${{ env.cache-name }} - - name: Download Stable Diffusion v1.4 model - if: ${{ steps.cache-sd-v1-4.outputs.cache-hit != 'true' }} - continue-on-error: true - run: | - if [ ! -e models/ldm/stable-diffusion-v1 ]; then - mkdir -p models/ldm/stable-diffusion-v1 - fi - if [ ! -e models/ldm/stable-diffusion-v1/model.ckpt ]; then - curl -o models/ldm/stable-diffusion-v1/model.ckpt ${{ secrets.SD_V1_4_URL }} - fi -# Uncomment this when we no longer make changes to environment-mac.yaml -# - name: Cache environment -# id: cache-conda-env-ldm -# uses: actions/cache@v3 -# env: -# cache-name: cache-conda-env-ldm -# with: -# path: ~/.conda/envs/ldm -# key: ${{ env.cache-name }} -# restore-keys: | -# ${{ env.cache-name }} - - name: Install dependencies -# if: ${{ steps.cache-conda-env-ldm.outputs.cache-hit != 'true' }} - run: | - conda env create -f environment-mac.yaml - - name: Cache hugginface and torch models - id: cache-hugginface-torch - uses: actions/cache@v3 - env: - cache-name: cache-hugginface-torch - with: - path: ~/.cache - key: ${{ env.cache-name }} - restore-keys: | - ${{ env.cache-name }} - - name: Download Huggingface and Torch models - if: ${{ steps.cache-hugginface-torch.outputs.cache-hit != 'true' }} - continue-on-error: true - run: | - export PYTHON_BIN=/usr/local/miniconda/envs/ldm/bin/python - $PYTHON_BIN scripts/preload_models.py - - name: Run the tests - run: | - # Note, can't "activate" via automation, and activation is just env vars and path - export PYTHON_BIN=/usr/local/miniconda/envs/ldm/bin/python - export PYTORCH_ENABLE_MPS_FALLBACK=1 - $PYTHON_BIN scripts/preload_models.py - mkdir -p outputs/img-samples - time $PYTHON_BIN scripts/dream.py --from_file tests/prompts.txt outputs/img-samples/err.log > outputs/img-samples/out.log - - name: Archive results - uses: actions/upload-artifact@v3 - with: - name: results - path: outputs/img-samples \ No newline at end of file diff --git a/.github/workflows/mkdocs-flow.yml b/.github/workflows/mkdocs-flow.yml index c4a6365304..49a9cbd423 100644 --- a/.github/workflows/mkdocs-flow.yml +++ b/.github/workflows/mkdocs-flow.yml @@ -12,7 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Build uses: Tiryoh/actions-mkdocs@v0 with: diff --git a/.github/workflows/test-dream-conda.yml b/.github/workflows/test-dream-conda.yml new file mode 100644 index 0000000000..6c51ebe718 --- /dev/null +++ b/.github/workflows/test-dream-conda.yml @@ -0,0 +1,97 @@ +name: Test Dream with Conda +on: + push: + branches: + - 'main' + - 'development' +jobs: + os_matrix: + strategy: + matrix: + os: [ ubuntu-latest, macos-12 ] + name: Test dream.py on ${{ matrix.os }} with conda + runs-on: ${{ matrix.os }} + steps: + - run: | + echo The PR was merged + - name: Set platform variables + id: vars + run: | + # Note, can't "activate" via github action; specifying the env's python has the same effect + if [ "$RUNNER_OS" = "macOS" ]; then + echo "::set-output name=ENV_FILE::environment-mac.yaml" + echo "::set-output name=PYTHON_BIN::/usr/local/miniconda/envs/ldm/bin/python" + elif [ "$RUNNER_OS" = "Linux" ]; then + echo "::set-output name=ENV_FILE::environment.yaml" + echo "::set-output name=PYTHON_BIN::/usr/share/miniconda/envs/ldm/bin/python" + fi + - name: Checkout sources + uses: actions/checkout@v3 + - name: Use Cached Stable Diffusion v1.4 Model + id: cache-sd-v1-4 + uses: actions/cache@v3 + env: + cache-name: cache-sd-v1-4 + with: + path: models/ldm/stable-diffusion-v1/model.ckpt + key: ${{ env.cache-name }} + restore-keys: | + ${{ env.cache-name }} + - name: Download Stable Diffusion v1.4 Model + if: ${{ steps.cache-sd-v1-4.outputs.cache-hit != 'true' }} + run: | + if [ ! -e models/ldm/stable-diffusion-v1 ]; then + mkdir -p models/ldm/stable-diffusion-v1 + fi + if [ ! -e models/ldm/stable-diffusion-v1/model.ckpt ]; then + curl -o models/ldm/stable-diffusion-v1/model.ckpt ${{ secrets.SD_V1_4_URL }} + fi + - name: Use Cached Dependencies + id: cache-conda-env-ldm + uses: actions/cache@v3 + env: + cache-name: cache-conda-env-ldm + with: + path: ~/.conda/envs/ldm + key: ${{ env.cache-name }} + restore-keys: | + ${{ env.cache-name }}-${{ runner.os }}-${{ hashFiles(steps.vars.outputs.ENV_FILE) }} + - name: Install Dependencies + if: ${{ steps.cache-conda-env-ldm.outputs.cache-hit != 'true' }} + run: | + conda env create -f ${{ steps.vars.outputs.ENV_FILE }} + - name: Use Cached Huggingface and Torch models + id: cache-hugginface-torch + uses: actions/cache@v3 + env: + cache-name: cache-hugginface-torch + with: + path: ~/.cache + key: ${{ env.cache-name }} + restore-keys: | + ${{ env.cache-name }}-${{ hashFiles('scripts/preload_models.py') }} + - name: Download Huggingface and Torch models + if: ${{ steps.cache-hugginface-torch.outputs.cache-hit != 'true' }} + run: | + ${{ steps.vars.outputs.PYTHON_BIN }} scripts/preload_models.py +# - name: Run tmate +# uses: mxschmitt/action-tmate@v3 +# timeout-minutes: 30 + - name: Run the tests + run: | + # Note, can't "activate" via github action; specifying the env's python has the same effect + if [ $(uname) = "Darwin" ]; then + export PYTORCH_ENABLE_MPS_FALLBACK=1 + fi + # Utterly hacky, but I don't know how else to do this + if [[ ${{ github.ref }} == 'refs/heads/master' ]]; then + time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/dream.py --from_file tests/preflight_prompts.txt + elif [[ ${{ github.ref }} == 'refs/heads/development' ]]; then + time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/dream.py --from_file tests/dev_prompts.txt + fi + mkdir -p outputs/img-samples + - name: Archive results + uses: actions/upload-artifact@v3 + with: + name: results + path: outputs/img-samples diff --git a/.gitignore b/.gitignore index 7e4dd4bea9..da74df3a1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # ignore default image save location and model symbolic link outputs/ models/ldm/stable-diffusion-v1/model.ckpt +ldm/dream/restoration/codeformer/weights + +# ignore the Anaconda/Miniconda installer used while building Docker image +anaconda.sh # ignore a directory which serves as a place for initial images inputs/ diff --git a/.prettierrc.yaml b/.prettierrc.yaml index 68eae1ba15..ce4b99a07b 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -5,9 +5,9 @@ singleQuote: true quoteProps: as-needed embeddedLanguageFormatting: auto overrides: - - files: "*.md" + - files: '*.md' options: proseWrap: always - printWidth: 100 + printWidth: 80 parser: markdown cursorOffset: -1 diff --git a/README.md b/README.md index 2dacbc7866..2c962dba8d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,45 @@ -

Stable Diffusion Dream Script

+
-

- -

+# InvokeAI: A Stable Diffusion Toolkit -

- last-commit - stars -
- issues - pull-requests -

+_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 ``` -

- -

+![dream-py-demo](../assets/dream-py-demo.png) -The `dream>` prompt's arguments are pretty much identical to those used in the Discord bot, except -you don't need to type "!dream" (it doesn't hurt if you do). A significant change is that creation -of individual images is now the default unless --grid (-g) is given. A full list is given in [List -of prompt arguments] (#list-of-prompt-arguments). +The `dream>` prompt's arguments are pretty much identical to those used in the +Discord bot, except you don't need to type "!dream" (it doesn't hurt if you do). +A significant change is that creation of individual images is now the default +unless `--grid` (`-g`) is given. A full list is given in +[List of prompt arguments](#list-of-prompt-arguments). ## Arguments -The script itself also recognizes a series of command-line switches that will change important -global defaults, such as the directory for image outputs and the location of the model weight files. +The script itself also recognizes a series of command-line switches that will +change important global defaults, such as the directory for image outputs and +the location of the model weight files. -## List of arguments recognized at the command line +### List of arguments recognized at the command line -These command-line arguments can be passed to dream.py when you first run it from the Windows, Mac -or Linux command line. Some set defaults that can be overridden on a per-prompt basis (see [List of -prompt arguments] (#list-of-prompt-arguments). Others +These command-line arguments can be passed to `dream.py` when you first run it +from the Windows, Mac or Linux command line. Some set defaults that can be +overridden on a per-prompt basis (see [List of prompt arguments] +(#list-of-prompt-arguments). Others -| Argument | Shortcut | Default | Description | -| :---------------------- | :---------: | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | -| --help | -h | | Print a concise help message. | -| --outdir | -o | outputs/img_samples | Location for generated images. | -| --prompt_as_dir | -p | False | Name output directories using the prompt text. | -| --from_file | | None | Read list of prompts from a file. Use "-" to read from standard input | -| --model | | stable-diffusion-1.4 | Loads model specified in configs/models.yaml. Currently one of "stable-diffusion-1.4" or "laion400m" | -| --full_precision | -F | False | Run in slower full-precision mode. Needed for Macintosh M1/M2 hardware and some older video cards. | -| --web | | False | Start in web server mode | -| --host | | localhost | Which network interface web server should listen on. Set to 0.0.0.0 to listen on any. | -| --port | | 9090 | Which port web server should listen for requests on. | -| --config | | configs/models.yaml | Configuration file for models and their weights. | -| --iterations | -n | 1 | How many images to generate per prompt. | -| --grid | -g | False | Save all image series as a grid rather than individually. | -| --sampler | -A | k_lms | Sampler to use. Use -h to get list of available samplers. | -| --seamless | | False | Create interesting effects by tiling elements of the image. | -| --embedding_path | | None | Path to pre-trained embedding manager checkpoints, for custom models | -| --gfpgan_dir | | src/gfpgan | Path to where GFPGAN is installed. | -| --gfpgan_model_path | | experiments/pretrained_models
/GFPGANv1.3.pth | Path to GFPGAN model file, relative to --gfpgan_dir. | -| --device | -d | torch.cuda.current_device() | Device to run SD on, e.g. "cuda:0" | +| Argument | Shortcut | Default | Description | +| ----------------------------------------- | ----------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `--help` | `-h` | | Print a concise help message. | +| `--outdir ` | `-o` | `outputs/img_samples` | Location for generated images. | +| `--prompt_as_dir` | `-p` | `False` | Name output directories using the prompt text. | +| `--from_file ` | | `None` | Read list of prompts from a file. Use `-` to read from standard input | +| `--model ` | | `stable-diffusion-1.4` | Loads model specified in configs/models.yaml. Currently one of "stable-diffusion-1.4" or "laion400m" | +| `--full_precision` | `-F` | `False` | Run in slower full-precision mode. Needed for Macintosh M1/M2 hardware and some older video cards. | +| `--web` | | `False` | Start in web server mode | +| `--host ` | | `localhost` | Which network interface web server should listen on. Set to 0.0.0.0 to listen on any. | +| `--port ` | | `9090` | Which port web server should listen for requests on. | +| `--config ` | | `configs/models.yaml` | Configuration file for models and their weights. | +| `--iterations ` | `-n` | `1` | How many images to generate per prompt. | +| `--grid` | `-g` | `False` | Save all image series as a grid rather than individually. | +| `--sampler ` | `-A` | `k_lms` | Sampler to use. Use `-h` to get list of available samplers. | +| `--seamless` | | `False` | Create interesting effects by tiling elements of the image. | +| `--embedding_path ` | | `None` | Path to pre-trained embedding manager checkpoints, for custom models | +| `--gfpgan_dir` | | `src/gfpgan` | Path to where GFPGAN is installed. | +| `--gfpgan_model_path` | | `experiments/pretrained_models/GFPGANv1.4.pth` | Path to GFPGAN model file, relative to `--gfpgan_dir`. | +| `--device ` | `-d` | `torch.cuda.current_device()` | Device to run SD on, e.g. "cuda:0" | + +#### deprecated These arguments are deprecated but still work: -| Argument | Shortcut | Default | Description | -| ---------------- | -------- | ------- | --------------------------------------------------------------- | -| --weights | | None | Pth to weights file; use `--model stable-diffusion-1.4` instead | -| --laion400m | -l | False | Use older LAION400m weights; use `--model=laion400m` instead | -### **A note on path names:** +| Argument | Shortcut | Default | Description | +|--------------------|------------|---------------------|--------------| +| --weights | | None | Pth to weights file; use `--model stable-diffusion-1.4` instead | +| --laion400m | -l | False | Use older LAION400m weights; use `--model=laion400m` instead | -On Windows systems, you may run into problems when passing the dream script standard backslashed -path names because the Python interpreter treats "\" as an escape. You can either double your -slashes (ick): `C:\\\\path\\\\to\\\\my\\\\file`, or use Linux/Mac style forward slashes (better): -`C:/path/to/my/file`. +**A note on path names:** On Windows systems, you may run into + problems when passing the dream script standard backslashed path + names because the Python interpreter treats "\" as an escape. + You can either double your slashes (ick): C:\\\\path\\\\to\\\\my\\\\file, or + use Linux/Mac style forward slashes (better): C:/path/to/my/file. -### List of prompt arguments +## List of prompt arguments -After the dream.py script initializes, it will present you with a **dream>** prompt. Here you can -enter information to generate images from text (txt2img), to embellish an existing image or sketch -(img2img), or to selectively alter chosen regions of the image (inpainting). +After the dream.py script initializes, it will present you with a +**dream>** prompt. Here you can enter information to generate images +from text (txt2img), to embellish an existing image or sketch +(img2img), or to selectively alter chosen regions of the image +(inpainting). -### This is an example of txt2img +### This is an example of txt2img: -```bash -dream> "waterfall and rainbow" -W640 -H480 -``` +~~~~ +dream> waterfall and rainbow -W640 -H480 +~~~~ -This will create the requested image with the dimensions 640 (width) and 480 (height). +This will create the requested image with the dimensions 640 (width) +and 480 (height). -Those are the `dream` commands that apply to txt2img: +Here are the dream> command that apply to txt2img: -| Argument | Shortcut | Default | Description | -| --------------------------- | ---------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| "my prompt" | | | Text prompt to use. The quotation marks are optional. | -| --width | -W | 512 | Width of generated image | -| --height | -H | 512 | Height of generated image | -| --iterations | -n | 1 | How many images to generate from this prompt | -| --steps | -s | 50 | How many steps of refinement to apply | -| --cfg_scale | -C | 7.5 | How hard to try to match the prompt to the generated image; any number greater than 0.0 works, but the useful range is roughly 5.0 to 20.0 | -| --seed | -S | None | Set the random seed for the next series of images. This can be used to recreate an image generated previously. | -| --sampler | -A | k_lms | Sampler to use. Use -h to get list of available samplers. | -| --grid | -g | False | Turn on grid mode to return a single image combining all the images generated by this prompt | -| --individual | -i | True | Turn off grid mode (deprecated; leave off --grid instead) | -| --outdir | -o | outputs/img_samples | Temporarily change the location of these images | -| --seamless | | False | Activate seamless tiling for interesting effects | -| --log_tokenization | -t | False | Display a color-coded list of the parsed tokens derived from the prompt | -| --skip_normalization | -x | False | Weighted subprompts will not be normalized. See [Weighted Prompts](./OTHER.md#weighted-prompts) | -| --upscale | -U | -U 1 0.75 | Upscale image by magnification factor (2, 4), and set strength of upscaling (0.0-1.0). If strength not set, will default to 0.75. | -| --gfpgan_strength | -G | -G0 | Fix faces using the GFPGAN algorithm; argument indicates how hard the algorithm should try (0.0-1.0) | -| --save_original | -save_orig | False | When upscaling or fixing faces, this will cause the original image to be saved rather than replaced. | -| --variation | -v | 0.0 | Add a bit of noise (0.0=none, 1.0=high) to the image in order to generate a series of variations. Usually used in combination with -S and -n to generate a series a riffs on a starting image. See [Variations](./VARIATIONS.md). | -| --with_variations | -V | None | Combine two or more variations. See [Variations](./VARIATIONS.md) for now to use this. | +| Argument | Shortcut | Default | Description | +|--------------------|------------|---------------------|--------------| +| "my prompt" | | | Text prompt to use. The quotation marks are optional. | +| --width | -W | 512 | Width of generated image | +| --height | -H | 512 | Height of generated image | +| --iterations | -n | 1 | How many images to generate from this prompt | +| --steps | -s | 50 | How many steps of refinement to apply | +| --cfg_scale | -C | 7.5 | How hard to try to match the prompt to the generated image; any number greater than 1.0 works, but the useful range is roughly 5.0 to 20.0 | +| --seed | -S | None | Set the random seed for the next series of images. This can be used to recreate an image generated previously.| +| --sampler | -A| k_lms | Sampler to use. Use -h to get list of available samplers. | +| --grid | -g | False | Turn on grid mode to return a single image combining all the images generated by this prompt | +| --individual | -i | True | Turn off grid mode (deprecated; leave off --grid instead) | +| --outdir | -o | outputs/img_samples | Temporarily change the location of these images | +| --seamless | | False | Activate seamless tiling for interesting effects | +| --log_tokenization | -t | False | Display a color-coded list of the parsed tokens derived from the prompt | +| --skip_normalization| -x | False | Weighted subprompts will not be normalized. See [Weighted Prompts](./OTHER.md#weighted-prompts) | +| --upscale | -U | -U 1 0.75| Upscale image by magnification factor (2, 4), and set strength of upscaling (0.0-1.0). If strength not set, will default to 0.75. | +| --gfpgan_strength | -G | -G0 | Fix faces using the GFPGAN algorithm; argument indicates how hard the algorithm should try (0.0-1.0) | +| --save_original | -save_orig| False | When upscaling or fixing faces, this will cause the original image to be saved rather than replaced. | +| --variation |-v| 0.0 | Add a bit of noise (0.0=none, 1.0=high) to the image in order to generate a series of variations. Usually used in combination with -S and -n to generate a series a riffs on a starting image. See [Variations](./VARIATIONS.md). | +| --with_variations | -V| None | Combine two or more variations. See [Variations](./VARIATIONS.md) for now to use this. | -Note that the width and height of the image must be multiples of 64. You can provide different -values, but they will be rounded down to the nearest multiple of 64. +Note that the width and height of the image must be multiples of +64. You can provide different values, but they will be rounded down to +the nearest multiple of 64. -### This is an example of img2img -```bash +### This is an example of img2img: + +~~~~ dream> waterfall and rainbow -I./vacation-photo.png -W640 -H480 --fit -``` +~~~~ -This will modify the indicated vacation photograph by making it more like the prompt. Results will -vary greatly depending on what is in the image. We also ask to --fit the image into a box no bigger -than 640x480. Otherwise the image size will be identical to the provided photo and you may run out -of memory if it is large. +This will modify the indicated vacation photograph by making it more +like the prompt. Results will vary greatly depending on what is in the +image. We also ask to --fit the image into a box no bigger than +640x480. Otherwise the image size will be identical to the provided +photo and you may run out of memory if it is large. -In addition to the command-line options recognized by txt2img, img2img accepts additional options: +In addition to the command-line options recognized by txt2img, img2img +accepts additional options: -| Argument | Shortcut | Default | Description | -| ------------------ | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| --init_img | -I | None | Path to the initialization image | -| --fit | -F | False | Scale the image to fit into the specified -H and -W dimensions | -| --strength | -s | 0.75 | How hard to try to match the prompt to the initial image. Ranges from 0.0-0.99, with higher values replacing the initial image completely. | +| Argument | Shortcut | Default | Description | +|--------------------|------------|---------------------|--------------| +| --init_img | -I | None | Path to the initialization image | +| --fit | -F | False | Scale the image to fit into the specified -H and -W dimensions | +| --strength | -s | 0.75 | How hard to try to match the prompt to the initial image. Ranges from 0.0-0.99, with higher values replacing the initial image completely.| -### This is an example of inpainting +### This is an example of inpainting: -```bash -dream> "waterfall and rainbow" -I./vacation-photo.png -M./vacation-mask.png -W640 -H480 --fit -``` +~~~~ +dream> waterfall and rainbow -I./vacation-photo.png -M./vacation-mask.png -W640 -H480 --fit +~~~~ -This will do the same thing as img2img, but image alterations will only occur within transparent -areas defined by the mask file specified by -M. You may also supply just a single initial image with -the areas to overpaint made transparent, but you must be careful not to destroy the pixels -underneath when you create the transparent areas. See [Inpainting](./INPAINTING.md) for details. +This will do the same thing as img2img, but image alterations will +only occur within transparent areas defined by the mask file specified +by -M. You may also supply just a single initial image with the areas +to overpaint made transparent, but you must be careful not to destroy +the pixels underneath when you create the transparent areas. See +[Inpainting](./INPAINTING.md) for details. -inpainting accepts all the arguments used for txt2img and img2img, as well as the --mask (-M) -argument: +inpainting accepts all the arguments used for txt2img and img2img, as +well as the --mask (-M) argument: -| Argument | Shortcut | Default | Description | -| ------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------ | -| --init_mask | -M | None | Path to an image the same size as the initial_image, with areas for inpainting made transparent. | +| Argument | Shortcut | Default | Description | +|--------------------|------------|---------------------|--------------| +| --init_mask | -M | None |Path to an image the same size as the initial_image, with areas for inpainting made transparent.| -## Command-line editing and completion -If you are on a Macintosh or Linux machine, the command-line offers convenient history tracking, -editing, and command completion. +# Command-line editing and completion -- To scroll through previous commands and potentially edit/reuse them, use the up and down cursor - keys. -- To edit the current command, use the left and right cursor keys to position the cursor, and then - backspace, delete or insert characters. +If you are on a Macintosh or Linux machine, the command-line offers +convenient history tracking, editing, and command completion. + +- To scroll through previous commands and potentially edit/reuse them, use the up and down cursor keys. +- To edit the current command, use the left and right cursor keys to position the cursor, and then backspace, delete or insert characters. - To move to the very beginning of the command, type CTRL-A (or command-A on the Mac) - To move to the end of the command, type CTRL-E. -- To cut a section of the command, position the cursor where you want to start cutting and type - CTRL-K. +- To cut a section of the command, position the cursor where you want to start cutting and type CTRL-K. - To paste a cut section back in, position the cursor where you want to paste, and type CTRL-Y -Windows users can get similar, but more limited, functionality if they launch dream.py with the -"winpty" program: +Windows users can get similar, but more limited, functionality if they +launch dream.py with the "winpty" program: -``` +~~~ > winpty python scripts\dream.py -``` +~~~ -On the Mac and Linux platforms, when you exit dream.py, the last 1000 lines of your command-line -history will be saved. When you restart dream.py, you can access the saved history using the -up-arrow key. +On the Mac and Linux platforms, when you exit dream.py, the last 1000 +lines of your command-line history will be saved. When you restart +dream.py, you can access the saved history using the up-arrow key. -In addition, limited command-line completion is installed. In various contexts, you can start typing -your command and press tab. A list of potential completions will be presented to you. You can then -type a little more, hit tab again, and eventually autocomplete what you want. +In addition, limited command-line completion is installed. In various +contexts, you can start typing your command and press tab. A list of +potential completions will be presented to you. You can then type a +little more, hit tab again, and eventually autocomplete what you want. -When specifying file paths using the one-letter shortcuts, the CLI will attempt to complete -pathnames for you. This is most handy for the -I (init image) and -M (init mask) paths. To initiate -completion, start the path with a slash ("/") or "./". For example: +When specifying file paths using the one-letter shortcuts, the CLI +will attempt to complete pathnames for you. This is most handy for the +-I (init image) and -M (init mask) paths. To initiate completion, start +the path with a slash ("/") or "./". For example: -``` +~~~ dream> zebra with a mustache -I./test-pictures -I./test-pictures/Lincoln-and-Parrot.png -I./test-pictures/zebra.jpg -I./test-pictures/madonna.png -I./test-pictures/bad-sketch.png -I./test-pictures/man_with_eagle/ ``` -You can then type "z", hit tab again, and it will autofill to "zebra.jpg". +You can then type ++z++, hit ++tab++ again, and it will autofill to `zebra.jpg`. More text completion features (such as autocompleting seeds) are on their way. diff --git a/docs/features/EMBIGGEN.md b/docs/features/EMBIGGEN.md index 70f35fe758..92b379b64b 100644 --- a/docs/features/EMBIGGEN.md +++ b/docs/features/EMBIGGEN.md @@ -1,4 +1,10 @@ -# **Embiggen -- upscale your images on limited memory machines** +--- +title: Embiggen +--- + +# :material-loupe: Embiggen + +**upscale your images on limited memory machines** GFPGAN and Real-ESRGAN are both memory intensive. In order to avoid crashes and memory overloads during the Stable Diffusion process, @@ -16,7 +22,7 @@ face restore a particular generated image, pass it again with the same prompt and generated seed along with the `-U` and `-G` prompt arguments to perform those actions. -## Embiggen +## Embiggen If you wanted to be able to do more (pixels) without running out of VRAM, or you want to upscale with details that couldn't possibly appear @@ -37,7 +43,7 @@ it's similar to that, except it can work up to an arbitrarily large size has extra logic to re-run any number of the tile sub-sections of the image if for example a small part of a huge run got messed up. -**Usage** +## Usage `-embiggen ` @@ -55,7 +61,6 @@ and it can also be less than one if the init_img is too big. Esrgan_strength defaults to 0.75, and the overlap_ratio defaults to 0.25, both are optional. - Unlike Img2Img, the `--width` (`-W`) and `--height` (`-H`) arguments do not control the size of the image as a whole, but the size of the tiles used to Embiggen the image. @@ -95,12 +100,12 @@ Tiles are numbered starting with one, and left-to-right, top-to-bottom. So, if you are generating a 3x3 tiled image, the middle row would be `4 5 6`. -**Example Usage** +## Example Usage Running Embiggen with 512x512 tiles on an existing image, scaling up by a factor of 2.5x; and doing the same again (default ESRGAN strength is 0.75, default overlap between tiles is 0.25): -``` +```bash dream > a photo of a forest at sunset -s 100 -W 512 -H 512 -I outputs/forest.png -f 0.4 -embiggen 2.5 dream > a photo of a forest at sunset -s 100 -W 512 -H 512 -I outputs/forest.png -f 0.4 -embiggen 2.5 0.75 0.25 ``` @@ -112,12 +117,28 @@ If there weren't enough clouds in the sky of that forest you just made 512x512 tiles with 0.25 overlaps wide) we can replace that top row of tiles: -``` +```bash dream> a photo of puffy clouds over a forest at sunset -s 100 -W 512 -H 512 -I outputs/000002.seed.png -f 0.5 -embiggen_tiles 1 2 3 ``` -**Note** +## Fixing Previously-Generated Images +It is easy to apply embiggen to any previously-generated file without having to +look up the original prompt and provide an initial image. Just use the +syntax `!fix path/to/file.png `. For example, you can rewrite the +previous command to look like this: + +~~~~ +dream> !fix ./outputs/000002.seed.png -embiggen_tiles 1 2 3 +~~~~ + +A new file named `000002.seed.fixed.png` will be created in the output directory. Note that +the `!fix` command does not replace the original file, unlike the behavior at generate time. +You do not need to provide the prompt, and `!fix` automatically selects a good strength for +embiggen-ing. + + +**Note** Because the same prompt is used on all the tiled images, and the model doesn't have the context of anything outside the tile being run - it can end up creating repeated pattern (also called 'motifs') across all @@ -128,7 +149,6 @@ create the detail. Anecdotally `--strength` 0.35-0.45 works pretty well on most things. It may also work great in some examples even with the `--strength` set high for patterns, landscapes, or subjects that are more abstract. Because this is (relatively) fast, you can also -always create a few Embiggen'ed images and manually composite them to preserve the best parts from each. -Author: [Travco](https://github.com/travco) \ No newline at end of file +Author: [Travco](https://github.com/travco) diff --git a/docs/features/IMG2IMG.md b/docs/features/IMG2IMG.md index eeaecbf981..e61f365c01 100644 --- a/docs/features/IMG2IMG.md +++ b/docs/features/IMG2IMG.md @@ -2,7 +2,8 @@ title: Image-to-Image --- -## **IMG2IMG** +# :material-image-multiple: **IMG2IMG** + This script also provides an `img2img` feature that lets you seed your creations with an initial drawing or photo. This is a really cool feature that tells stable diffusion to build the prompt on top of the image you provide, preserving the original's basic shape and layout. To use it, provide diff --git a/docs/features/INPAINTING.md b/docs/features/INPAINTING.md index 85f631ac12..497bbc7a48 100644 --- a/docs/features/INPAINTING.md +++ b/docs/features/INPAINTING.md @@ -2,6 +2,8 @@ title: Inpainting --- +# :octicons-paintbrush-16: Inpainting + ## **Creating Transparent Regions for Inpainting** Inpainting is really cool. To do it, you start with an initial image and use a photoeditor to make @@ -26,6 +28,8 @@ dream> "man with cat on shoulder" -I./images/man.png -M./images/man-transparent. We are hoping to get rid of the need for this workaround in an upcoming release. +--- + ## Recipe for GIMP [GIMP](https://www.gimp.org/) is a popular Linux photoediting tool. @@ -34,8 +38,39 @@ We are hoping to get rid of the need for this workaround in an upcoming release. 2. Layer->Transparency->Add Alpha Channel 3. Use lasoo tool to select region to mask 4. Choose Select -> Float to create a floating selection -5. Open the Layers toolbar (^L) and select "Floating Selection" +5. Open the Layers toolbar (++ctrl+l++) and select "Floating Selection" 6. Set opacity to 0% 7. Export as PNG -8. In the export dialogue, Make sure the "Save colour values from transparent pixels" checkbox is - selected. +8. In the export dialogue, Make sure the "Save colour values from + transparent pixels" checkbox is selected. + + +## Recipe for Adobe Photoshop + +1. Open image in Photoshop + +![step1](../assets/step1.png) + +2. Use any of the selection tools (Marquee, Lasso, or Wand) to select the area you desire to inpaint. + +![step2](../assets/step2.png) + +3. Because we'll be applying a mask over the area we want to preserve, you should now select the inverse by using the ++shift+ctrl+i++ shortcut, or right clicking and using the "Select Inverse" option. + +4. You'll now create a mask by selecting the image layer, and Masking the selection. Make sure that you don't delete any of the undrlying image, or your inpainting results will be dramatically impacted. + +![step4](../assets/step4.png) + +5. Make sure to hide any background layers that are present. You should see the mask applied to your image layer, and the image on your canvas should display the checkered background. + +![step5](../assets/step5.png) + +6. Save the image as a transparent PNG by using the "Save a Copy" option in the File menu, or using the Alt + Ctrl + S keyboard shortcut + +![step6](../assets/step6.png) + +7. After following the inpainting instructions above (either through the CLI or the Web UI), marvel at your newfound ability to selectively dream. Lookin' good! + +![step7](../assets/step7.png) + +8. In the export dialogue, Make sure the "Save colour values from transparent pixels" checkbox is selected. diff --git a/docs/features/OTHER.md b/docs/features/OTHER.md index 5bede1f89b..5faa1d32d0 100644 --- a/docs/features/OTHER.md +++ b/docs/features/OTHER.md @@ -2,6 +2,8 @@ title: Others --- +# :fontawesome-regular-share-from-square: Others + ## **Google Colab** Stable Diffusion AI Notebook: "pond garden with lotus by claude monet" --seamless -s100 -n4 --- -## **Reading Prompts from a File** - -You can automate `dream.py` by providing a text file with the prompts you want to run, one line per -prompt. The text file must be composed with a text editor (e.g. Notepad) and not a word processor. -Each line should look like what you would type at the dream> prompt: - -```bash -a beautiful sunny day in the park, children playing -n4 -C10 -stormy weather on a mountain top, goats grazing -s100 -innovative packaging for a squid's dinner -S137038382 -``` - -Then pass this file's name to `dream.py` when you invoke it: - -```bash -(ldm) ~/stable-diffusion$ python3 scripts/dream.py --from_file "path/to/prompts.txt" -``` - -You may read a series of prompts from standard input by providing a filename of `-`: - -```bash -(ldm) ~/stable-diffusion$ echo "a beautiful day" | python3 scripts/dream.py --from_file - -``` - ---- - ## **Shortcuts: Reusing Seeds** Since it is so common to reuse seeds while refining a prompt, there is now a shortcut as of version @@ -79,22 +55,6 @@ outputs/img-samples/000040.3498014304.png: "a cute child playing hopscotch" -G1. --- -## **Weighted Prompts** - -You may weight different sections of the prompt to tell the sampler to attach different levels of -priority to them, by adding `:(number)` to the end of the section you wish to up- or downweight. For -example consider this prompt: - -```bash -tabby cat:0.25 white duck:0.75 hybrid -``` - -This will tell the sampler to invest 25% of its effort on the tabby cat aspect of the image and 75% -on the white duck aspect (surprisingly, this example actually works). The prompt weights can use any -combination of integers and floating point numbers, and they do not need to add up to 1. - ---- - ## **Simplified API** For programmers who wish to incorporate stable-diffusion into other products, this repository diff --git a/docs/features/OUTPAINTING.md b/docs/features/OUTPAINTING.md new file mode 100644 index 0000000000..9f72a5cb3c --- /dev/null +++ b/docs/features/OUTPAINTING.md @@ -0,0 +1,78 @@ +--- +title: Outpainting +--- + +# :octicons-paintbrush-16: Outpainting + +## Continous outpainting + +This extension uses the inpainting code to extend an existing image to +any direction of "top", "right", "bottom" or "left". To use it you +need to provide an initial image with -I and an extension direction +with -D (direction). When extending using outpainting a higher img2img +strength value of 0.83 is the default. + +The code is not foolproof. Sometimes it will do a good job extending +the image, and other times it will generate ghost images and other +artifacts. In addition, the code works best on images that were +generated by dream.py, because it will be able to recover the original +prompt that generated the file and "understand" what you are trying to +achieve. + +### Basic Usage + +To illustrate, consider this image generated with the prompt "fantasy +portrait of eleven princess." It's nice, but rather annoying that the +top of the head has been cropped off. + +![elven_princess](../assets/outpainting/elven_princess.png) + +We can fix that using the `!fix` command! + +~~~~ +dream> !fix my_images/elven_princess.png -D top 50 +~~~~ + +This is telling dream.py to open up a rectangle 50 pixels high at the +top of the image and outpaint into it. The result is: + +![elven_princess.fixed](../assets/outpainting/elven_princess.outpainted.png) + +Viola! You can similarly specify `bottom`, `left` or `right` to +outpaint into these margins. + +There are some limitations to be aware of: + +1. You cannot change the size of the image rectangle. In the example, + notice that the whole image is shifted downwards by 50 pixels, rather + than the top being extended upwards. + +2. Attempting to outpaint larger areas will frequently give rise to ugly + ghosting effects. + +3. For best results, try increasing the step number. + +4. If you don't specify a pixel value in -D, it will default to half + of the whole image, which is likely not what you want. + +You can do more with `!fix` including upscaling and facial +reconstruction of previously-generated images. See +[./UPSCALE.md#fixing-previously-generated-images] for the details. + +### Advanced Usage + +For more control over the outpaintihg process, you can provide the +`-D` option at image generation time. This allows you to apply all the +controls, including the ability to resize the image and apply face-fixing +and upscaling. For example: + +~~~~ +dream> man with cat on shoulder -I./images/man.png -D bottom 100 -W960 -H960 -fit +~~~~ + +Or even shorter, since the prompt is read from the metadata of the old image: + +~~~~ +dream> -I./images/man.png -D bottom 100 -W960 -H960 -fit -U2 -G1 +~~~~ + diff --git a/docs/features/PROMPTS.md b/docs/features/PROMPTS.md new file mode 100644 index 0000000000..f3bb9cef86 --- /dev/null +++ b/docs/features/PROMPTS.md @@ -0,0 +1,90 @@ +--- +title: Prompting Features +--- + +# :octicons-command-palette-24: Prompting Features + +## **Reading Prompts from a File** + +You can automate `dream.py` by providing a text file with the prompts you want to run, one line per +prompt. The text file must be composed with a text editor (e.g. Notepad) and not a word processor. +Each line should look like what you would type at the dream> prompt: + +```bash +a beautiful sunny day in the park, children playing -n4 -C10 +stormy weather on a mountain top, goats grazing -s100 +innovative packaging for a squid's dinner -S137038382 +``` + +Then pass this file's name to `dream.py` when you invoke it: + +```bash +(ldm) ~/stable-diffusion$ python3 scripts/dream.py --from_file "path/to/prompts.txt" +``` + +You may read a series of prompts from standard input by providing a filename of `-`: + +```bash +(ldm) ~/stable-diffusion$ echo "a beautiful day" | python3 scripts/dream.py --from_file - +``` + +--- + +## **Weighted Prompts** + +You may weight different sections of the prompt to tell the sampler to attach different levels of +priority to them, by adding `:(number)` to the end of the section you wish to up- or downweight. For +example consider this prompt: + +```bash +tabby cat:0.25 white duck:0.75 hybrid +``` + +This will tell the sampler to invest 25% of its effort on the tabby cat aspect of the image and 75% +on the white duck aspect (surprisingly, this example actually works). The prompt weights can use any +combination of integers and floating point numbers, and they do not need to add up to 1. + +--- + +## **Negative and Unconditioned Prompts** + +Any words between a pair of square brackets will try and be ignored by Stable Diffusion's model during generation of images. + +```bash +this is a test prompt [not really] to make you understand [cool] how this works. +``` + +In the above statement, the words 'not really cool` will be ignored by Stable Diffusion. + +Here's a prompt that depicts what it does. + +original prompt: + +`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180` + +![step1](../assets/negative_prompt_walkthru/step1.png) + +That image has a woman, so if we want the horse without a rider, we can influence the image not to have a woman by putting [woman] in the prompt, like this: + +`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180` + +![step2](../assets/negative_prompt_walkthru/step2.png) + +That's nice - but say we also don't want the image to be quite so blue. We can add "blue" to the list of negative prompts, so it's now [woman blue]: + +`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180` + +![step3](../assets/negative_prompt_walkthru/step3.png) + +Getting close - but there's no sense in having a saddle when our horse doesn't have a rider, so we'll add one more negative prompt: [woman blue saddle]. + +`#!bash "A fantastical translucent poney made of water and foam, ethereal, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve [woman blue saddle]" -s 20 -W 512 -H 768 -C 7.5 -A k_euler_a -S 1654590180` + +![step4](../assets/negative_prompt_walkthru/step4.png) + +!!! notes "Notes about this feature:" + + * The only requirement for words to be ignored is that they are in between a pair of square brackets. + * You can provide multiple words within the same bracket. + * You can provide multiple brackets with multiple words in different places of your prompt. That works just fine. + * To improve typical anatomy problems, you can add negative prompts like `[bad anatomy, extra legs, extra arms, extra fingers, poorly drawn hands, poorly drawn feet, disfigured, out of frame, tiling, bad art, deformed, mutated]`. diff --git a/docs/features/TEXTUAL_INVERSION.md b/docs/features/TEXTUAL_INVERSION.md index b8dbc21192..50532968a8 100644 --- a/docs/features/TEXTUAL_INVERSION.md +++ b/docs/features/TEXTUAL_INVERSION.md @@ -2,6 +2,8 @@ title: TEXTUAL_INVERSION --- +# :material-file-document-plus-outline: TEXTUAL_INVERSION + ## **Personalizing Text-to-Image Generation** You may personalize the generated images to provide your own styles or objects @@ -39,7 +41,7 @@ and one with the init word provided. On a RTX3090, the process for SD will take ~1h @1.6 iterations/sec. -!!! Info _Note_ +!!! note According to the associated paper, the optimal number of images is 3-5. Your model may not converge if you use more images than @@ -57,9 +59,7 @@ Once the model is trained, specify the trained .pt or .bin file when starting dream using ```bash -python3 ./scripts/dream.py \ - --embedding_path /path/to/embedding.pt \ - --full_precision +python3 ./scripts/dream.py --embedding_path /path/to/embedding.pt ``` Then, to utilize your subject at the dream prompt diff --git a/docs/features/UPSCALE.md b/docs/features/UPSCALE.md index 231e6bfbb6..f4c06f8b42 100644 --- a/docs/features/UPSCALE.md +++ b/docs/features/UPSCALE.md @@ -2,76 +2,91 @@ title: Upscale --- -## **GFPGAN and Real-ESRGAN Support** +## Intro -The script also provides the ability to do face restoration and upscaling with the help of GFPGAN -and Real-ESRGAN respectively. +The script provides the ability to restore faces and upscale. You can apply +these operations at the time you generate the images, or at any time to a +previously-generated PNG file, using the +[!fix](#fixing-previously-generated-images) command. -As of version 1.14, environment.yaml will install the Real-ESRGAN package into the standard install -location for python packages, and will put GFPGAN into a subdirectory of "src" in the -stable-diffusion directory. (The reason for this is that the standard GFPGAN distribution has a -minor bug that adversely affects image color.) Upscaling with Real-ESRGAN should "just work" without -further intervention. Simply pass the --upscale (-U) option on the dream> command line, or indicate -the desired scale on the popup in the Web GUI. +## Face Fixing -For **GFPGAN** to work, there is one additional step needed. You will need to download and copy the -GFPGAN [models file](https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth) -into **src/gfpgan/experiments/pretrained_models**. On Mac and Linux systems, here's how you'd do it -using **wget**: +The default face restoration module is GFPGAN. The default upscale is +Real-ESRGAN. For an alternative face restoration module, see [CodeFormer +Support] below. + +As of version 1.14, environment.yaml will install the Real-ESRGAN package into +the standard install location for python packages, and will put GFPGAN into a +subdirectory of "src" in the stable-diffusion directory. (The reason for this is +that the standard GFPGAN distribution has a minor bug that adversely affects +image color.) Upscaling with Real-ESRGAN should "just work" without further +intervention. Simply pass the --upscale (-U) option on the dream> command line, +or indicate the desired scale on the popup in the Web GUI. + +For **GFPGAN** to work, there is one additional step needed. You will need to +download and copy the GFPGAN +[models file](https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth) +into **src/gfpgan/experiments/pretrained_models**. On Mac and Linux systems, +here's how you'd do it using **wget**: ```bash -> wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth src/gfpgan/experiments/pretrained_models/ +> wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth src/gfpgan/experiments/pretrained_models/ ``` Make sure that you're in the stable-diffusion directory when you do this. -Alternatively, if you have GFPGAN installed elsewhere, or if you are using an earlier version of -this package which asked you to install GFPGAN in a sibling directory, you may use the -`--gfpgan_dir` argument with `dream.py` to set a custom path to your GFPGAN directory. _There are -other GFPGAN related boot arguments if you wish to customize further._ +Alternatively, if you have GFPGAN installed elsewhere, or if you are using an +earlier version of this package which asked you to install GFPGAN in a sibling +directory, you may use the `--gfpgan_dir` argument with `dream.py` to set a +custom path to your GFPGAN directory. _There are other GFPGAN related boot +arguments if you wish to customize further._ -**Note: Internet connection needed:** Users whose GPU machines are isolated from the Internet (e.g. -on a University cluster) should be aware that the first time you run dream.py with GFPGAN and -Real-ESRGAN turned on, it will try to download model files from the Internet. To rectify this, you -may run `python3 scripts/preload_models.py` after you have installed GFPGAN and all its -dependencies. +!!! warning "Internet connection needed" -## **Usage** + Users whose GPU machines are isolated from the Internet (e.g. + on a University cluster) should be aware that the first time you run dream.py with GFPGAN and + Real-ESRGAN turned on, it will try to download model files from the Internet. To rectify this, you + may run `python3 scripts/preload_models.py` after you have installed GFPGAN and all its + dependencies. + +## Usage You will now have access to two new prompt arguments. -### **Upscaling** +### Upscaling `-U : ` -The upscaling prompt argument takes two values. The first value is a scaling factor and should be -set to either `2` or `4` only. This will either scale the image 2x or 4x respectively using -different models. +The upscaling prompt argument takes two values. The first value is a scaling +factor and should be set to either `2` or `4` only. This will either scale the +image 2x or 4x respectively using different models. -You can set the scaling stength between `0` and `1.0` to control intensity of the of the scaling. -This is handy because AI upscalers generally tend to smooth out texture details. If you wish to -retain some of those for natural looking results, we recommend using values between `0.5 to 0.8`. +You can set the scaling stength between `0` and `1.0` to control intensity of +the of the scaling. This is handy because AI upscalers generally tend to smooth +out texture details. If you wish to retain some of those for natural looking +results, we recommend using values between `0.5 to 0.8`. If you do not explicitly specify an upscaling_strength, it will default to 0.75. -### **Face Restoration** +### Face Restoration `-G : ` -This prompt argument controls the strength of the face restoration that is being applied. Similar to -upscaling, values between `0.5 to 0.8` are recommended. +This prompt argument controls the strength of the face restoration that is being +applied. Similar to upscaling, values between `0.5 to 0.8` are recommended. -You can use either one or both without any conflicts. In cases where you use both, the image will be -first upscaled and then the face restoration process will be executed to ensure you get the highest -quality facial features. +You can use either one or both without any conflicts. In cases where you use +both, the image will be first upscaled and then the face restoration process +will be executed to ensure you get the highest quality facial features. `--save_orig` -When you use either `-U` or `-G`, the final result you get is upscaled or face modified. If you want -to save the original Stable Diffusion generation, you can use the `-save_orig` prompt argument to -save the original unaffected version too. +When you use either `-U` or `-G`, the final result you get is upscaled or face +modified. If you want to save the original Stable Diffusion generation, you can +use the `-save_orig` prompt argument to save the original unaffected version +too. -### **Example Usage** +### Example Usage ```bash dream> superman dancing with a panda bear -U 2 0.6 -G 0.4 @@ -83,17 +98,80 @@ This also works with img2img: dream> a man wearing a pineapple hat -I path/to/your/file.png -U 2 0.5 -G 0.6 ``` -### **Note** +!!! note -GFPGAN and Real-ESRGAN are both memory intensive. In order to avoid crashes and memory overloads -during the Stable Diffusion process, these effects are applied after Stable Diffusion has completed -its work. + GFPGAN and Real-ESRGAN are both memory intensive. In order to avoid crashes and memory overloads + during the Stable Diffusion process, these effects are applied after Stable Diffusion has completed + its work. -In single image generations, you will see the output right away but when you are using multiple -iterations, the images will first be generated and then upscaled and face restored after that -process is complete. While the image generation is taking place, you will still be able to preview -the base images. + In single image generations, you will see the output right away but when you are using multiple + iterations, the images will first be generated and then upscaled and face restored after that + process is complete. While the image generation is taking place, you will still be able to preview + the base images. -If you wish to stop during the image generation but want to upscale or face restore a particular -generated image, pass it again with the same prompt and generated seed along with the `-U` and `-G` -prompt arguments to perform those actions. +If you wish to stop during the image generation but want to upscale or face +restore a particular generated image, pass it again with the same prompt and +generated seed along with the `-U` and `-G` prompt arguments to perform those +actions. + +## CodeFormer Support + +This repo also allows you to perform face restoration using +[CodeFormer](https://github.com/sczhou/CodeFormer). + +In order to setup CodeFormer to work, you need to download the models like with +GFPGAN. You can do this either by running `preload_models.py` or by manually +downloading the +[model file](https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth) +and saving it to `ldm/restoration/codeformer/weights` folder. + +You can use `-ft` prompt argument to swap between CodeFormer and the default +GFPGAN. The above mentioned `-G` prompt argument will allow you to control the +strength of the restoration effect. + +### Usage: + +The following command will perform face restoration with CodeFormer instead of +the default gfpgan. + +` -G 0.8 -ft codeformer` + +### Other Options: + +- `-cf` - cf or CodeFormer Fidelity takes values between `0` and `1`. 0 produces + high quality results but low accuracy and 1 produces lower quality results but + higher accuacy to your original face. + +The following command will perform face restoration with CodeFormer. CodeFormer +will output a result that is closely matching to the input face. + +` -G 1.0 -ft codeformer -cf 0.9` + +The following command will perform face restoration with CodeFormer. CodeFormer +will output a result that is the best restoration possible. This may deviate +slightly from the original face. This is an excellent option to use in +situations when there is very little facial data to work with. + +` -G 1.0 -ft codeformer -cf 0.1` + +## Fixing Previously-Generated Images + +It is easy to apply face restoration and/or upscaling to any +previously-generated file. Just use the syntax +`!fix path/to/file.png `. For example, to apply GFPGAN at strength 0.8 +and upscale 2X for a file named `./outputs/img-samples/000044.2945021133.png`, +just run: + +``` +dream> !fix ./outputs/img-samples/000044.2945021133.png -G 0.8 -U 2 +``` + +A new file named `000044.2945021133.fixed.png` will be created in the output +directory. Note that the `!fix` command does not replace the original file, +unlike the behavior at generate time. + +### Disabling: + +If, for some reason, you do not wish to load the GFPGAN and/or ESRGAN libraries, +you can disable them on the dream.py command line with the `--no_restore` and +`--no_upscale` options, respectively. diff --git a/docs/features/VARIATIONS.md b/docs/features/VARIATIONS.md index 23b6cf2917..e021c300fb 100644 --- a/docs/features/VARIATIONS.md +++ b/docs/features/VARIATIONS.md @@ -2,6 +2,10 @@ title: Variations --- +# :material-tune-variant: Variations + +## Intro + Release 1.13 of SD-Dream adds support for image variations. You are able to do the following: @@ -29,7 +33,7 @@ This will be indicated as `prompt` in the examples below. First we let SD create a series of images in the usual way, in this case requesting six iterations: -``` +```bash dream> lucy lawless as xena, warrior princess, character portrait, high resolution -n6 ... Outputs: @@ -41,8 +45,6 @@ Outputs: ./outputs/Xena/000001.3357757885.png: "prompt" -s50 -W512 -H512 -C7.5 -Ak_lms -S3357757885 ``` -The one with seed 3357757885 looks nice: - ![var1](../assets/variation_walkthru/000001.3357757885.png) --- @@ -75,7 +77,7 @@ used to generate it. This gives us a series of closely-related variations, including the two shown here. -![var2](../assets/variation_walkthru/000002.3647897225.png) +![var2](../assets/variation_walkthru/000002.3647897225.png) ![var3](../assets/variation_walkthru/000002.1614299449.png) @@ -83,7 +85,7 @@ I like the expression on Xena's face in the first one (subseed 3647897225), and the armor on her shoulder in the second one (subseed 1614299449). Can we combine them to get the best of both worlds? -We combine the two variations using `-V` (--with_variations). Again, we must +We combine the two variations using `-V` (`--with_variations`). Again, we must provide the seed for the originally-chosen image in order for this to work. ```bash @@ -102,6 +104,7 @@ generate more variations around the almost-but-not-quite image. We do the latter, using both the `-V` (combining) and `-v` (variation strength) options. Note that we use `-n6` to generate 6 variations: +```bash dream> "prompt" -S3357757885 -V3647897225,0.1,1614299449,0.1 -v0.05 -n6 Outputs: ./outputs/Xena/000004.3279757577.png: "prompt" -s50 -W512 -H512 -C7.5 -Ak_lms -V 3647897225:0.1,1614299449:0.1,3279757577:0.05 -S3357757885 diff --git a/docs/features/WEB.md b/docs/features/WEB.md index 1bbf5f3b55..833b18cdfc 100644 --- a/docs/features/WEB.md +++ b/docs/features/WEB.md @@ -2,8 +2,10 @@ title: Barebones Web Server --- +# :material-web: Barebones Web Server + As of version 1.10, this distribution comes with a bare bones web server (see -screenshot). To use it, run the `dream.py` script by adding the `**--web**` +screenshot). To use it, run the `dream.py` script by adding the `--web` option. ```bash diff --git a/docs/help/SAMPLER_CONVERGENCE.md b/docs/help/SAMPLER_CONVERGENCE.md new file mode 100644 index 0000000000..5dfee5dc4e --- /dev/null +++ b/docs/help/SAMPLER_CONVERGENCE.md @@ -0,0 +1,141 @@ +--- +title: SAMPLER CONVERGENCE +--- + +## *Sampler Convergence* + +As features keep increasing, making the right choices for your needs can become increasingly difficult. What sampler to use? And for how many steps? Do you change the CFG value? Do you use prompt weighting? Do you allow variations? + +Even once you have a result, do you blend it with other images? Pass it through `img2img`? With what strength? Do you use inpainting to correct small details? Outpainting to extend cropped sections? + +The purpose of this series of documents is to help you better understand these tools, so you can make the best out of them. Feel free to contribute with your own findings! + +In this document, we will talk about sampler convergence. + +Looking for a short version? Here's a TL;DR in 3 tables. + +| Remember | +|:---| +| Results converge as steps (`-s`) are increased (except for `K_DPM_2_A` and `K_EULER_A`). Often at ≥ `-s100`, but may require ≥ `-s700`). | +| Producing a batch of candidate images at low (`-s8` to `-s30`) step counts can save you hours of computation. | +| `K_HEUN` and `K_DPM_2` converge in less steps (but are slower). | +| `K_DPM_2_A` and `K_EULER_A` incorporate a lot of creativity/variability. | + +| Sampler | (3 sample avg) it/s (M1 Max 64GB, 512x512) | +|---|---| +| `DDIM` | 1.89 | +| `PLMS` | 1.86 | +| `K_EULER` | 1.86 | +| `K_LMS` | 1.91 | +| `K_HEUN` | 0.95 (slower) | +| `K_DPM_2` | 0.95 (slower) | +| `K_DPM_2_A` | 0.95 (slower) | +| `K_EULER_A` | 1.86 | + +| Suggestions | +|:---| +| For most use cases, `K_LMS`, `K_HEUN` and `K_DPM_2` are the best choices (the latter 2 run 0.5x as quick, but tend to converge 2x as quick as `K_LMS`). At very low steps (≤ `-s8`), `K_HEUN` and `K_DPM_2` are not recommended. Use `K_LMS` instead.| +| For variability, use `K_EULER_A` (runs 2x as quick as `K_DPM_2_A`). | + +--- + +### *Sampler results* + +Let's start by choosing a prompt and using it with each of our 8 samplers, running it for 10, 20, 30, 40, 50 and 100 steps. + +Anime. `"an anime girl" -W512 -H512 -C7.5 -S3031912972` + +![191636411-083c8282-6ed1-4f78-9273-ee87c0a0f1b6-min (1)](https://user-images.githubusercontent.com/50542132/191868725-7f7af991-e254-4c1f-83e7-bed8c9b2d34f.png) + +### *Sampler convergence* + +Immediately, you can notice results tend to converge -that is, as `-s` (step) values increase, images look more and more similar until there comes a point where the image no longer changes. + +You can also notice how `DDIM` and `PLMS` eventually tend to converge to K-sampler results as steps are increased. +Among K-samplers, `K_HEUN` and `K_DPM_2` seem to require the fewest steps to converge, and even at low step counts they are good indicators of the final result. And finally, `K_DPM_2_A` and `K_EULER_A` seem to do a bit of their own thing and don't keep much similarity with the rest of the samplers. + +### *Batch generation speedup* + +This realization is very useful because it means you don't need to create a batch of 100 images (`-n100`) at `-s100` to choose your favorite 2 or 3 images. +You can produce the same 100 images at `-s10` to `-s30` using a K-sampler (since they converge faster), get a rough idea of the final result, choose your 2 or 3 favorite ones, and then run `-s100` on those images to polish some details. +The latter technique is 3-8x as quick. + +Example: + +At 60s per 100 steps. + +(Option A) 60s * 100 images = 6000s (100 images at `-s100`, manually picking 3 favorites) + +(Option B) 6s * 100 images + 60s * 3 images = 780s (100 images at `-s10`, manually picking 3 favorites, and running those 3 at `-s100` to polish details) + +The result is 1 hour and 40 minutes (Option A) vs 13 minutes (Option B). + +### *Topic convergance* + +Now, these results seem interesting, but do they hold for other topics? How about nature? Food? People? Animals? Let's try! + +Nature. `"valley landscape wallpaper, d&d art, fantasy, painted, 4k, high detail, sharp focus, washed colors, elaborate excellent painted illustration" -W512 -H512 -C7.5 -S1458228930` + +![191736091-dda76929-00d1-4590-bef4-7314ea4ea419-min (1)](https://user-images.githubusercontent.com/50542132/191868763-b151c69e-0a72-4cf1-a151-5a64edd0c93e.png) + +With nature, you can see how initial results are even more indicative of final result -more so than with characters/people. `K_HEUN` and `K_DPM_2` are again the quickest indicators, almost right from the start. Results also converge faster (e.g. `K_HEUN` converged at `-s21`). + +Food. `"a hamburger with a bowl of french fries" -W512 -H512 -C7.5 -S4053222918` + +![191639011-f81d9d38-0a15-45f0-9442-a5e8d5c25f1f-min (1)](https://user-images.githubusercontent.com/50542132/191868898-98801a62-885f-4ea1-aee8-563503522aa9.png) + +Again, `K_HEUN` and `K_DPM_2` take the fewest number of steps to be good indicators of the final result. `K_DPM_2_A` and `K_EULER_A` seem to incorporate a lot of creativity/variability, capable of producing rotten hamburgers, but also of adding lettuce to the mix. And they're the only samplers that produced an actual 'bowl of fries'! + +Animals. `"grown tiger, full body" -W512 -H512 -C7.5 -S3721629802` + +![191771922-6029a4f5-f707-4684-9011-c6f96e25fe56-min (1)](https://user-images.githubusercontent.com/50542132/191868870-9e3b7d82-b909-429f-893a-13f6ec343454.png) + +`K_HEUN` and `K_DPM_2` once again require the least number of steps to be indicative of the final result (around `-s30`), while other samplers are still struggling with several tails or malformed back legs. + +It also takes longer to converge (for comparison, `K_HEUN` required around 150 steps to converge). This is normal, as producing human/animal faces/bodies is one of the things the model struggles the most with. For these topics, running for more steps will often increase coherence within the composition. + +People. `"Ultra realistic photo, (Miranda Bloom-Kerr), young, stunning model, blue eyes, blond hair, beautiful face, intricate, highly detailed, smooth, art by artgerm and greg rutkowski and alphonse mucha, stained glass" -W512 -H512 -C7.5 -S2131956332`. This time, we will go up to 300 steps. + +![Screenshot 2022-09-23 at 02 05 48-min (1)](https://user-images.githubusercontent.com/50542132/191871743-6802f199-0ffd-4986-98c5-df2d8db30d18.png) + +Observing the results, it again takes longer for all samplers to converge (`K_HEUN` took around 150 steps), but we can observe good indicative results much earlier (see: `K_HEUN`). Conversely, `DDIM` and `PLMS` are still undergoing moderate changes (see: lace around her neck), even at `-s300`. + +In fact, as we can see in this other experiment, some samplers can take 700+ steps to converge when generating people. + +![191988191-c586b75a-2d7f-4351-b705-83cc1149881a-min (1)](https://user-images.githubusercontent.com/50542132/191992123-7e0759d6-6220-42c4-a961-88c7071c5ee6.png) + +Note also the point of convergence may not be the most desirable state (e.g. I prefer an earlier version of the face, more rounded), but it will probably be the most coherent arms/hands/face attributes-wise. You can always merge different images with a photo editing tool and pass it through `img2img` to smoothen the composition. + +### *Sampler generation times* + +Once we understand the concept of sampler convergence, we must look into the performance of each sampler in terms of steps (iterations) per second, as not all samplers run at the same speed. + +On my M1 Max with 64GB of RAM, for a 512x512 image: +| Sampler | (3 sample average) it/s | +|---|---| +| `DDIM` | 1.89 | +| `PLMS` | 1.86 | +| `K_EULER` | 1.86 | +| `K_LMS` | 1.91 | +| `K_HEUN` | 0.95 (slower) | +| `K_DPM_2` | 0.95 (slower) | +| `K_DPM_2_A` | 0.95 (slower) | +| `K_EULER_A` | 1.86 | + +Combining our results with the steps per second of each sampler, three choices come out on top: `K_LMS`, `K_HEUN` and `K_DPM_2` (where the latter two run 0.5x as quick but tend to converge 2x as quick as `K_LMS`). For creativity and a lot of variation between iterations, `K_EULER_A` can be a good choice (which runs 2x as quick as `K_DPM_2_A`). + +Additionally, image generation at very low steps (≤ `-s8`) is not recommended for `K_HEUN` and `K_DPM_2`. Use `K_LMS` instead. + +192044949-67d5d441-a0d5-4d5a-be30-5dda4fc28a00-min + +### *Three key points* + +Finally, it is relevant to mention that, in general, there are 3 important moments in the process of image formation as steps increase: + +* The (earliest) point at which an image becomes a good indicator of the final result (useful for batch generation at low step values, to then improve the quality/coherence of the chosen images via running the same prompt and seed for more steps). + +* The (earliest) point at which an image becomes coherent, even if different from the result if steps are increased (useful for batch generation at low step values, where quality/coherence is improved via techniques other than increasing the steps -e.g. via inpainting). + +* The point at which an image fully converges. + +Hence, remember that your workflow/strategy should define your optimal number of steps, even for the same prompt and seed (for example, if you seek full convergence, you may run `K_LMS` for `-s200` in the case of the red-haired girl, but `K_LMS` and `-s20`-taking one tenth the time- may do as well if your workflow includes adding small details, such as the missing shoulder strap, via `img2img`). diff --git a/docs/help/TROUBLESHOOT.md b/docs/help/TROUBLESHOOT.md index 6458d8ceb9..d513fed7d0 100644 --- a/docs/help/TROUBLESHOOT.md +++ b/docs/help/TROUBLESHOOT.md @@ -2,6 +2,8 @@ title: F.A.Q. --- +# :material-frequently-asked-questions: F.A.Q. + ## **Frequently-Asked-Questions** Here are a few common installation problems and their solutions. Often these are caused by @@ -15,10 +17,25 @@ During `conda env create -f environment.yaml`, conda hangs indefinitely. ### **SOLUTION** -Enter the stable-diffusion directory and completely remove the `src` directory and all its contents. -The safest way to do this is to enter the stable-diffusion directory and give the command -`git clean -f`. If this still doesn't fix the problem, try "conda clean -all" and then restart at -the `conda env create` step. +Conda sometimes gets stuck at the last PIP step, in which several git repositories are +cloned and built. + +Enter the stable-diffusion directory and completely remove the `src` +directory and all its contents. The safest way to do this is to enter +the stable-diffusion directory and give the command `git clean -f`. If +this still doesn't fix the problem, try "conda clean -all" and then +restart at the `conda env create` step. + +To further understand the problem to checking the install lot using this method: + +```bash +export PIP_LOG="/tmp/pip_log.txt" +touch ${PIP_LOG} +tail -f ${PIP_LOG} & +conda env create -f environment-mac.yaml --debug --verbose +killall tail +rm ${PIP_LOG} +``` --- diff --git a/docs/index.md b/docs/index.md index bdde3cabd7..c356c2cee5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,102 +1,106 @@ --- title: Home +template: main.html --- +
-

Stable Diffusion Dream Script

+# :material-script-text-outline: Stable Diffusion Dream Script -

- -

+![project logo](assets/logo.png) -

- last-commit - stars -
- issues - pull-requests -

+[![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/lstein/stable-diffusion/development?label=CI%20status%20on%20dev&cache=900&icon=github +[CI checks on dev link]: https://github.com/lstein/stable-diffusion/actions?query=branch%3Adevelopment +[CI checks on main badge]: https://flat.badgen.net/github/checks/lstein/stable-diffusion/main?label=CI%20status%20on%20main&cache=900&icon=github +[CI checks on main link]: https://github.com/lstein/stable-diffusion/actions/workflows/test-dream-conda.yml +[discord badge]: https://flat.badgen.net/discord/members/htRgbc7e?icon=discord +[discord link]: https://discord.com/invite/htRgbc7e +[github forks badge]: https://flat.badgen.net/github/forks/lstein/stable-diffusion?icon=github +[github forks link]: https://useful-forks.github.io/?repo=lstein%2Fstable-diffusion +[github open issues badge]: https://flat.badgen.net/github/open-issues/lstein/stable-diffusion?icon=github +[github open issues link]: https://github.com/lstein/stable-diffusion/issues?q=is%3Aissue+is%3Aopen +[github open prs badge]: https://flat.badgen.net/github/open-prs/lstein/stable-diffusion?icon=github +[github open prs link]: https://github.com/lstein/stable-diffusion/pulls?q=is%3Apr+is%3Aopen +[github stars badge]: https://flat.badgen.net/github/stars/lstein/stable-diffusion?icon=github +[github stars link]: https://github.com/lstein/stable-diffusion/stargazers +[latest commit to dev badge]: https://flat.badgen.net/github/last-commit/lstein/stable-diffusion/development?icon=github&color=yellow&label=last%20dev%20commit&cache=900 +[latest commit to dev link]: https://github.com/lstein/stable-diffusion/commits/development +[latest release badge]: https://flat.badgen.net/github/release/lstein/stable-diffusion/development?icon=github +[latest release link]: https://github.com/lstein/stable-diffusion/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 options to aid the image generation process. It runs on Windows, Mac and Linux machines, and runs on 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 -requests. Be sure to use the provided templates. They will help aid diagnose issues faster._ +!!! note -## Installation + This fork is rapidly evolving. Please use the + [Issues](https://github.com/lstein/stable-diffusion/issues) tab to report bugs and make feature + requests. Be sure to use the provided templates. They will help aid diagnose issues faster. + +## :octicons-package-dependencies-24: Installation This fork is supported across multiple platforms. You can find individual installation instructions below. -- [Linux](installation/INSTALL_LINUX.md) -- [Windows](installation/INSTALL_WINDOWS.md) -- [Macintosh](installation/INSTALL_MAC.md) +- :fontawesome-brands-linux: [Linux](installation/INSTALL_LINUX.md) +- :fontawesome-brands-windows: [Windows](installation/INSTALL_WINDOWS.md) +- :fontawesome-brands-apple: [Macintosh](installation/INSTALL_MAC.md) -## Hardware Requirements +## :fontawesome-solid-computer: Hardware Requirements -### System +### :octicons-cpu-24: 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. +- :simple-nvidia: An NVIDIA-based graphics card with 4 GB or more VRAM memory. +- :fontawesome-brands-apple: An Apple computer with an M1 chip. -### Memory +### :fontawesome-solid-memory: Memory - At least 12 GB Main Memory RAM. -### Disk +### :fontawesome-regular-hard-drive: 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. + 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. + Similarly, specify full-precision mode on Apple M1 hardware. -To run in full-precision mode, start `dream.py` with the `--full_precision` flag: + To run in full-precision mode, start `dream.py` with the `--full_precision` flag: -```bash -(ldm) ~/stable-diffusion$ python scripts/dream.py --full_precision -``` + ```bash + (ldm) ~/stable-diffusion$ python scripts/dream.py --full_precision + ``` +## :octicons-log-16: Latest Changes -## Features +### vNEXT (TODO 2022) -### Major Features - -- [Interactive Command Line Interface](features/CLI.md) -- [Image To Image](features/IMG2IMG.md) -- [Inpainting Support](features/INPAINTING.md) -- [GFPGAN and Real-ESRGAN Support](features/UPSCALE.md) -- [Seamless Tiling](features/OTHER.md#seamless-tiling) -- [Google Colab](features/OTHER.md#google-colab) -- [Web Server](features/WEB.md) -- [Reading Prompts From File](features/OTHER.md#reading-prompts-from-a-file) -- [Shortcut: Reusing Seeds](features/OTHER.md#shortcuts-reusing-seeds) -- [Weighted Prompts](features/OTHER.md#weighted-prompts) -- [Variations](features/VARIATIONS.md) -- [Personalizing Text-to-Image Generation](features/TEXTUAL_INVERSION.md) -- [Simplified API for text to image generation](features/OTHER.md#simplified-api) - -### Other Features - -- [Creating Transparent Regions for Inpainting](features/INPAINTING.md#creating-transparent-regions-for-inpainting) -- [Preload Models](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) @@ -127,12 +131,12 @@ To run in full-precision mode, start `dream.py` with the `--full_precision` flag For older changelogs, please visit the **[CHANGELOG](features/CHANGELOG.md)**. -## Troubleshooting +## :material-target: Troubleshooting -Please check out our **[Q&A](help/TROUBLESHOOT.md)** to get solutions for common installation +Please check out our **[:material-frequently-asked-questions: Q&A](help/TROUBLESHOOT.md)** to get solutions for common installation problems and other issues. -## Contributing +## :octicons-repo-push-24: 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 @@ -144,13 +148,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 +## :octicons-person-24: Contributors This fork is a combined effort of various people from across the world. [Check out the list of all these amazing people](other/CONTRIBUTORS.md). We thank them for their time, hard work and effort. -## Support +## :octicons-question-24: 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. @@ -158,7 +162,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 +## :octicons-book-24: Further Reading Please see the original README for more information on this software and underlying algorithm, located in the file [README-CompViz.md](other/README-CompViz.md). diff --git a/docs/installation/INSTALL_DOCKER.md b/docs/installation/INSTALL_DOCKER.md new file mode 100644 index 0000000000..c7dd3582d5 --- /dev/null +++ b/docs/installation/INSTALL_DOCKER.md @@ -0,0 +1,255 @@ +# Before you begin + +- For end users: Install Stable Diffusion locally using the instructions for + your OS. +- For developers: For container-related development tasks or for enabling easy + deployment to other environments (on-premises or cloud), follow these + instructions. For general use, install locally to leverage your machine's GPU. + +# Why containers? + +They provide a flexible, reliable way to build and deploy Stable Diffusion. +You'll also use a Docker volume to store the largest model files and image +outputs as a first step in decoupling storage and compute. Future enhancements +can do this for other assets. See [Processes](https://12factor.net/processes) +under the Twelve-Factor App methodology for details on why running applications +in such a stateless fashion is important. + +You can specify the target platform when building the image and running the +container. You'll also need to specify the Stable Diffusion requirements file +that matches the container's OS and the architecture it will run on. + +Developers on Apple silicon (M1/M2): You +[can't access your GPU cores from Docker containers](https://github.com/pytorch/pytorch/issues/81224) +and performance is reduced compared with running it directly on macOS but for +development purposes it's fine. Once you're done with development tasks on your +laptop you can build for the target platform and architecture and deploy to +another environment with NVIDIA GPUs on-premises or in the cloud. + +# Installation on a Linux container + +## Prerequisites + +### Get the data files + +Go to +[Hugging Face](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original), +and click "Access repository" to Download the model file `sd-v1-4.ckpt` (~4 GB) +to `~/Downloads`. You'll need to create an account but it's quick and free. + +Also download the face restoration model. + +```Shell +cd ~/Downloads +wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth +``` + +### Install [Docker](https://github.com/santisbon/guides#docker) + +On the Docker Desktop app, go to Preferences, Resources, Advanced. Increase the +CPUs and Memory to avoid this +[Issue](https://github.com/invoke-ai/InvokeAI/issues/342). You may need to +increase Swap and Disk image size too. + +## Setup + +Set the fork you want to use and other variables. + +```Shell +TAG_STABLE_DIFFUSION="santisbon/stable-diffusion" +PLATFORM="linux/arm64" +GITHUB_STABLE_DIFFUSION="-b orig-gfpgan https://github.com/santisbon/stable-diffusion.git" +REQS_STABLE_DIFFUSION="requirements-linux-arm64.txt" +CONDA_SUBDIR="osx-arm64" + +echo $TAG_STABLE_DIFFUSION +echo $PLATFORM +echo $GITHUB_STABLE_DIFFUSION +echo $REQS_STABLE_DIFFUSION +echo $CONDA_SUBDIR +``` + +Create a Docker volume for the downloaded model files. + +```Shell +docker volume create my-vol +``` + +Copy the data files to the Docker volume using a lightweight Linux container. +We'll need the models at run time. You just need to create the container with +the mountpoint; no need to run this dummy container. + +```Shell +cd ~/Downloads # or wherever you saved the files + +docker create --platform $PLATFORM --name dummy --mount source=my-vol,target=/data alpine + +docker cp sd-v1-4.ckpt dummy:/data +docker cp GFPGANv1.4.pth dummy:/data +``` + +Get the repo and download the Miniconda installer (we'll need it at build time). +Replace the URL with the version matching your container OS and the architecture +it will run on. + +```Shell +cd ~ +git clone $GITHUB_STABLE_DIFFUSION + +cd stable-diffusion/docker-build +chmod +x entrypoint.sh +wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh -O anaconda.sh && chmod +x anaconda.sh +``` + +Build the Docker image. Give it any tag `-t` that you want. +Choose the Linux container's host platform: x86-64/Intel is `amd64`. Apple +silicon is `arm64`. If deploying the container to the cloud to leverage powerful +GPU instances you'll be on amd64 hardware but if you're just trying this out +locally on Apple silicon choose arm64. +The application uses libraries that need to match the host environment so use +the appropriate requirements file. +Tip: Check that your shell session has the env variables set above. + +```Shell +docker build -t $TAG_STABLE_DIFFUSION \ +--platform $PLATFORM \ +--build-arg gsd=$GITHUB_STABLE_DIFFUSION \ +--build-arg rsd=$REQS_STABLE_DIFFUSION \ +--build-arg cs=$CONDA_SUBDIR \ +. +``` + +Run a container using your built image. +Tip: Make sure you've created and populated the Docker volume (above). + +```Shell +docker run -it \ +--rm \ +--platform $PLATFORM \ +--name stable-diffusion \ +--hostname stable-diffusion \ +--mount source=my-vol,target=/data \ +$TAG_STABLE_DIFFUSION +``` + +# Usage (time to have fun) + +## Startup + +If you're on a **Linux container** the `dream` script is **automatically +started** and the output dir set to the Docker volume you created earlier. + +If you're **directly on macOS follow these startup instructions**. +With the Conda environment activated (`conda activate ldm`), run the interactive +interface that combines the functionality of the original scripts `txt2img` and +`img2img`: +Use the more accurate but VRAM-intensive full precision math because +half-precision requires autocast and won't work. +By default the images are saved in `outputs/img-samples/`. + +```Shell +python3 scripts/dream.py --full_precision +``` + +You'll get the script's prompt. You can see available options or quit. + +```Shell +dream> -h +dream> q +``` + +## Text to Image + +For quick (but bad) image results test with 5 steps (default 50) and 1 sample +image. This will let you know that everything is set up correctly. +Then increase steps to 100 or more for good (but slower) results. +The prompt can be in quotes or not. + +```Shell +dream> The hulk fighting with sheldon cooper -s5 -n1 +dream> "woman closeup highly detailed" -s 150 +# Reuse previous seed and apply face restoration +dream> "woman closeup highly detailed" --steps 150 --seed -1 -G 0.75 +``` + +You'll need to experiment to see if face restoration is making it better or +worse for your specific prompt. + +If you're on a container the output is set to the Docker volume. You can copy it +wherever you want. +You can download it from the Docker Desktop app, Volumes, my-vol, data. +Or you can copy it from your Mac terminal. Keep in mind `docker cp` can't expand +`*.png` so you'll need to specify the image file name. + +On your host Mac (you can use the name of any container that mounted the +volume): + +```Shell +docker cp dummy:/data/000001.928403745.png /Users//Pictures +``` + +## Image to Image + +You can also do text-guided image-to-image translation. For example, turning a +sketch into a detailed drawing. + +`strength` is a value between 0.0 and 1.0 that controls the amount of noise that +is added to the input image. Values that approach 1.0 allow for lots of +variations but will also produce images that are not semantically consistent +with the input. 0.0 preserves image exactly, 1.0 replaces it completely. + +Make sure your input image size dimensions are multiples of 64 e.g. 512x512. +Otherwise you'll get `Error: product of dimension sizes > 2**31'`. If you still +get the error +[try a different size](https://support.apple.com/guide/preview/resize-rotate-or-flip-an-image-prvw2015/mac#:~:text=image's%20file%20size-,In%20the%20Preview%20app%20on%20your%20Mac%2C%20open%20the%20file,is%20shown%20at%20the%20bottom.) +like 512x256. + +If you're on a Docker container, copy your input image into the Docker volume + +```Shell +docker cp /Users//Pictures/sketch-mountains-input.jpg dummy:/data/ +``` + +Try it out generating an image (or more). The `dream` script needs absolute +paths to find the image so don't use `~`. + +If you're on your Mac + +```Shell +dream> "A fantasy landscape, trending on artstation" -I /Users//Pictures/sketch-mountains-input.jpg --strength 0.75 --steps 100 -n4 +``` + +If you're on a Linux container on your Mac + +```Shell +dream> "A fantasy landscape, trending on artstation" -I /data/sketch-mountains-input.jpg --strength 0.75 --steps 50 -n1 +``` + +## Web Interface + +You can use the `dream` script with a graphical web interface. Start the web +server with: + +```Shell +python3 scripts/dream.py --full_precision --web +``` + +If it's running on your Mac point your Mac web browser to http://127.0.0.1:9090 + +Press Control-C at the command line to stop the web server. + +## Notes + +Some text you can add at the end of the prompt to make it very pretty: + +```Shell +cinematic photo, highly detailed, cinematic lighting, ultra-detailed, ultrarealistic, photorealism, Octane Rendering, cyberpunk lights, Hyper Detail, 8K, HD, Unreal Engine, V-Ray, full hd, cyberpunk, abstract, 3d octane render + 4k UHD + immense detail + dramatic lighting + well lit + black, purple, blue, pink, cerulean, teal, metallic colours, + fine details, ultra photoreal, photographic, concept art, cinematic composition, rule of thirds, mysterious, eerie, photorealism, breathtaking detailed, painting art deco pattern, by hsiao, ron cheng, john james audubon, bizarre compositions, exquisite detail, extremely moody lighting, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli, akihiko yoshida +``` + +The original scripts should work as well. + +```Shell +python3 scripts/orig_scripts/txt2img.py --help +python3 scripts/orig_scripts/txt2img.py --ddim_steps 100 --n_iter 1 --n_samples 1 --plms --prompt "new born baby kitten. Hyper Detail, Octane Rendering, Unreal Engine, V-Ray" +python3 scripts/orig_scripts/txt2img.py --ddim_steps 5 --n_iter 1 --n_samples 1 --plms --prompt "ocean" # or --klms +``` diff --git a/docs/installation/INSTALL_LINUX.md b/docs/installation/INSTALL_LINUX.md index 312ab60482..a865016bd5 100644 --- a/docs/installation/INSTALL_LINUX.md +++ b/docs/installation/INSTALL_LINUX.md @@ -2,6 +2,10 @@ title: Linux --- +# :fontawesome-brands-linux: Linux + +## Installation + 1. You will need to install the following prerequisites if they are not already available. Use your operating system's preferred installer. @@ -20,44 +24,46 @@ title: Linux the installation worked, your command prompt will be prefixed by the name of the current anaconda environment - `(base)`. -3. Copy the stable-diffusion source code from GitHub: +3. Copy the InvokeAI source code from GitHub: - ```bash - (base) ~$ git clone https://github.com/lstein/stable-diffusion.git - ``` +``` +(base) ~$ git clone https://github.com/invoke-ai/InvokeAI.git +``` - This will create stable-diffusion folder where you will follow the rest of the - steps. +This will create InvokeAI folder where you will follow the rest of the steps. -4. Enter the newly-created stable-diffusion folder. From this step forward make - sure that you are working in the stable-diffusion directory! +4. Enter the newly-created InvokeAI folder. From this step forward make sure that you are working in the InvokeAI directory! - ```bash - (base) ~$ cd stable-diffusion - (base) ~/stable-diffusion$ - ``` +``` +(base) ~$ cd InvokeAI +(base) ~/InvokeAI$ +``` 5. Use anaconda to copy necessary python packages, create a new python environment named `ldm` and activate the environment. - ```bash - (base) ~/stable-diffusion$ conda env create -f environment.yaml - (base) ~/stable-diffusion$ conda activate ldm - (ldm) ~/stable-diffusion$ - ``` + +``` +(base) ~/InvokeAI$ conda env create -f environment.yaml +(base) ~/InvokeAI$ conda activate ldm +(ldm) ~/InvokeAI$ +``` After these steps, your command prompt will be prefixed by `(ldm)` as shown above. 6. Load a couple of small machine-learning models required by stable diffusion: - ```bash - (ldm) ~/stable-diffusion$ python3 scripts/preload_models.py - ``` - Note that this step is necessary because I modified the original just-in-time - model loading scheme to allow the script to work on GPU machines that are not - internet connected. See [Preload Models](../features/OTHER.md#preload-models) +``` +(ldm) ~/InvokeAI$ python3 scripts/preload_models.py +``` + + !!! note + + This step is necessary because I modified the original just-in-time + model loading scheme to allow the script to work on GPU machines that are not + internet connected. See [Preload Models](../features/OTHER.md#preload-models) 7. Now you need to install the weights for the stable diffusion model. @@ -73,38 +79,34 @@ title: Linux This will create a symbolic link from the stable-diffusion model.ckpt file, to the true location of the `sd-v1-4.ckpt` file. - ```bash - (ldm) ~/stable-diffusion$ mkdir -p models/ldm/stable-diffusion-v1 - (ldm) ~/stable-diffusion$ ln -sf /path/to/sd-v1-4.ckpt models/ldm/stable-diffusion-v1/model.ckpt - ``` + +``` +(ldm) ~/InvokeAI$ mkdir -p models/ldm/stable-diffusion-v1 +(ldm) ~/InvokeAI$ ln -sf /path/to/sd-v1-4.ckpt models/ldm/stable-diffusion-v1/model.ckpt +``` 8. Start generating images! - ```bash - # for the pre-release weights use the -l or --liaon400m switch - (ldm) ~/stable-diffusion$ python3 scripts/dream.py -l +``` +# for the pre-release weights use the -l or --liaon400m switch +(ldm) ~/InvokeAI$ python3 scripts/dream.py -l - # for the post-release weights do not use the switch - (ldm) ~/stable-diffusion$ python3 scripts/dream.py +# for the post-release weights do not use the switch +(ldm) ~/InvokeAI$ python3 scripts/dream.py - # for additional configuration switches and arguments, use -h or --help - (ldm) ~/stable-diffusion$ python3 scripts/dream.py -h - ``` +# for additional configuration switches and arguments, use -h or --help +(ldm) ~/InvokeAI$ python3 scripts/dream.py -h +``` -9. Subsequently, to relaunch the script, be sure to run "conda activate ldm" - (step 5, second command), enter the `stable-diffusion` directory, and then - launch the dream script (step 8). If you forget to activate the ldm - environment, the script will fail with multiple `ModuleNotFound` errors. +9. Subsequently, to relaunch the script, be sure to run "conda activate ldm" (step 5, second command), enter the `InvokeAI` directory, and then launch the dream script (step 8). If you forget to activate the ldm environment, the script will fail with multiple `ModuleNotFound` errors. - ### Updating to newer versions of the script +## Updating to newer versions of the script - This distribution is changing rapidly. If you used the `git clone` method - (step 5) to download the stable-diffusion directory, then to update to the - latest and greatest version, launch the Anaconda window, enter - `stable-diffusion` and type: - ```bash - (ldm) ~/stable-diffusion$ git pull - ``` +This distribution is changing rapidly. If you used the `git clone` method (step 5) to download the InvokeAI directory, then to update to the latest and greatest version, launch the Anaconda window, enter `InvokeAI` and type: - This will bring your local copy into sync with the remote one. +``` +(ldm) ~/InvokeAI$ git pull +``` + +This will bring your local copy into sync with the remote one. diff --git a/docs/installation/INSTALL_MAC.md b/docs/installation/INSTALL_MAC.md index 71535980f5..37f1fe083d 100644 --- a/docs/installation/INSTALL_MAC.md +++ b/docs/installation/INSTALL_MAC.md @@ -2,6 +2,8 @@ title: macOS --- +# :fontawesome-brands-apple: macOS + ## Requirements - macOS 12.3 Monterey or later @@ -9,18 +11,21 @@ title: macOS - Patience - Apple Silicon or Intel Mac -Things have moved really fast and so these instructions change often and are -often out-of-date. One of the problems is that there are so many different ways -to run this. +Things have moved really fast and so these instructions change often which makes +them outdated pretty fast. One of the problems is that there are so many +different ways to run this. We are trying to build a testing setup so that when we make changes it doesn't always break. -How to (this hasn't been 100% tested yet): +## How to -First get the weights checkpoint download started - it's big: +(this hasn't been 100% tested yet) -1. Sign up at https://huggingface.co +First get the weights checkpoint download started since it's big and will take +some time: + +1. Sign up at [huggingface.co](https://huggingface.co) 2. Go to the [Stable diffusion diffusion model page](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original) 3. Accept the terms and click Access Repository: @@ -28,114 +33,148 @@ First get the weights checkpoint download started - it's big: [sd-v1-4.ckpt (4.27 GB)](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/blob/main/sd-v1-4.ckpt) and note where you have saved it (probably the Downloads folder) - While that is downloading, open Terminal and run the following commands one - at a time. +While that is downloading, open a Terminal and run the following commands: -```bash -# install brew (and Xcode command line tools): +!!! todo "Homebrew" -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + === "no brew installation yet" -# Now there are two different routes to get the Python (miniconda) environment up and running: -# 1. Alongside pyenv -# 2. No pyenv -# -# If you don't know what we are talking about, choose 2. -# -# NOW EITHER DO -# 1. Installing alongside pyenv + ```bash title="install brew (and Xcode command line tools)" + /bin/bash -c \ + "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` - brew install pyenv-virtualenv # you might have this from before, no problem - pyenv install anaconda3-2022.05 - pyenv virtualenv anaconda3-2022.05 - eval "$(pyenv init -)" - pyenv activate anaconda3-2022.05 + === "brew is already installed" + + Only if you installed protobuf in a previous version of this tutorial, otherwise skip -# OR, -# 2. Installing standalone -# install python 3, git, cmake, protobuf: -brew install cmake protobuf rust + `#!bash brew uninstall protobuf` -# install miniconda for M1 arm64: -curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o Miniconda3-latest-MacOSX-arm64.sh -/bin/bash Miniconda3-latest-MacOSX-arm64.sh +!!! todo "Conda Installation" -# OR install miniconda for Intel: -curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -o Miniconda3-latest-MacOSX-x86_64.sh -/bin/bash Miniconda3-latest-MacOSX-x86_64.sh + Now there are two different ways to set up the Python (miniconda) environment: + 1. Standalone + 2. with pyenv + If you don't know what we are talking about, choose Standalone + === "Standalone" -# EITHER WAY, -# continue from here + ```bash + # install cmake and rust: + brew install cmake rust + ``` + === "M1 arm64" + + ```bash title="Install miniconda for M1 arm64" + curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh \ + -o Miniconda3-latest-MacOSX-arm64.sh + /bin/bash Miniconda3-latest-MacOSX-arm64.sh + ``` + + === "Intel x86_64" + + ```bash title="Install miniconda for Intel" + curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh \ + -o Miniconda3-latest-MacOSX-x86_64.sh + /bin/bash Miniconda3-latest-MacOSX-x86_64.sh + ``` + + === "with pyenv" + + ```{.bash .annotate} + brew install rust pyenv-virtualenv # (1)! + pyenv install anaconda3-2022.05 + pyenv virtualenv anaconda3-2022.05 + eval "$(pyenv init -)" + pyenv activate anaconda3-2022.05 + ``` + + 1. You might already have this installed, if that is the case just continue. + +```{.bash .annotate title="local repo setup"} # clone the repo - git clone https://github.com/lstein/stable-diffusion.git - cd stable-diffusion +git clone https://github.com/invoke-ai/InvokeAI.git + +cd InvokeAI -# # wait until the checkpoint file has downloaded, then proceed -# # create symlink to checkpoint - mkdir -p models/ldm/stable-diffusion-v1/ +mkdir -p models/ldm/stable-diffusion-v1/ - PATH_TO_CKPT="$HOME/Downloads" # or wherever you saved sd-v1-4.ckpt +PATH_TO_CKPT="$HOME/Downloads" # (1)! - ln -s "$PATH_TO_CKPT/sd-v1-4.ckpt" models/ldm/stable-diffusion-v1/model.ckpt +ln -s "$PATH_TO_CKPT/sd-v1-4.ckpt" \ + models/ldm/stable-diffusion-v1/model.ckpt +``` -# install packages for arm64 -PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 conda env create -f environment-mac.yaml -conda activate ldm +1. or wherever you saved sd-v1-4.ckpt -# OR install packages for x86_64 -PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-x86_64 conda env create -f environment-mac.yaml -conda activate ldm +!!! todo "create Conda Environment" + === "M1 arm64" + + ```bash + PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 \ + conda env create \ + -f environment-mac.yaml \ + && conda activate ldm + ``` + + === "Intel x86_64" + + ```bash + PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-x86_64 \ + conda env create \ + -f environment-mac.yaml \ + && conda activate ldm + ``` + +```{.bash .annotate title="preload models and run script"} # only need to do this once python scripts/preload_models.py -# run SD! -python scripts/dream.py --full_precision # half-precision requires autocast and won't work +# now you can run SD in CLI mode +python scripts/dream.py --full_precision # (1)! # or run the web interface! python scripts/dream.py --web + +# The original scripts should work as well. +python scripts/orig_scripts/txt2img.py \ + --prompt "a photograph of an astronaut riding a horse" \ + --plms ``` -The original scripts should work as well. +1. half-precision requires autocast which is unfortunatelly incompatible -```bash -python scripts/orig_scripts/txt2img.py --prompt "a photograph of an astronaut riding a horse" --plms -``` +!!! note -Note, + `#!bash export PIP_EXISTS_ACTION=w` is a precaution to fix a problem where -```bash -export PIP_EXISTS_ACTION=w -``` + ```bash + conda env create \ + -f environment-mac.yaml + ``` -is a precaution to fix - -```bash -conda env create -f environment-mac.yaml -``` - -never finishing in some situations. So it isn't required but wont hurt. - -After you follow all the instructions and run dream.py you might get several -errors. Here's the errors I've seen and found solutions for. + did never finish in some situations. So it isn't required but wont hurt. --- +## Common problems + +After you followed all the instructions and try to run dream.py, you might +get several errors. Here's the errors I've seen and found solutions for. + ### Is it slow? -Be sure to specify 1 sample and 1 iteration. - -```bash +```bash title="Be sure to specify 1 sample and 1 iteration." python ./scripts/orig_scripts/txt2img.py \ - --prompt "ocean" \ - --ddim_steps 5 \ - --n_samples 1 \ - --n_iter 1 + --prompt "ocean" \ + --ddim_steps 5 \ + --n_samples 1 \ + --n_iter 1 ``` --- @@ -148,60 +187,80 @@ what I did until I switched to miniforge. However, I have another Mac that works just fine with Anaconda. If you can't get it to work, please search a little first because many of the errors will get posted and solved. If you can't find a solution please -[create an issue](https://github.com/lstein/stable-diffusion/issues). +[create an issue](https://github.com/invoke-ai/InvokeAI/issues). One debugging step is to update to the latest version of PyTorch nightly. ```bash -conda install pytorch torchvision torchaudio -c pytorch-nightly +conda install \ + pytorch \ + torchvision \ + -c pytorch-nightly \ + -n ldm ``` If it takes forever to run ```bash -conda env create -f environment-mac.yaml +conda env create \ + -f environment-mac.yaml ``` -you could try to run `git clean -f` followed by: +you could try to run: -`conda clean --yes --all` +```bash +git clean -f +conda clean \ + --yes \ + --all +``` Or you could try to completley reset Anaconda: ```bash -conda update --force-reinstall -y -n base -c defaults conda +conda update \ + --force-reinstall \ + -y \ + -n base \ + -c defaults conda ``` --- ### "No module named cv2", torch, 'ldm', 'transformers', 'taming', etc -There are several causes of these errors. +There are several causes of these errors: -- First, did you remember to `conda activate ldm`? If your terminal prompt - begins with "(ldm)" then you activated it. If it begins with "(base)" or - something else you haven't. +1. Did you remember to `conda activate ldm`? If your terminal prompt begins with + "(ldm)" then you activated it. If it begins with "(base)" or something else + you haven't. -- Second, you might've run `./scripts/preload_models.py` or `./scripts/dream.py` - instead of `python ./scripts/preload_models.py` or - `python ./scripts/dream.py`. The cause of this error is long so it's below. +2. You might've run `./scripts/preload_models.py` or `./scripts/dream.py` + instead of `python ./scripts/preload_models.py` or + `python ./scripts/dream.py`. The cause of this error is long so it's below. -- Third, if it says you're missing taming you need to rebuild your virtual - environment. + -````bash -conda deactivate +3. if it says you're missing taming you need to rebuild your virtual + environment. -conda env remove -n ldm -PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 conda env create -f environment-mac.yaml -``` + ```bash + conda deactivate + conda env remove -n ldm + PIP_EXISTS_ACTION=w CONDA_SUBDIR=osx-arm64 \ + conda env create \ + -f environment-mac.yaml + ``` -Fourth, If you have activated the ldm virtual environment and tried rebuilding -it, maybe the problem could be that I have something installed that you don't -and you'll just need to manually install it. Make sure you activate the virtual -environment so it installs there instead of globally. +4. If you have activated the ldm virtual environment and tried rebuilding it, + maybe the problem could be that I have something installed that you don't and + you'll just need to manually install it. Make sure you activate the virtual + environment so it installs there instead of globally. -`conda activate ldm pip install _name_` + ```bash + conda activate ldm + pip install + ``` You might also need to install Rust (I mention this again below). @@ -261,21 +320,20 @@ output of `python3 -V` and `python -V`. /Users/name/miniforge3/envs/ldm/bin/python ``` -The above is what you'll see if you have miniforge and you've correctly -activated the ldm environment, and you used option 2 in the setup instructions -above ("no pyenv"). +The above is what you'll see if you have miniforge and correctly activated the +ldm environment, while usingd the standalone setup instructions above. + +If you otherwise installed via pyenv, you will get this result: ```bash (anaconda3-2022.05) % which python /Users/name/.pyenv/shims/python ``` -... and the above is what you'll see if you used option 1 ("Alongside pyenv"). - It's all a mess and you should know [how to modify the path environment variable](https://support.apple.com/guide/terminal/use-environment-variables-apd382cc5fa-4f58-4449-b20a-41c53c006f8f/mac) -if you want to fix it. Here's a brief hint of all the ways you can modify it -(don't really have the time to explain it all here). +if you want to fix it. Here's a brief hint of the most common ways you can +modify it (don't really have the time to explain it all here). - ~/.zshrc - ~/.bash_profile @@ -283,16 +341,21 @@ if you want to fix it. Here's a brief hint of all the ways you can modify it - /etc/paths.d - /etc/path -Which one you use will depend on what you have installed except putting a file -in /etc/paths.d is what I prefer to do. +Which one you use will depend on what you have installed, except putting a file +in /etc/paths.d - which also is the way I prefer to do. Finally, to answer the question posed by this section's title, it may help to list all of the `python` / `python3` things found in `$PATH` instead of just the -one that will be executed by default. To do that, add the `-a` switch to -`which`: +first hit. To do so, add the `-a` switch to `which`: - % which -a python3 - ... +```bash +% which -a python3 +... +``` + +This will show a list of all binaries which are actually available in your PATH. + +--- ### Debugging? @@ -300,37 +363,56 @@ Tired of waiting for your renders to finish before you can see if it works? Reduce the steps! The image quality will be horrible but at least you'll get quick feedback. - python ./scripts/txt2img.py --prompt "ocean" --ddim_steps 5 --n_samples 1 --n_iter 1 +```bash +python ./scripts/txt2img.py \ + --prompt "ocean" \ + --ddim_steps 5 \ + --n_samples 1 \ + --n_iter 1 +``` -### OSError: Can't load tokenizer for 'openai/clip-vit-large-patch14'... +--- - python scripts/preload_models.py +### OSError: Can't load tokenizer for 'openai/clip-vit-large-patch14' + +```bash +python scripts/preload_models.py +``` + +--- ### "The operator [name] is not current implemented for the MPS device." (sic) -Example error. +!!! example "example error" -``` + ```bash + ... NotImplementedError: The operator 'aten::_index_put_impl_' is not current + implemented for the MPS device. If you want this op to be added in priority + during the prototype phase of this feature, please comment on + https://github.com/pytorch/pytorch/issues/77764. + As a temporary fix, you can set the environment variable + `PYTORCH_ENABLE_MPS_FALLBACK=1` to use the CPU as a fallback for this op. + WARNING: this will be slower than running natively on MPS. + ``` -... NotImplementedError: The operator 'aten::_index_put_impl_' is not current -implemented for the MPS device. If you want this op to be added in priority -during the prototype phase of this feature, please comment on -[https://github.com/pytorch/pytorch/issues/77764](https://github.com/pytorch/pytorch/issues/77764). -As a temporary fix, you can set the environment variable -`PYTORCH_ENABLE_MPS_FALLBACK=1` to use the CPU as a fallback for this op. -WARNING: this will be slower than running natively on MPS. +This fork already includes a fix for this in +[environment-mac.yaml](https://github.com/invoke-ai/InvokeAI/blob/main/environment-mac.yaml). -``` - -The lstein branch includes this fix in -[environment-mac.yaml](https://github.com/lstein/stable-diffusion/blob/main/environment-mac.yaml). +--- ### "Could not build wheels for tokenizers" I have not seen this error because I had Rust installed on my computer before I started playing with Stable Diffusion. The fix is to install Rust. - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +```bash +curl \ + --proto '=https' \ + --tlsv1.2 \ + -sSf https://sh.rustup.rs | sh +``` + +--- ### How come `--seed` doesn't work? @@ -347,7 +429,9 @@ still working on it. ### libiomp5.dylib error? - OMP: Error #15: Initializing libiomp5.dylib, but found libomp.dylib already initialized. +```bash +OMP: Error #15: Initializing libiomp5.dylib, but found libomp.dylib already initialized. +``` You are likely using an Intel package by mistake. Be sure to run conda with the environment variable `CONDA_SUBDIR=osx-arm64`, like so: @@ -363,6 +447,8 @@ is a metapackage designed to prevent this, by making it impossible to install Do _not_ use `os.environ['KMP_DUPLICATE_LIB_OK']='True'` or equivalents as this masks the underlying issue of using Intel packages. +--- + ### Not enough memory This seems to be a common problem and is probably the underlying problem for a @@ -374,6 +460,8 @@ how that would affect the quality of the images though. See [this issue](https://github.com/CompVis/stable-diffusion/issues/71). +--- + ### "Error: product of dimension sizes > 2\*\*31'" This error happens with img2img, which I haven't played with too much yet. But I @@ -388,18 +476,22 @@ BTW, 2\*\*31-1 = is also 32-bit signed [LONG_MAX](https://en.wikipedia.org/wiki/C_data_types) in C. +--- + ### I just got Rickrolled! Do I have a virus? You don't have a virus. It's part of the project. Here's -[Rick](https://github.com/lstein/stable-diffusion/blob/main/assets/rick.jpeg) +[Rick](https://github.com/invoke-ai/InvokeAI/blob/main/assets/rick.jpeg) and here's -[the code](https://github.com/lstein/stable-diffusion/blob/69ae4b35e0a0f6ee1af8bb9a5d0016ccb27e36dc/scripts/txt2img.py#L79) +[the code](https://github.com/invoke-ai/InvokeAI/blob/69ae4b35e0a0f6ee1af8bb9a5d0016ccb27e36dc/scripts/txt2img.py#L79) that swaps him in. It's a NSFW filter, which IMO, doesn't work very good (and we call this "computer vision", sheesh). Actually, this could be happening because there's not enough RAM. You could try the `model.half()` suggestion or specify smaller output images. +--- + ### My images come out black We might have this fixed, we are still testing. @@ -419,7 +511,7 @@ return torch.layer_norm(input, normalized_shape, weight, bias, eps, torch.backen RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead. ``` -Update to the latest version of lstein/stable-diffusion. We were patching +Update to the latest version of invoke-ai/InvokeAI. We were patching pytorch but we found a file in stable-diffusion that we could change instead. This is a 32-bit vs 16-bit problem. @@ -428,7 +520,10 @@ This is a 32-bit vs 16-bit problem. ### The processor must support the Intel bla bla bla What? Intel? On an Apple Silicon? -`bash Intel MKL FATAL ERROR: This system does not meet the minimum requirements for use of the Intel(R) Math Kernel Library. The processor must support the Intel(R) Supplemental Streaming SIMD Extensions 3 (Intel(R) SSSE3) instructions. The processor must support the Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) instructions. The processor must support the Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions. ` + +```bash +Intel MKL FATAL ERROR: This system does not meet the minimum requirements for use of the Intel(R) Math Kernel Library. The processor must support the Intel(R) Supplemental Streaming SIMD Extensions 3 (Intel(R) SSSE3) instructions. The processor must support the Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) instructions. The processor must support the Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions. +``` This is due to the Intel `mkl` package getting picked up when you try to install something that depends on it-- Rosetta can translate some Intel instructions but @@ -453,5 +548,3 @@ Abort trap: 6 warnings.warn('resource_tracker: There appear to be %d ' ``` -Macs do not support `autocast/mixed-precision`, so you need to supply -`--full_precision` to use float32 everywhere. diff --git a/docs/installation/INSTALL_WINDOWS.md b/docs/installation/INSTALL_WINDOWS.md index 8119449717..fc98c33ae0 100644 --- a/docs/installation/INSTALL_WINDOWS.md +++ b/docs/installation/INSTALL_WINDOWS.md @@ -2,10 +2,12 @@ title: Windows --- +# :fontawesome-brands-windows: Windows + ## **Notebook install (semi-automated)** We have a -[Jupyter notebook](https://github.com/lstein/stable-diffusion/blob/main/notebooks/Stable-Diffusion-local-Windows.ipynb) +[Jupyter notebook](https://github.com/invoke-ai/InvokeAI/blob/main/notebooks/Stable-Diffusion-local-Windows.ipynb) with cell-by-cell installation steps. It will download the code in this repo as one of the steps, so instead of cloning this repo, simply download the notebook from the link above and load it up in VSCode (with the appropriate extensions @@ -13,7 +15,7 @@ installed)/Jupyter/JupyterLab and start running the cells one-by-one. Note that you will need NVIDIA drivers, Python 3.10, and Git installed beforehand - simplified -[step-by-step instructions](https://github.com/lstein/stable-diffusion/wiki/Easy-peasy-Windows-install) +[step-by-step instructions](https://github.com/invoke-ai/InvokeAI/wiki/Easy-peasy-Windows-install) are available in the wiki (you'll only need steps 1, 2, & 3 ). ## **Manual Install** @@ -21,25 +23,24 @@ are available in the wiki (you'll only need steps 1, 2, & 3 ). ### **pip** See -[Easy-peasy Windows install](https://github.com/lstein/stable-diffusion/wiki/Easy-peasy-Windows-install) +[Easy-peasy Windows install](https://github.com/invoke-ai/InvokeAI/wiki/Easy-peasy-Windows-install) in the wiki --- ### **Conda** -1. Install Anaconda3 (miniconda3 version) from here: - https://docs.anaconda.com/anaconda/install/windows/ +1. Install Anaconda3 (miniconda3 version) from [here](https://docs.anaconda.com/anaconda/install/windows/) -2. Install Git from here: https://git-scm.com/download/win +2. Install Git from [here](https://git-scm.com/download/win) 3. Launch Anaconda from the Windows Start menu. This will bring up a command window. Type all the remaining commands in this window. 4. Run the command: - ```bash - git clone https://github.com/lstein/stable-diffusion.git + ```batch + git clone https://github.com/invoke-ai/InvokeAI.git ``` This will create stable-diffusion folder where you will follow the rest of @@ -48,15 +49,15 @@ in the wiki 5. Enter the newly-created stable-diffusion folder. From this step forward make sure that you are working in the stable-diffusion directory! - ```bash + ```batch cd stable-diffusion ``` 6. Run the following two commands: - ```bash - conda env create -f environment.yaml (step 6a) - conda activate ldm (step 6b) + ```batch + conda env create -f environment.yaml + conda activate ldm ``` This will install all python requirements and activate the "ldm" environment @@ -64,7 +65,7 @@ in the wiki 7. Run the command: - ```bash + ```batch python scripts\preload_models.py ``` @@ -77,9 +78,9 @@ in the wiki 8. Now you need to install the weights for the big stable diffusion model. - For running with the released weights, you will first need to set up an - acount with Hugging Face (https://huggingface.co). + acount with [Hugging Face](https://huggingface.co). - Use your credentials to log in, and then point your browser at - https://huggingface.co/CompVis/stable-diffusion-v-1-4-original. + [https://huggingface.co/CompVis/stable-diffusion-v-1-4-original](https://huggingface.co/CompVis/stable-diffusion-v-1-4-original). - You may be asked to sign a license agreement at this point. - Click on "Files and versions" near the top of the page, and then click on the file named `sd-v1-4.ckpt`. You'll be taken to a page that prompts you @@ -90,7 +91,7 @@ in the wiki Now run the following commands from **within the stable-diffusion directory** to copy the weights file to the right place: - ```bash + ```batch mkdir -p models\ldm\stable-diffusion-v1 copy C:\path\to\sd-v1-4.ckpt models\ldm\stable-diffusion-v1\model.ckpt ``` @@ -102,7 +103,7 @@ in the wiki 9. Start generating images! - ```bash + ```batch # for the pre-release weights python scripts\dream.py -l @@ -116,20 +117,20 @@ in the wiki then launch the dream script (step 9). **Note:** Tildebyte has written an alternative - ["Easy peasy Windows install"](https://github.com/lstein/stable-diffusion/wiki/Easy-peasy-Windows-install) + ["Easy peasy Windows install"](https://github.com/invoke-ai/InvokeAI/wiki/Easy-peasy-Windows-install) which uses the Windows Powershell and pew. If you are having trouble with Anaconda on Windows, give this a try (or try it first!) --- -### Updating to newer versions of the script +## Updating to newer versions of the script This distribution is changing rapidly. If you used the `git clone` method (step 5) to download the stable-diffusion directory, then to update to the latest and greatest version, launch the Anaconda window, enter `stable-diffusion`, and type: -```bash +```batch git pull conda env update -f environment.yaml ``` diff --git a/docs/other/CONTRIBUTORS.md b/docs/other/CONTRIBUTORS.md index 948795d3f2..8f40419791 100644 --- a/docs/other/CONTRIBUTORS.md +++ b/docs/other/CONTRIBUTORS.md @@ -2,15 +2,18 @@ title: Contributors --- -The list of all the amazing people who have contributed to the various features that you get to experience in this fork. +# :octicons-person-24: Contributors + +The list of all the amazing people who have contributed to the various features that you get to +experience in this fork. We thank them for all of their time and hard work. -## __Original Author:__ +## **Original Author:** - [Lincoln D. Stein](mailto:lincoln.stein@gmail.com) -## __Contributions by:__ +## **Contributions by:** - [Sean McLellan](https://github.com/Oceanswave) - [Kevin Gibbons](https://github.com/bakkot) @@ -52,8 +55,10 @@ We thank them for all of their time and hard work. - [Doggettx](https://github.com/doggettx) - [Matthias Wild](https://github.com/mauwii) - [Kyle Schouviller](https://github.com/kyle0654) +- [rabidcopy](https://github.com/rabidcopy) +- [Dominic Letz](https://github.com/dominicletz) -## __Original CompVis Authors:__ +## **Original CompVis Authors:** - [Robin Rombach](https://github.com/rromb) - [Patrick von Platen](https://github.com/patrickvonplaten) @@ -65,4 +70,5 @@ We thank them for all of their time and hard work. --- -_If you have contributed and don't see your name on the list of contributors, please let one of the collaborators know about the omission, or feel free to make a pull request._ +_If you have contributed and don't see your name on the list of contributors, please let one of the +collaborators know about the omission, or feel free to make a pull request._ diff --git a/environment-mac.yaml b/environment-mac.yaml index 8e1007d4cf..1693306c26 100644 --- a/environment-mac.yaml +++ b/environment-mac.yaml @@ -30,8 +30,10 @@ dependencies: - nomkl - numpy==1.23.2 - omegaconf==2.1.1 + - openh264==2.3.0 - onnx==1.12.0 - onnxruntime==1.12.1 + - protobuf==3.20.1 - pudb==2022.1 - pytorch-lightning==1.6.5 - scipy==1.9.1 @@ -48,6 +50,7 @@ dependencies: - opencv-python==4.6.0 - protobuf==3.20.1 - realesrgan==0.2.5.0 + - send2trash==1.8.0 - test-tube==0.7.5 - transformers==4.21.2 - torch-fidelity==0.3.0 diff --git a/environment.yaml b/environment.yaml index 6ca403e204..eaf4d0e02a 100644 --- a/environment.yaml +++ b/environment.yaml @@ -20,7 +20,8 @@ dependencies: - realesrgan==0.2.5.0 - test-tube>=0.7.5 - streamlit==1.12.0 - - pillow==6.2.0 + - send2trash==1.8.0 + - pillow==9.2.0 - einops==0.3.0 - torch-fidelity==0.3.0 - transformers==4.19.2 diff --git a/frontend/README.md b/frontend/README.md index 6928e27b49..94934b2bce 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,85 +1,37 @@ # Stable Diffusion Web UI -Demo at https://peaceful-otter-7a427f.netlify.app/ (not connected to back end) +## Run -much of this readme is just notes for myself during dev work +- `python backend/server.py` serves both frontend and backend at http://localhost:9090 -numpy rand: 0 to 4294967295 +## Evironment -## Test and Build +Install [node](https://nodejs.org/en/download/) (includes npm) and optionally +[yarn](https://yarnpkg.com/getting-started/install). -from `frontend/`: +From `frontend/` run `npm install` / `yarn install` to install the frontend packages. -- `yarn dev` runs `tsc-watch`, which runs `vite build` on successful `tsc` transpilation +## Dev -from `.`: +1. From `frontend/`, run `npm dev` / `yarn dev` to start the dev server. +2. Note the address it starts up on (probably `http://localhost:5173/`). +3. Edit `backend/server.py`'s `additional_allowed_origins` to include this address, e.g. + `additional_allowed_origins = ['http://localhost:5173']`. +4. Leaving the dev server running, open a new terminal and go to the project root. +5. Run `python backend/server.py`. +6. Navigate to the dev server address e.g. `http://localhost:5173/`. -- `python backend/server.py` serves both frontend and backend at http://localhost:9090 +To build for dev: `npm build-dev` / `yarn build-dev` -## API - -`backend/server.py` serves the UI and provides a [socket.io](https://github.com/socketio/socket.io) API via [flask-socketio](https://github.com/miguelgrinberg/flask-socketio). - -### Server Listeners - -The server listens for these socket.io events: - -`cancel` - -- Cancels in-progress image generation -- Returns ack only - -`generateImage` - -- Accepts object of image parameters -- Generates an image -- Returns ack only (image generation function sends progress and result via separate events) - -`deleteImage` - -- Accepts file path to image -- Deletes image -- Returns ack only - -`deleteAllImages` WIP - -- Deletes all images in `outputs/` -- Returns ack only - -`requestAllImages` - -- Returns array of all images in `outputs/` - -`requestCapabilities` WIP - -- Returns capabilities of server (torch device, GFPGAN and ESRGAN availability, ???) - -`sendImage` WIP - -- Accepts a File and attributes -- Saves image -- Used to save init images which are not generated images - -### Server Emitters - -`progress` - -- Emitted during each step in generation -- Sends a number from 0 to 1 representing percentage of steps completed - -`result` WIP - -- Emitted when an image generation has completed -- Sends a object: - -``` -{ - url: relative_file_path, - metadata: image_metadata_object -} -``` +To build for production: `npm build` / `yarn build` ## TODO -- Search repo for "TODO" -- My one gripe with Chakra: no way to disable all animations right now and drop the dependence on `framer-motion`. I would prefer to save the ~30kb on bundle and have zero animations. This is on the Chakra roadmap. See https://github.com/chakra-ui/chakra-ui/pull/6368 for last discussion on this. Need to check in on this issue periodically. +- Search repo for "TODO" +- My one gripe with Chakra: no way to disable all animations right now and drop the dependence on + `framer-motion`. I would prefer to save the ~30kb on bundle and have zero animations. This is on + the Chakra roadmap. See https://github.com/chakra-ui/chakra-ui/pull/6368 for last discussion on + this. Need to check in on this issue periodically. +- Mobile friendly layout +- Proper image gallery/viewer/manager +- Help tooltips and such diff --git a/frontend/dist/assets/index.66192cce.js b/frontend/dist/assets/index.66192cce.js new file mode 100644 index 0000000000..351018cfc1 --- /dev/null +++ b/frontend/dist/assets/index.66192cce.js @@ -0,0 +1,694 @@ +function Yj(e,t){for(var n=0;ni[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))i(o);new MutationObserver(o=>{for(const c of o)if(c.type==="childList")for(const u of c.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&i(u)}).observe(document,{childList:!0,subtree:!0});function n(o){const c={};return o.integrity&&(c.integrity=o.integrity),o.referrerpolicy&&(c.referrerPolicy=o.referrerpolicy),o.crossorigin==="use-credentials"?c.credentials="include":o.crossorigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function i(o){if(o.ep)return;o.ep=!0;const c=n(o);fetch(o.href,c)}})();var vc=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function qj(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var k={exports:{}},e5={exports:{}};/** + * @license React + * react.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(e,t){(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var n="18.2.0",i=Symbol.for("react.element"),o=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),u=Symbol.for("react.strict_mode"),m=Symbol.for("react.profiler"),h=Symbol.for("react.provider"),v=Symbol.for("react.context"),b=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),w=Symbol.for("react.suspense_list"),N=Symbol.for("react.memo"),R=Symbol.for("react.lazy"),O=Symbol.for("react.offscreen"),M=Symbol.iterator,B="@@iterator";function I(S){if(S===null||typeof S!="object")return null;var A=M&&S[M]||S[B];return typeof A=="function"?A:null}var F={current:null},z={transition:null},$={current:null,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1},q={current:null},K={},le=null;function me(S){le=S}K.setExtraStackFrame=function(S){le=S},K.getCurrentStack=null,K.getStackAddendum=function(){var S="";le&&(S+=le);var A=K.getCurrentStack;return A&&(S+=A()||""),S};var se=!1,Se=!1,qe=!1,ae=!1,ue=!1,Ee={ReactCurrentDispatcher:F,ReactCurrentBatchConfig:z,ReactCurrentOwner:q};Ee.ReactDebugCurrentFrame=K,Ee.ReactCurrentActQueue=$;function de(S){{for(var A=arguments.length,V=new Array(A>1?A-1:0),G=1;G1?A-1:0),G=1;G1){for(var Ot=Array(St),yt=0;yt1){for(var jt=Array(yt),Tt=0;Tt is not supported and will be removed in a future major release. Did you mean to render instead?")),A.Provider},set:function(we){A.Provider=we}},_currentValue:{get:function(){return A._currentValue},set:function(we){A._currentValue=we}},_currentValue2:{get:function(){return A._currentValue2},set:function(we){A._currentValue2=we}},_threadCount:{get:function(){return A._threadCount},set:function(we){A._threadCount=we}},Consumer:{get:function(){return V||(V=!0,ie("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?")),A.Consumer}},displayName:{get:function(){return A.displayName},set:function(we){te||(de("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.",we),te=!0)}}}),A.Consumer=ze}return A._currentRenderer=null,A._currentRenderer2=null,A}var vr=-1,Ia=0,Fi=1,La=2;function X(S){if(S._status===vr){var A=S._result,V=A();if(V.then(function(ze){if(S._status===Ia||S._status===vr){var we=S;we._status=Fi,we._result=ze}},function(ze){if(S._status===Ia||S._status===vr){var we=S;we._status=La,we._result=ze}}),S._status===vr){var G=S;G._status=Ia,G._result=V}}if(S._status===Fi){var te=S._result;return te===void 0&&ie(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent')) + +Did you accidentally put curly braces around the import?`,te),"default"in te||ie(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent'))`,te),te.default}else throw S._result}function Ue(S){var A={_status:vr,_result:S},V={$$typeof:R,_payload:A,_init:X};{var G,te;Object.defineProperties(V,{defaultProps:{configurable:!0,get:function(){return G},set:function(ze){ie("React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),G=ze,Object.defineProperty(V,"defaultProps",{enumerable:!0})}},propTypes:{configurable:!0,get:function(){return te},set:function(ze){ie("React.lazy(...): It is not supported to assign `propTypes` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),te=ze,Object.defineProperty(V,"propTypes",{enumerable:!0})}}})}return V}function Ke(S){S!=null&&S.$$typeof===N?ie("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof S!="function"?ie("forwardRef requires a render function but was given %s.",S===null?"null":typeof S):S.length!==0&&S.length!==2&&ie("forwardRef render functions accept exactly two parameters: props and ref. %s",S.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),S!=null&&(S.defaultProps!=null||S.propTypes!=null)&&ie("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?");var A={$$typeof:b,render:S};{var V;Object.defineProperty(A,"displayName",{enumerable:!1,configurable:!0,get:function(){return V},set:function(G){V=G,!S.name&&!S.displayName&&(S.displayName=G)}})}return A}var Ct;Ct=Symbol.for("react.module.reference");function on(S){return!!(typeof S=="string"||typeof S=="function"||S===c||S===m||ue||S===u||S===x||S===w||ae||S===O||se||Se||qe||typeof S=="object"&&S!==null&&(S.$$typeof===R||S.$$typeof===N||S.$$typeof===h||S.$$typeof===v||S.$$typeof===b||S.$$typeof===Ct||S.getModuleId!==void 0))}function Cn(S,A){on(S)||ie("memo: The first argument must be a component. Instead received: %s",S===null?"null":typeof S);var V={$$typeof:N,type:S,compare:A===void 0?null:A};{var G;Object.defineProperty(V,"displayName",{enumerable:!1,configurable:!0,get:function(){return G},set:function(te){G=te,!S.name&&!S.displayName&&(S.displayName=te)}})}return V}function nt(){var S=F.current;return S===null&&ie(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: +1. You might have mismatching versions of React and the renderer (such as React DOM) +2. You might be breaking the Rules of Hooks +3. You might have more than one copy of React in the same app +See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.`),S}function qt(S){var A=nt();if(S._context!==void 0){var V=S._context;V.Consumer===S?ie("Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be removed in a future major release. Did you mean to call useContext(Context) instead?"):V.Provider===S&&ie("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?")}return A.useContext(S)}function Vn(S){var A=nt();return A.useState(S)}function Bn(S,A,V){var G=nt();return G.useReducer(S,A,V)}function ln(S){var A=nt();return A.useRef(S)}function Ur(S,A){var V=nt();return V.useEffect(S,A)}function vi(S,A){var V=nt();return V.useInsertionEffect(S,A)}function Po(S,A){var V=nt();return V.useLayoutEffect(S,A)}function ya(S,A){var V=nt();return V.useCallback(S,A)}function oo(S,A){var V=nt();return V.useMemo(S,A)}function Nu(S,A,V){var G=nt();return G.useImperativeHandle(S,A,V)}function gi(S,A){{var V=nt();return V.useDebugValue(S,A)}}function Hs(){var S=nt();return S.useTransition()}function zi(S){var A=nt();return A.useDeferredValue(S)}function Jt(){var S=nt();return S.useId()}function Bi(S,A,V){var G=nt();return G.useSyncExternalStore(S,A,V)}var ba=0,Mo,os,Io,ss,ls,Lo,Fo;function us(){}us.__reactDisabledLog=!0;function Ws(){{if(ba===0){Mo=console.log,os=console.info,Io=console.warn,ss=console.error,ls=console.group,Lo=console.groupCollapsed,Fo=console.groupEnd;var S={configurable:!0,enumerable:!0,value:us,writable:!0};Object.defineProperties(console,{info:S,log:S,warn:S,error:S,group:S,groupCollapsed:S,groupEnd:S})}ba++}}function Gs(){{if(ba--,ba===0){var S={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Me({},S,{value:Mo}),info:Me({},S,{value:os}),warn:Me({},S,{value:Io}),error:Me({},S,{value:ss}),group:Me({},S,{value:ls}),groupCollapsed:Me({},S,{value:Lo}),groupEnd:Me({},S,{value:Fo})})}ba<0&&ie("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var yi=Ee.ReactCurrentDispatcher,Mr;function Fa(S,A,V){{if(Mr===void 0)try{throw Error()}catch(te){var G=te.stack.trim().match(/\n( *(at )?)/);Mr=G&&G[1]||""}return` +`+Mr+S}}var Sa=!1,za;{var cs=typeof WeakMap=="function"?WeakMap:Map;za=new cs}function zo(S,A){if(!S||Sa)return"";{var V=za.get(S);if(V!==void 0)return V}var G;Sa=!0;var te=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var ze;ze=yi.current,yi.current=null,Ws();try{if(A){var we=function(){throw Error()};if(Object.defineProperty(we.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(we,[])}catch(Bt){G=Bt}Reflect.construct(S,[],we)}else{try{we.call()}catch(Bt){G=Bt}S.call(we.prototype)}}else{try{throw Error()}catch(Bt){G=Bt}S()}}catch(Bt){if(Bt&&G&&typeof Bt.stack=="string"){for(var Ve=Bt.stack.split(` +`),st=G.stack.split(` +`),St=Ve.length-1,Ot=st.length-1;St>=1&&Ot>=0&&Ve[St]!==st[Ot];)Ot--;for(;St>=1&&Ot>=0;St--,Ot--)if(Ve[St]!==st[Ot]){if(St!==1||Ot!==1)do if(St--,Ot--,Ot<0||Ve[St]!==st[Ot]){var yt=` +`+Ve[St].replace(" at new "," at ");return S.displayName&&yt.includes("")&&(yt=yt.replace("",S.displayName)),typeof S=="function"&&za.set(S,yt),yt}while(St>=1&&Ot>=0);break}}}finally{Sa=!1,yi.current=ze,Gs(),Error.prepareStackTrace=te}var jt=S?S.displayName||S.name:"",Tt=jt?Fa(jt):"";return typeof S=="function"&&za.set(S,Tt),Tt}function fs(S,A,V){return zo(S,!1)}function Ml(S){var A=S.prototype;return!!(A&&A.isReactComponent)}function xa(S,A,V){if(S==null)return"";if(typeof S=="function")return zo(S,Ml(S));if(typeof S=="string")return Fa(S);switch(S){case x:return Fa("Suspense");case w:return Fa("SuspenseList")}if(typeof S=="object")switch(S.$$typeof){case b:return fs(S.render);case N:return xa(S.type,A,V);case R:{var G=S,te=G._payload,ze=G._init;try{return xa(ze(te),A,V)}catch{}}}return""}var Bo={},Ba=Ee.ReactDebugCurrentFrame;function bi(S){if(S){var A=S._owner,V=xa(S.type,S._source,A?A.type:null);Ba.setExtraStackFrame(V)}else Ba.setExtraStackFrame(null)}function Ys(S,A,V,G,te){{var ze=Function.call.bind(an);for(var we in S)if(ze(S,we)){var Ve=void 0;try{if(typeof S[we]!="function"){var st=Error((G||"React class")+": "+V+" type `"+we+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof S[we]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw st.name="Invariant Violation",st}Ve=S[we](A,we,G,V,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(St){Ve=St}Ve&&!(Ve instanceof Error)&&(bi(te),ie("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",G||"React class",V,we,typeof Ve),bi(null)),Ve instanceof Error&&!(Ve.message in Bo)&&(Bo[Ve.message]=!0,bi(te),ie("Failed %s type: %s",V,Ve.message),bi(null))}}}function un(S){if(S){var A=S._owner,V=xa(S.type,S._source,A?A.type:null);me(V)}else me(null)}var Si;Si=!1;function Uo(){if(q.current){var S=Lt(q.current.type);if(S)return` + +Check the render method of \``+S+"`."}return""}function $t(S){if(S!==void 0){var A=S.fileName.replace(/^.*[\\\/]/,""),V=S.lineNumber;return` + +Check your code at `+A+":"+V+"."}return""}function qs(S){return S!=null?$t(S.__source):""}var xr={};function Ui(S){var A=Uo();if(!A){var V=typeof S=="string"?S:S.displayName||S.name;V&&(A=` + +Check the top-level render call using <`+V+">.")}return A}function Ya(S,A){if(!(!S._store||S._store.validated||S.key!=null)){S._store.validated=!0;var V=Ui(A);if(!xr[V]){xr[V]=!0;var G="";S&&S._owner&&S._owner!==q.current&&(G=" It was passed a child from "+Lt(S._owner.type)+"."),un(S),ie('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',V,G),un(null)}}}function so(S,A){if(typeof S=="object"){if(Wt(S))for(var V=0;V",te=" Did you accidentally export a JSX literal instead of a component?"):we=typeof S,ie("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",we,te)}var Ve=it.apply(this,arguments);if(Ve==null)return Ve;if(G)for(var st=2;st10&&de("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."),G._updatedFibers.clear()}}}var lo=!1,xi=null;function Ks(S){if(xi===null)try{var A=("require"+Math.random()).slice(0,7),V=e&&e[A];xi=V.call(e,"timers").setImmediate}catch{xi=function(te){lo===!1&&(lo=!0,typeof MessageChannel>"u"&&ie("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var ze=new MessageChannel;ze.port1.onmessage=te,ze.port2.postMessage(void 0)}}return xi(S)}var vn=0,Fn=!1;function Il(S){{var A=vn;vn++,$.current===null&&($.current=[]);var V=$.isBatchingLegacy,G;try{if($.isBatchingLegacy=!0,G=S(),!V&&$.didScheduleLegacyUpdate){var te=$.current;te!==null&&($.didScheduleLegacyUpdate=!1,pe(te))}}catch(jt){throw Ua(A),jt}finally{$.isBatchingLegacy=V}if(G!==null&&typeof G=="object"&&typeof G.then=="function"){var ze=G,we=!1,Ve={then:function(jt,Tt){we=!0,ze.then(function(Bt){Ua(A),vn===0?W(Bt,jt,Tt):jt(Bt)},function(Bt){Ua(A),Tt(Bt)})}};return!Fn&&typeof Promise<"u"&&Promise.resolve().then(function(){}).then(function(){we||(Fn=!0,ie("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),Ve}else{var st=G;if(Ua(A),vn===0){var St=$.current;St!==null&&(pe(St),$.current=null);var Ot={then:function(jt,Tt){$.current===null?($.current=[],W(st,jt,Tt)):jt(st)}};return Ot}else{var yt={then:function(jt,Tt){jt(st)}};return yt}}}}function Ua(S){S!==vn-1&&ie("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),vn=S}function W(S,A,V){{var G=$.current;if(G!==null)try{pe(G),Ks(function(){G.length===0?($.current=null,A(S)):W(S,A,V)})}catch(te){V(te)}else A(S)}}var Q=!1;function pe(S){if(!Q){Q=!0;var A=0;try{for(;A0;){var Qt=hn-1>>>1,Tn=je[Qt];if(v(Tn,it)>0)je[Qt]=it,je[hn]=Tn,hn=Qt;else return}}function h(je,it,_t){for(var hn=_t,Qt=je.length,Tn=Qt>>>1;hn_t&&(!je||$n()));){var hn=ae.callback;if(typeof hn=="function"){ae.callback=null,ue=ae.priorityLevel;var Qt=ae.expirationTime<=_t,Tn=hn(Qt);_t=e.unstable_now(),typeof Tn=="function"?ae.callback=Tn:ae===c(se)&&u(se),Ce(_t)}else u(se);ae=c(se)}if(ae!==null)return!0;var Ln=c(Se);return Ln!==null&&kt(Me,Ln.startTime-_t),!1}function at(je,it){switch(je){case b:case x:case w:case N:case R:break;default:je=w}var _t=ue;ue=je;try{return it()}finally{ue=_t}}function gt(je){var it;switch(ue){case b:case x:case w:it=w;break;default:it=ue;break}var _t=ue;ue=it;try{return je()}finally{ue=_t}}function Ht(je){var it=ue;return function(){var _t=ue;ue=it;try{return je.apply(this,arguments)}finally{ue=_t}}}function Ze(je,it,_t){var hn=e.unstable_now(),Qt;if(typeof _t=="object"&&_t!==null){var Tn=_t.delay;typeof Tn=="number"&&Tn>0?Qt=hn+Tn:Qt=hn}else Qt=hn;var Ln;switch(je){case b:Ln=$;break;case x:Ln=q;break;case R:Ln=me;break;case N:Ln=le;break;case w:default:Ln=K;break}var kr=Qt+Ln,Dn={id:qe++,callback:it,priorityLevel:je,startTime:Qt,expirationTime:kr,sortIndex:-1};return Qt>hn?(Dn.sortIndex=Qt,o(Se,Dn),c(se)===null&&Dn===c(Se)&&(ie?Oe():ie=!0,kt(Me,Qt-hn))):(Dn.sortIndex=kr,o(se,Dn),!de&&!Ee&&(de=!0,tn(Ie))),Dn}function ct(){}function wt(){!de&&!Ee&&(de=!0,tn(Ie))}function zt(){return c(se)}function Ge(je){je.callback=null}function Wt(){return ue}var ye=!1,et=null,Nt=-1,lt=i,xn=-1;function $n(){var je=e.unstable_now()-xn;return!(je125){console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported");return}je>0?lt=Math.floor(1e3/je):lt=i}var On=function(){if(et!==null){var je=e.unstable_now();xn=je;var it=!0,_t=!0;try{_t=et(it,je)}finally{_t?gn():(ye=!1,et=null)}}else ye=!1},gn;if(typeof he=="function")gn=function(){he(On)};else if(typeof MessageChannel<"u"){var He=new MessageChannel,Je=He.port2;He.port1.onmessage=On,gn=function(){Je.postMessage(null)}}else gn=function(){xe(On,0)};function tn(je){et=je,ye||(ye=!0,gn())}function kt(je,it){Nt=xe(function(){je(e.unstable_now())},it)}function Oe(){Ne(Nt),Nt=-1}var Yt=Lt,_n=null;e.unstable_IdlePriority=R,e.unstable_ImmediatePriority=b,e.unstable_LowPriority=N,e.unstable_NormalPriority=w,e.unstable_Profiling=_n,e.unstable_UserBlockingPriority=x,e.unstable_cancelCallback=Ge,e.unstable_continueExecution=wt,e.unstable_forceFrameRate=an,e.unstable_getCurrentPriorityLevel=Wt,e.unstable_getFirstCallbackNode=zt,e.unstable_next=gt,e.unstable_pauseExecution=ct,e.unstable_requestPaint=Yt,e.unstable_runWithPriority=at,e.unstable_scheduleCallback=Ze,e.unstable_shouldYield=$n,e.unstable_wrapCallback=Ht,typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error)})()})(ZI);(function(e){e.exports=ZI})(XI);/** + * @license React + * react-dom.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var e=k.exports,t=XI.exports,n=e.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,i=!1;function o(r){i=r}function c(r){if(!i){for(var a=arguments.length,s=new Array(a>1?a-1:0),f=1;f1?a-1:0),f=1;f2&&(r[0]==="o"||r[0]==="O")&&(r[1]==="n"||r[1]==="N")}function kr(r,a,s,f){if(s!==null&&s.type===He)return!1;switch(typeof a){case"function":case"symbol":return!0;case"boolean":{if(f)return!1;if(s!==null)return!s.acceptsBooleans;var p=r.toLowerCase().slice(0,5);return p!=="data-"&&p!=="aria-"}default:return!1}}function Dn(r,a,s,f){if(a===null||typeof a>"u"||kr(r,a,s,f))return!0;if(f)return!1;if(s!==null)switch(s.type){case kt:return!a;case Oe:return a===!1;case Yt:return isNaN(a);case _n:return isNaN(a)||a<1}return!1}function va(r){return Rn.hasOwnProperty(r)?Rn[r]:null}function jn(r,a,s,f,p,y,C){this.acceptsBooleans=a===tn||a===kt||a===Oe,this.attributeName=f,this.attributeNamespace=p,this.mustUseProperty=s,this.propertyName=r,this.type=a,this.sanitizeURL=y,this.removeEmptyString=C}var Rn={},ga=["children","dangerouslySetInnerHTML","defaultValue","defaultChecked","innerHTML","suppressContentEditableWarning","suppressHydrationWarning","style"];ga.forEach(function(r){Rn[r]=new jn(r,He,!1,r,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(r){var a=r[0],s=r[1];Rn[a]=new jn(a,Je,!1,s,null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(r){Rn[r]=new jn(r,tn,!1,r.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(r){Rn[r]=new jn(r,tn,!1,r,null,!1,!1)}),["allowFullScreen","async","autoFocus","autoPlay","controls","default","defer","disabled","disablePictureInPicture","disableRemotePlayback","formNoValidate","hidden","loop","noModule","noValidate","open","playsInline","readOnly","required","reversed","scoped","seamless","itemScope"].forEach(function(r){Rn[r]=new jn(r,kt,!1,r.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(r){Rn[r]=new jn(r,kt,!0,r,null,!1,!1)}),["capture","download"].forEach(function(r){Rn[r]=new jn(r,Oe,!1,r,null,!1,!1)}),["cols","rows","size","span"].forEach(function(r){Rn[r]=new jn(r,_n,!1,r,null,!1,!1)}),["rowSpan","start"].forEach(function(r){Rn[r]=new jn(r,Yt,!1,r.toLowerCase(),null,!1,!1)});var Sr=/[\-\:]([a-z])/g,ko=function(r){return r[1].toUpperCase()};["accent-height","alignment-baseline","arabic-form","baseline-shift","cap-height","clip-path","clip-rule","color-interpolation","color-interpolation-filters","color-profile","color-rendering","dominant-baseline","enable-background","fill-opacity","fill-rule","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","glyph-name","glyph-orientation-horizontal","glyph-orientation-vertical","horiz-adv-x","horiz-origin-x","image-rendering","letter-spacing","lighting-color","marker-end","marker-mid","marker-start","overline-position","overline-thickness","paint-order","panose-1","pointer-events","rendering-intent","shape-rendering","stop-color","stop-opacity","strikethrough-position","strikethrough-thickness","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-anchor","text-decoration","text-rendering","underline-position","underline-thickness","unicode-bidi","unicode-range","units-per-em","v-alphabetic","v-hanging","v-ideographic","v-mathematical","vector-effect","vert-adv-y","vert-origin-x","vert-origin-y","word-spacing","writing-mode","xmlns:xlink","x-height"].forEach(function(r){var a=r.replace(Sr,ko);Rn[a]=new jn(a,Je,!1,r,null,!1,!1)}),["xlink:actuate","xlink:arcrole","xlink:role","xlink:show","xlink:title","xlink:type"].forEach(function(r){var a=r.replace(Sr,ko);Rn[a]=new jn(a,Je,!1,r,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(r){var a=r.replace(Sr,ko);Rn[a]=new jn(a,Je,!1,r,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(r){Rn[r]=new jn(r,Je,!1,r.toLowerCase(),null,!1,!1)});var as="xlinkHref";Rn[as]=new jn("xlinkHref",Je,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(r){Rn[r]=new jn(r,Je,!1,r.toLowerCase(),null,!0,!0)});var is=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i,Oo=!1;function Do(r){!Oo&&is.test(r)&&(Oo=!0,u("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.",JSON.stringify(r)))}function vr(r,a,s,f){if(f.mustUseProperty){var p=f.propertyName;return r[p]}else{xn(s,a),f.sanitizeURL&&Do(""+s);var y=f.attributeName,C=null;if(f.type===Oe){if(r.hasAttribute(y)){var T=r.getAttribute(y);return T===""?!0:Dn(a,s,f,!1)?T:T===""+s?s:T}}else if(r.hasAttribute(y)){if(Dn(a,s,f,!1))return r.getAttribute(y);if(f.type===kt)return s;C=r.getAttribute(y)}return Dn(a,s,f,!1)?C===null?s:C:C===""+s?s:C}}function Ia(r,a,s,f){{if(!Tn(a))return;if(!r.hasAttribute(a))return s===void 0?void 0:null;var p=r.getAttribute(a);return xn(s,a),p===""+s?s:p}}function Fi(r,a,s,f){var p=va(a);if(!Ln(a,p,f)){if(Dn(a,s,p,f)&&(s=null),f||p===null){if(Tn(a)){var y=a;s===null?r.removeAttribute(y):(xn(s,a),r.setAttribute(y,""+s))}return}var C=p.mustUseProperty;if(C){var T=p.propertyName;if(s===null){var D=p.type;r[T]=D===kt?!1:""}else r[T]=s;return}var U=p.attributeName,H=p.attributeNamespace;if(s===null)r.removeAttribute(U);else{var ee=p.type,J;ee===kt||ee===Oe&&s===!0?J="":(xn(s,U),J=""+s,p.sanitizeURL&&Do(J.toString())),H?r.setAttributeNS(H,U,J):r.setAttribute(U,J)}}}var La=Symbol.for("react.element"),X=Symbol.for("react.portal"),Ue=Symbol.for("react.fragment"),Ke=Symbol.for("react.strict_mode"),Ct=Symbol.for("react.profiler"),on=Symbol.for("react.provider"),Cn=Symbol.for("react.context"),nt=Symbol.for("react.forward_ref"),qt=Symbol.for("react.suspense"),Vn=Symbol.for("react.suspense_list"),Bn=Symbol.for("react.memo"),ln=Symbol.for("react.lazy"),Ur=Symbol.for("react.scope"),vi=Symbol.for("react.debug_trace_mode"),Po=Symbol.for("react.offscreen"),ya=Symbol.for("react.legacy_hidden"),oo=Symbol.for("react.cache"),Nu=Symbol.for("react.tracing_marker"),gi=Symbol.iterator,Hs="@@iterator";function zi(r){if(r===null||typeof r!="object")return null;var a=gi&&r[gi]||r[Hs];return typeof a=="function"?a:null}var Jt=Object.assign,Bi=0,ba,Mo,os,Io,ss,ls,Lo;function Fo(){}Fo.__reactDisabledLog=!0;function us(){{if(Bi===0){ba=console.log,Mo=console.info,os=console.warn,Io=console.error,ss=console.group,ls=console.groupCollapsed,Lo=console.groupEnd;var r={configurable:!0,enumerable:!0,value:Fo,writable:!0};Object.defineProperties(console,{info:r,log:r,warn:r,error:r,group:r,groupCollapsed:r,groupEnd:r})}Bi++}}function Ws(){{if(Bi--,Bi===0){var r={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Jt({},r,{value:ba}),info:Jt({},r,{value:Mo}),warn:Jt({},r,{value:os}),error:Jt({},r,{value:Io}),group:Jt({},r,{value:ss}),groupCollapsed:Jt({},r,{value:ls}),groupEnd:Jt({},r,{value:Lo})})}Bi<0&&u("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var Gs=n.ReactCurrentDispatcher,yi;function Mr(r,a,s){{if(yi===void 0)try{throw Error()}catch(p){var f=p.stack.trim().match(/\n( *(at )?)/);yi=f&&f[1]||""}return` +`+yi+r}}var Fa=!1,Sa;{var za=typeof WeakMap=="function"?WeakMap:Map;Sa=new za}function cs(r,a){if(!r||Fa)return"";{var s=Sa.get(r);if(s!==void 0)return s}var f;Fa=!0;var p=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var y;y=Gs.current,Gs.current=null,us();try{if(a){var C=function(){throw Error()};if(Object.defineProperty(C.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(C,[])}catch(ge){f=ge}Reflect.construct(r,[],C)}else{try{C.call()}catch(ge){f=ge}r.call(C.prototype)}}else{try{throw Error()}catch(ge){f=ge}r()}}catch(ge){if(ge&&f&&typeof ge.stack=="string"){for(var T=ge.stack.split(` +`),D=f.stack.split(` +`),U=T.length-1,H=D.length-1;U>=1&&H>=0&&T[U]!==D[H];)H--;for(;U>=1&&H>=0;U--,H--)if(T[U]!==D[H]){if(U!==1||H!==1)do if(U--,H--,H<0||T[U]!==D[H]){var ee=` +`+T[U].replace(" at new "," at ");return r.displayName&&ee.includes("")&&(ee=ee.replace("",r.displayName)),typeof r=="function"&&Sa.set(r,ee),ee}while(U>=1&&H>=0);break}}}finally{Fa=!1,Gs.current=y,Ws(),Error.prepareStackTrace=p}var J=r?r.displayName||r.name:"",ve=J?Mr(J):"";return typeof r=="function"&&Sa.set(r,ve),ve}function zo(r,a,s){return cs(r,!0)}function fs(r,a,s){return cs(r,!1)}function Ml(r){var a=r.prototype;return!!(a&&a.isReactComponent)}function xa(r,a,s){if(r==null)return"";if(typeof r=="function")return cs(r,Ml(r));if(typeof r=="string")return Mr(r);switch(r){case qt:return Mr("Suspense");case Vn:return Mr("SuspenseList")}if(typeof r=="object")switch(r.$$typeof){case nt:return fs(r.render);case Bn:return xa(r.type,a,s);case ln:{var f=r,p=f._payload,y=f._init;try{return xa(y(p),a,s)}catch{}}}return""}function Bo(r){switch(r._debugOwner&&r._debugOwner.type,r._debugSource,r.tag){case N:return Mr(r.type);case le:return Mr("Lazy");case $:return Mr("Suspense");case Se:return Mr("SuspenseList");case h:case b:case K:return fs(r.type);case F:return fs(r.type.render);case v:return zo(r.type);default:return""}}function Ba(r){try{var a="",s=r;do a+=Bo(s),s=s.return;while(s);return a}catch(f){return` +Error generating stack: `+f.message+` +`+f.stack}}function bi(r,a,s){var f=r.displayName;if(f)return f;var p=a.displayName||a.name||"";return p!==""?s+"("+p+")":s}function Ys(r){return r.displayName||"Context"}function un(r){if(r==null)return null;if(typeof r.tag=="number"&&u("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof r=="function")return r.displayName||r.name||null;if(typeof r=="string")return r;switch(r){case Ue:return"Fragment";case X:return"Portal";case Ct:return"Profiler";case Ke:return"StrictMode";case qt:return"Suspense";case Vn:return"SuspenseList"}if(typeof r=="object")switch(r.$$typeof){case Cn:var a=r;return Ys(a)+".Consumer";case on:var s=r;return Ys(s._context)+".Provider";case nt:return bi(r,r.render,"ForwardRef");case Bn:var f=r.displayName||null;return f!==null?f:un(r.type)||"Memo";case ln:{var p=r,y=p._payload,C=p._init;try{return un(C(y))}catch{return null}}}return null}function Si(r,a,s){var f=a.displayName||a.name||"";return r.displayName||(f!==""?s+"("+f+")":s)}function Uo(r){return r.displayName||"Context"}function $t(r){var a=r.tag,s=r.type;switch(a){case Ee:return"Cache";case B:var f=s;return Uo(f)+".Consumer";case I:var p=s;return Uo(p._context)+".Provider";case se:return"DehydratedFragment";case F:return Si(s,s.render,"ForwardRef");case O:return"Fragment";case N:return s;case w:return"Portal";case x:return"Root";case R:return"Text";case le:return un(s);case M:return s===Ke?"StrictMode":"Mode";case ae:return"Offscreen";case z:return"Profiler";case qe:return"Scope";case $:return"Suspense";case Se:return"SuspenseList";case de:return"TracingMarker";case v:case h:case me:case b:case q:case K:if(typeof s=="function")return s.displayName||s.name||null;if(typeof s=="string")return s;break}return null}var qs=n.ReactDebugCurrentFrame,xr=null,Ui=!1;function Ya(){{if(xr===null)return null;var r=xr._debugOwner;if(r!==null&&typeof r<"u")return $t(r)}return null}function so(){return xr===null?"":Ba(xr)}function _r(){qs.getCurrentStack=null,xr=null,Ui=!1}function or(r){qs.getCurrentStack=r===null?null:so,xr=r,Ui=!1}function $o(){return xr}function Kr(r){Ui=r}function pr(r){return""+r}function aa(r){switch(typeof r){case"boolean":case"number":case"string":case"undefined":return r;case"object":return gn(r),r;default:return""}}var Eu={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0};function lo(r,a){Eu[a.type]||a.onChange||a.onInput||a.readOnly||a.disabled||a.value==null||u("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."),a.onChange||a.readOnly||a.disabled||a.checked==null||u("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.")}function xi(r){var a=r.type,s=r.nodeName;return s&&s.toLowerCase()==="input"&&(a==="checkbox"||a==="radio")}function Ks(r){return r._valueTracker}function vn(r){r._valueTracker=null}function Fn(r){var a="";return r&&(xi(r)?a=r.checked?"true":"false":a=r.value),a}function Il(r){var a=xi(r)?"checked":"value",s=Object.getOwnPropertyDescriptor(r.constructor.prototype,a);gn(r[a]);var f=""+r[a];if(!(r.hasOwnProperty(a)||typeof s>"u"||typeof s.get!="function"||typeof s.set!="function")){var p=s.get,y=s.set;Object.defineProperty(r,a,{configurable:!0,get:function(){return p.call(this)},set:function(T){gn(T),f=""+T,y.call(this,T)}}),Object.defineProperty(r,a,{enumerable:s.enumerable});var C={getValue:function(){return f},setValue:function(T){gn(T),f=""+T},stopTracking:function(){vn(r),delete r[a]}};return C}}function Ua(r){Ks(r)||(r._valueTracker=Il(r))}function W(r){if(!r)return!1;var a=Ks(r);if(!a)return!0;var s=a.getValue(),f=Fn(r);return f!==s?(a.setValue(f),!0):!1}function Q(r){if(r=r||(typeof document<"u"?document:void 0),typeof r>"u")return null;try{return r.activeElement||r.body}catch{return r.body}}var pe=!1,ot=!1,cn=!1,zn=!1;function Kt(r){var a=r.type==="checkbox"||r.type==="radio";return a?r.checked!=null:r.value!=null}function S(r,a){var s=r,f=a.checked,p=Jt({},a,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:f??s._wrapperState.initialChecked});return p}function A(r,a){lo("input",a),a.checked!==void 0&&a.defaultChecked!==void 0&&!ot&&(u("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ya()||"A component",a.type),ot=!0),a.value!==void 0&&a.defaultValue!==void 0&&!pe&&(u("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ya()||"A component",a.type),pe=!0);var s=r,f=a.defaultValue==null?"":a.defaultValue;s._wrapperState={initialChecked:a.checked!=null?a.checked:a.defaultChecked,initialValue:aa(a.value!=null?a.value:f),controlled:Kt(a)}}function V(r,a){var s=r,f=a.checked;f!=null&&Fi(s,"checked",f,!1)}function G(r,a){var s=r;{var f=Kt(a);!s._wrapperState.controlled&&f&&!zn&&(u("A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),zn=!0),s._wrapperState.controlled&&!f&&!cn&&(u("A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),cn=!0)}V(r,a);var p=aa(a.value),y=a.type;if(p!=null)y==="number"?(p===0&&s.value===""||s.value!=p)&&(s.value=pr(p)):s.value!==pr(p)&&(s.value=pr(p));else if(y==="submit"||y==="reset"){s.removeAttribute("value");return}a.hasOwnProperty("value")?Ve(s,a.type,p):a.hasOwnProperty("defaultValue")&&Ve(s,a.type,aa(a.defaultValue)),a.checked==null&&a.defaultChecked!=null&&(s.defaultChecked=!!a.defaultChecked)}function te(r,a,s){var f=r;if(a.hasOwnProperty("value")||a.hasOwnProperty("defaultValue")){var p=a.type,y=p==="submit"||p==="reset";if(y&&(a.value===void 0||a.value===null))return;var C=pr(f._wrapperState.initialValue);s||C!==f.value&&(f.value=C),f.defaultValue=C}var T=f.name;T!==""&&(f.name=""),f.defaultChecked=!f.defaultChecked,f.defaultChecked=!!f._wrapperState.initialChecked,T!==""&&(f.name=T)}function ze(r,a){var s=r;G(s,a),we(s,a)}function we(r,a){var s=a.name;if(a.type==="radio"&&s!=null){for(var f=r;f.parentNode;)f=f.parentNode;xn(s,"name");for(var p=f.querySelectorAll("input[name="+JSON.stringify(""+s)+'][type="radio"]'),y=0;y.")))}):a.dangerouslySetInnerHTML!=null&&(Ot||(Ot=!0,u("Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.")))),a.selected!=null&&!st&&(u("Use the `defaultValue` or `value` props on must be a scalar value if `multiple` is false.%s",s,Ca())}}}}function Pn(r,a,s,f){var p=r.options;if(a){for(var y=s,C={},T=0;T.");var f=Jt({},a,{value:void 0,defaultValue:void 0,children:pr(s._wrapperState.initialValue)});return f}function Nv(r,a){var s=r;lo("textarea",a),a.value!==void 0&&a.defaultValue!==void 0&&!lb&&(u("%s contains a textarea with both value and defaultValue props. Textarea elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled textarea and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ya()||"A component"),lb=!0);var f=a.value;if(f==null){var p=a.children,y=a.defaultValue;if(p!=null){u("Use the `defaultValue` or `value` props instead of setting children on - - - -
-
Basic options
- - - - - - - - - - -
- - - - - - - - - - - - - - - - -
-
-
Image-to-image options
+ + Stable Diffusion Dream Server + + + + + + + + + + + +
+

Stable Diffusion Dream Server

+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + +
+
+
+ + + + +
-
- - - - -
+ +
+ + + + + + + + +
+
+
-
Post-processing options
- - - + + + + + + +
+
+ + + + +
- -
-
-
- - -
- -
- Postprocessing...1/3 -
- -
- -
-
-

No results...

+
+ + +
+
+
+ + +
+ +
+ Postprocessing...1/3
- - +
+ +
+
+ + + diff --git a/static/dream_web/index.js b/static/dream_web/index.js index ac68034920..5de690297d 100644 --- a/static/dream_web/index.js +++ b/static/dream_web/index.js @@ -1,3 +1,109 @@ +const socket = io(); + +var priorResultsLoadState = { + page: 0, + pages: 1, + per_page: 10, + total: 20, + offset: 0, // number of items generated since last load + loading: false, + initialized: false +}; + +function loadPriorResults() { + // Fix next page by offset + let offsetPages = priorResultsLoadState.offset / priorResultsLoadState.per_page; + priorResultsLoadState.page += offsetPages; + priorResultsLoadState.pages += offsetPages; + priorResultsLoadState.total += priorResultsLoadState.offset; + priorResultsLoadState.offset = 0; + + if (priorResultsLoadState.loading) { + return; + } + + if (priorResultsLoadState.page >= priorResultsLoadState.pages) { + return; // Nothing more to load + } + + // Load + priorResultsLoadState.loading = true + let url = new URL('/api/images', document.baseURI); + url.searchParams.append('page', priorResultsLoadState.initialized ? priorResultsLoadState.page + 1 : priorResultsLoadState.page); + url.searchParams.append('per_page', priorResultsLoadState.per_page); + fetch(url.href, { + method: 'GET', + headers: new Headers({'content-type': 'application/json'}) + }) + .then(response => response.json()) + .then(data => { + priorResultsLoadState.page = data.page; + priorResultsLoadState.pages = data.pages; + priorResultsLoadState.per_page = data.per_page; + priorResultsLoadState.total = data.total; + + data.items.forEach(function(dreamId, index) { + let src = 'api/images/' + dreamId; + fetch('/api/images/' + dreamId + '/metadata', { + method: 'GET', + headers: new Headers({'content-type': 'application/json'}) + }) + .then(response => response.json()) + .then(metadata => { + let seed = metadata.seed || 0; // TODO: Parse old metadata + appendOutput(src, seed, metadata, true); + }); + }); + + // Load until page is full + if (!priorResultsLoadState.initialized) { + if (document.body.scrollHeight <= window.innerHeight) { + loadPriorResults(); + } + } + }) + .finally(() => { + priorResultsLoadState.loading = false; + priorResultsLoadState.initialized = true; + }); +} + +function resetForm() { + var form = document.getElementById('generate-form'); + form.querySelector('fieldset').removeAttribute('disabled'); +} + +function initProgress(totalSteps, showProgressImages) { + // TODO: Progress could theoretically come from multiple jobs at the same time (in the future) + let progressSectionEle = document.querySelector('#progress-section'); + progressSectionEle.style.display = 'initial'; + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('max', totalSteps); + + let progressImageEle = document.querySelector('#progress-image'); + progressImageEle.src = BLANK_IMAGE_URL; + progressImageEle.style.display = showProgressImages ? 'initial': 'none'; +} + +function setProgress(step, totalSteps, src) { + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('value', step); + + if (src) { + let progressImageEle = document.querySelector('#progress-image'); + progressImageEle.src = src; + } +} + +function resetProgress(hide = true) { + if (hide) { + let progressSectionEle = document.querySelector('#progress-section'); + progressSectionEle.style.display = 'none'; + } + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('value', 0); +} + function toBase64(file) { return new Promise((resolve, reject) => { const r = new FileReader(); @@ -7,17 +113,41 @@ function toBase64(file) { }); } -function appendOutput(src, seed, config) { - let outputNode = document.createElement("figure"); - - let variations = config.with_variations; - if (config.variation_amount > 0) { - variations = (variations ? variations + ',' : '') + seed + ':' + config.variation_amount; +function ondragdream(event) { + let dream = event.target.dataset.dream; + event.dataTransfer.setData("dream", dream); +} + +function seedClick(event) { + // Get element + var image = event.target.closest('figure').querySelector('img'); + var dream = JSON.parse(decodeURIComponent(image.dataset.dream)); + + let form = document.querySelector("#generate-form"); + for (const [k, v] of new FormData(form)) { + if (k == 'initimg') { continue; } + let formElem = form.querySelector(`*[name=${k}]`); + formElem.value = dream[k] !== undefined ? dream[k] : formElem.defaultValue; } - let baseseed = (config.with_variations || config.variation_amount > 0) ? config.seed : seed; - let altText = baseseed + ' | ' + (variations ? variations + ' | ' : '') + config.prompt; + + document.querySelector("#seed").value = dream.seed; + document.querySelector('#iterations').value = 1; // Reset to 1 iteration since we clicked a single image (not a full job) + + // NOTE: leaving this manual for the user for now - it was very confusing with this behavior + // document.querySelector("#with_variations").value = variations || ''; + // if (document.querySelector("#variation_amount").value <= 0) { + // document.querySelector("#variation_amount").value = 0.2; + // } + + saveFields(document.querySelector("#generate-form")); +} + +function appendOutput(src, seed, config, toEnd=false) { + let outputNode = document.createElement("figure"); + let altText = seed.toString() + " | " + config.prompt; // img needs width and height for lazy loading to work + // TODO: store the full config in a data attribute on the image? const figureContents = ` + height="256" + draggable="true" + ondragstart="ondragdream(event, this)" + data-dream="${encodeURIComponent(JSON.stringify(config))}" + data-dreamId="${encodeURIComponent(config.dreamId)}"> -
${seed}
+
${seed}
`; outputNode.innerHTML = figureContents; - let figcaption = outputNode.querySelector('figcaption'); - // Reload image config - figcaption.addEventListener('click', () => { - let form = document.querySelector("#generate-form"); - for (const [k, v] of new FormData(form)) { - if (k == 'initimg') { continue; } - form.querySelector(`*[name=${k}]`).value = config[k]; - } - - document.querySelector("#seed").value = baseseed; - document.querySelector("#with_variations").value = variations || ''; - if (document.querySelector("#variation_amount").value <= 0) { - document.querySelector("#variation_amount").value = 0.2; - } - - saveFields(document.querySelector("#generate-form")); - }); - - document.querySelector("#results").prepend(outputNode); + if (toEnd) { + document.querySelector("#results").append(outputNode); + } else { + document.querySelector("#results").prepend(outputNode); + } + document.querySelector("#no-results-message")?.remove(); } function saveFields(form) { @@ -79,93 +200,109 @@ function clearFields(form) { const BLANK_IMAGE_URL = 'data:image/svg+xml,'; async function generateSubmit(form) { - const prompt = document.querySelector("#prompt").value; - // Convert file data to base64 + // TODO: Should probably uplaod files with formdata or something, and store them in the backend? let formData = Object.fromEntries(new FormData(form)); + if (!formData.enable_generate && !formData.enable_init_image) { + gen_label = document.querySelector("label[for=enable_generate]").innerHTML; + initimg_label = document.querySelector("label[for=enable_init_image]").innerHTML; + alert(`Error: one of "${gen_label}" or "${initimg_label}" must be set`); + } + + formData.initimg_name = formData.initimg.name formData.initimg = formData.initimg.name !== '' ? await toBase64(formData.initimg) : null; - let strength = formData.strength; - let totalSteps = formData.initimg ? Math.floor(strength * formData.steps) : formData.steps; - - let progressSectionEle = document.querySelector('#progress-section'); - progressSectionEle.style.display = 'initial'; - let progressEle = document.querySelector('#progress-bar'); - progressEle.setAttribute('max', totalSteps); - let progressImageEle = document.querySelector('#progress-image'); - progressImageEle.src = BLANK_IMAGE_URL; - - progressImageEle.style.display = {}.hasOwnProperty.call(formData, 'progress_images') ? 'initial': 'none'; - - // Post as JSON, using Fetch streaming to get results - fetch(form.action, { - method: form.method, - body: JSON.stringify(formData), - }).then(async (response) => { - const reader = response.body.getReader(); - - let noOutputs = true; - while (true) { - let {value, done} = await reader.read(); - value = new TextDecoder().decode(value); - if (done) { - progressSectionEle.style.display = 'none'; - break; - } - - for (let event of value.split('\n').filter(e => e !== '')) { - const data = JSON.parse(event); - - if (data.event === 'result') { - noOutputs = false; - appendOutput(data.url, data.seed, data.config); - progressEle.setAttribute('value', 0); - progressEle.setAttribute('max', totalSteps); - } else if (data.event === 'upscaling-started') { - document.getElementById("processing_cnt").textContent=data.processed_file_cnt; - document.getElementById("scaling-inprocess-message").style.display = "block"; - } else if (data.event === 'upscaling-done') { - document.getElementById("scaling-inprocess-message").style.display = "none"; - } else if (data.event === 'step') { - progressEle.setAttribute('value', data.step); - if (data.url) { - progressImageEle.src = data.url; - } - } else if (data.event === 'canceled') { - // avoid alerting as if this were an error case - noOutputs = false; - } - } - } - - // Re-enable form, remove no-results-message - form.querySelector('fieldset').removeAttribute('disabled'); - document.querySelector("#prompt").value = prompt; - document.querySelector('progress').setAttribute('value', '0'); - - if (noOutputs) { - alert("Error occurred while generating."); - } + // Evaluate all checkboxes + let checkboxes = form.querySelectorAll('input[type=checkbox]'); + checkboxes.forEach(function (checkbox) { + if (checkbox.checked) { + formData[checkbox.name] = 'true'; + } + }); + + let strength = formData.strength; + let totalSteps = formData.initimg ? Math.floor(strength * formData.steps) : formData.steps; + let showProgressImages = formData.progress_images; + + // Set enabling flags + + + // Initialize the progress bar + initProgress(totalSteps, showProgressImages); + + // POST, use response to listen for events + fetch(form.action, { + method: form.method, + headers: new Headers({'content-type': 'application/json'}), + body: JSON.stringify(formData), + }) + .then(response => response.json()) + .then(data => { + var jobId = data.jobId; + socket.emit('join_room', { 'room': jobId }); }); - // Disable form while generating form.querySelector('fieldset').setAttribute('disabled',''); - document.querySelector("#prompt").value = `Generating: "${prompt}"`; } -async function fetchRunLog() { - try { - let response = await fetch('/run_log.json') - const data = await response.json(); - for(let item of data.run_log) { - appendOutput(item.url, item.seed, item); - } - } catch (e) { - console.error(e); - } +function fieldSetEnableChecked(event) { + cb = event.target; + fields = cb.closest('fieldset'); + fields.disabled = !cb.checked; } +// Socket listeners +socket.on('job_started', (data) => {}) + +socket.on('dream_result', (data) => { + var jobId = data.jobId; + var dreamId = data.dreamId; + var dreamRequest = data.dreamRequest; + var src = 'api/images/' + dreamId; + + priorResultsLoadState.offset += 1; + appendOutput(src, dreamRequest.seed, dreamRequest); + + resetProgress(false); +}) + +socket.on('dream_progress', (data) => { + // TODO: it'd be nice if we could get a seed reported here, but the generator would need to be updated + var step = data.step; + var totalSteps = data.totalSteps; + var jobId = data.jobId; + var dreamId = data.dreamId; + + var progressType = data.progressType + if (progressType === 'GENERATION') { + var src = data.hasProgressImage ? + 'api/intermediates/' + dreamId + '/' + step + : null; + setProgress(step, totalSteps, src); + } else if (progressType === 'UPSCALING_STARTED') { + // step and totalSteps are used for upscale count on this message + document.getElementById("processing_cnt").textContent = step; + document.getElementById("processing_total").textContent = totalSteps; + document.getElementById("scaling-inprocess-message").style.display = "block"; + } else if (progressType == 'UPSCALING_DONE') { + document.getElementById("scaling-inprocess-message").style.display = "none"; + } +}) + +socket.on('job_canceled', (data) => { + resetForm(); + resetProgress(); +}) + +socket.on('job_done', (data) => { + jobId = data.jobId + socket.emit('leave_room', { 'room': jobId }); + + resetForm(); + resetProgress(); +}) + window.onload = async () => { document.querySelector("#prompt").addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { @@ -183,7 +320,7 @@ window.onload = async () => { saveFields(e.target.form); }); document.querySelector("#reset-seed").addEventListener('click', (e) => { - document.querySelector("#seed").value = -1; + document.querySelector("#seed").value = 0; saveFields(e.target.form); }); document.querySelector("#reset-all").addEventListener('click', (e) => { @@ -195,13 +332,13 @@ window.onload = async () => { loadFields(document.querySelector("#generate-form")); document.querySelector('#cancel-button').addEventListener('click', () => { - fetch('/cancel').catch(e => { + fetch('/api/cancel').catch(e => { console.error(e); }); }); document.documentElement.addEventListener('keydown', (e) => { if (e.key === "Escape") - fetch('/cancel').catch(err => { + fetch('/api/cancel').catch(err => { console.error(err); }); }); @@ -209,5 +346,51 @@ window.onload = async () => { if (!config.gfpgan_model_exists) { document.querySelector("#gfpgan").style.display = 'none'; } - await fetchRunLog() + + window.addEventListener("scroll", () => { + if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) { + loadPriorResults(); + } + }); + + + + // Enable/disable forms by checkboxes + document.querySelectorAll("legend > input[type=checkbox]").forEach(function(cb) { + cb.addEventListener('change', fieldSetEnableChecked); + fieldSetEnableChecked({ target: cb}) + }); + + + // Load some of the previous results + loadPriorResults(); + + // Image drop/upload WIP + /* + let drop = document.getElementById('dropper'); + function ondrop(event) { + let dreamData = event.dataTransfer.getData('dream'); + if (dreamData) { + var dream = JSON.parse(decodeURIComponent(dreamData)); + alert(dream.dreamId); + } + }; + + function ondragenter(event) { + event.preventDefault(); + }; + + function ondragover(event) { + event.preventDefault(); + }; + + function ondragleave(event) { + + } + + drop.addEventListener('drop', ondrop); + drop.addEventListener('dragenter', ondragenter); + drop.addEventListener('dragover', ondragover); + drop.addEventListener('dragleave', ondragleave); + */ }; diff --git a/static/legacy_web/favicon.ico b/static/legacy_web/favicon.ico new file mode 100644 index 0000000000..51eb844a6a Binary files /dev/null and b/static/legacy_web/favicon.ico differ diff --git a/static/legacy_web/index.css b/static/legacy_web/index.css new file mode 100644 index 0000000000..51f0f267c3 --- /dev/null +++ b/static/legacy_web/index.css @@ -0,0 +1,152 @@ +* { + font-family: 'Arial'; + font-size: 100%; +} +body { + font-size: 1em; +} +textarea { + font-size: 0.95em; +} +header, form, #progress-section { + margin-left: auto; + margin-right: auto; + max-width: 1024px; + text-align: center; +} +fieldset { + border: none; + line-height: 2.2em; +} +select, input { + margin-right: 10px; + padding: 2px; +} +input[type=submit] { + background-color: #666; + color: white; +} +input[type=checkbox] { + margin-right: 0px; + width: 20px; + height: 20px; + vertical-align: middle; +} +input#seed { + margin-right: 0px; +} +div { + padding: 10px 10px 10px 10px; +} +header { + margin-bottom: 16px; +} +header h1 { + margin-bottom: 0; + font-size: 2em; +} +#search-box { + display: flex; +} +#scaling-inprocess-message { + font-weight: bold; + font-style: italic; + display: none; +} +#prompt { + flex-grow: 1; + padding: 5px 10px 5px 10px; + border: 1px solid #999; + outline: none; +} +#submit { + padding: 5px 10px 5px 10px; + border: 1px solid #999; +} +#reset-all, #remove-image { + margin-top: 12px; + font-size: 0.8em; + background-color: pink; + border: 1px solid #999; + border-radius: 4px; +} +#results { + text-align: center; + margin: auto; + padding-top: 10px; +} +#results figure { + display: inline-block; + margin: 10px; +} +#results figcaption { + font-size: 0.8em; + padding: 3px; + color: #888; + cursor: pointer; +} +#results img { + border-radius: 5px; + object-fit: cover; +} +#fieldset-config { + line-height:2em; + background-color: #F0F0F0; +} +input[type="number"] { + width: 60px; +} +#seed { + width: 150px; +} +button#reset-seed { + font-size: 1.7em; + background: #efefef; + border: 1px solid #999; + border-radius: 4px; + line-height: 0.8; + margin: 0 10px 0 0; + padding: 0 5px 3px; + vertical-align: middle; +} +label { + white-space: nowrap; +} +#progress-section { + display: none; +} +#progress-image { + width: 30vh; + height: 30vh; +} +#cancel-button { + cursor: pointer; + color: red; +} +#basic-parameters { + background-color: #EEEEEE; +} +#txt2img { + background-color: #DCDCDC; +} +#variations { + background-color: #EEEEEE; +} +#img2img { + background-color: #DCDCDC; +} +#gfpgan { + background-color: #EEEEEE; +} +#progress-section { + background-color: #F5F5F5; +} +.section-header { + text-align: left; + font-weight: bold; + padding: 0 0 0 0; +} +#no-results-message:not(:only-child) { + display: none; +} + diff --git a/static/legacy_web/index.html b/static/legacy_web/index.html new file mode 100644 index 0000000000..5ce8b45baf --- /dev/null +++ b/static/legacy_web/index.html @@ -0,0 +1,129 @@ + + + Stable Diffusion Dream Server + + + + + + + + +
+

Stable Diffusion Dream Server

+
+ For news and support for this web service, visit our GitHub site +
+
+ +
+
+
+ +
+
+
Basic options
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+
Image-to-image options
+ + + +
+ + + + +
+
+
Post-processing options
+ + + + + + +
+
+
+
+
+ + +
+ +
+ Postprocessing...1/3 +
+ +
+ +
+
+

No results...

+
+
+
+ + diff --git a/static/legacy_web/index.js b/static/legacy_web/index.js new file mode 100644 index 0000000000..ac68034920 --- /dev/null +++ b/static/legacy_web/index.js @@ -0,0 +1,213 @@ +function toBase64(file) { + return new Promise((resolve, reject) => { + const r = new FileReader(); + r.readAsDataURL(file); + r.onload = () => resolve(r.result); + r.onerror = (error) => reject(error); + }); +} + +function appendOutput(src, seed, config) { + let outputNode = document.createElement("figure"); + + let variations = config.with_variations; + if (config.variation_amount > 0) { + variations = (variations ? variations + ',' : '') + seed + ':' + config.variation_amount; + } + let baseseed = (config.with_variations || config.variation_amount > 0) ? config.seed : seed; + let altText = baseseed + ' | ' + (variations ? variations + ' | ' : '') + config.prompt; + + // img needs width and height for lazy loading to work + const figureContents = ` + + ${altText} + +
${seed}
+ `; + + outputNode.innerHTML = figureContents; + let figcaption = outputNode.querySelector('figcaption'); + + // Reload image config + figcaption.addEventListener('click', () => { + let form = document.querySelector("#generate-form"); + for (const [k, v] of new FormData(form)) { + if (k == 'initimg') { continue; } + form.querySelector(`*[name=${k}]`).value = config[k]; + } + + document.querySelector("#seed").value = baseseed; + document.querySelector("#with_variations").value = variations || ''; + if (document.querySelector("#variation_amount").value <= 0) { + document.querySelector("#variation_amount").value = 0.2; + } + + saveFields(document.querySelector("#generate-form")); + }); + + document.querySelector("#results").prepend(outputNode); +} + +function saveFields(form) { + for (const [k, v] of new FormData(form)) { + if (typeof v !== 'object') { // Don't save 'file' type + localStorage.setItem(k, v); + } + } +} + +function loadFields(form) { + for (const [k, v] of new FormData(form)) { + const item = localStorage.getItem(k); + if (item != null) { + form.querySelector(`*[name=${k}]`).value = item; + } + } +} + +function clearFields(form) { + localStorage.clear(); + let prompt = form.prompt.value; + form.reset(); + form.prompt.value = prompt; +} + +const BLANK_IMAGE_URL = 'data:image/svg+xml,'; +async function generateSubmit(form) { + const prompt = document.querySelector("#prompt").value; + + // Convert file data to base64 + let formData = Object.fromEntries(new FormData(form)); + formData.initimg_name = formData.initimg.name + formData.initimg = formData.initimg.name !== '' ? await toBase64(formData.initimg) : null; + + let strength = formData.strength; + let totalSteps = formData.initimg ? Math.floor(strength * formData.steps) : formData.steps; + + let progressSectionEle = document.querySelector('#progress-section'); + progressSectionEle.style.display = 'initial'; + let progressEle = document.querySelector('#progress-bar'); + progressEle.setAttribute('max', totalSteps); + let progressImageEle = document.querySelector('#progress-image'); + progressImageEle.src = BLANK_IMAGE_URL; + + progressImageEle.style.display = {}.hasOwnProperty.call(formData, 'progress_images') ? 'initial': 'none'; + + // Post as JSON, using Fetch streaming to get results + fetch(form.action, { + method: form.method, + body: JSON.stringify(formData), + }).then(async (response) => { + const reader = response.body.getReader(); + + let noOutputs = true; + while (true) { + let {value, done} = await reader.read(); + value = new TextDecoder().decode(value); + if (done) { + progressSectionEle.style.display = 'none'; + break; + } + + for (let event of value.split('\n').filter(e => e !== '')) { + const data = JSON.parse(event); + + if (data.event === 'result') { + noOutputs = false; + appendOutput(data.url, data.seed, data.config); + progressEle.setAttribute('value', 0); + progressEle.setAttribute('max', totalSteps); + } else if (data.event === 'upscaling-started') { + document.getElementById("processing_cnt").textContent=data.processed_file_cnt; + document.getElementById("scaling-inprocess-message").style.display = "block"; + } else if (data.event === 'upscaling-done') { + document.getElementById("scaling-inprocess-message").style.display = "none"; + } else if (data.event === 'step') { + progressEle.setAttribute('value', data.step); + if (data.url) { + progressImageEle.src = data.url; + } + } else if (data.event === 'canceled') { + // avoid alerting as if this were an error case + noOutputs = false; + } + } + } + + // Re-enable form, remove no-results-message + form.querySelector('fieldset').removeAttribute('disabled'); + document.querySelector("#prompt").value = prompt; + document.querySelector('progress').setAttribute('value', '0'); + + if (noOutputs) { + alert("Error occurred while generating."); + } + }); + + // Disable form while generating + form.querySelector('fieldset').setAttribute('disabled',''); + document.querySelector("#prompt").value = `Generating: "${prompt}"`; +} + +async function fetchRunLog() { + try { + let response = await fetch('/run_log.json') + const data = await response.json(); + for(let item of data.run_log) { + appendOutput(item.url, item.seed, item); + } + } catch (e) { + console.error(e); + } +} + +window.onload = async () => { + document.querySelector("#prompt").addEventListener("keydown", (e) => { + if (e.key === "Enter" && !e.shiftKey) { + const form = e.target.form; + generateSubmit(form); + } + }); + document.querySelector("#generate-form").addEventListener('submit', (e) => { + e.preventDefault(); + const form = e.target; + + generateSubmit(form); + }); + document.querySelector("#generate-form").addEventListener('change', (e) => { + saveFields(e.target.form); + }); + document.querySelector("#reset-seed").addEventListener('click', (e) => { + document.querySelector("#seed").value = -1; + saveFields(e.target.form); + }); + document.querySelector("#reset-all").addEventListener('click', (e) => { + clearFields(e.target.form); + }); + document.querySelector("#remove-image").addEventListener('click', (e) => { + initimg.value=null; + }); + loadFields(document.querySelector("#generate-form")); + + document.querySelector('#cancel-button').addEventListener('click', () => { + fetch('/cancel').catch(e => { + console.error(e); + }); + }); + document.documentElement.addEventListener('keydown', (e) => { + if (e.key === "Escape") + fetch('/cancel').catch(err => { + console.error(err); + }); + }); + + if (!config.gfpgan_model_exists) { + document.querySelector("#gfpgan").style.display = 'none'; + } + await fetchRunLog() +}; diff --git a/tests/dev_prompts.txt b/tests/dev_prompts.txt new file mode 100644 index 0000000000..9ebca4e9f7 --- /dev/null +++ b/tests/dev_prompts.txt @@ -0,0 +1 @@ +banana sushi -Ak_lms -S42 diff --git a/tests/preflight_prompts.txt b/tests/preflight_prompts.txt new file mode 100644 index 0000000000..5c5b8233a1 --- /dev/null +++ b/tests/preflight_prompts.txt @@ -0,0 +1,9 @@ +banana sushi -Ak_lms -S42 +banana sushi -Addim -S42 +banana sushi -Ak_lms -W640 -H480 -S42 +banana sushi -Ak_lms -S42 -G1 -U 2 0.5 +banana sushi -Ak_lms -S42 -v0.2 -n3 +banana sushi -Ak_lms -S42 -V1349749425:0.1,4145759947:0.1 +snake -I outputs/preflight/000006.4145759947.png -S42 +snake -I outputs/preflight/000006.4145759947.png -S42 -W640 -H640 --fit +strawberry sushi -I./image-and-mask.png -S42 -f0.9 -s100 -C15 \ No newline at end of file diff --git a/tests/prompts.txt b/tests/prompts.txt deleted file mode 100644 index 955220a5e6..0000000000 --- a/tests/prompts.txt +++ /dev/null @@ -1 +0,0 @@ -test trending on artstation -s 1 -S 1