From 98fe044deea6fb709dfbb831fa59e0b9343146cd Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Sat, 8 Oct 2022 09:32:06 -0400 Subject: [PATCH] rebrand CLI from "dream" to "invoke" - rename dream.py to invoke.py - create a compatibility script named dream.py that execs() invoke.py - redo documentation - change help message in args - this does **not** rename the libraries, which are still ldm.dream.util, etc --- ...-dream-conda.yml => test-invoke-conda.yml} | 8 +- README.md | 10 +- docs/CHANGELOG.md | 18 +- docs/features/CHANGELOG.md | 18 +- docs/features/CLI.md | 64 +- docs/features/EMBIGGEN.md | 8 +- docs/features/IMG2IMG.md | 12 +- docs/features/INPAINTING.md | 8 +- docs/features/OTHER.md | 8 +- docs/features/OUTPAINTING.md | 4 +- docs/features/POSTPROCESS.md | 14 +- docs/features/PROMPTS.md | 10 +- docs/features/TEXTUAL_INVERSION.md | 10 +- docs/features/VARIATIONS.md | 8 +- docs/features/WEB.md | 6 +- docs/help/TROUBLESHOOT.md | 4 +- docs/index.md | 12 +- docs/installation/INSTALL_DOCKER.md | 24 +- docs/installation/INSTALL_LINUX.md | 8 +- docs/installation/INSTALL_MAC.md | 12 +- docs/installation/INSTALL_WINDOWS.md | 6 +- ldm/dream/args.py | 20 +- scripts/dream.py | 570 +----------------- scripts/invoke.py | 570 ++++++++++++++++++ 24 files changed, 722 insertions(+), 710 deletions(-) rename .github/workflows/{test-dream-conda.yml => test-invoke-conda.yml} (92%) create mode 100644 scripts/invoke.py diff --git a/.github/workflows/test-dream-conda.yml b/.github/workflows/test-invoke-conda.yml similarity index 92% rename from .github/workflows/test-dream-conda.yml rename to .github/workflows/test-invoke-conda.yml index b426275b26..b5314cfd6b 100644 --- a/.github/workflows/test-dream-conda.yml +++ b/.github/workflows/test-invoke-conda.yml @@ -1,4 +1,4 @@ -name: Test Dream with Conda +name: Test Invoke with Conda on: push: branches: @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest, macos-12 ] - name: Test dream.py on ${{ matrix.os }} with conda + name: Test invoke.py on ${{ matrix.os }} with conda runs-on: ${{ matrix.os }} steps: - run: | @@ -85,9 +85,9 @@ jobs: 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 + time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/invoke.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 + time ${{ steps.vars.outputs.PYTHON_BIN }} scripts/invoke.py --from_file tests/dev_prompts.txt fi mkdir -p outputs/img-samples - name: Archive results diff --git a/README.md b/README.md index 144ee9f3af..a108f4c172 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ _This repository was formally known as lstein/stable-diffusion_ [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 +[CI checks on main link]: https://github.com/invoke-ai/InvokeAI/actions/workflows/test-invoke-conda.yml [discord badge]: https://flat.badgen.net/discord/members/ZmtBAhwWhy?icon=discord [discord link]: https://discord.gg/ZmtBAhwWhy [github forks badge]: https://flat.badgen.net/github/forks/invoke-ai/InvokeAI?icon=github @@ -94,10 +94,10 @@ You wil need one of the following: 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: +you can try starting `invoke.py` with the `--precision=float32` flag: ```bash -(ldm) ~/stable-diffusion$ python scripts/dream.py --precision=float32 +(ldm) ~/stable-diffusion$ python scripts/invoke.py --precision=float32 ``` ### Features @@ -130,7 +130,7 @@ you can try starting `dream.py` with the `--precision=float32` flag: - vNEXT (TODO 2022) - - Deprecated `--full_precision` / `-F`. Simply omit it and `dream.py` will auto + - Deprecated `--full_precision` / `-F`. Simply omit it and `invoke.py` will auto configure. To switch away from auto use the new flag like `--precision=float32`. - v1.14 (11 September 2022) @@ -156,7 +156,7 @@ you can try starting `dream.py` with the `--precision=float32` flag: - A new configuration file scheme that allows new models (including upcoming stable-diffusion-v1.5) to be added without altering the code. ([David Wager](https://github.com/maddavid12)) - - Can specify --grid on dream.py command line as the default. + - Can specify --grid on invoke.py command line as the default. - Miscellaneous internal bug and stability fixes. - Works on M1 Apple hardware. - Multiple bug fixes. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f0d61aeb97..3628e5e245 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,9 +5,9 @@ - Supports a Google Colab notebook for a standalone server running on Google hardware [Arturo Mendivil](https://github.com/artmen1516) - WebUI supports GFPGAN/ESRGAN facial reconstruction and upscaling [Kevin Gibbons](https://github.com/bakkot) - WebUI supports incremental display of in-progress images during generation [Kevin Gibbons](https://github.com/bakkot) -- Output directory can be specified on the dream> command line. +- Output directory can be specified on the invoke> command line. - The grid was displaying duplicated images when not enough images to fill the final row [Muhammad Usama](https://github.com/SMUsamaShah) -- Can specify --grid on dream.py command line as the default. +- Can specify --grid on invoke.py command line as the default. - Miscellaneous internal bug and stability fixes. --- @@ -16,13 +16,13 @@ - Improved file handling, including ability to read prompts from standard input. (kudos to [Yunsaki](https://github.com/yunsaki) -- The web server is now integrated with the dream.py script. Invoke by adding --web to - the dream.py command arguments. +- The web server is now integrated with the invoke.py script. Invoke by adding --web to + the invoke.py command arguments. - Face restoration and upscaling via GFPGAN and Real-ESGAN are now automatically enabled if the GFPGAN directory is located as a sibling to Stable Diffusion. VRAM requirements are modestly reduced. Thanks to both [Blessedcoolant](https://github.com/blessedcoolant) and [Oceanswave](https://github.com/oceanswave) for their work on this. -- You can now swap samplers on the dream> command line. [Blessedcoolant](https://github.com/blessedcoolant) +- You can now swap samplers on the invoke> command line. [Blessedcoolant](https://github.com/blessedcoolant) --- @@ -32,7 +32,7 @@ - You now can specify a seed of -1 to use the previous image's seed, -2 to use the seed for the image generated before that, etc. Seed memory only extends back to the previous command, but will work on all images generated with the -n# switch. - Variant generation support temporarily disabled pending more general solution. -- Created a feature branch named **yunsaki-morphing-dream** which adds experimental support for +- Created a feature branch named **yunsaki-morphing-invoke** which adds experimental support for iteratively modifying the prompt and its parameters. Please see[ Pull Request #86](https://github.com/lstein/stable-diffusion/pull/86) for a synopsis of how this works. Note that when this feature is eventually added to the main branch, it will may be modified significantly. @@ -57,7 +57,7 @@ ## v1.08 (24 August 2022) -- Escape single quotes on the dream> command before trying to parse. This avoids +- Escape single quotes on the invoke> command before trying to parse. This avoids parse errors. - Removed instruction to get Python3.8 as first step in Windows install. Anaconda3 does it for you. @@ -94,7 +94,7 @@ be regenerated with the indicated key - It should no longer be possible for one image to overwrite another -- You can use the "cd" and "pwd" commands at the dream> prompt to set and retrieve +- You can use the "cd" and "pwd" commands at the invoke> prompt to set and retrieve the path of the output directory. --- @@ -128,7 +128,7 @@ - added k_lms sampling. **Please run "conda env update" to load the k_lms dependencies!!** - use half precision arithmetic by default, resulting in faster execution and lower memory requirements - Pass argument --full_precision to dream.py to get slower but more accurate image generation + Pass argument --full_precision to invoke.py to get slower but more accurate image generation --- diff --git a/docs/features/CHANGELOG.md b/docs/features/CHANGELOG.md index a6258f6a56..c6fbf092e1 100644 --- a/docs/features/CHANGELOG.md +++ b/docs/features/CHANGELOG.md @@ -12,10 +12,10 @@ title: Changelog [Kevin Gibbons](https://github.com/bakkot) - WebUI supports incremental display of in-progress images during generation [Kevin Gibbons](https://github.com/bakkot) -- Output directory can be specified on the dream> command line. +- Output directory can be specified on the invoke> command line. - The grid was displaying duplicated images when not enough images to fill the final row [Muhammad Usama](https://github.com/SMUsamaShah) -- Can specify --grid on dream.py command line as the default. +- Can specify --grid on invoke.py command line as the default. - Miscellaneous internal bug and stability fixes. --- @@ -24,14 +24,14 @@ title: Changelog - Improved file handling, including ability to read prompts from standard input. (kudos to [Yunsaki](https://github.com/yunsaki) -- The web server is now integrated with the dream.py script. Invoke by adding - --web to the dream.py command arguments. +- The web server is now integrated with the invoke.py script. Invoke by adding + --web to the invoke.py command arguments. - Face restoration and upscaling via GFPGAN and Real-ESGAN are now automatically enabled if the GFPGAN directory is located as a sibling to Stable Diffusion. VRAM requirements are modestly reduced. Thanks to both [Blessedcoolant](https://github.com/blessedcoolant) and [Oceanswave](https://github.com/oceanswave) for their work on this. -- You can now swap samplers on the dream> command line. +- You can now swap samplers on the invoke> command line. [Blessedcoolant](https://github.com/blessedcoolant) --- @@ -45,7 +45,7 @@ title: Changelog back to the previous command, but will work on all images generated with the -n# switch. - Variant generation support temporarily disabled pending more general solution. -- Created a feature branch named **yunsaki-morphing-dream** which adds +- Created a feature branch named **yunsaki-morphing-invoke** which adds experimental support for iteratively modifying the prompt and its parameters. Please see[ Pull Request #86](https://github.com/lstein/stable-diffusion/pull/86) for @@ -75,7 +75,7 @@ title: Changelog ## v1.08 (24 August 2022) -- Escape single quotes on the dream> command before trying to parse. This avoids +- Escape single quotes on the invoke> command before trying to parse. This avoids parse errors. - Removed instruction to get Python3.8 as first step in Windows install. Anaconda3 does it for you. @@ -112,7 +112,7 @@ title: Changelog can be regenerated with the indicated key - It should no longer be possible for one image to overwrite another -- You can use the "cd" and "pwd" commands at the dream> prompt to set and +- You can use the "cd" and "pwd" commands at the invoke> prompt to set and retrieve the path of the output directory. ## v1.04 (22 August 2022 - after the drop) @@ -139,5 +139,5 @@ title: Changelog - added k_lms sampling. **Please run "conda env update -f environment.yaml" to load the k_lms dependencies!!** - use half precision arithmetic by default, resulting in faster execution and - lower memory requirements Pass argument --full_precision to dream.py to get + lower memory requirements Pass argument --full_precision to invoke.py to get slower but more accurate image generation diff --git a/docs/features/CLI.md b/docs/features/CLI.md index 312614c348..cf86b0cd64 100644 --- a/docs/features/CLI.md +++ b/docs/features/CLI.md @@ -8,8 +8,8 @@ hide: ## **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 +The `invoke.py` script, located in `scripts/dream.py`, provides an interactive +interface to image generation similar to the "invoke mothership" bot that Stable AI provided on its Discord server. Unlike the `txt2img.py` and `img2img.py` scripts provided in the original @@ -34,21 +34,21 @@ The script is confirmed to work on Linux, Windows and Mac systems. currently rudimentary, but a much better replacement is on its way. ```bash -(ldm) ~/stable-diffusion$ python3 ./scripts/dream.py +(ldm) ~/stable-diffusion$ python3 ./scripts/invoke.py * Initializing, be patient... Loading model from models/ldm/text2img-large/model.ckpt (...more initialization messages...) * Initialization done! Awaiting your command... -dream> ashley judd riding a camel -n2 -s150 +invoke> ashley judd riding a camel -n2 -s150 Outputs: outputs/img-samples/00009.png: "ashley judd riding a camel" -n2 -s150 -S 416354203 outputs/img-samples/00010.png: "ashley judd riding a camel" -n2 -s150 -S 1362479620 -dream> "there's a fly in my soup" -n6 -g +invoke> "there's a fly in my soup" -n6 -g outputs/img-samples/00011.png: "there's a fly in my soup" -n6 -g -S 2685670268 seeds for individual rows: [2685670268, 1216708065, 2335773498, 822223658, 714542046, 3395302430] -dream> q +invoke> q # this shows how to retrieve the prompt stored in the saved image's metadata (ldm) ~/stable-diffusion$ python ./scripts/images2prompt.py outputs/img_samples/*.png @@ -57,10 +57,10 @@ dream> q 00011.png: "there's a fly in my soup" -n6 -g -S 2685670268 ``` -![dream-py-demo](../assets/dream-py-demo.png) +![invoke-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). +The `invoke>` prompt's arguments are pretty much identical to those used in the +Discord bot, except you don't need to type "!invoke" (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). @@ -73,7 +73,7 @@ the location of the model weight files. ### List of arguments recognized at the command line -These command-line arguments can be passed to `dream.py` when you first run it +These command-line arguments can be passed to `invoke.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 @@ -112,15 +112,15 @@ These arguments are deprecated but still work: | --laion400m | -l | False | Use older LAION400m weights; use `--model=laion400m` instead | **A note on path names:** On Windows systems, you may run into - problems when passing the dream script standard backslashed path + problems when passing the invoke 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 -After the dream.py script initializes, it will present you with a -**dream>** prompt. Here you can enter information to generate images +After the invoke.py script initializes, it will present you with a +**invoke>** 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). @@ -128,13 +128,13 @@ from text (txt2img), to embellish an existing image or sketch ### This is an example of txt2img: ~~~~ -dream> waterfall and rainbow -W640 -H480 +invoke> waterfall and rainbow -W640 -H480 ~~~~ This will create the requested image with the dimensions 640 (width) and 480 (height). -Here are the dream> command that apply to txt2img: +Here are the invoke> command that apply to txt2img: | Argument | Shortcut | Default | Description | |--------------------|------------|---------------------|--------------| @@ -167,7 +167,7 @@ the nearest multiple of 64. ### This is an example of img2img: ~~~~ -dream> waterfall and rainbow -I./vacation-photo.png -W640 -H480 --fit +invoke> waterfall and rainbow -I./vacation-photo.png -W640 -H480 --fit ~~~~ This will modify the indicated vacation photograph by making it more @@ -188,7 +188,7 @@ accepts additional options: ### This is an example of inpainting: ~~~~ -dream> waterfall and rainbow -I./vacation-photo.png -M./vacation-mask.png -W640 -H480 --fit +invoke> 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 @@ -224,20 +224,20 @@ Some examples: Upscale to 4X its original size and fix faces using codeformer: ~~~ -dream> !fix 0000045.4829112.png -G1 -U4 -ft codeformer +invoke> !fix 0000045.4829112.png -G1 -U4 -ft codeformer ~~~ Use the GFPGAN algorithm to fix faces, then upscale to 3X using --embiggen: ~~~ -dream> !fix 0000045.4829112.png -G0.8 -ft gfpgan +invoke> !fix 0000045.4829112.png -G0.8 -ft gfpgan >> fixing outputs/img-samples/0000045.4829112.png >> retrieved seed 4829112 and prompt "boy enjoying a banana split" >> GFPGAN - Restoring Faces for image seed:4829112 Outputs: [1] outputs/img-samples/000017.4829112.gfpgan-00.png: !fix "outputs/img-samples/0000045.4829112.png" -s 50 -S -W 512 -H 512 -C 7.5 -A k_lms -G 0.8 -dream> !fix 000017.4829112.gfpgan-00.png --embiggen 3 +invoke> !fix 000017.4829112.gfpgan-00.png --embiggen 3 ...lots of text... Outputs: [2] outputs/img-samples/000018.2273800735.embiggen-00.png: !fix "outputs/img-samples/000017.243781548.gfpgan-00.png" -s 50 -S 2273800735 -W 512 -H 512 -C 7.5 -A k_lms --embiggen 3.0 0.75 0.25 @@ -251,9 +251,9 @@ provide either the name of a file in the current output directory, or a full file path. ~~~ -dream> !fetch 0000015.8929913.png +invoke> !fetch 0000015.8929913.png # the script returns the next line, ready for editing and running: -dream> a fantastic alien landscape -W 576 -H 512 -s 60 -A plms -C 7.5 +invoke> a fantastic alien landscape -W 576 -H 512 -s 60 -A plms -C 7.5 ~~~ Note that this command may behave unexpectedly if given a PNG file that @@ -261,7 +261,7 @@ was not generated by InvokeAI. ## !history -The dream script keeps track of all the commands you issue during a +The invoke script keeps track of all the commands you issue during a session, allowing you to re-run them. On Mac and Linux systems, it also writes the command-line history out to disk, giving you access to the most recent 1000 commands issued. @@ -272,7 +272,7 @@ issued during the session (Windows), or the most recent 1000 commands where "NNN" is the history line number. For example: ~~~ -dream> !history +invoke> !history ... [14] happy woman sitting under tree wearing broad hat and flowing garment [15] beautiful woman sitting under tree wearing broad hat and flowing garment @@ -280,8 +280,8 @@ dream> !history [20] watercolor of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 [21] surrealist painting of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 ... -dream> !20 -dream> watercolor of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 +invoke> !20 +invoke> watercolor of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 ~~~ ## !search @@ -290,7 +290,7 @@ This is similar to !history but it only returns lines that contain `search string`. For example: ~~~ -dream> !search surreal +invoke> !search surreal [21] surrealist painting of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 ~~~ @@ -312,16 +312,16 @@ command completion. - 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 and have the `pyreadline3` +launch invoke.py with the "winpty" program and have the `pyreadline3` library installed: ~~~ -> winpty python scripts\dream.py +> winpty python scripts\invoke.py ~~~ -On the Mac and Linux platforms, when you exit dream.py, the last 1000 +On the Mac and Linux platforms, when you exit invoke.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. +invoke.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 @@ -334,7 +334,7 @@ will attempt to complete pathnames for you. This is most handy for the the path with a slash ("/") or "./". For example: ~~~ -dream> zebra with a mustache -I./test-pictures +invoke> 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/ ``` diff --git a/docs/features/EMBIGGEN.md b/docs/features/EMBIGGEN.md index 92b379b64b..6d74822a66 100644 --- a/docs/features/EMBIGGEN.md +++ b/docs/features/EMBIGGEN.md @@ -106,8 +106,8 @@ Running Embiggen with 512x512 tiles on an existing image, scaling up by a factor 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 +invoke > a photo of a forest at sunset -s 100 -W 512 -H 512 -I outputs/forest.png -f 0.4 -embiggen 2.5 +invoke > 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 ``` If your starting image was also 512x512 this should have taken 9 tiles. @@ -118,7 +118,7 @@ If there weren't enough clouds in the sky of that forest you just made 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 +invoke> 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 ``` ## Fixing Previously-Generated Images @@ -129,7 +129,7 @@ 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 +invoke> !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 diff --git a/docs/features/IMG2IMG.md b/docs/features/IMG2IMG.md index b8830c0f3d..769e3b546a 100644 --- a/docs/features/IMG2IMG.md +++ b/docs/features/IMG2IMG.md @@ -67,7 +67,7 @@ gaussian noise and progressively refines it over the requested number of steps, **Let's start** by thinking about vanilla `prompt2img`, just generating an image from a prompt. If the step count is 10, then the "latent space" (Stable Diffusion's internal representation of the image) for the prompt "fire" with seed `1592514025` develops something like this: ```commandline -dream> "fire" -s10 -W384 -H384 -S1592514025 +invoke> "fire" -s10 -W384 -H384 -S1592514025 ``` ![latent steps](../assets/img2img/000019.steps.png) @@ -95,7 +95,7 @@ Notice how much more fuzzy the starting image is for strength `0.7` compared to | | strength = 0.7 | strength = 0.4 | | -- | -- | -- | | initial image that SD sees | ![](../assets/img2img/000032.step-0.png) | ![](../assets/img2img/000030.step-0.png) | -| steps argument to `dream>` | `-S10` | `-S10` | +| steps argument to `invoke>` | `-S10` | `-S10` | | steps actually taken | 7 | 4 | | latent space at each step | ![](../assets/img2img/000032.steps.gravity.png) | ![](../assets/img2img/000030.steps.gravity.png) | | output | ![](../assets/img2img/000032.1592514025.png) | ![](../assets/img2img/000030.1592514025.png) | @@ -106,10 +106,10 @@ Both of the outputs look kind of like what I was thinking of. With the strength If you want to try this out yourself, all of these are using a seed of `1592514025` with a width/height of `384`, step count `10`, the default sampler (`k_lms`), and the single-word prompt `fire`: ```commandline -dream> "fire" -s10 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png --strength 0.7 +invoke> "fire" -s10 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png --strength 0.7 ``` -The code for rendering intermediates is on my (damian0815's) branch [document-img2img](https://github.com/damian0815/InvokeAI/tree/document-img2img) - run `dream.py` and check your `outputs/img-samples/intermediates` folder while generating an image. +The code for rendering intermediates is on my (damian0815's) branch [document-img2img](https://github.com/damian0815/InvokeAI/tree/document-img2img) - run `invoke.py` and check your `outputs/img-samples/intermediates` folder while generating an image. ### Compensating for the reduced step count @@ -118,7 +118,7 @@ After putting this guide together I was curious to see how the difference would Here's strength `0.4` (note step count `50`, which is `20 ÷ 0.4` to make sure SD does `20` steps from my image): ```commandline -dream> "fire" -s50 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png -f 0.4 +invoke> "fire" -s50 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png -f 0.4 ``` ![](../assets/img2img/000035.1592514025.png) @@ -126,7 +126,7 @@ dream> "fire" -s50 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png -f 0.4 and strength `0.7` (note step count `30`, which is roughly `20 ÷ 0.7` to make sure SD does `20` steps from my image): ```commandline -dream> "fire" -s30 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png -f 0.7 +invoke> "fire" -s30 -W384 -H384 -S1592514025 -I /tmp/fire-drawing.png -f 0.7 ``` ![](../assets/img2img/000046.1592514025.png) diff --git a/docs/features/INPAINTING.md b/docs/features/INPAINTING.md index 497bbc7a48..32578a3dfc 100644 --- a/docs/features/INPAINTING.md +++ b/docs/features/INPAINTING.md @@ -8,7 +8,7 @@ title: Inpainting Inpainting is really cool. To do it, you start with an initial image and use a photoeditor to make one or more regions transparent (i.e. they have a "hole" in them). You then provide the path to this -image at the dream> command line using the `-I` switch. Stable Diffusion will only paint within the +image at the invoke> command line using the `-I` switch. Stable Diffusion will only paint within the transparent region. There's a catch. In the current implementation, you have to prepare the initial image correctly so @@ -17,13 +17,13 @@ applications will by default erase the color information under the transparent p them with white or black, which will lead to suboptimal inpainting. You also must take care to export the PNG file in such a way that the color information is preserved. -If your photoeditor is erasing the underlying color information, `dream.py` will give you a big fat +If your photoeditor is erasing the underlying color information, `invoke.py` will give you a big fat warning. If you can't find a way to coax your photoeditor to retain color values under transparent areas, then you can combine the `-I` and `-M` switches to provide both the original unedited image and the masked (partially transparent) image: ```bash -dream> "man with cat on shoulder" -I./images/man.png -M./images/man-transparent.png +invoke> "man with cat on shoulder" -I./images/man.png -M./images/man-transparent.png ``` We are hoping to get rid of the need for this workaround in an upcoming release. @@ -69,7 +69,7 @@ We are hoping to get rid of the need for this workaround in an upcoming release. ![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! +7. After following the inpainting instructions above (either through the CLI or the Web UI), marvel at your newfound ability to selectively invoke. Lookin' good! ![step7](../assets/step7.png) diff --git a/docs/features/OTHER.md b/docs/features/OTHER.md index 6aab528275..820b46c32d 100644 --- a/docs/features/OTHER.md +++ b/docs/features/OTHER.md @@ -22,10 +22,10 @@ Output Example: ![Colab Notebook](../assets/colab_notebook.png) The seamless tiling mode causes generated images to seamlessly tile with itself. To use it, add the `--seamless` option when starting the script which will result in all generated images to tile, or -for each `dream>` prompt as shown here: +for each `invoke>` prompt as shown here: ```python -dream> "pond garden with lotus by claude monet" --seamless -s100 -n4 +invoke> "pond garden with lotus by claude monet" --seamless -s100 -n4 ``` --- @@ -42,12 +42,12 @@ Here's an example of using this to do a quick refinement. It also illustrates us switch to turn on upscaling and face enhancement (see previous section): ```bash -dream> a cute child playing hopscotch -G0.5 +invoke> a cute child playing hopscotch -G0.5 [...] outputs/img-samples/000039.3498014304.png: "a cute child playing hopscotch" -s50 -W512 -H512 -C7.5 -mk_lms -S3498014304 # I wonder what it will look like if I bump up the steps and set facial enhancement to full strength? -dream> a cute child playing hopscotch -G1.0 -s100 -S -1 +invoke> a cute child playing hopscotch -G1.0 -s100 -S -1 reusing previous seed 3498014304 [...] outputs/img-samples/000040.3498014304.png: "a cute child playing hopscotch" -G1.0 -s100 -W512 -H512 -C7.5 -mk_lms -S3498014304 diff --git a/docs/features/OUTPAINTING.md b/docs/features/OUTPAINTING.md index 952bbc97fc..7d54f1bfc9 100644 --- a/docs/features/OUTPAINTING.md +++ b/docs/features/OUTPAINTING.md @@ -31,7 +31,7 @@ Pretty nice, but it's annoying that the top of her head is cut off. She's also a bit off center. Let's fix that! ~~~~ -dream> !fix images/curly.png --outcrop top 64 right 64 +invoke> !fix images/curly.png --outcrop top 64 right 64 ~~~~ This is saying to apply the `outcrop` extension by extending the top @@ -67,7 +67,7 @@ differences. Starting with the same image, here is how we would add an additional 64 pixels to the top of the image: ~~~ -dream> !fix images/curly.png --out_direction top 64 +invoke> !fix images/curly.png --out_direction top 64 ~~~ (you can abbreviate ``--out_direction` as `-D`. diff --git a/docs/features/POSTPROCESS.md b/docs/features/POSTPROCESS.md index cd4fd7e9e6..fbcd1c8005 100644 --- a/docs/features/POSTPROCESS.md +++ b/docs/features/POSTPROCESS.md @@ -25,7 +25,7 @@ the standard install location for python packages, and will put GFPGAN into a subdirectory of "src" in the InvokeAI 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, +intervention. Simply pass the --upscale (-U) option on the invoke> 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 @@ -42,14 +42,14 @@ Make sure that you're in the InvokeAI 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 +directory, you may use the `--gfpgan_dir` argument with `invoke.py` to set a custom path to your GFPGAN directory. _There are other GFPGAN related boot arguments if you wish to customize further._ !!! warning "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 + on a University cluster) should be aware that the first time you run invoke.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. @@ -94,13 +94,13 @@ too. ### Example Usage ```bash -dream> superman dancing with a panda bear -U 2 0.6 -G 0.4 +invoke> superman dancing with a panda bear -U 2 0.6 -G 0.4 ``` This also works with img2img: ```bash -dream> a man wearing a pineapple hat -I path/to/your/file.png -U 2 0.5 -G 0.6 +invoke> a man wearing a pineapple hat -I path/to/your/file.png -U 2 0.5 -G 0.6 ``` !!! note @@ -168,7 +168,7 @@ previously-generated file. Just use the syntax `!fix path/to/file.png just run: ``` -dream> !fix ./outputs/img-samples/000044.2945021133.png -G 0.8 -U 2 +invoke> !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 @@ -178,5 +178,5 @@ 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 +you can disable them on the invoke.py command line with the `--no_restore` and `--no_upscale` options, respectively. diff --git a/docs/features/PROMPTS.md b/docs/features/PROMPTS.md index cda7d6ba56..361a2fd5bc 100644 --- a/docs/features/PROMPTS.md +++ b/docs/features/PROMPTS.md @@ -6,9 +6,9 @@ title: 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 +You can automate `invoke.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: +Each line should look like what you would type at the invoke> prompt: ```bash a beautiful sunny day in the park, children playing -n4 -C10 @@ -16,16 +16,16 @@ 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: +Then pass this file's name to `invoke.py` when you invoke it: ```bash -(ldm) ~/stable-diffusion$ python3 scripts/dream.py --from_file "path/to/prompts.txt" +(ldm) ~/stable-diffusion$ python3 scripts/invoke.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 - +(ldm) ~/stable-diffusion$ echo "a beautiful day" | python3 scripts/invoke.py --from_file - ``` --- diff --git a/docs/features/TEXTUAL_INVERSION.md b/docs/features/TEXTUAL_INVERSION.md index 50532968a8..15ac0891ad 100644 --- a/docs/features/TEXTUAL_INVERSION.md +++ b/docs/features/TEXTUAL_INVERSION.md @@ -56,22 +56,22 @@ configs/stable_diffusion/v1-finetune.yaml (currently set to 4000000) ## **Run the Model** Once the model is trained, specify the trained .pt or .bin file when starting -dream using +invoke using ```bash -python3 ./scripts/dream.py --embedding_path /path/to/embedding.pt +python3 ./scripts/invoke.py --embedding_path /path/to/embedding.pt ``` -Then, to utilize your subject at the dream prompt +Then, to utilize your subject at the invoke prompt ```bash -dream> "a photo of *" +invoke> "a photo of *" ``` This also works with image2image ```bash -dream> "waterfall and rainbow in the style of *" --init_img=./init-images/crude_drawing.png --strength=0.5 -s100 -n4 +invoke> "waterfall and rainbow in the style of *" --init_img=./init-images/crude_drawing.png --strength=0.5 -s100 -n4 ``` For .pt files it's also possible to train multiple tokens (modify the diff --git a/docs/features/VARIATIONS.md b/docs/features/VARIATIONS.md index e021c300fb..b8640d3a4f 100644 --- a/docs/features/VARIATIONS.md +++ b/docs/features/VARIATIONS.md @@ -34,7 +34,7 @@ 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 +invoke> lucy lawless as xena, warrior princess, character portrait, high resolution -n6 ... Outputs: ./outputs/Xena/000001.1579445059.png: "prompt" -s50 -W512 -H512 -C7.5 -Ak_lms -S1579445059 @@ -57,7 +57,7 @@ differing by a variation amount of 0.2. This number ranges from `0` to `1.0`, with higher numbers being larger amounts of variation. ```bash -dream> "prompt" -n6 -S3357757885 -v0.2 +invoke> "prompt" -n6 -S3357757885 -v0.2 ... Outputs: ./outputs/Xena/000002.784039624.png: "prompt" -s50 -W512 -H512 -C7.5 -Ak_lms -V 784039624:0.2 -S3357757885 @@ -89,7 +89,7 @@ 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 -dream> "prompt" -S3357757885 -V3647897225,0.1,1614299449,0.1 +invoke> "prompt" -S3357757885 -V3647897225,0.1,1614299449,0.1 Outputs: ./outputs/Xena/000003.1614299449.png: "prompt" -s50 -W512 -H512 -C7.5 -Ak_lms -V 3647897225:0.1,1614299449:0.1 -S3357757885 ``` @@ -105,7 +105,7 @@ 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 +invoke> "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 ./outputs/Xena/000004.2853129515.png: "prompt" -s50 -W512 -H512 -C7.5 -Ak_lms -V 3647897225:0.1,1614299449:0.1,2853129515:0.05 -S3357757885 diff --git a/docs/features/WEB.md b/docs/features/WEB.md index 833b18cdfc..9bc0d38e88 100644 --- a/docs/features/WEB.md +++ b/docs/features/WEB.md @@ -5,11 +5,11 @@ 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 `invoke.py` script by adding the `--web` option. ```bash -(ldm) ~/stable-diffusion$ python3 scripts/dream.py --web +(ldm) ~/stable-diffusion$ python3 scripts/invoke.py --web ``` You can then connect to the server by pointing your web browser at @@ -18,4 +18,4 @@ http://localhost:9090, or to the network name or IP address of the server. Kudos to [Tesseract Cat](https://github.com/TesseractCat) for contributing this code, and to [dagf2101](https://github.com/dagf2101) for refining it. -![Dream Web Server](../assets/dream_web_server.png) +![Dream Web Server](../assets/invoke_web_server.png) diff --git a/docs/help/TROUBLESHOOT.md b/docs/help/TROUBLESHOOT.md index 22dc364556..8ff92f5be0 100644 --- a/docs/help/TROUBLESHOOT.md +++ b/docs/help/TROUBLESHOOT.md @@ -51,7 +51,7 @@ rm ${PIP_LOG} ### **QUESTION** -`dream.py` crashes with the complaint that it can't find `ldm.simplet2i.py`. Or it complains that +`invoke.py` crashes with the complaint that it can't find `ldm.simplet2i.py`. Or it complains that function is being passed incorrect parameters. ### **SOLUTION** @@ -63,7 +63,7 @@ Reinstall the stable diffusion modules. Enter the `stable-diffusion` directory a ### **QUESTION** -`dream.py` dies, complaining of various missing modules, none of which starts with `ldm``. +`invoke.py` dies, complaining of various missing modules, none of which starts with `ldm``. ### **SOLUTION** diff --git a/docs/index.md b/docs/index.md index c356c2cee5..bd04545904 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,7 +28,7 @@ template: main.html [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 +[CI checks on main link]: https://github.com/lstein/stable-diffusion/actions/workflows/test-invoke-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 @@ -85,21 +85,21 @@ You wil need one of the following: !!! note - If you are have a Nvidia 10xx series card (e.g. the 1080ti), please run the dream script in + If you are have a Nvidia 10xx series card (e.g. the 1080ti), please run the invoke 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: + To run in full-precision mode, start `invoke.py` with the `--full_precision` flag: ```bash - (ldm) ~/stable-diffusion$ python scripts/dream.py --full_precision + (ldm) ~/stable-diffusion$ python scripts/invoke.py --full_precision ``` ## :octicons-log-16: Latest Changes ### vNEXT (TODO 2022) - - Deprecated `--full_precision` / `-F`. Simply omit it and `dream.py` will auto + - Deprecated `--full_precision` / `-F`. Simply omit it and `invoke.py` will auto configure. To switch away from auto use the new flag like `--precision=float32`. ### v1.14 (11 September 2022) @@ -124,7 +124,7 @@ You wil need one of the following: [Kevin Gibbons](https://github.com/bakkot) - A new configuration file scheme that allows new models (including upcoming stable-diffusion-v1.5) to be added without altering the code. ([David Wager](https://github.com/maddavid12)) -- Can specify --grid on dream.py command line as the default. +- Can specify --grid on invoke.py command line as the default. - Miscellaneous internal bug and stability fixes. - Works on M1 Apple hardware. - Multiple bug fixes. diff --git a/docs/installation/INSTALL_DOCKER.md b/docs/installation/INSTALL_DOCKER.md index c7dd3582d5..880b216f3c 100644 --- a/docs/installation/INSTALL_DOCKER.md +++ b/docs/installation/INSTALL_DOCKER.md @@ -136,7 +136,7 @@ $TAG_STABLE_DIFFUSION ## Startup -If you're on a **Linux container** the `dream` script is **automatically +If you're on a **Linux container** the `invoke` 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**. @@ -148,14 +148,14 @@ 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 +python3 scripts/invoke.py --full_precision ``` You'll get the script's prompt. You can see available options or quit. ```Shell -dream> -h -dream> q +invoke> -h +invoke> q ``` ## Text to Image @@ -166,10 +166,10 @@ 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 +invoke> The hulk fighting with sheldon cooper -s5 -n1 +invoke> "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 +invoke> "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 @@ -210,28 +210,28 @@ If you're on a Docker container, copy your input image into the Docker volume docker cp /Users//Pictures/sketch-mountains-input.jpg dummy:/data/ ``` -Try it out generating an image (or more). The `dream` script needs absolute +Try it out generating an image (or more). The `invoke` 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 +invoke> "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 +invoke> "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 +You can use the `invoke` script with a graphical web interface. Start the web server with: ```Shell -python3 scripts/dream.py --full_precision --web +python3 scripts/invoke.py --full_precision --web ``` If it's running on your Mac point your Mac web browser to http://127.0.0.1:9090 diff --git a/docs/installation/INSTALL_LINUX.md b/docs/installation/INSTALL_LINUX.md index 88f327c734..56c2f91246 100644 --- a/docs/installation/INSTALL_LINUX.md +++ b/docs/installation/INSTALL_LINUX.md @@ -89,16 +89,16 @@ This will create InvokeAI folder where you will follow the rest of the steps. ``` # for the pre-release weights use the -l or --liaon400m switch -(ldm) ~/InvokeAI$ python3 scripts/dream.py -l +(ldm) ~/InvokeAI$ python3 scripts/invoke.py -l # for the post-release weights do not use the switch -(ldm) ~/InvokeAI$ python3 scripts/dream.py +(ldm) ~/InvokeAI$ python3 scripts/invoke.py # for additional configuration switches and arguments, use -h or --help -(ldm) ~/InvokeAI$ python3 scripts/dream.py -h +(ldm) ~/InvokeAI$ python3 scripts/invoke.py -h ``` -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. +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 invoke 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 diff --git a/docs/installation/INSTALL_MAC.md b/docs/installation/INSTALL_MAC.md index 972943f674..8345876476 100644 --- a/docs/installation/INSTALL_MAC.md +++ b/docs/installation/INSTALL_MAC.md @@ -137,10 +137,10 @@ ln -s "$PATH_TO_CKPT/sd-v1-4.ckpt" \ python scripts/preload_models.py # now you can run SD in CLI mode -python scripts/dream.py --full_precision # (1)! +python scripts/invoke.py --full_precision # (1)! # or run the web interface! -python scripts/dream.py --web +python scripts/invoke.py --web # The original scripts should work as well. python scripts/orig_scripts/txt2img.py \ @@ -155,7 +155,7 @@ it isn't required but wont hurt. ## Common problems -After you followed all the instructions and try to run dream.py, you might +After you followed all the instructions and try to run invoke.py, you might get several errors. Here's the errors I've seen and found solutions for. ### Is it slow? @@ -220,9 +220,9 @@ There are several causes of these errors: "(ldm)" then you activated it. If it begins with "(base)" or something else you haven't. -2. You might've run `./scripts/preload_models.py` or `./scripts/dream.py` +2. You might've run `./scripts/preload_models.py` or `./scripts/invoke.py` instead of `python ./scripts/preload_models.py` or - `python ./scripts/dream.py`. The cause of this error is long so it's below. + `python ./scripts/invoke.py`. The cause of this error is long so it's below. @@ -519,7 +519,7 @@ use ARM packages, and use `nomkl` as described above. May appear when just starting to generate, e.g.: ```bash -dream> clouds +invoke> clouds Generating: 0%| | 0/1 [00:00' and 'tensor<*xf16>' are not broadcast compatible diff --git a/docs/installation/INSTALL_WINDOWS.md b/docs/installation/INSTALL_WINDOWS.md index eabf3e2ae2..a74275fd62 100644 --- a/docs/installation/INSTALL_WINDOWS.md +++ b/docs/installation/INSTALL_WINDOWS.md @@ -101,13 +101,13 @@ you may instead create a shortcut to it from within `models\ldm\stable-diffusion ```bash # for the pre-release weights - python scripts\dream.py -l + python scripts\invoke.py -l # for the post-release weights - python scripts\dream.py + python scripts\invoke.py ``` -10. Subsequently, to relaunch the script, first activate the Anaconda command window (step 3),enter the InvokeAI directory (step 5, `cd \path\to\InvokeAI`), run `conda activate ldm` (step 6b), and then launch the dream script (step 9). +10. Subsequently, to relaunch the script, first activate the Anaconda command window (step 3),enter the InvokeAI directory (step 5, `cd \path\to\InvokeAI`), run `conda activate ldm` (step 6b), and then launch the invoke script (step 9). **Note:** Tildebyte has written an alternative ["Easy peasy Windows install"](https://github.com/invoke-ai/InvokeAI/wiki/Easy-peasy-Windows-install) diff --git a/ldm/dream/args.py b/ldm/dream/args.py index 5d7c5278c6..6121aa6445 100644 --- a/ldm/dream/args.py +++ b/ldm/dream/args.py @@ -1,7 +1,7 @@ """Helper class for dealing with image generation arguments. The Args class parses both the command line (shell) arguments, as well as the -command string passed at the dream> prompt. It serves as the definitive repository +command string passed at the invoke> prompt. It serves as the definitive repository of all the arguments used by Generate and their default values, and implements the preliminary metadata standards discussed here: @@ -19,7 +19,7 @@ To use: print('oops') sys.exit(-1) - # read in a command passed to the dream> prompt: + # read in a command passed to the invoke> prompt: opts = opt.parse_cmd('do androids dream of electric sheep? -H256 -W1024 -n4') # The Args object acts like a namespace object @@ -64,7 +64,7 @@ To generate a dict representing RFC266 metadata: This will generate an RFC266 dictionary that can then be turned into a JSON and written to the PNG file. The optional seeds, weights, model_hash and postprocesser arguments are not available to the opt object and so must be -provided externally. See how dream.py does it. +provided externally. See how invoke.py does it. Note that this function was originally called format_metadata() and a wrapper is provided that issues a deprecation notice. @@ -120,7 +120,7 @@ class Args(object): ''' Initialize new Args class. It takes two optional arguments, an argparse parser for switches given on the shell command line, and an argparse - parser for switches given on the dream> CLI line. If one or both are + parser for switches given on the invoke> CLI line. If one or both are missing, it creates appropriate parsers internally. ''' self._arg_parser = arg_parser or self._create_arg_parser() @@ -137,7 +137,7 @@ class Args(object): return None def parse_cmd(self,cmd_string): - '''Parse a dream>-style command string ''' + '''Parse a invoke>-style command string ''' command = cmd_string.replace("'", "\\'") try: elements = shlex.split(command) @@ -478,23 +478,23 @@ class Args(object): ) return parser - # This creates the parser that processes commands on the dream> command line + # This creates the parser that processes commands on the invoke> command line def _create_dream_cmd_parser(self): parser = argparse.ArgumentParser( formatter_class=RawTextHelpFormatter, description= """ *Image generation:* - dream> a fantastic alien landscape -W576 -H512 -s60 -n4 + invoke> a fantastic alien landscape -W576 -H512 -s60 -n4 *postprocessing* !fix applies upscaling/facefixing to a previously-generated image. - dream> !fix 0000045.4829112.png -G1 -U4 -ft codeformer + invoke> !fix 0000045.4829112.png -G1 -U4 -ft codeformer *History manipulation* !fetch retrieves the command used to generate an earlier image. - dream> !fetch 0000015.8929913.png - dream> a fantastic alien landscape -W 576 -H 512 -s 60 -A plms -C 7.5 + invoke> !fetch 0000015.8929913.png + invoke> a fantastic alien landscape -W 576 -H 512 -s 60 -A plms -C 7.5 !history lists all the commands issued during the current session. diff --git a/scripts/dream.py b/scripts/dream.py index b1882db827..05cd9c9aeb 100644 --- a/scripts/dream.py +++ b/scripts/dream.py @@ -1,570 +1,12 @@ #!/usr/bin/env python3 # Copyright (c) 2022 Lincoln D. Stein (https://github.com/lstein) -import os -import re import sys -import shlex -import copy -import warnings -import time -import traceback -sys.path.append('.') # corrects a weird problem on Macs -from ldm.dream.readline import get_completer -from ldm.dream.args import Args, metadata_dumps, metadata_from_png, dream_cmd_from_png -from ldm.dream.pngwriter import PngWriter, retrieve_metadata, write_metadata -from ldm.dream.image_util import make_grid -from ldm.dream.log import write_log -from omegaconf import OmegaConf -from backend.invoke_ai_web_server import InvokeAIWebServer +import os.path +script_path = sys.argv[0] +script_args = sys.argv[1:] +script_dir,script_name = os.path.split(script_path) +script_dest = os.path.join(script_dir,'invoke.py') +os.execlp('python3','python3',script_dest,*script_args) -def main(): - """Initialize command-line parsers and the diffusion model""" - opt = Args() - args = opt.parse_args() - if not args: - sys.exit(-1) - - if args.laion400m: - print('--laion400m flag has been deprecated. Please use --model laion400m instead.') - sys.exit(-1) - if args.weights: - print('--weights argument has been deprecated. Please edit ./configs/models.yaml, and select the weights using --model instead.') - sys.exit(-1) - - print('* Initializing, be patient...\n') - from ldm.generate import Generate - - # these two lines prevent a horrible warning message from appearing - # when the frozen CLIP tokenizer is imported - import transformers - transformers.logging.set_verbosity_error() - - # Loading Face Restoration and ESRGAN Modules - try: - gfpgan, codeformer, esrgan = None, None, None - if opt.restore or opt.esrgan: - from ldm.dream.restoration import Restoration - restoration = Restoration() - if opt.restore: - gfpgan, codeformer = restoration.load_face_restore_models(opt.gfpgan_dir, opt.gfpgan_model_path) - else: - print('>> Face restoration disabled') - if opt.esrgan: - esrgan = restoration.load_esrgan(opt.esrgan_bg_tile) - else: - print('>> Upscaling disabled') - else: - print('>> Face restoration and upscaling disabled') - except (ModuleNotFoundError, ImportError): - print(traceback.format_exc(), file=sys.stderr) - print('>> You may need to install the ESRGAN and/or GFPGAN modules') - - # creating a simple text2image object with a handful of - # defaults passed on the command line. - # additional parameters will be added (or overriden) during - # the user input loop - try: - gen = Generate( - conf = opt.conf, - model = opt.model, - sampler_name = opt.sampler_name, - embedding_path = opt.embedding_path, - full_precision = opt.full_precision, - precision = opt.precision, - gfpgan=gfpgan, - codeformer=codeformer, - esrgan=esrgan, - free_gpu_mem=opt.free_gpu_mem, - ) - except (FileNotFoundError, IOError, KeyError) as e: - print(f'{e}. Aborting.') - sys.exit(-1) - - # make sure the output directory exists - if not os.path.exists(opt.outdir): - os.makedirs(opt.outdir) - - # load the infile as a list of lines - infile = None - if opt.infile: - try: - if os.path.isfile(opt.infile): - infile = open(opt.infile, 'r', encoding='utf-8') - elif opt.infile == '-': # stdin - infile = sys.stdin - else: - raise FileNotFoundError(f'{opt.infile} not found.') - except (FileNotFoundError, IOError) as e: - print(f'{e}. Aborting.') - sys.exit(-1) - - if opt.seamless: - print(">> changed to seamless tiling mode") - - # preload the model - gen.load_model() - - # web server loops forever - if opt.web or opt.gui: - invoke_ai_web_server_loop(gen, gfpgan, codeformer, esrgan) - sys.exit(0) - - if not infile: - print( - "\n* Initialization done! Awaiting your command (-h for help, 'q' to quit)" - ) - - main_loop(gen, opt, infile) - -# TODO: main_loop() has gotten busy. Needs to be refactored. -def main_loop(gen, opt, infile): - """prompt/read/execute loop""" - done = False - path_filter = re.compile(r'[<>:"/\\|?*]') - last_results = list() - model_config = OmegaConf.load(opt.conf)[opt.model] - - # The readline completer reads history from the .dream_history file located in the - # output directory specified at the time of script launch. We do not currently support - # changing the history file midstream when the output directory is changed. - completer = get_completer(opt) - output_cntr = completer.get_current_history_length()+1 - - # os.pathconf is not available on Windows - if hasattr(os, 'pathconf'): - path_max = os.pathconf(opt.outdir, 'PC_PATH_MAX') - name_max = os.pathconf(opt.outdir, 'PC_NAME_MAX') - else: - path_max = 260 - name_max = 255 - - while not done: - operation = 'generate' # default operation, alternative is 'postprocess' - - if completer: - completer.set_default_dir(opt.outdir) - - try: - command = get_next_command(infile) - except EOFError: - done = True - continue - - # skip empty lines - if not command.strip(): - continue - - if command.startswith(('#', '//')): - continue - - if len(command.strip()) == 1 and command.startswith('q'): - done = True - break - - if command.startswith('!'): - subcommand = command[1:] - - if subcommand.startswith('dream'): # in case a stored prompt still contains the !dream command - command = command.replace('!dream ','',1) - - elif subcommand.startswith('fix'): - command = command.replace('!fix ','',1) - operation = 'postprocess' - - elif subcommand.startswith('fetch'): - file_path = command.replace('!fetch ','',1) - retrieve_dream_command(opt,file_path,completer) - continue - - elif subcommand.startswith('history'): - completer.show_history() - continue - - elif subcommand.startswith('search'): - search_str = command.replace('!search ','',1) - completer.show_history(search_str) - continue - - elif subcommand.startswith('clear'): - completer.clear_history() - continue - - elif re.match('^(\d+)',subcommand): - command_no = re.match('^(\d+)',subcommand).groups()[0] - command = completer.get_line(int(command_no)) - completer.set_line(command) - continue - - else: # not a recognized subcommand, so give the --help text - command = '-h' - - if opt.parse_cmd(command) is None: - continue - - if opt.init_img: - try: - if not opt.prompt: - oldargs = metadata_from_png(opt.init_img) - opt.prompt = oldargs.prompt - print(f'>> Retrieved old prompt "{opt.prompt}" from {opt.init_img}') - except (OSError, AttributeError, KeyError): - pass - - if len(opt.prompt) == 0: - print('\nTry again with a prompt!') - continue - - # width and height are set by model if not specified - if not opt.width: - opt.width = model_config.width - if not opt.height: - opt.height = model_config.height - - # retrieve previous value of init image if requested - if opt.init_img is not None and re.match('^-\\d+$', opt.init_img): - try: - opt.init_img = last_results[int(opt.init_img)][0] - print(f'>> Reusing previous image {opt.init_img}') - except IndexError: - print( - f'>> No previous initial image at position {opt.init_img} found') - opt.init_img = None - continue - - # try to relativize pathnames - for attr in ('init_img','init_mask','init_color','embedding_path'): - if getattr(opt,attr) and not os.path.exists(getattr(opt,attr)): - basename = getattr(opt,attr) - path = os.path.join(opt.outdir,basename) - setattr(opt,attr,path) - - # retrieve previous value of seed if requested - if opt.seed is not None and opt.seed < 0: - try: - opt.seed = last_results[opt.seed][1] - print(f'>> Reusing previous seed {opt.seed}') - except IndexError: - print(f'>> No previous seed at position {opt.seed} found') - opt.seed = None - continue - - if opt.strength is None: - opt.strength = 0.75 if opt.out_direction is None else 0.83 - - if opt.with_variations is not None: - opt.with_variations = split_variations(opt.with_variations) - - if opt.prompt_as_dir: - # sanitize the prompt to a valid folder name - subdir = path_filter.sub('_', opt.prompt)[:name_max].rstrip(' .') - - # truncate path to maximum allowed length - # 39 is the length of '######.##########.##########-##.png', plus two separators and a NUL - subdir = subdir[:(path_max - 39 - len(os.path.abspath(opt.outdir)))] - current_outdir = os.path.join(opt.outdir, subdir) - - print('Writing files to directory: "' + current_outdir + '"') - - # make sure the output directory exists - if not os.path.exists(current_outdir): - os.makedirs(current_outdir) - else: - if not os.path.exists(opt.outdir): - os.makedirs(opt.outdir) - current_outdir = opt.outdir - - # Here is where the images are actually generated! - last_results = [] - try: - file_writer = PngWriter(current_outdir) - results = [] # list of filename, prompt pairs - grid_images = dict() # seed -> Image, only used if `opt.grid` - prior_variations = opt.with_variations or [] - prefix = file_writer.unique_prefix() - - def image_writer(image, seed, upscaled=False, first_seed=None, use_prefix=None): - # note the seed is the seed of the current image - # the first_seed is the original seed that noise is added to - # when the -v switch is used to generate variations - nonlocal prior_variations - nonlocal prefix - if use_prefix is not None: - prefix = use_prefix - - path = None - if opt.grid: - grid_images[seed] = image - else: - postprocessed = upscaled if upscaled else operation=='postprocess' - filename, formatted_dream_prompt = prepare_image_metadata( - opt, - prefix, - seed, - operation, - prior_variations, - postprocessed, - first_seed - ) - path = file_writer.save_image_and_prompt_to_png( - image = image, - dream_prompt = formatted_dream_prompt, - metadata = metadata_dumps( - opt, - seeds = [seed if opt.variation_amount==0 and len(prior_variations)==0 else first_seed], - model_hash = gen.model_hash, - ), - name = filename, - ) - - # update rfc metadata - if operation == 'postprocess': - tool = re.match('postprocess:(\w+)',opt.last_operation).groups()[0] - add_postprocessing_to_metadata( - opt, - opt.prompt, - filename, - tool, - formatted_dream_prompt, - ) - - if (not postprocessed) or opt.save_original: - # only append to results if we didn't overwrite an earlier output - results.append([path, formatted_dream_prompt]) - - # so that the seed autocompletes (on linux|mac when -S or --seed specified - if completer: - completer.add_seed(seed) - completer.add_seed(first_seed) - last_results.append([path, seed]) - - if operation == 'generate': - catch_ctrl_c = infile is None # if running interactively, we catch keyboard interrupts - opt.last_operation='generate' - gen.prompt2image( - image_callback=image_writer, - catch_interrupts=catch_ctrl_c, - **vars(opt) - ) - elif operation == 'postprocess': - print(f'>> fixing {opt.prompt}') - opt.last_operation = do_postprocess(gen,opt,image_writer) - - if opt.grid and len(grid_images) > 0: - grid_img = make_grid(list(grid_images.values())) - grid_seeds = list(grid_images.keys()) - first_seed = last_results[0][1] - filename = f'{prefix}.{first_seed}.png' - formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed,grid=True,iterations=len(grid_images)) - formatted_dream_prompt += f' # {grid_seeds}' - metadata = metadata_dumps( - opt, - seeds = grid_seeds, - model_hash = gen.model_hash - ) - path = file_writer.save_image_and_prompt_to_png( - image = grid_img, - dream_prompt = formatted_dream_prompt, - metadata = metadata, - name = filename - ) - results = [[path, formatted_dream_prompt]] - - except AssertionError as e: - print(e) - continue - - except OSError as e: - print(e) - continue - - print('Outputs:') - log_path = os.path.join(current_outdir, 'dream_log') - output_cntr = write_log(results, log_path ,('txt', 'md'), output_cntr) - print() - if operation == 'postprocess': - completer.add_history(f'!fix {command}') - else: - completer.add_history(command) - - print('goodbye!') - -def do_postprocess (gen, opt, callback): - file_path = opt.prompt # treat the prompt as the file pathname - if os.path.dirname(file_path) == '': #basename given - file_path = os.path.join(opt.outdir,file_path) - - tool=None - if opt.gfpgan_strength > 0: - tool = opt.facetool - elif opt.embiggen: - tool = 'embiggen' - elif opt.upscale: - tool = 'upscale' - elif opt.out_direction: - tool = 'outpaint' - elif opt.outcrop: - tool = 'outcrop' - opt.save_original = True # do not overwrite old image! - opt.last_operation = f'postprocess:{tool}' - try: - gen.apply_postprocessor( - image_path = file_path, - tool = tool, - gfpgan_strength = opt.gfpgan_strength, - codeformer_fidelity = opt.codeformer_fidelity, - save_original = opt.save_original, - upscale = opt.upscale, - out_direction = opt.out_direction, - outcrop = opt.outcrop, - callback = callback, - opt = opt, - ) - except OSError: - print(f'** {file_path}: file could not be read') - return - except (KeyError, AttributeError): - print(traceback.format_exc(), file=sys.stderr) - return - return opt.last_operation - -def add_postprocessing_to_metadata(opt,original_file,new_file,tool,command): - original_file = original_file if os.path.exists(original_file) else os.path.join(opt.outdir,original_file) - new_file = new_file if os.path.exists(new_file) else os.path.join(opt.outdir,new_file) - meta = retrieve_metadata(original_file)['sd-metadata'] - img_data = meta['image'] - pp = img_data.get('postprocessing',[]) or [] - pp.append( - { - 'tool':tool, - 'dream_command':command, - } - ) - meta['image']['postprocessing'] = pp - write_metadata(new_file,meta) - -def prepare_image_metadata( - opt, - prefix, - seed, - operation='generate', - prior_variations=[], - postprocessed=False, - first_seed=None -): - - if postprocessed and opt.save_original: - filename = choose_postprocess_name(opt,prefix,seed) - else: - filename = f'{prefix}.{seed}.png' - - if opt.variation_amount > 0: - first_seed = first_seed or seed - this_variation = [[seed, opt.variation_amount]] - opt.with_variations = prior_variations + this_variation - formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed) - elif len(prior_variations) > 0: - formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed) - elif operation == 'postprocess': - formatted_dream_prompt = '!fix '+opt.dream_prompt_str(seed=seed) - else: - formatted_dream_prompt = opt.dream_prompt_str(seed=seed) - return filename,formatted_dream_prompt - -def choose_postprocess_name(opt,prefix,seed) -> str: - match = re.search('postprocess:(\w+)',opt.last_operation) - if match: - modifier = match.group(1) # will look like "gfpgan", "upscale", "outpaint" or "embiggen" - else: - modifier = 'postprocessed' - - counter = 0 - filename = None - available = False - while not available: - if counter == 0: - filename = f'{prefix}.{seed}.{modifier}.png' - else: - filename = f'{prefix}.{seed}.{modifier}-{counter:02d}.png' - available = not os.path.exists(os.path.join(opt.outdir,filename)) - counter += 1 - return filename - -def get_next_command(infile=None) -> str: # command string - if infile is None: - command = input('dream> ') - else: - command = infile.readline() - if not command: - raise EOFError - else: - command = command.strip() - if len(command)>0: - print(f'#{command}') - return command - -def invoke_ai_web_server_loop(gen, gfpgan, codeformer, esrgan): - print('\n* --web was specified, starting web server...') - # Change working directory to the stable-diffusion directory - os.chdir( - os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) - ) - - invoke_ai_web_server = InvokeAIWebServer(generate=gen, gfpgan=gfpgan, codeformer=codeformer, esrgan=esrgan) - - try: - invoke_ai_web_server.run() - except KeyboardInterrupt: - pass - - -def split_variations(variations_string) -> list: - # shotgun parsing, woo - parts = [] - broken = False # python doesn't have labeled loops... - for part in variations_string.split(','): - seed_and_weight = part.split(':') - if len(seed_and_weight) != 2: - print(f'** Could not parse with_variation part "{part}"') - broken = True - break - try: - seed = int(seed_and_weight[0]) - weight = float(seed_and_weight[1]) - except ValueError: - print(f'** Could not parse with_variation part "{part}"') - broken = True - break - parts.append([seed, weight]) - if broken: - return None - elif len(parts) == 0: - return None - else: - return parts - -def retrieve_dream_command(opt,file_path,completer): - ''' - Given a full or partial path to a previously-generated image file, - will retrieve and format the dream command used to generate the image, - and pop it into the readline buffer (linux, Mac), or print out a comment - for cut-and-paste (windows) - ''' - dir,basename = os.path.split(file_path) - if len(dir) == 0: - path = os.path.join(opt.outdir,basename) - else: - path = file_path - try: - cmd = dream_cmd_from_png(path) - except OSError: - print(f'** {path}: file could not be read') - return - except (KeyError, AttributeError): - print(f'** {path}: file has no metadata') - return - completer.set_line(cmd) - -if __name__ == '__main__': - main() diff --git a/scripts/invoke.py b/scripts/invoke.py new file mode 100644 index 0000000000..90febb98ca --- /dev/null +++ b/scripts/invoke.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 Lincoln D. Stein (https://github.com/lstein) + +import os +import re +import sys +import shlex +import copy +import warnings +import time +import traceback +sys.path.append('.') # corrects a weird problem on Macs +from ldm.dream.readline import get_completer +from ldm.dream.args import Args, metadata_dumps, metadata_from_png, dream_cmd_from_png +from ldm.dream.pngwriter import PngWriter, retrieve_metadata, write_metadata +from ldm.dream.image_util import make_grid +from ldm.dream.log import write_log +from omegaconf import OmegaConf +from backend.invoke_ai_web_server import InvokeAIWebServer + + +def main(): + """Initialize command-line parsers and the diffusion model""" + opt = Args() + args = opt.parse_args() + if not args: + sys.exit(-1) + + if args.laion400m: + print('--laion400m flag has been deprecated. Please use --model laion400m instead.') + sys.exit(-1) + if args.weights: + print('--weights argument has been deprecated. Please edit ./configs/models.yaml, and select the weights using --model instead.') + sys.exit(-1) + + print('* Initializing, be patient...\n') + from ldm.generate import Generate + + # these two lines prevent a horrible warning message from appearing + # when the frozen CLIP tokenizer is imported + import transformers + transformers.logging.set_verbosity_error() + + # Loading Face Restoration and ESRGAN Modules + try: + gfpgan, codeformer, esrgan = None, None, None + if opt.restore or opt.esrgan: + from ldm.dream.restoration import Restoration + restoration = Restoration() + if opt.restore: + gfpgan, codeformer = restoration.load_face_restore_models(opt.gfpgan_dir, opt.gfpgan_model_path) + else: + print('>> Face restoration disabled') + if opt.esrgan: + esrgan = restoration.load_esrgan(opt.esrgan_bg_tile) + else: + print('>> Upscaling disabled') + else: + print('>> Face restoration and upscaling disabled') + except (ModuleNotFoundError, ImportError): + print(traceback.format_exc(), file=sys.stderr) + print('>> You may need to install the ESRGAN and/or GFPGAN modules') + + # creating a simple text2image object with a handful of + # defaults passed on the command line. + # additional parameters will be added (or overriden) during + # the user input loop + try: + gen = Generate( + conf = opt.conf, + model = opt.model, + sampler_name = opt.sampler_name, + embedding_path = opt.embedding_path, + full_precision = opt.full_precision, + precision = opt.precision, + gfpgan=gfpgan, + codeformer=codeformer, + esrgan=esrgan, + free_gpu_mem=opt.free_gpu_mem, + ) + except (FileNotFoundError, IOError, KeyError) as e: + print(f'{e}. Aborting.') + sys.exit(-1) + + # make sure the output directory exists + if not os.path.exists(opt.outdir): + os.makedirs(opt.outdir) + + # load the infile as a list of lines + infile = None + if opt.infile: + try: + if os.path.isfile(opt.infile): + infile = open(opt.infile, 'r', encoding='utf-8') + elif opt.infile == '-': # stdin + infile = sys.stdin + else: + raise FileNotFoundError(f'{opt.infile} not found.') + except (FileNotFoundError, IOError) as e: + print(f'{e}. Aborting.') + sys.exit(-1) + + if opt.seamless: + print(">> changed to seamless tiling mode") + + # preload the model + gen.load_model() + + # web server loops forever + if opt.web or opt.gui: + invoke_ai_web_server_loop(gen, gfpgan, codeformer, esrgan) + sys.exit(0) + + if not infile: + print( + "\n* Initialization done! Awaiting your command (-h for help, 'q' to quit)" + ) + + main_loop(gen, opt, infile) + +# TODO: main_loop() has gotten busy. Needs to be refactored. +def main_loop(gen, opt, infile): + """prompt/read/execute loop""" + done = False + path_filter = re.compile(r'[<>:"/\\|?*]') + last_results = list() + model_config = OmegaConf.load(opt.conf)[opt.model] + + # The readline completer reads history from the .dream_history file located in the + # output directory specified at the time of script launch. We do not currently support + # changing the history file midstream when the output directory is changed. + completer = get_completer(opt) + output_cntr = completer.get_current_history_length()+1 + + # os.pathconf is not available on Windows + if hasattr(os, 'pathconf'): + path_max = os.pathconf(opt.outdir, 'PC_PATH_MAX') + name_max = os.pathconf(opt.outdir, 'PC_NAME_MAX') + else: + path_max = 260 + name_max = 255 + + while not done: + operation = 'generate' # default operation, alternative is 'postprocess' + + if completer: + completer.set_default_dir(opt.outdir) + + try: + command = get_next_command(infile) + except EOFError: + done = True + continue + + # skip empty lines + if not command.strip(): + continue + + if command.startswith(('#', '//')): + continue + + if len(command.strip()) == 1 and command.startswith('q'): + done = True + break + + if command.startswith('!'): + subcommand = command[1:] + + if subcommand.startswith('dream'): # in case a stored prompt still contains the !dream command + command = command.replace('!dream ','',1) + + elif subcommand.startswith('fix'): + command = command.replace('!fix ','',1) + operation = 'postprocess' + + elif subcommand.startswith('fetch'): + file_path = command.replace('!fetch ','',1) + retrieve_dream_command(opt,file_path,completer) + continue + + elif subcommand.startswith('history'): + completer.show_history() + continue + + elif subcommand.startswith('search'): + search_str = command.replace('!search ','',1) + completer.show_history(search_str) + continue + + elif subcommand.startswith('clear'): + completer.clear_history() + continue + + elif re.match('^(\d+)',subcommand): + command_no = re.match('^(\d+)',subcommand).groups()[0] + command = completer.get_line(int(command_no)) + completer.set_line(command) + continue + + else: # not a recognized subcommand, so give the --help text + command = '-h' + + if opt.parse_cmd(command) is None: + continue + + if opt.init_img: + try: + if not opt.prompt: + oldargs = metadata_from_png(opt.init_img) + opt.prompt = oldargs.prompt + print(f'>> Retrieved old prompt "{opt.prompt}" from {opt.init_img}') + except (OSError, AttributeError, KeyError): + pass + + if len(opt.prompt) == 0: + print('\nTry again with a prompt!') + continue + + # width and height are set by model if not specified + if not opt.width: + opt.width = model_config.width + if not opt.height: + opt.height = model_config.height + + # retrieve previous value of init image if requested + if opt.init_img is not None and re.match('^-\\d+$', opt.init_img): + try: + opt.init_img = last_results[int(opt.init_img)][0] + print(f'>> Reusing previous image {opt.init_img}') + except IndexError: + print( + f'>> No previous initial image at position {opt.init_img} found') + opt.init_img = None + continue + + # try to relativize pathnames + for attr in ('init_img','init_mask','init_color','embedding_path'): + if getattr(opt,attr) and not os.path.exists(getattr(opt,attr)): + basename = getattr(opt,attr) + path = os.path.join(opt.outdir,basename) + setattr(opt,attr,path) + + # retrieve previous value of seed if requested + if opt.seed is not None and opt.seed < 0: + try: + opt.seed = last_results[opt.seed][1] + print(f'>> Reusing previous seed {opt.seed}') + except IndexError: + print(f'>> No previous seed at position {opt.seed} found') + opt.seed = None + continue + + if opt.strength is None: + opt.strength = 0.75 if opt.out_direction is None else 0.83 + + if opt.with_variations is not None: + opt.with_variations = split_variations(opt.with_variations) + + if opt.prompt_as_dir: + # sanitize the prompt to a valid folder name + subdir = path_filter.sub('_', opt.prompt)[:name_max].rstrip(' .') + + # truncate path to maximum allowed length + # 39 is the length of '######.##########.##########-##.png', plus two separators and a NUL + subdir = subdir[:(path_max - 39 - len(os.path.abspath(opt.outdir)))] + current_outdir = os.path.join(opt.outdir, subdir) + + print('Writing files to directory: "' + current_outdir + '"') + + # make sure the output directory exists + if not os.path.exists(current_outdir): + os.makedirs(current_outdir) + else: + if not os.path.exists(opt.outdir): + os.makedirs(opt.outdir) + current_outdir = opt.outdir + + # Here is where the images are actually generated! + last_results = [] + try: + file_writer = PngWriter(current_outdir) + results = [] # list of filename, prompt pairs + grid_images = dict() # seed -> Image, only used if `opt.grid` + prior_variations = opt.with_variations or [] + prefix = file_writer.unique_prefix() + + def image_writer(image, seed, upscaled=False, first_seed=None, use_prefix=None): + # note the seed is the seed of the current image + # the first_seed is the original seed that noise is added to + # when the -v switch is used to generate variations + nonlocal prior_variations + nonlocal prefix + if use_prefix is not None: + prefix = use_prefix + + path = None + if opt.grid: + grid_images[seed] = image + else: + postprocessed = upscaled if upscaled else operation=='postprocess' + filename, formatted_dream_prompt = prepare_image_metadata( + opt, + prefix, + seed, + operation, + prior_variations, + postprocessed, + first_seed + ) + path = file_writer.save_image_and_prompt_to_png( + image = image, + dream_prompt = formatted_dream_prompt, + metadata = metadata_dumps( + opt, + seeds = [seed if opt.variation_amount==0 and len(prior_variations)==0 else first_seed], + model_hash = gen.model_hash, + ), + name = filename, + ) + + # update rfc metadata + if operation == 'postprocess': + tool = re.match('postprocess:(\w+)',opt.last_operation).groups()[0] + add_postprocessing_to_metadata( + opt, + opt.prompt, + filename, + tool, + formatted_dream_prompt, + ) + + if (not postprocessed) or opt.save_original: + # only append to results if we didn't overwrite an earlier output + results.append([path, formatted_dream_prompt]) + + # so that the seed autocompletes (on linux|mac when -S or --seed specified + if completer: + completer.add_seed(seed) + completer.add_seed(first_seed) + last_results.append([path, seed]) + + if operation == 'generate': + catch_ctrl_c = infile is None # if running interactively, we catch keyboard interrupts + opt.last_operation='generate' + gen.prompt2image( + image_callback=image_writer, + catch_interrupts=catch_ctrl_c, + **vars(opt) + ) + elif operation == 'postprocess': + print(f'>> fixing {opt.prompt}') + opt.last_operation = do_postprocess(gen,opt,image_writer) + + if opt.grid and len(grid_images) > 0: + grid_img = make_grid(list(grid_images.values())) + grid_seeds = list(grid_images.keys()) + first_seed = last_results[0][1] + filename = f'{prefix}.{first_seed}.png' + formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed,grid=True,iterations=len(grid_images)) + formatted_dream_prompt += f' # {grid_seeds}' + metadata = metadata_dumps( + opt, + seeds = grid_seeds, + model_hash = gen.model_hash + ) + path = file_writer.save_image_and_prompt_to_png( + image = grid_img, + dream_prompt = formatted_dream_prompt, + metadata = metadata, + name = filename + ) + results = [[path, formatted_dream_prompt]] + + except AssertionError as e: + print(e) + continue + + except OSError as e: + print(e) + continue + + print('Outputs:') + log_path = os.path.join(current_outdir, 'dream_log') + output_cntr = write_log(results, log_path ,('txt', 'md'), output_cntr) + print() + if operation == 'postprocess': + completer.add_history(f'!fix {command}') + else: + completer.add_history(command) + + print('goodbye!') + +def do_postprocess (gen, opt, callback): + file_path = opt.prompt # treat the prompt as the file pathname + if os.path.dirname(file_path) == '': #basename given + file_path = os.path.join(opt.outdir,file_path) + + tool=None + if opt.gfpgan_strength > 0: + tool = opt.facetool + elif opt.embiggen: + tool = 'embiggen' + elif opt.upscale: + tool = 'upscale' + elif opt.out_direction: + tool = 'outpaint' + elif opt.outcrop: + tool = 'outcrop' + opt.save_original = True # do not overwrite old image! + opt.last_operation = f'postprocess:{tool}' + try: + gen.apply_postprocessor( + image_path = file_path, + tool = tool, + gfpgan_strength = opt.gfpgan_strength, + codeformer_fidelity = opt.codeformer_fidelity, + save_original = opt.save_original, + upscale = opt.upscale, + out_direction = opt.out_direction, + outcrop = opt.outcrop, + callback = callback, + opt = opt, + ) + except OSError: + print(f'** {file_path}: file could not be read') + return + except (KeyError, AttributeError): + print(traceback.format_exc(), file=sys.stderr) + return + return opt.last_operation + +def add_postprocessing_to_metadata(opt,original_file,new_file,tool,command): + original_file = original_file if os.path.exists(original_file) else os.path.join(opt.outdir,original_file) + new_file = new_file if os.path.exists(new_file) else os.path.join(opt.outdir,new_file) + meta = retrieve_metadata(original_file)['sd-metadata'] + img_data = meta['image'] + pp = img_data.get('postprocessing',[]) or [] + pp.append( + { + 'tool':tool, + 'dream_command':command, + } + ) + meta['image']['postprocessing'] = pp + write_metadata(new_file,meta) + +def prepare_image_metadata( + opt, + prefix, + seed, + operation='generate', + prior_variations=[], + postprocessed=False, + first_seed=None +): + + if postprocessed and opt.save_original: + filename = choose_postprocess_name(opt,prefix,seed) + else: + filename = f'{prefix}.{seed}.png' + + if opt.variation_amount > 0: + first_seed = first_seed or seed + this_variation = [[seed, opt.variation_amount]] + opt.with_variations = prior_variations + this_variation + formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed) + elif len(prior_variations) > 0: + formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed) + elif operation == 'postprocess': + formatted_dream_prompt = '!fix '+opt.dream_prompt_str(seed=seed) + else: + formatted_dream_prompt = opt.dream_prompt_str(seed=seed) + return filename,formatted_dream_prompt + +def choose_postprocess_name(opt,prefix,seed) -> str: + match = re.search('postprocess:(\w+)',opt.last_operation) + if match: + modifier = match.group(1) # will look like "gfpgan", "upscale", "outpaint" or "embiggen" + else: + modifier = 'postprocessed' + + counter = 0 + filename = None + available = False + while not available: + if counter == 0: + filename = f'{prefix}.{seed}.{modifier}.png' + else: + filename = f'{prefix}.{seed}.{modifier}-{counter:02d}.png' + available = not os.path.exists(os.path.join(opt.outdir,filename)) + counter += 1 + return filename + +def get_next_command(infile=None) -> str: # command string + if infile is None: + command = input('invoke> ') + else: + command = infile.readline() + if not command: + raise EOFError + else: + command = command.strip() + if len(command)>0: + print(f'#{command}') + return command + +def invoke_ai_web_server_loop(gen, gfpgan, codeformer, esrgan): + print('\n* --web was specified, starting web server...') + # Change working directory to the stable-diffusion directory + os.chdir( + os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + ) + + invoke_ai_web_server = InvokeAIWebServer(generate=gen, gfpgan=gfpgan, codeformer=codeformer, esrgan=esrgan) + + try: + invoke_ai_web_server.run() + except KeyboardInterrupt: + pass + + +def split_variations(variations_string) -> list: + # shotgun parsing, woo + parts = [] + broken = False # python doesn't have labeled loops... + for part in variations_string.split(','): + seed_and_weight = part.split(':') + if len(seed_and_weight) != 2: + print(f'** Could not parse with_variation part "{part}"') + broken = True + break + try: + seed = int(seed_and_weight[0]) + weight = float(seed_and_weight[1]) + except ValueError: + print(f'** Could not parse with_variation part "{part}"') + broken = True + break + parts.append([seed, weight]) + if broken: + return None + elif len(parts) == 0: + return None + else: + return parts + +def retrieve_dream_command(opt,file_path,completer): + ''' + Given a full or partial path to a previously-generated image file, + will retrieve and format the dream command used to generate the image, + and pop it into the readline buffer (linux, Mac), or print out a comment + for cut-and-paste (windows) + ''' + dir,basename = os.path.split(file_path) + if len(dir) == 0: + path = os.path.join(opt.outdir,basename) + else: + path = file_path + try: + cmd = dream_cmd_from_png(path) + except OSError: + print(f'** {path}: file could not be read') + return + except (KeyError, AttributeError): + print(f'** {path}: file has no metadata') + return + completer.set_line(cmd) + +if __name__ == '__main__': + main()