diff --git a/.eslintrc.json b/.eslintrc.json index 22a9edee5..7ea7b8dae 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -112,6 +112,8 @@ "idbPut": "readonly", "idbDel": "readonly", "idbAdd": "readonly", + // changelog.js + "initChangelog": "readonly", // notification.js "sendNotification": "readonly" }, diff --git a/.gitignore b/.gitignore index 4d0ab36c6..dca4e17ad 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,11 @@ tunableop_results*.csv !webui.sh !package.json +# pyinstaller +*.spec +build/ +dist/ + # dynamically generated /repositories/ip-instruct/ @@ -69,3 +74,4 @@ tunableop_results*.csv !/models/VAE-approx/model.pt !/models/Reference !/models/Reference/**/* + diff --git a/CHANGELOG.md b/CHANGELOG.md index 39892eb0f..6b60a7a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Change Log for SD.Next +## Update for 2024-11-01 + +Smaller release just 3 days after the last one, but with some important fixes and improvements. +This release can be considered an LTS release before we kick off the next round of major updates. + +- XYZ grid: + - optional per-image time benchmark info +- UI: + - add additional [hotkeys](https://github.com/vladmandic/automatic/wiki/Hotkeys) +- Docs: + - add built-in [changelog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) search + since changelog is the best up-to-date source of info + go to system -> changelog and search/highligh/navigate directly in UI! +- SD3: ControlNets: + - *InstantX Canny, Pose, Depth, Tile* + - *Alimama Inpainting, SoftEdge* + - *note*: that just like with FLUX.1 or any large model, ControlNet are also large and can push your system over the limit + e.g. SD3 controlnets vary from 1GB to over 4GB in size +- SD3: all-in-one safetensors + - *examples*: [large](https://civitai.com/models/882666/sd35-large-google-flan?modelVersionId=1003031), [medium](https://civitai.com/models/900327) + - *note*: enable *bnb* on-the-fly quantization for even bigger gains +- CLI: + - refactor command line params + run `webui.sh`/`webui.bat` with `--help` to see all options +- Other: + - Repo: move screenshots to GH pages + - Update requirements +- Fixes: + - detailer min/max size as fractions of image size + - ipadapter load on-demand + - ipadapter face use correct yolo model + - list diffusers remove duplicates + - fix legacy extensions access to shared objects + - fix diffusers load from folder + - fix lora enum logging on windows + - fix xyz grid with batch count + - move dowwloads of some auxillary models to hfcache instead of models folder + ## Update for 2024-10-29 ### Highlights for 2024-10-29 diff --git a/CITATION.cff b/CITATION.cff index f7fd4bba3..c39efe474 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -24,5 +24,5 @@ abstract: >- generation keywords: - stablediffusion diffusers sdnext -license: AGPL-3.0 +license: Apache-2.0 date-released: 2022-12-24 diff --git a/LICENSE.txt b/LICENSE.txt index 211d32e75..0ad25db4b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,6 @@ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 - Copyright (c) 2023 AUTOMATIC1111 - Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -635,8 +633,8 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, diff --git a/README.md b/README.md index ac96b2e32..a2caa5cb7 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,13 @@ All individual features are not listed here, instead check [ChangeLog](CHANGELOG
*Main interface using **StandardUI***: -![Screenshot-Dark](html/screenshot-text2image.jpg) +![screenshot-text2image](https://github.com/user-attachments/assets/87ac2813-65c2-45f4-80b8-67b26ccf5cd6) *Main interface using **ModernUI***: -![Screenshot-Dark](html/screenshot-modernui-f1.jpg) -![Screenshot-Dark](html/screenshot-modernui.jpg) -![Screenshot-Dark](html/screenshot-modernui-sd3.jpg) + +![screenshot-modernui-f1](https://github.com/user-attachments/assets/b509a280-8d3b-48b5-8525-363bad8c1ed2) +![screenshot-modernui](https://github.com/user-attachments/assets/fef33127-f733-4e78-b66e-17729539512f) +![screenshot-modernui-sd3](https://github.com/user-attachments/assets/1ed02ecc-23e4-4fda-8ae5-2d7393dc530c) For screenshots and informations on other available themes, see [Themes Wiki](https://github.com/vladmandic/automatic/wiki/Themes) @@ -63,12 +64,13 @@ For screenshots and informations on other available themes, see [Themes Wiki](ht ## Model support -Additional models will be added as they become available and there is public interest in them +Additional models will be added as they become available and there is public interest in them +See [models overview](https://github.com/vladmandic/automatic/wiki/Models) for details on each model, including their architecture, complexity and other info - [RunwayML Stable Diffusion](https://github.com/Stability-AI/stablediffusion/) 1.x and 2.x *(all variants)* - [StabilityAI Stable Diffusion XL](https://github.com/Stability-AI/generative-models) -- [StabilityAI Stable Diffusion 3 Medium](https://stability.ai/news/stable-diffusion-3-medium) -- [Stable Diffusion 3.5 Large](https://huggingface.co/stabilityai/stable-diffusion-3.5-large) +- [StabilityAI Stable Diffusion](https://stability.ai/news/stable-diffusion-3-medium) +- [Stable Diffusion 3.x](https://huggingface.co/stabilityai/stable-diffusion-3.5-large) 3.0 Medium, 3.5 Medium, 3.5 Large, 3.5 Large Turbo - [StabilityAI Stable Video Diffusion](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid) Base, XT 1.0, XT 1.1 - [StabilityAI Stable Cascade](https://github.com/Stability-AI/StableCascade) *Full* and *Lite* - [Black Forest Labs FLUX.1](https://blackforestlabs.ai/announcing-black-forest-labs/) Dev, Schnell @@ -136,13 +138,13 @@ Also supported are modifiers such as: ## Examples *IP Adapters*: -![Screenshot-IPAdapter](html/screenshot-ipadapter.jpg) +![screenshot-ipadapter](https://github.com/user-attachments/assets/92830894-845c-49ec-92d9-18c8a577d04f) *Color grading*: -![Screenshot-Color](html/screenshot-color.jpg) +![screenshot-control](https://github.com/user-attachments/assets/cdad2722-ae7c-4c9c-94d6-5ea35a4b1356) *InstantID*: -![Screenshot-InstantID](html/screenshot-instantid.jpg) +![screenshot-instantid](https://github.com/user-attachments/assets/f38a5660-32b3-4235-9da1-c79eccf5372f) > [!IMPORTANT] > - Loading any model other than standard SD 1.x / SD 2.x requires use of backend **Diffusers** @@ -173,92 +175,34 @@ Also supported are modifiers such as: Once SD.Next is installed, simply run `webui.ps1` or `webui.bat` (*Windows*) or `webui.sh` (*Linux or MacOS*) -List of available parameters, run `webui --help` for the full & up-to-date list: +For list of available command line options, run `webui --help` for the full & up-to-date list - Server options: - --config CONFIG Use specific server configuration file, default: config.json - --ui-config UI_CONFIG Use specific UI configuration file, default: ui-config.json - --medvram Split model stages and keep only active part in VRAM, default: False - --lowvram Split model components and keep only active part in VRAM, default: False - --ckpt CKPT Path to model checkpoint to load immediately, default: None - --vae VAE Path to VAE checkpoint to load immediately, default: None - --data-dir DATA_DIR Base path where all user data is stored, default: - --models-dir MODELS_DIR Base path where all models are stored, default: models - --allow-code Allow custom script execution, default: False - --share Enable UI accessible through Gradio site, default: False - --insecure Enable extensions tab regardless of other options, default: False - --use-cpu USE_CPU [USE_CPU ...] Force use CPU for specified modules, default: [] - --listen Launch web server using public IP address, default: False - --port PORT Launch web server with given server port, default: 7860 - --freeze Disable editing settings - --auth AUTH Set access authentication like "user:pwd,user:pwd"" - --auth-file AUTH_FILE Set access authentication using file, default: None - --autolaunch Open the UI URL in the system's default browser upon launch - --docs Mount API docs, default: False - --api-only Run in API only mode without starting UI - --api-log Enable logging of all API requests, default: False - --device-id DEVICE_ID Select the default CUDA device to use, default: None - --cors-origins CORS_ORIGINS Allowed CORS origins as comma-separated list, default: None - --cors-regex CORS_REGEX Allowed CORS origins as regular expression, default: None - --tls-keyfile TLS_KEYFILE Enable TLS and specify key file, default: None - --tls-certfile TLS_CERTFILE Enable TLS and specify cert file, default: None - --tls-selfsign Enable TLS with self-signed certificates, default: False - --server-name SERVER_NAME Sets hostname of server, default: None - --no-hashing Disable hashing of checkpoints, default: False - --no-metadata Disable reading of metadata from models, default: False - --disable-queue Disable queues, default: False - --subpath SUBPATH Customize the URL subpath for usage with reverse proxy - --backend {original,diffusers} force model pipeline type - --allowed-paths ALLOWED_PATHS [ALLOWED_PATHS ...] add additional paths to paths allowed for web access - - Setup options: - --reset Reset main repository to latest version, default: False - --upgrade Upgrade main repository to latest version, default: False - --requirements Force re-check of requirements, default: False - --quick Bypass version checks, default: False - --use-directml Use DirectML if no compatible GPU is detected, default: False - --use-openvino Use Intel OpenVINO backend, default: False - --use-ipex Force use Intel OneAPI XPU backend, default: False - --use-cuda Force use nVidia CUDA backend, default: False - --use-rocm Force use AMD ROCm backend, default: False - --use-zluda Force use ZLUDA, AMD GPUs only, default: False - --use-xformers Force use xFormers cross-optimization, default: False - --skip-requirements Skips checking and installing requirements, default: False - --skip-extensions Skips running individual extension installers, default: False - --skip-git Skips running all GIT operations, default: False - --skip-torch Skips running Torch checks, default: False - --skip-all Skips running all checks, default: False - --skip-env Skips setting of env variables during startup, default: False - --experimental Allow unsupported versions of libraries, default: False - --reinstall Force reinstallation of all requirements, default: False - --test Run test only and exit - --version Print version information - --ignore Ignore any errors and attempt to continue - --safe Run in safe mode with no user extensions - --uv Use uv as installer, default: False - - Logging options: - --log LOG Set log file, default: None - --debug Run installer with debug logging, default: False - --profile Run profiler, default: False +> [!TIP] +> All command line options can also be set via env variable +> For example `--debug` is same as `set SD_DEBUG=true` ## Notes > [!TIP] > If you don't want to use built-in `venv` support and prefer to run SD.Next in your own environment such as *Docker* container, *Conda* environment or any other virtual environment, you can skip `venv` create/activate and launch SD.Next directly using `python launch.py` (command line flags noted above still apply). +### Quantization + +**SD.Next** comes with broad quantization support, including support for BitsAndBytes, Optimum.Quanto, TorchAO, NNCF and GGUF +See [Quantization Wiki](https://github.com/vladmandic/automatic/wiki/Quantization) + ### Control **SD.Next** comes with built-in control for all types of text2image, image2image, video2video and batch processing *Control interface*: -![Screenshot-Control](html/screenshot-control.jpg) +![screenshot-control](https://github.com/user-attachments/assets/cdad2722-ae7c-4c9c-94d6-5ea35a4b1356) *Control processors*: -![Screenshot-Process](html/screenshot-processors.jpg) +![screenshot-processors](https://github.com/user-attachments/assets/7bccb82b-366e-4bdb-ae57-cc53fac95d3c) *Masking*: -![Screenshot-Mask](html/screenshot-mask.jpg) +![screenshot-mask](https://github.com/user-attachments/assets/4b057e65-64f0-44ea-93b4-c3b69bc55532) ### Extensions diff --git a/TODO.md b/TODO.md index 5726e67da..2f3f90852 100644 --- a/TODO.md +++ b/TODO.md @@ -6,7 +6,7 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - async lowvram: - fp8: -- ipadapter-negative: https://github.com/huggingface/diffusers/discussions/7167 +- ipadapter-negative: - include reference styles ### Missing diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 83aa6b40b..160487e88 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -128,7 +128,7 @@ def load_diffusers(name, network_on_disk, lora_scale=shared.opts.extra_networks_ def load_network(name, network_on_disk) -> network.Network: if not shared.sd_loaded: - return + return None t0 = time.time() cached = lora_cache.get(name, None) if debug: @@ -533,18 +533,14 @@ def network_MultiheadAttention_load_state_dict(self, *args, **kwargs): def list_available_networks(): + t0 = time.time() available_networks.clear() available_network_aliases.clear() forbidden_network_aliases.clear() available_network_hash_lookup.clear() forbidden_network_aliases.update({"none": 1, "Addams": 1}) - directories = [] - if os.path.exists(shared.cmd_opts.lora_dir): - directories.append(shared.cmd_opts.lora_dir) - else: + if not os.path.exists(shared.cmd_opts.lora_dir): shared.log.warning(f'LoRA directory not found: path="{shared.cmd_opts.lora_dir}"') - if os.path.exists(shared.cmd_opts.lyco_dir) and shared.cmd_opts.lyco_dir != shared.cmd_opts.lora_dir: - directories.append(shared.cmd_opts.lyco_dir) def add_network(filename): if not os.path.isfile(filename): @@ -565,11 +561,12 @@ def list_available_networks(): except OSError as e: # should catch FileNotFoundError and PermissionError etc. shared.log.error(f'LoRA: filename="{filename}" {e}') - candidates = list(files_cache.list_files(*directories, ext_filter=[".pt", ".ckpt", ".safetensors"])) + candidates = list(files_cache.list_files(shared.cmd_opts.lora_dir, ext_filter=[".pt", ".ckpt", ".safetensors"])) with concurrent.futures.ThreadPoolExecutor(max_workers=shared.max_workers) as executor: for fn in candidates: executor.submit(add_network, fn) - shared.log.info(f'Available LoRAs: items={len(available_networks)} folders={len(forbidden_network_aliases)}') + t1 = time.time() + shared.log.info(f'Available LoRAs: path="{shared.cmd_opts.lora_dir}" items={len(available_networks)} folders={len(forbidden_network_aliases)} time={t1 - t0:.2f}') def infotext_pasted(infotext, params): # pylint: disable=W0613 diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index 9b721248d..71bdbbd9c 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit 9b721248d55021cbb3e2976ccfa984a8e5b96f39 +Subproject commit 71bdbbd9c0a55ccea38cbf6fb01483323ac93676 diff --git a/html/card-no-preview.png b/html/card-no-preview.png index 352b0a9df..6b89aad1a 100644 Binary files a/html/card-no-preview.png and b/html/card-no-preview.png differ diff --git a/html/screenshot-color.jpg b/html/screenshot-color.jpg deleted file mode 100644 index 2cd070a3a..000000000 Binary files a/html/screenshot-color.jpg and /dev/null differ diff --git a/html/screenshot-control.jpg b/html/screenshot-control.jpg deleted file mode 100644 index ffc71cdbe..000000000 Binary files a/html/screenshot-control.jpg and /dev/null differ diff --git a/html/screenshot-corrections.jpg b/html/screenshot-corrections.jpg deleted file mode 100644 index e202fa746..000000000 Binary files a/html/screenshot-corrections.jpg and /dev/null differ diff --git a/html/screenshot-differential.jpg b/html/screenshot-differential.jpg deleted file mode 100644 index c0bb1e78d..000000000 Binary files a/html/screenshot-differential.jpg and /dev/null differ diff --git a/html/screenshot-instantid.jpg b/html/screenshot-instantid.jpg deleted file mode 100644 index 5f780a3c0..000000000 Binary files a/html/screenshot-instantid.jpg and /dev/null differ diff --git a/html/screenshot-ipadapter-mask.jpg b/html/screenshot-ipadapter-mask.jpg deleted file mode 100644 index 3dd36a2af..000000000 Binary files a/html/screenshot-ipadapter-mask.jpg and /dev/null differ diff --git a/html/screenshot-ipadapter.jpg b/html/screenshot-ipadapter.jpg deleted file mode 100644 index b1d5c80b6..000000000 Binary files a/html/screenshot-ipadapter.jpg and /dev/null differ diff --git a/html/screenshot-ipadapter02.jpg b/html/screenshot-ipadapter02.jpg deleted file mode 100644 index d0e59a2ec..000000000 Binary files a/html/screenshot-ipadapter02.jpg and /dev/null differ diff --git a/html/screenshot-ledit.jpg b/html/screenshot-ledit.jpg deleted file mode 100644 index f43189d81..000000000 Binary files a/html/screenshot-ledit.jpg and /dev/null differ diff --git a/html/screenshot-mask.jpg b/html/screenshot-mask.jpg deleted file mode 100644 index c286803f2..000000000 Binary files a/html/screenshot-mask.jpg and /dev/null differ diff --git a/html/screenshot-modernui-control.jpg b/html/screenshot-modernui-control.jpg deleted file mode 100644 index 20a22a88a..000000000 Binary files a/html/screenshot-modernui-control.jpg and /dev/null differ diff --git a/html/screenshot-modernui-f1.jpg b/html/screenshot-modernui-f1.jpg deleted file mode 100644 index 3e7b27ce1..000000000 Binary files a/html/screenshot-modernui-f1.jpg and /dev/null differ diff --git a/html/screenshot-modernui-img2img.jpg b/html/screenshot-modernui-img2img.jpg deleted file mode 100644 index 22afd166d..000000000 Binary files a/html/screenshot-modernui-img2img.jpg and /dev/null differ diff --git a/html/screenshot-modernui-sd3.jpg b/html/screenshot-modernui-sd3.jpg deleted file mode 100644 index 81d0eb459..000000000 Binary files a/html/screenshot-modernui-sd3.jpg and /dev/null differ diff --git a/html/screenshot-modernui.jpg b/html/screenshot-modernui.jpg deleted file mode 100644 index dafadc28f..000000000 Binary files a/html/screenshot-modernui.jpg and /dev/null differ diff --git a/html/screenshot-outpaint.jpg b/html/screenshot-outpaint.jpg deleted file mode 100644 index 485c67c52..000000000 Binary files a/html/screenshot-outpaint.jpg and /dev/null differ diff --git a/html/screenshot-processors.jpg b/html/screenshot-processors.jpg deleted file mode 100644 index 8d3207fa2..000000000 Binary files a/html/screenshot-processors.jpg and /dev/null differ diff --git a/html/screenshot-regional.jpg b/html/screenshot-regional.jpg deleted file mode 100644 index 335297b37..000000000 Binary files a/html/screenshot-regional.jpg and /dev/null differ diff --git a/html/screenshot-text2image.jpg b/html/screenshot-text2image.jpg deleted file mode 100644 index f5b48dc3a..000000000 Binary files a/html/screenshot-text2image.jpg and /dev/null differ diff --git a/installer.py b/installer.py index a17efa83a..492961439 100644 --- a/installer.py +++ b/installer.py @@ -552,7 +552,7 @@ def install_rocm_zluda(): from modules import zluda_installer zluda_installer.set_default_agent(device) try: - if args.reinstall_zluda: + if args.reinstall: zluda_installer.uninstall() zluda_path = zluda_installer.get_path() zluda_installer.install(zluda_path) @@ -1234,39 +1234,44 @@ def check_timestamp(): def add_args(parser): - group = parser.add_argument_group('Setup options') - group.add_argument('--reset', default = os.environ.get("SD_RESET",False), action='store_true', help = "Reset main repository to latest version, default: %(default)s") - group.add_argument('--upgrade', '--update', default = os.environ.get("SD_UPGRADE",False), action='store_true', help = "Upgrade main repository to latest version, default: %(default)s") - group.add_argument('--requirements', default = os.environ.get("SD_REQUIREMENTS",False), action='store_true', help = "Force re-check of requirements, default: %(default)s") - group.add_argument('--quick', default = os.environ.get("SD_QUICK",False), action='store_true', help = "Bypass version checks, default: %(default)s") - group.add_argument('--use-directml', default = os.environ.get("SD_USEDIRECTML",False), action='store_true', help = "Use DirectML if no compatible GPU is detected, default: %(default)s") - group.add_argument("--use-openvino", default = os.environ.get("SD_USEOPENVINO",False), action='store_true', help="Use Intel OpenVINO backend, default: %(default)s") - group.add_argument("--use-ipex", default = os.environ.get("SD_USEIPEX",False), action='store_true', help="Force use Intel OneAPI XPU backend, default: %(default)s") - group.add_argument("--use-cuda", default = os.environ.get("SD_USECUDA",False), action='store_true', help="Force use nVidia CUDA backend, default: %(default)s") - group.add_argument("--use-rocm", default = os.environ.get("SD_USEROCM",False), action='store_true', help="Force use AMD ROCm backend, default: %(default)s") - group.add_argument('--use-zluda', default=os.environ.get("SD_USEZLUDA", False), action='store_true', help = "Force use ZLUDA, AMD GPUs only, default: %(default)s") - group.add_argument("--use-xformers", default = os.environ.get("SD_USEXFORMERS",False), action='store_true', help="Force use xFormers cross-optimization, default: %(default)s") - group.add_argument('--skip-requirements', default = os.environ.get("SD_SKIPREQUIREMENTS",False), action='store_true', help = "Skips checking and installing requirements, default: %(default)s") - group.add_argument('--skip-extensions', default = os.environ.get("SD_SKIPEXTENSION",False), action='store_true', help = "Skips running individual extension installers, default: %(default)s") - group.add_argument('--skip-git', default = os.environ.get("SD_SKIPGIT",False), action='store_true', help = "Skips running all GIT operations, default: %(default)s") - group.add_argument('--skip-torch', default = os.environ.get("SD_SKIPTORCH",False), action='store_true', help = "Skips running Torch checks, default: %(default)s") - group.add_argument('--skip-all', default = os.environ.get("SD_SKIPALL",False), action='store_true', help = "Skips running all checks, default: %(default)s") - group.add_argument('--skip-env', default = os.environ.get("SD_SKIPENV",False), action='store_true', help = "Skips setting of env variables during startup, default: %(default)s") - group.add_argument('--experimental', default = os.environ.get("SD_EXPERIMENTAL",False), action='store_true', help = "Allow unsupported versions of libraries, default: %(default)s") - group.add_argument('--reinstall', default = os.environ.get("SD_REINSTALL",False), action='store_true', help = "Force reinstallation of all requirements, default: %(default)s") - group.add_argument('--reinstall-zluda', default = os.environ.get("SD_REINSTALL_ZLUDA",False), action='store_true', help = "Force reinstallation of ZLUDA, default: %(default)s") - group.add_argument('--test', default = os.environ.get("SD_TEST",False), action='store_true', help = "Run test only and exit") - group.add_argument('--version', default = False, action='store_true', help = "Print version information") - group.add_argument('--ignore', default = os.environ.get("SD_IGNORE",False), action='store_true', help = "Ignore any errors and attempt to continue") - group.add_argument('--safe', default = os.environ.get("SD_SAFE",False), action='store_true', help = "Run in safe mode with no user extensions") - group.add_argument('--uv', default = os.environ.get("SD_UV",False), action='store_true', help = "Use uv instead of pip to install the packages") + group_setup = parser.add_argument_group('Setup') + group_setup.add_argument('--reset', default = os.environ.get("SD_RESET",False), action='store_true', help = "Reset main repository to latest version, default: %(default)s") + group_setup.add_argument('--upgrade', '--update', default = os.environ.get("SD_UPGRADE",False), action='store_true', help = "Upgrade main repository to latest version, default: %(default)s") + group_setup.add_argument('--requirements', default = os.environ.get("SD_REQUIREMENTS",False), action='store_true', help = "Force re-check of requirements, default: %(default)s") + group_setup.add_argument('--reinstall', default = os.environ.get("SD_REINSTALL",False), action='store_true', help = "Force reinstallation of all requirements, default: %(default)s") + group_setup.add_argument('--uv', default = os.environ.get("SD_UV",False), action='store_true', help = "Use uv instead of pip to install the packages") - group = parser.add_argument_group('Logging options') - group.add_argument("--log", type=str, default=os.environ.get("SD_LOG", None), help="Set log file, default: %(default)s") - group.add_argument('--debug', default = os.environ.get("SD_DEBUG",False), action='store_true', help = "Run installer with debug logging, default: %(default)s") - group.add_argument("--profile", default=os.environ.get("SD_PROFILE", False), action='store_true', help="Run profiler, default: %(default)s") - group.add_argument('--docs', default=os.environ.get("SD_DOCS", False), action='store_true', help = "Mount API docs, default: %(default)s") - group.add_argument("--api-log", default=os.environ.get("SD_APILOG", False), action='store_true', help="Enable logging of all API requests, default: %(default)s") + group_startup = parser.add_argument_group('Startup') + group_startup.add_argument('--quick', default = os.environ.get("SD_QUICK",False), action='store_true', help = "Bypass version checks, default: %(default)s") + group_startup.add_argument('--skip-requirements', default = os.environ.get("SD_SKIPREQUIREMENTS",False), action='store_true', help = "Skips checking and installing requirements, default: %(default)s") + group_startup.add_argument('--skip-extensions', default = os.environ.get("SD_SKIPEXTENSION",False), action='store_true', help = "Skips running individual extension installers, default: %(default)s") + group_startup.add_argument('--skip-git', default = os.environ.get("SD_SKIPGIT",False), action='store_true', help = "Skips running all GIT operations, default: %(default)s") + group_startup.add_argument('--skip-torch', default = os.environ.get("SD_SKIPTORCH",False), action='store_true', help = "Skips running Torch checks, default: %(default)s") + group_startup.add_argument('--skip-all', default = os.environ.get("SD_SKIPALL",False), action='store_true', help = "Skips running all checks, default: %(default)s") + group_startup.add_argument('--skip-env', default = os.environ.get("SD_SKIPENV",False), action='store_true', help = "Skips setting of env variables during startup, default: %(default)s") + + group_compute = parser.add_argument_group('Compute Engine') + group_compute.add_argument('--use-directml', default = os.environ.get("SD_USEDIRECTML",False), action='store_true', help = "Use DirectML if no compatible GPU is detected, default: %(default)s") + group_compute.add_argument("--use-openvino", default = os.environ.get("SD_USEOPENVINO",False), action='store_true', help="Use Intel OpenVINO backend, default: %(default)s") + group_compute.add_argument("--use-ipex", default = os.environ.get("SD_USEIPEX",False), action='store_true', help="Force use Intel OneAPI XPU backend, default: %(default)s") + group_compute.add_argument("--use-cuda", default = os.environ.get("SD_USECUDA",False), action='store_true', help="Force use nVidia CUDA backend, default: %(default)s") + group_compute.add_argument("--use-rocm", default = os.environ.get("SD_USEROCM",False), action='store_true', help="Force use AMD ROCm backend, default: %(default)s") + group_compute.add_argument('--use-zluda', default=os.environ.get("SD_USEZLUDA", False), action='store_true', help = "Force use ZLUDA, AMD GPUs only, default: %(default)s") + group_compute.add_argument("--use-xformers", default = os.environ.get("SD_USEXFORMERS",False), action='store_true', help="Force use xFormers cross-optimization, default: %(default)s") + + group_diag = parser.add_argument_group('Diagnostics') + group_diag.add_argument('--safe', default = os.environ.get("SD_SAFE",False), action='store_true', help = "Run in safe mode with no user extensions") + group_diag.add_argument('--experimental', default = os.environ.get("SD_EXPERIMENTAL",False), action='store_true', help = "Allow unsupported versions of libraries, default: %(default)s") + group_diag.add_argument('--test', default = os.environ.get("SD_TEST",False), action='store_true', help = "Run test only and exit") + group_diag.add_argument('--version', default = False, action='store_true', help = "Print version information") + group_diag.add_argument('--ignore', default = os.environ.get("SD_IGNORE",False), action='store_true', help = "Ignore any errors and attempt to continue") + + group_log = parser.add_argument_group('Logging') + group_log.add_argument("--log", type=str, default=os.environ.get("SD_LOG", None), help="Set log file, default: %(default)s") + group_log.add_argument('--debug', default = os.environ.get("SD_DEBUG",False), action='store_true', help = "Run installer with debug logging, default: %(default)s") + group_log.add_argument("--profile", default=os.environ.get("SD_PROFILE", False), action='store_true', help="Run profiler, default: %(default)s") + group_log.add_argument('--docs', default=os.environ.get("SD_DOCS", False), action='store_true', help = "Mount API docs, default: %(default)s") + group_log.add_argument("--api-log", default=os.environ.get("SD_APILOG", False), action='store_true', help="Enable logging of all API requests, default: %(default)s") def parse_args(parser): diff --git a/javascript/changelog.js b/javascript/changelog.js new file mode 100644 index 000000000..80c9956b8 --- /dev/null +++ b/javascript/changelog.js @@ -0,0 +1,77 @@ +let changelogElements = []; + +const getAllChildren = (el) => { + const elements = []; + for (let i = 0; i < el.children.length; i++) { + elements.push(el.children[i]); + if (el.children[i].children.length) elements.push(...getAllChildren(el.children[i])); + } + return elements; +}; + +function getText(el) { + let text = ''; + el.childNodes.forEach((node) => { + if (node.nodeType === Node.TEXT_NODE) text += node.nodeValue; + }); + return text.trim(); +} + +let currentElement = -1; + +function changelogNavigate(found) { + const result = gradioApp().getElementById('changelog_result'); + result.innerHTML = ''; + const text = document.createElement('p'); + + const onPrev = () => { + if (currentElement > 0) { + currentElement--; + found[currentElement].scrollIntoView(); + text.innerHTML = `   search item ${currentElement + 1} of ${found.length}`; + } + }; + const onNext = () => { + if (currentElement < found.length - 1) { + currentElement++; + found[currentElement].scrollIntoView(); + text.innerHTML = `   search item ${currentElement + 1} of ${found.length}`; + } + }; + + const prev = document.createElement('p'); + prev.innerHTML = ' ⇦ '; + prev.className = 'changelog_arrow'; + prev.onclick = onPrev; + prev.title = 'Search previous'; + result.appendChild(prev); + + const next = document.createElement('p'); + next.innerHTML = ' ⇨ '; + next.className = 'changelog_arrow'; + next.title = 'Search next'; + next.onclick = onNext; + result.appendChild(next); + + text.innerHTML = `   found ${found.length} items`; + result.appendChild(text); +} + +async function initChangelog() { + const search = gradioApp().querySelector('#changelog_search > label> textarea'); + const md = gradioApp().getElementById('changelog_markdown'); + const searchChangelog = async (e) => { + if (changelogElements.length < 100) changelogElements = getAllChildren(md); + const found = []; + for (const el of changelogElements) { + if (search.value.length > 1 && getText(el).toLowerCase().includes(search.value.toLowerCase())) { + el.classList.add('changelog_highlight'); + found.push(el); + } else { + el.classList.remove('changelog_highlight'); + } + } + changelogNavigate(found); + }; + search.addEventListener('keyup', searchChangelog); +} diff --git a/javascript/script.js b/javascript/script.js index 6f52f4a3d..104567dd7 100644 --- a/javascript/script.js +++ b/javascript/script.js @@ -133,19 +133,23 @@ document.addEventListener('DOMContentLoaded', () => { }); /** - * Add a ctrl+enter as a shortcut to start a generation + * Add a listener to the document for keydown events */ document.addEventListener('keydown', (e) => { - let handled = false; - if (e.key !== undefined) { - if ((e.key === 'Enter' && (e.metaKey || e.ctrlKey || e.altKey))) handled = true; - } else if (e.keyCode !== undefined) { - if ((e.keyCode === 13 && (e.metaKey || e.ctrlKey || e.altKey))) handled = true; - } - if (handled) { - const button = getUICurrentTabContent().querySelector('button[id$=_generate]'); - if (button) button.click(); + let elem; + if (e.key === 'Escape') elem = getUICurrentTabContent().querySelector('button[id$=_interrupt]'); + if (e.key === 'Enter' && e.ctrlKey) elem = getUICurrentTabContent().querySelector('button[id$=_generate]'); + if (e.key === 'Backspace' && e.ctrlKey) elem = getUICurrentTabContent().querySelector('button[id$=_reprocess]'); + if (e.key === ' ' && e.ctrlKey) elem = getUICurrentTabContent().querySelector('button[id$=_extra_networks_btn]'); + if (e.key === 's' && e.ctrlKey) elem = getUICurrentTabContent().querySelector('button[id^=save_]'); + if (e.key === 'Insert' && e.ctrlKey) elem = getUICurrentTabContent().querySelector('button[id^=save_]'); + if (e.key === 'Delete' && e.ctrlKey) elem = getUICurrentTabContent().querySelector('button[id^=delete_]'); + // if (e.key === 'm' && e.ctrlKey) elem = gradioApp().getElementById('setting_sd_model_checkpoint'); + if (elem) { e.preventDefault(); + log('hotkey', { key: e.key, meta: e.metaKey, ctrl: e.ctrlKey, alt: e.altKey }, elem?.id, elem.nodeName); + if (elem.nodeName === 'BUTTON') elem.click(); + else elem.focus(); } }); diff --git a/javascript/sdnext.css b/javascript/sdnext.css index cbd3cac83..1b2a6b3c8 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -319,6 +319,13 @@ div:has(>#tab-gallery-folders) { flex-grow: 0 !important; background-color: var( .gallery-sort { background: var(--input-background-fill) !important; margin: 0 !important; padding: 6px !important; } .gallery-sort:hover { background: var(--button-primary-background-fill-hover) !important; } +/* changelog */ +#changelog_markdown { max-height: 55vh; margin-top: 1em; } +#changelog_result { display: flex; margin-left: 1em; align-items: center; } +.changelog_arrow { font-size: 2em; padding: 0.1em; cursor: pointer; height: 1em; background-color: var(--button-secondary-background-fill); } +.changelog_arrow:hover { background-color: var(--button-primary-border-color-hover); } +.changelog_highlight { background-color: var(--color-warning); } + /* loader */ .splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } .motd { margin-top: 2em; color: var(--body-text-color-subdued); font-family: monospace; font-variant: all-petite-caps; } diff --git a/javascript/startup.js b/javascript/startup.js index 81bfa1a61..637a8d15e 100644 --- a/javascript/startup.js +++ b/javascript/startup.js @@ -16,6 +16,7 @@ async function initStartup() { initImageViewer(); initGallery(); initiGenerationParams(); + initChangelog(); setupControlUI(); // reconnect server session diff --git a/launch.py b/launch.py index 222c24fe2..8379a5d6d 100755 --- a/launch.py +++ b/launch.py @@ -164,7 +164,6 @@ def start_server(immediate=True, server=None): module_spec = importlib.util.spec_from_file_location('webui', 'webui.py') server = importlib.util.module_from_spec(module_spec) installer.log.debug(f'Starting module: {server}') - get_custom_args() module_spec.loader.exec_module(server) uvicorn = None if args.test: @@ -209,6 +208,7 @@ def main(): installer.log.info('Skipping GIT operations') installer.check_version() installer.log.info(f'Platform: {installer.print_dict(installer.get_platform())}') + installer.log.info(f'Args: {sys.argv[1:]}') if not args.skip_env: installer.set_environment() if args.uv: @@ -246,6 +246,7 @@ def main(): installer.log.warning(f'See log file for more details: {installer.log_file}') installer.extensions_preload(parser) # adds additional args from extensions args = installer.parse_args(parser) + get_custom_args() uv, instance = start_server(immediate=True, server=None) while True: diff --git a/modules/api/process.py b/modules/api/process.py index dcb3b6487..f50d58381 100644 --- a/modules/api/process.py +++ b/modules/api/process.py @@ -113,10 +113,9 @@ class APIProcess(): images = [] scores = [] with self.queue_lock: - yolo.load() - faces = yolo.predict(image) + faces = yolo.predict('face-yolo8n', image) for face in faces: - images.append(encode_pil_to_base64(face.face)) + images.append(encode_pil_to_base64(face.item)) scores.append(face.score) shared.state.end(api=False) return ResFace(images=images, scores=scores) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index afbe239fe..752ad02c0 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -2,104 +2,134 @@ import os import argparse from modules.paths import data_path + parser = argparse.ArgumentParser(description="SD.Next", conflict_handler='resolve', epilog='For other options see UI Settings page', prog='', add_help=True, formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=55, indent_increment=2, width=200)) parser._optionals = parser.add_argument_group('Other options') # pylint: disable=protected-access -group = parser.add_argument_group('Server options') - -# main server args -group.add_argument("--config", type=str, default=os.environ.get("SD_CONFIG", os.path.join(data_path, 'config.json')), help="Use specific server configuration file, default: %(default)s") -group.add_argument("--ui-config", type=str, default=os.environ.get("SD_UICONFIG", os.path.join(data_path, 'ui-config.json')), help="Use specific UI configuration file, default: %(default)s") -group.add_argument("--medvram", default=os.environ.get("SD_MEDVRAM", False), action='store_true', help="Split model stages and keep only active part in VRAM, default: %(default)s") -group.add_argument("--lowvram", default=os.environ.get("SD_LOWVRAM", False), action='store_true', help="Split model components and keep only active part in VRAM, default: %(default)s") -group.add_argument("--ckpt", type=str, default=os.environ.get("SD_MODEL", None), help="Path to model checkpoint to load immediately, default: %(default)s") -group.add_argument('--vae', type=str, default=os.environ.get("SD_VAE", None), help='Path to VAE checkpoint to load immediately, default: %(default)s') -group.add_argument("--data-dir", type=str, default=os.environ.get("SD_DATADIR", ''), help="Base path where all user data is stored, default: %(default)s") -group.add_argument("--models-dir", type=str, default=os.environ.get("SD_MODELSDIR", 'models'), help="Base path where all models are stored, default: %(default)s",) -group.add_argument("--allow-code", default=os.environ.get("SD_ALLOWCODE", False), action='store_true', help="Allow custom script execution, default: %(default)s") -group.add_argument("--share", default=os.environ.get("SD_SHARE", False), action='store_true', help="Enable UI accessible through Gradio site, default: %(default)s") -group.add_argument("--insecure", default=os.environ.get("SD_INSECURE", False), action='store_true', help="Enable extensions tab regardless of other options, default: %(default)s") -group.add_argument("--use-cpu", nargs='+', default=[], type=str.lower, help="Force use CPU for specified modules, default: %(default)s") -group.add_argument("--listen", default=os.environ.get("SD_LISTEN", False), action='store_true', help="Launch web server using public IP address, default: %(default)s") -group.add_argument("--port", type=int, default=os.environ.get("SD_PORT", 7860), help="Launch web server with given server port, default: %(default)s") -group.add_argument("--freeze", default=os.environ.get("SD_FREEZE", False), action='store_true', help="Disable editing settings") -group.add_argument("--auth", type=str, default=os.environ.get("SD_AUTH", None), help='Set access authentication like "user:pwd,user:pwd""') -group.add_argument("--auth-file", type=str, default=os.environ.get("SD_AUTHFILE", None), help='Set access authentication using file, default: %(default)s') -group.add_argument("--autolaunch", default=os.environ.get("SD_AUTOLAUNCH", False), action='store_true', help="Open the UI URL in the system's default browser upon launch") -group.add_argument('--docs', default=os.environ.get("SD_DOCS", False), action='store_true', help = "Mount API docs, default: %(default)s") -group.add_argument('--api-only', default=os.environ.get("SD_APIONLY", False), action='store_true', help = "Run in API only mode without starting UI") -group.add_argument("--device-id", type=str, default=os.environ.get("SD_DEVICEID", None), help="Select the default CUDA device to use, default: %(default)s") -group.add_argument("--cors-origins", type=str, default=os.environ.get("SD_CORSORIGINS", None), help="Allowed CORS origins as comma-separated list, default: %(default)s") -group.add_argument("--cors-regex", type=str, default=os.environ.get("SD_CORSREGEX", None), help="Allowed CORS origins as regular expression, default: %(default)s") -group.add_argument("--tls-keyfile", type=str, default=os.environ.get("SD_TLSKEYFILE", None), help="Enable TLS and specify key file, default: %(default)s") -group.add_argument("--tls-certfile", type=str, default=os.environ.get("SD_TLSCERTFILE", None), help="Enable TLS and specify cert file, default: %(default)s") -group.add_argument("--tls-selfsign", action="store_true", default=os.environ.get("SD_TLSSELFSIGN", False), help="Enable TLS with self-signed certificates, default: %(default)s") -group.add_argument("--server-name", type=str, default=os.environ.get("SD_SERVERNAME", None), help="Sets hostname of server, default: %(default)s") -group.add_argument("--no-hashing", default=os.environ.get("SD_NOHASHING", False), action='store_true', help="Disable hashing of checkpoints, default: %(default)s") -group.add_argument("--no-metadata", default=os.environ.get("SD_NOMETADATA", False), action='store_true', help="Disable reading of metadata from models, default: %(default)s") -group.add_argument("--profile", default=os.environ.get("SD_PROFILE", False), action='store_true', help="Run profiler, default: %(default)s") -group.add_argument("--disable-queue", default=os.environ.get("SD_DISABLEQUEUE", False), action='store_true', help="Disable queues, default: %(default)s") -group.add_argument('--debug', default=os.environ.get("SD_DEBUG", False), action='store_true', help = "Run installer with debug logging, default: %(default)s") -group.add_argument('--use-directml', default=os.environ.get("SD_USEDIRECTML", False), action='store_true', help = "Use DirectML if no compatible GPU is detected, default: %(default)s") -group.add_argument('--use-zluda', default=os.environ.get("SD_USEZLUDA", False), action='store_true', help = "Force use ZLUDA, AMD GPUs only, default: %(default)s") -group.add_argument("--use-openvino", default=os.environ.get("SD_USEOPENVINO", False), action='store_true', help="Use Intel OpenVINO backend, default: %(default)s") -group.add_argument("--use-ipex", default=os.environ.get("SD_USEIPX", False), action='store_true', help="Force use Intel OneAPI XPU backend, default: %(default)s") -group.add_argument("--use-cuda", default=os.environ.get("SD_USECUDA", False), action='store_true', help="Force use nVidia CUDA backend, default: %(default)s") -group.add_argument("--use-rocm", default=os.environ.get("SD_USEROCM", False), action='store_true', help="Force use AMD ROCm backend, default: %(default)s") -group.add_argument('--subpath', type=str, default=os.environ.get("SD_SUBPATH", None), help='Customize the URL subpath for usage with reverse proxy') -group.add_argument('--backend', type=str, default=os.environ.get("SD_BACKEND", None), choices=['original', 'diffusers'], required=False, help='force model pipeline type') -group.add_argument('--theme', type=str, default=os.environ.get("SD_THEME", None), help='Override UI theme') -# removed args are added here as hidden in fixed format for compatbility reasons -group.add_argument("-f", action='store_true', help=argparse.SUPPRESS) # allows running as root; implemented outside of webui -group.add_argument("--ui-settings-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'config.json')) -group.add_argument("--ui-config-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'ui-config.json')) -group.add_argument("--hide-ui-dir-config", action='store_true', help=argparse.SUPPRESS, default=False) -group.add_argument("--theme", type=str, help=argparse.SUPPRESS, default=None) -group.add_argument("--disable-console-progressbars", action='store_true', help=argparse.SUPPRESS, default=True) -group.add_argument("--disable-safe-unpickle", action='store_true', help=argparse.SUPPRESS, default=True) -group.add_argument("--lowram", action='store_true', help=argparse.SUPPRESS) -group.add_argument("--disable-extension-access", default=False, action='store_true', help=argparse.SUPPRESS) -group.add_argument("--allowed-paths", nargs='+', default=[], type=str, required=False, help="add additional paths to paths allowed for web access") -group.add_argument("--api", help=argparse.SUPPRESS, default=True) -group.add_argument("--api-auth", type=str, help=argparse.SUPPRESS, default=None) +def main_args(): + # main server args + group_config = parser.add_argument_group('Configuration') + group_config.add_argument('--backend', type=str, default=os.environ.get("SD_BACKEND", None), choices=['original', 'diffusers'], required=False, help='force model pipeline type') + group_config.add_argument("--config", type=str, default=os.environ.get("SD_CONFIG", os.path.join(data_path, 'config.json')), help="Use specific server configuration file, default: %(default)s") + group_config.add_argument("--ui-config", type=str, default=os.environ.get("SD_UICONFIG", os.path.join(data_path, 'ui-config.json')), help="Use specific UI configuration file, default: %(default)s") + group_config.add_argument("--medvram", default=os.environ.get("SD_MEDVRAM", False), action='store_true', help="Split model stages and keep only active part in VRAM, default: %(default)s") + group_config.add_argument("--lowvram", default=os.environ.get("SD_LOWVRAM", False), action='store_true', help="Split model components and keep only active part in VRAM, default: %(default)s") + group_config.add_argument("--freeze", default=os.environ.get("SD_FREEZE", False), action='store_true', help="Disable editing settings") + + group_paths = parser.add_argument_group('Paths') + group_paths.add_argument("--ckpt", type=str, default=os.environ.get("SD_MODEL", None), help="Path to model checkpoint to load immediately, default: %(default)s") + group_paths.add_argument("--data-dir", type=str, default=os.environ.get("SD_DATADIR", ''), help="Base path where all user data is stored, default: %(default)s") + group_paths.add_argument("--models-dir", type=str, default=os.environ.get("SD_MODELSDIR", 'models'), help="Base path where all models are stored, default: %(default)s",) + + group_diag = parser.add_argument_group('Diagnostics') + group_diag.add_argument("--no-hashing", default=os.environ.get("SD_NOHASHING", False), action='store_true', help="Disable hashing of checkpoints, default: %(default)s") + group_diag.add_argument("--no-metadata", default=os.environ.get("SD_NOMETADATA", False), action='store_true', help="Disable reading of metadata from models, default: %(default)s") + group_diag.add_argument("--profile", default=os.environ.get("SD_PROFILE", False), action='store_true', help="Run profiler, default: %(default)s") + group_diag.add_argument("--disable-queue", default=os.environ.get("SD_DISABLEQUEUE", False), action='store_true', help="Disable queues, default: %(default)s") + group_diag.add_argument('--debug', default=os.environ.get("SD_DEBUG", False), action='store_true', help = "Run installer with debug logging, default: %(default)s") + + group_compute = parser.add_argument_group('Compute Engine') + group_compute.add_argument('--use-directml', default=os.environ.get("SD_USEDIRECTML", False), action='store_true', help = "Use DirectML if no compatible GPU is detected, default: %(default)s") + group_compute.add_argument('--use-zluda', default=os.environ.get("SD_USEZLUDA", False), action='store_true', help = "Force use ZLUDA, AMD GPUs only, default: %(default)s") + group_compute.add_argument("--use-openvino", default=os.environ.get("SD_USEOPENVINO", False), action='store_true', help="Use Intel OpenVINO backend, default: %(default)s") + group_compute.add_argument("--use-ipex", default=os.environ.get("SD_USEIPX", False), action='store_true', help="Force use Intel OneAPI XPU backend, default: %(default)s") + group_compute.add_argument("--use-cuda", default=os.environ.get("SD_USECUDA", False), action='store_true', help="Force use nVidia CUDA backend, default: %(default)s") + group_compute.add_argument("--use-rocm", default=os.environ.get("SD_USEROCM", False), action='store_true', help="Force use AMD ROCm backend, default: %(default)s") + group_diag.add_argument("--device-id", type=str, default=os.environ.get("SD_DEVICEID", None), help="Select the default CUDA device to use, default: %(default)s") + + group_http = parser.add_argument_group('HTTP') + group_http.add_argument('--theme', type=str, default=os.environ.get("SD_THEME", None), help='Override UI theme') + group_http.add_argument("--server-name", type=str, default=os.environ.get("SD_SERVERNAME", None), help="Sets hostname of server, default: %(default)s") + group_http.add_argument("--tls-keyfile", type=str, default=os.environ.get("SD_TLSKEYFILE", None), help="Enable TLS and specify key file, default: %(default)s") + group_http.add_argument("--tls-certfile", type=str, default=os.environ.get("SD_TLSCERTFILE", None), help="Enable TLS and specify cert file, default: %(default)s") + group_http.add_argument("--tls-selfsign", action="store_true", default=os.environ.get("SD_TLSSELFSIGN", False), help="Enable TLS with self-signed certificates, default: %(default)s") + group_http.add_argument("--cors-origins", type=str, default=os.environ.get("SD_CORSORIGINS", None), help="Allowed CORS origins as comma-separated list, default: %(default)s") + group_http.add_argument("--cors-regex", type=str, default=os.environ.get("SD_CORSREGEX", None), help="Allowed CORS origins as regular expression, default: %(default)s") + group_http.add_argument('--subpath', type=str, default=os.environ.get("SD_SUBPATH", None), help='Customize the URL subpath for usage with reverse proxy') + group_http.add_argument("--autolaunch", default=os.environ.get("SD_AUTOLAUNCH", False), action='store_true', help="Open the UI URL in the system's default browser upon launch") + group_http.add_argument('--docs', default=os.environ.get("SD_DOCS", False), action='store_true', help = "Mount API docs, default: %(default)s") + group_http.add_argument("--auth", type=str, default=os.environ.get("SD_AUTH", None), help='Set access authentication like "user:pwd,user:pwd""') + group_http.add_argument("--auth-file", type=str, default=os.environ.get("SD_AUTHFILE", None), help='Set access authentication using file, default: %(default)s') + group_http.add_argument('--api-only', default=os.environ.get("SD_APIONLY", False), action='store_true', help = "Run in API only mode without starting UI") + group_http.add_argument("--allowed-paths", nargs='+', default=[], type=str, required=False, help="add additional paths to paths allowed for web access") + group_http.add_argument("--share", default=os.environ.get("SD_SHARE", False), action='store_true', help="Enable UI accessible through Gradio site, default: %(default)s") + group_http.add_argument("--insecure", default=os.environ.get("SD_INSECURE", False), action='store_true', help="Enable extensions tab regardless of other options, default: %(default)s") + group_http.add_argument("--listen", default=os.environ.get("SD_LISTEN", False), action='store_true', help="Launch web server using public IP address, default: %(default)s") + group_http.add_argument("--port", type=int, default=os.environ.get("SD_PORT", 7860), help="Launch web server with given server port, default: %(default)s") -def compatibility_args(opts, args): +def compatibility_args(): + group_compat = parser.add_argument_group('Compatibility options') + # removed args are added here as hidden in fixed format for compatbility reasons + group_compat.add_argument("--allow-code", default=os.environ.get("SD_ALLOWCODE", False), action='store_true', help=argparse.SUPPRESS) + group_compat.add_argument("--use-cpu", nargs='+', default=[], type=str.lower, help=argparse.SUPPRESS) + group_compat.add_argument("-f", action='store_true', help=argparse.SUPPRESS) # allows running as root; implemented outside of webui + group_compat.add_argument('--vae', type=str, default=os.environ.get("SD_VAE", None), help=argparse.SUPPRESS) + group_compat.add_argument("--ui-settings-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'config.json')) + group_compat.add_argument("--ui-config-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'ui-config.json')) + group_compat.add_argument("--hide-ui-dir-config", action='store_true', help=argparse.SUPPRESS, default=False) + group_compat.add_argument("--theme", type=str, help=argparse.SUPPRESS, default=None) + group_compat.add_argument("--disable-console-progressbars", action='store_true', help=argparse.SUPPRESS, default=True) + group_compat.add_argument("--disable-safe-unpickle", action='store_true', help=argparse.SUPPRESS, default=True) + group_compat.add_argument("--lowram", action='store_true', help=argparse.SUPPRESS) + group_compat.add_argument("--disable-extension-access", default=False, action='store_true', help=argparse.SUPPRESS) + group_compat.add_argument("--api", help=argparse.SUPPRESS, default=True) + group_compat.add_argument("--api-auth", type=str, help=argparse.SUPPRESS, default=None) + + +def settings_args(opts, args): + group_compat = parser.add_argument_group('Compatibility options') + # removed args are added here as hidden in fixed format for compatbility reasons + group_compat.add_argument("--allow-code", default=os.environ.get("SD_ALLOWCODE", False), action='store_true', help=argparse.SUPPRESS) + group_compat.add_argument("--use-cpu", nargs='+', default=[], type=str.lower, help=argparse.SUPPRESS) + group_compat.add_argument("-f", action='store_true', help=argparse.SUPPRESS) # allows running as root; implemented outside of webui + group_compat.add_argument('--vae', type=str, default=os.environ.get("SD_VAE", None), help=argparse.SUPPRESS) + group_compat.add_argument("--ui-settings-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'config.json')) + group_compat.add_argument("--ui-config-file", type=str, help=argparse.SUPPRESS, default=os.path.join(data_path, 'ui-config.json')) + group_compat.add_argument("--hide-ui-dir-config", action='store_true', help=argparse.SUPPRESS, default=False) + group_compat.add_argument("--theme", type=str, help=argparse.SUPPRESS, default=None) + group_compat.add_argument("--disable-console-progressbars", action='store_true', help=argparse.SUPPRESS, default=True) + group_compat.add_argument("--disable-safe-unpickle", action='store_true', help=argparse.SUPPRESS, default=True) + group_compat.add_argument("--lowram", action='store_true', help=argparse.SUPPRESS) + group_compat.add_argument("--disable-extension-access", default=False, action='store_true', help=argparse.SUPPRESS) + group_compat.add_argument("--allowed-paths", nargs='+', default=[], type=str, required=False, help="add additional paths to paths allowed for web access") + group_compat.add_argument("--api", help=argparse.SUPPRESS, default=True) + group_compat.add_argument("--api-auth", type=str, help=argparse.SUPPRESS, default=None) # removed args that have been moved to opts are added here as hidden with default values as defined in opts - group.add_argument("--ckpt-dir", type=str, help=argparse.SUPPRESS, default=opts.ckpt_dir) - group.add_argument("--vae-dir", type=str, help=argparse.SUPPRESS, default=opts.vae_dir) - group.add_argument("--embeddings-dir", type=str, help=argparse.SUPPRESS, default=opts.embeddings_dir) - group.add_argument("--embeddings-templates-dir", type=str, help=argparse.SUPPRESS, default=opts.embeddings_templates_dir) - group.add_argument("--hypernetwork-dir", type=str, help=argparse.SUPPRESS, default=opts.hypernetwork_dir) - group.add_argument("--codeformer-models-path", type=str, help=argparse.SUPPRESS, default=opts.codeformer_models_path) - group.add_argument("--gfpgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.gfpgan_models_path) - group.add_argument("--esrgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.esrgan_models_path) - group.add_argument("--bsrgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.bsrgan_models_path) - group.add_argument("--realesrgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.realesrgan_models_path) - group.add_argument("--scunet-models-path", help=argparse.SUPPRESS, default=opts.scunet_models_path) - group.add_argument("--swinir-models-path", help=argparse.SUPPRESS, default=opts.swinir_models_path) - group.add_argument("--ldsr-models-path", help=argparse.SUPPRESS, default=opts.ldsr_models_path) - group.add_argument("--clip-models-path", type=str, help=argparse.SUPPRESS, default=opts.clip_models_path) - group.add_argument("--opt-channelslast", help=argparse.SUPPRESS, action='store_true', default=opts.opt_channelslast) - group.add_argument("--xformers", default=(opts.cross_attention_optimization == "xFormers"), action='store_true', help=argparse.SUPPRESS) - group.add_argument("--disable-nan-check", help=argparse.SUPPRESS, action='store_true', default=opts.disable_nan_check) - group.add_argument("--rollback-vae", help=argparse.SUPPRESS, default=opts.rollback_vae) - group.add_argument("--no-half", help=argparse.SUPPRESS, action='store_true', default=opts.no_half) - group.add_argument("--no-half-vae", help=argparse.SUPPRESS, action='store_true', default=opts.no_half_vae) - group.add_argument("--precision", help=argparse.SUPPRESS, default=opts.precision) - group.add_argument("--sub-quad-q-chunk-size", help=argparse.SUPPRESS, default=opts.sub_quad_q_chunk_size) - group.add_argument("--sub-quad-kv-chunk-size", help=argparse.SUPPRESS, default=opts.sub_quad_kv_chunk_size) - group.add_argument("--sub-quad-chunk-threshold", help=argparse.SUPPRESS, default=opts.sub_quad_chunk_threshold) - group.add_argument("--lora-dir", help=argparse.SUPPRESS, default=opts.lora_dir) - group.add_argument("--lyco-dir", help=argparse.SUPPRESS, default=opts.lyco_dir) - group.add_argument("--embeddings-dir", help=argparse.SUPPRESS, default=opts.embeddings_dir) - group.add_argument("--hypernetwork-dir", help=argparse.SUPPRESS, default=opts.hypernetwork_dir) - group.add_argument("--lyco-patch-lora", help=argparse.SUPPRESS, action='store_true', default=False) - group.add_argument("--lyco-debug", help=argparse.SUPPRESS, action='store_true', default=False) - group.add_argument("--enable-console-prompts", help=argparse.SUPPRESS, action='store_true', default=False) - group.add_argument("--safe", help=argparse.SUPPRESS, action='store_true', default=False) - group.add_argument("--use-xformers", help=argparse.SUPPRESS, action='store_true', default=False) + group_compat.add_argument("--ckpt-dir", type=str, help=argparse.SUPPRESS, default=opts.ckpt_dir) + group_compat.add_argument("--vae-dir", type=str, help=argparse.SUPPRESS, default=opts.vae_dir) + group_compat.add_argument("--embeddings-dir", type=str, help=argparse.SUPPRESS, default=opts.embeddings_dir) + group_compat.add_argument("--embeddings-templates-dir", type=str, help=argparse.SUPPRESS, default=opts.embeddings_templates_dir) + group_compat.add_argument("--hypernetwork-dir", type=str, help=argparse.SUPPRESS, default=opts.hypernetwork_dir) + group_compat.add_argument("--codeformer-models-path", type=str, help=argparse.SUPPRESS, default=opts.codeformer_models_path) + group_compat.add_argument("--gfpgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.gfpgan_models_path) + group_compat.add_argument("--esrgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.esrgan_models_path) + group_compat.add_argument("--bsrgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.bsrgan_models_path) + group_compat.add_argument("--realesrgan-models-path", type=str, help=argparse.SUPPRESS, default=opts.realesrgan_models_path) + group_compat.add_argument("--scunet-models-path", help=argparse.SUPPRESS, default=opts.scunet_models_path) + group_compat.add_argument("--swinir-models-path", help=argparse.SUPPRESS, default=opts.swinir_models_path) + group_compat.add_argument("--ldsr-models-path", help=argparse.SUPPRESS, default=opts.ldsr_models_path) + group_compat.add_argument("--clip-models-path", type=str, help=argparse.SUPPRESS, default=opts.clip_models_path) + group_compat.add_argument("--opt-channelslast", help=argparse.SUPPRESS, action='store_true', default=opts.opt_channelslast) + group_compat.add_argument("--xformers", default=(opts.cross_attention_optimization == "xFormers"), action='store_true', help=argparse.SUPPRESS) + group_compat.add_argument("--disable-nan-check", help=argparse.SUPPRESS, action='store_true', default=opts.disable_nan_check) + group_compat.add_argument("--rollback-vae", help=argparse.SUPPRESS, default=opts.rollback_vae) + group_compat.add_argument("--no-half", help=argparse.SUPPRESS, action='store_true', default=opts.no_half) + group_compat.add_argument("--no-half-vae", help=argparse.SUPPRESS, action='store_true', default=opts.no_half_vae) + group_compat.add_argument("--precision", help=argparse.SUPPRESS, default=opts.precision) + group_compat.add_argument("--sub-quad-q-chunk-size", help=argparse.SUPPRESS, default=opts.sub_quad_q_chunk_size) + group_compat.add_argument("--sub-quad-kv-chunk-size", help=argparse.SUPPRESS, default=opts.sub_quad_kv_chunk_size) + group_compat.add_argument("--sub-quad-chunk-threshold", help=argparse.SUPPRESS, default=opts.sub_quad_chunk_threshold) + group_compat.add_argument("--lora-dir", help=argparse.SUPPRESS, default=opts.lora_dir) + group_compat.add_argument("--lyco-dir", help=argparse.SUPPRESS, default=opts.lyco_dir) + group_compat.add_argument("--embeddings-dir", help=argparse.SUPPRESS, default=opts.embeddings_dir) + group_compat.add_argument("--hypernetwork-dir", help=argparse.SUPPRESS, default=opts.hypernetwork_dir) + group_compat.add_argument("--lyco-patch-lora", help=argparse.SUPPRESS, action='store_true', default=False) + group_compat.add_argument("--lyco-debug", help=argparse.SUPPRESS, action='store_true', default=False) + group_compat.add_argument("--enable-console-prompts", help=argparse.SUPPRESS, action='store_true', default=False) + group_compat.add_argument("--safe", help=argparse.SUPPRESS, action='store_true', default=False) + group_compat.add_argument("--use-xformers", help=argparse.SUPPRESS, action='store_true', default=False) # removed opts are added here with fixed values for compatibility reasons opts.use_old_emphasis_implementation = False @@ -126,3 +156,7 @@ def compatibility_args(opts, args): args = parser.parse_args() return args + + +main_args() +compatibility_args() diff --git a/modules/control/run.py b/modules/control/run.py index e3c7bbf76..74bab35c4 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -564,8 +564,8 @@ def control_run(state: str = '', return [], '', '', 'Reference mode without image' elif unit_type == 'controlnet' and has_models: if input_type == 0: # Control only - if shared.sd_model_type == 'f1' and 'control_image' not in p.task_args: - p.task_args['control_image'] = p.init_images # flux controlnet mandates this + if shared.sd_model_type in ['f1', 'sd3'] and 'control_image' not in p.task_args: + p.task_args['control_image'] = p.init_images # some controlnets mandate this p.task_args['strength'] = p.denoising_strength elif input_type == 1: # Init image same as control p.task_args['control_image'] = p.init_images # switch image and control_image diff --git a/modules/control/units/controlnet.py b/modules/control/units/controlnet.py index 236892eb5..48a3d440d 100644 --- a/modules/control/units/controlnet.py +++ b/modules/control/units/controlnet.py @@ -1,7 +1,7 @@ import os import time from typing import Union -from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, FluxPipeline, ControlNetModel +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, FluxPipeline, StableDiffusion3Pipeline, ControlNetModel from modules.control.units import detect from modules.shared import log, opts, listdir from modules import errors, sd_models, devices, model_quant @@ -50,7 +50,6 @@ predefined_sdxl = { 'Depth Zoe XL': 'diffusers/controlnet-zoe-depth-sdxl-1.0', 'Depth Mid XL': 'diffusers/controlnet-depth-sdxl-1.0-mid', 'OpenPose XL': 'thibaud/controlnet-openpose-sdxl-1.0/bin', - # 'OpenPose XL': 'thibaud/controlnet-openpose-sdxl-1.0/OpenPoseXL2.safetensors', 'Xinsir Union XL': 'xinsir/controlnet-union-sdxl-1.0', 'Xinsir OpenPose XL': 'xinsir/controlnet-openpose-sdxl-1.0', 'Xinsir Canny XL': 'xinsir/controlnet-canny-sdxl-1.0', @@ -79,11 +78,20 @@ predefined_f1 = { "XLabs-AI Depth": 'XLabs-AI/flux-controlnet-depth-diffusers', "XLabs-AI HED": 'XLabs-AI/flux-controlnet-hed-diffusers' } +predefined_sd3 = { + "InstantX Canny": 'InstantX/SD3-Controlnet-Canny', + "InstantX Pose": 'InstantX/SD3-Controlnet-Pose', + "InstantX Depth": 'InstantX/SD3-Controlnet-Depth', + "InstantX Tile": 'InstantX/SD3-Controlnet-Tile', + "Alimama Inpainting": 'alimama-creative/SD3-Controlnet-Inpainting', + "Alimama SoftEdge": 'alimama-creative/SD3-Controlnet-Softedge', +} models = {} all_models = {} all_models.update(predefined_sd15) all_models.update(predefined_sdxl) all_models.update(predefined_f1) +all_models.update(predefined_sd3) cache_dir = 'models/control/controlnet' @@ -118,9 +126,11 @@ def list_models(refresh=False): models = ['None'] + list(predefined_sd15) + sorted(find_models()) elif modules.shared.sd_model_type == 'f1': models = ['None'] + list(predefined_f1) + sorted(find_models()) + elif modules.shared.sd_model_type == 'sd3': + models = ['None'] + list(predefined_sd3) + sorted(find_models()) else: log.warning(f'Control {what} model list failed: unknown model type') - models = ['None'] + sorted(predefined_sd15) + sorted(predefined_sdxl) + sorted(find_models()) + models = ['None'] + sorted(predefined_sd15) + sorted(predefined_sdxl) + sorted(predefined_f1) + sorted(predefined_sd3) + sorted(find_models()) debug(f'Control list {what}: path={cache_dir} models={models}') return models @@ -151,6 +161,8 @@ class ControlNet(): from diffusers import ControlNetModel as model_class # pylint: disable=reimported # sdxl shares same model class elif modules.shared.sd_model_type == 'f1': from diffusers import FluxControlNetModel as model_class + elif modules.shared.sd_model_type == 'sd3': + from diffusers import SD3ControlNetModel as model_class else: log.error(f'Control {what}: type={modules.shared.sd_model_type} unsupported model') return None @@ -247,7 +259,11 @@ class ControlNet(): class ControlNetPipeline(): - def __init__(self, controlnet: Union[ControlNetModel, list[ControlNetModel]], pipeline: Union[StableDiffusionXLPipeline, StableDiffusionPipeline, FluxPipeline], dtype = None): + def __init__(self, + controlnet: Union[ControlNetModel, list[ControlNetModel]], + pipeline: Union[StableDiffusionXLPipeline, StableDiffusionPipeline, FluxPipeline, StableDiffusion3Pipeline], + dtype = None, + ): t0 = time.time() self.orig_pipeline = pipeline self.pipeline = None @@ -293,6 +309,20 @@ class ControlNetPipeline(): scheduler=pipeline.scheduler, controlnet=controlnet, # can be a list ) + elif detect.is_sd3(pipeline): + from diffusers import StableDiffusion3ControlNetPipeline + self.pipeline = StableDiffusion3ControlNetPipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + text_encoder_2=pipeline.text_encoder_2, + text_encoder_3=pipeline.text_encoder_3, + tokenizer=pipeline.tokenizer, + tokenizer_2=pipeline.tokenizer_2, + tokenizer_3=pipeline.tokenizer_3, + transformer=pipeline.transformer, + scheduler=pipeline.scheduler, + controlnet=controlnet, # can be a list + ) else: log.error(f'Control {what} pipeline: class={pipeline.__class__.__name__} unsupported model type') return diff --git a/modules/control/units/detect.py b/modules/control/units/detect.py index 70a1c8000..7cc8d144d 100644 --- a/modules/control/units/detect.py +++ b/modules/control/units/detect.py @@ -20,5 +20,12 @@ def is_f1(model): if model is None: return False if hasattr(model, '__name__'): - return model.__name__ == p.FluxPipeline.__name__ - return isinstance(model, p.FluxPipeline) + return model.__name__ == p.FluxPipeline.__name__ or model.__name__ == p.FluxImg2ImgPipeline.__name__ or model.__name__ == p.FluxInpaintPipeline.__name__ + return isinstance(model, p.FluxPipeline) or isinstance(model, p.FluxImg2ImgPipeline) or isinstance(model, p.FluxInpaintPipeline) + +def is_sd3(model): + if model is None: + return False + if hasattr(model, '__name__'): + return model.__name__ == p.StableDiffusion3Pipeline.__name__ or model.__name__ == p.StableDiffusion3Img2ImgPipeline.__name__ or model.__name__ == p.StableDiffusion3InpaintPipeline.__name__ + return isinstance(model, p.StableDiffusion3Pipeline) or isinstance(model, p.StableDiffusion3Img2ImgPipeline) or isinstance(model, p.StableDiffusion3InpaintPipeline) diff --git a/modules/face/faceid.py b/modules/face/faceid.py index e2d5efccb..b74e15dc5 100644 --- a/modules/face/faceid.py +++ b/modules/face/faceid.py @@ -79,7 +79,7 @@ def face_id( ip_ckpt = FACEID_MODELS[model] folder, filename = os.path.split(ip_ckpt) basename, _ext = os.path.splitext(filename) - model_path = hf.hf_hub_download(repo_id=folder, filename=filename, cache_dir=shared.opts.diffusers_dir) + model_path = hf.hf_hub_download(repo_id=folder, filename=filename, cache_dir=shared.opts.hfcache_dir) if model_path is None: shared.log.error(f'FaceID download failed: model={model} file="{ip_ckpt}"') return None diff --git a/modules/face/insightface.py b/modules/face/insightface.py index 3eb7171bf..a59e48b41 100644 --- a/modules/face/insightface.py +++ b/modules/face/insightface.py @@ -32,7 +32,7 @@ def get_app(mp_name): repo_id='vladmandic/insightface-faceanalysis', filename=f'{mp_name}.zip', local_dir_use_symlinks=False, - cache_dir=opts.diffusers_dir, + cache_dir=opts.hfcache_dir, local_dir=local_dir ) if not os.path.exists(extract_dir): diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index cf4278e80..36b5ea382 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -189,7 +189,7 @@ def create_override_settings_dict(text_pairs): def connect_paste(button, local_paste_fields, input_comp, override_settings_component, tabname): def paste_func(prompt): - if prompt is None or len(prompt.strip()) == 0 and not shared.cmd_opts.hide_ui_dir_config: + if prompt is None or len(prompt.strip()) == 0: filename = os.path.join(data_path, "params.txt") if os.path.exists(filename): with open(filename, "r", encoding="utf8") as file: diff --git a/modules/images.py b/modules/images.py index add7c5c7d..8e7ffd662 100644 --- a/modules/images.py +++ b/modules/images.py @@ -10,7 +10,7 @@ import threading import numpy as np import piexif import piexif.helper -from PIL import Image, PngImagePlugin, ExifTags +from PIL import Image, PngImagePlugin, ExifTags, ImageDraw from modules import sd_samplers, shared, script_callbacks, errors, paths from modules.images_grid import image_grid, get_grid_size, split_grid, combine_grid, check_grid_size, get_font, draw_grid_annotations, draw_prompt_matrix, GridAnnotation, Grid # pylint: disable=unused-import from modules.images_resize import resize_image # pylint: disable=unused-import @@ -361,6 +361,14 @@ def flatten(img, bgcolor): return img.convert('RGB') +def draw_overlay(im, text): + d = ImageDraw.Draw(im) + fontsize = (im.width + im.height) // 50 + font = get_font(fontsize) + d.text((fontsize//2, fontsize//2), text, font=font, fill=shared.opts.font_color) + return im + + def set_watermark(image, watermark): if shared.opts.image_watermark_position != 'none': # visible watermark wm_image = None diff --git a/modules/images_grid.py b/modules/images_grid.py index 194b0d1cd..51371a0fe 100644 --- a/modules/images_grid.py +++ b/modules/images_grid.py @@ -113,7 +113,7 @@ def get_font(fontsize): return ImageFont.truetype("javascript/notosans-nerdfont-regular.ttf", fontsize) -def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, title=None): +def draw_grid_annotations(im, width, height, x_texts, y_texts, margin=0, title=None): def wrap(drawing, text, font, line_length): lines = [''] for word in text.split(): @@ -140,15 +140,15 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, tit line_spacing = fontsize // 2 font = get_font(fontsize) color_inactive = (127, 127, 127) - pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in ver_texts]) == 0 else width * 3 // 4 - cols = len(hor_texts) - rows = len(ver_texts) + pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in y_texts]) == 0 else width * 3 // 4 + cols = len(x_texts) + rows = len(y_texts) # assert cols == len(hor_texts), f'bad number of horizontal texts: {len(hor_texts)}; must be {cols}' # assert rows == len(hor_texts), f'bad number of vertical texts: {len(ver_texts)}; must be {rows}' calc_img = Image.new("RGB", (1, 1), shared.opts.grid_background) calc_d = ImageDraw.Draw(calc_img) title_texts = [title] if title else [[GridAnnotation()]] - for texts, allowed_width in zip(hor_texts + ver_texts + title_texts, [width] * len(hor_texts) + [pad_left] * len(ver_texts) + [(width+margin)*cols]): + for texts, allowed_width in zip(x_texts + y_texts + title_texts, [width] * len(x_texts) + [pad_left] * len(y_texts) + [(width+margin)*cols]): items = [] + texts texts.clear() for line in items: @@ -158,8 +158,8 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, tit bbox = calc_d.multiline_textbbox((0, 0), line.text, font=font) line.size = (bbox[2] - bbox[0], bbox[3] - bbox[1]) line.allowed_width = allowed_width - hor_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing for lines in hor_texts] - ver_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing * len(lines) for lines in ver_texts] + hor_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing for lines in x_texts] + ver_text_heights = [sum([line.size[1] + line_spacing for line in lines]) - line_spacing * len(lines) for lines in y_texts] pad_top = 0 if sum(hor_text_heights) == 0 else max(hor_text_heights) + line_spacing * 2 title_pad = 0 if title: @@ -178,11 +178,11 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0, tit for col in range(cols): x = pad_left + (width + margin) * col + width / 2 y = (pad_top / 2 - hor_text_heights[col] / 2) + title_pad - draw_texts(d, x, y, hor_texts[col], font, fontsize) + draw_texts(d, x, y, x_texts[col], font, fontsize) for row in range(rows): x = pad_left / 2 y = (pad_top + (height + margin) * row + height / 2 - ver_text_heights[row] / 2) + title_pad - draw_texts(d, x, y, ver_texts[row], font, fontsize) + draw_texts(d, x, y, y_texts[row], font, fontsize) return result diff --git a/modules/ipadapter.py b/modules/ipadapter.py index 0a010c41c..4b67b9bb4 100644 --- a/modules/ipadapter.py +++ b/modules/ipadapter.py @@ -36,6 +36,7 @@ ADAPTERS_SDXL = { 'Ostris Composition ViT-H SDXL': { 'name': 'ip_plus_composition_sdxl.safetensors', 'repo': 'ostris/ip-composition-adapter', 'subfolder': '' }, } ADAPTERS = { **ADAPTERS_SD15, **ADAPTERS_SDXL } +ADAPTERS_ALL = { **ADAPTERS_SD15, **ADAPTERS_SDXL } def get_adapters(): @@ -101,13 +102,12 @@ def crop_images(images, crops): try: for i in range(len(images)): if crops[i]: - from shared import yolo # pylint: disable=no-name-in-module - yolo.load() + from modules.shared import yolo # pylint: disable=no-name-in-module cropped = [] for image in images[i]: - faces = yolo.predict(image) + faces = yolo.predict('face-yolo8n', image) if len(faces) > 0: - cropped.append(faces[0].face) + cropped.append(faces[0].item) if len(cropped) == len(images[i]): images[i] = cropped else: @@ -135,7 +135,7 @@ def apply(pipe, p: processing.StableDiffusionProcessing, adapter_names=[], adapt if hasattr(p, 'ip_adapter_names'): if isinstance(p.ip_adapter_names, str): p.ip_adapter_names = [p.ip_adapter_names] - adapters = [ADAPTERS.get(adapter_name, None) for adapter_name in p.ip_adapter_names if adapter_name is not None and adapter_name.lower() != 'none'] + adapters = [ADAPTERS_ALL.get(adapter_name, None) for adapter_name in p.ip_adapter_names if adapter_name is not None and adapter_name.lower() != 'none'] adapter_names = p.ip_adapter_names else: if isinstance(adapter_names, str): diff --git a/modules/model_quant.py b/modules/model_quant.py index fab3a93a0..547a3d7ae 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -7,10 +7,10 @@ bnb = None quanto = None -def create_bnb_config(kwargs): +def create_bnb_config(kwargs = None): from modules import shared, devices if len(shared.opts.bnb_quantization) > 0: - if 'Model' in shared.opts.bnb_quantization and 'transformer' not in kwargs: + if 'Model' in shared.opts.bnb_quantization and 'transformer' not in (kwargs or {}): load_bnb() bnb_config = diffusers.BitsAndBytesConfig( load_in_8bit=shared.opts.bnb_quantization_type in ['fp8'], @@ -19,8 +19,12 @@ def create_bnb_config(kwargs): bnb_4bit_quant_type=shared.opts.bnb_quantization_type, bnb_4bit_compute_dtype=devices.dtype ) - kwargs['quantization_config'] = bnb_config shared.log.debug(f'Quantization: module=all type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') + if kwargs is None: + return bnb_config + else: + kwargs['quantization_config'] = bnb_config + return kwargs return kwargs diff --git a/modules/model_sd3.py b/modules/model_sd3.py index e369b197a..78eee7b4d 100644 --- a/modules/model_sd3.py +++ b/modules/model_sd3.py @@ -1,7 +1,7 @@ import os import diffusers import transformers -from modules import shared, devices, sd_models, sd_unet, model_te, model_quant +from modules import shared, devices, sd_models, sd_unet, model_te, model_quant, model_tools def load_overrides(kwargs, cache_dir): @@ -69,7 +69,7 @@ def load_quants(kwargs, repo_id, cache_dir): def load_missing(kwargs, fn, cache_dir): - keys = sd_models.get_safetensor_keys(fn) + keys = model_tools.get_safetensor_keys(fn) size = os.stat(fn).st_size // 1024 // 1024 if size > 15000: repo_id = 'stabilityai/stable-diffusion-3.5-large' @@ -129,7 +129,11 @@ def load_sd3(checkpoint_info, cache_dir=None, config=None): if fn is not None and os.path.exists(fn) and os.path.isfile(fn): if fn.endswith('.safetensors'): loader = diffusers.StableDiffusion3Pipeline.from_single_file - kwargs = load_missing(kwargs, fn, cache_dir) + # required_modules = model_tools.get_modules(diffusers.StableDiffusion3Pipeline) + # have_modules = model_tools.get_safetensor_keys(fn) + # loaded_modules = model_tools.load_modules('stabilityai/stable-diffusion-3.5-medium', required_modules) + # kwargs = {**kwargs, **loaded_modules} + # kwargs = load_missing(kwargs, fn, cache_dir) repo_id = fn elif fn.endswith('.gguf'): kwargs = load_gguf(kwargs, fn) @@ -138,7 +142,7 @@ def load_sd3(checkpoint_info, cache_dir=None, config=None): else: kwargs['variant'] = 'fp16' - shared.log.debug(f'Load model: type=SD3 kwargs={list(kwargs)}') + shared.log.debug(f'Load model: type=SD3 kwargs={list(kwargs)} repo="{repo_id}"') kwargs = model_quant.create_bnb_config(kwargs) pipe = loader( diff --git a/modules/model_tools.py b/modules/model_tools.py new file mode 100644 index 000000000..1212da244 --- /dev/null +++ b/modules/model_tools.py @@ -0,0 +1,49 @@ +import inspect +import diffusers +import transformers +import safetensors.torch +from modules import shared, devices, model_quant + + +def get_safetensor_keys(filename): + keys = [] + try: + with safetensors.torch.safe_open(filename, framework="pt", device="cpu") as f: + keys = f.keys() + except Exception as e: + shared.log.error(f'Load dict: path="{filename}" {e}') + return keys + + +def get_modules(model: callable): + signature = inspect.signature(model.__init__, follow_wrapped=True) + params = {param.name: param.annotation for param in signature.parameters.values() if param.annotation != inspect._empty and hasattr(param.annotation, 'from_pretrained')} # pylint: disable=protected-access + for name, cls in params.items(): + shared.log.debug(f'Analyze: model={model} module={name} class={cls.__name__} loadable={getattr(cls, "from_pretrained", None)}') + return params + + +def load_modules(repo_id: str, params: dict): + cache_dir = shared.opts.hfcache_dir + modules = {} + for name, cls in params.items(): + subfolder = None + kwargs = {} + if cls == diffusers.AutoencoderKL: + subfolder = 'vae' + if cls == transformers.CLIPTextModel: # clip-vit-l + subfolder = 'text_encoder' + if cls == transformers.CLIPTextModelWithProjection: # clip-vit-g + subfolder = 'text_encoder_2' + if cls == transformers.T5EncoderModel: # t5-xxl + subfolder = 'text_encoder_3' + kwargs['quantization_config'] = model_quant.create_bnb_config() + kwargs['variant'] = 'fp16' + if cls == diffusers.SD3Transformer2DModel: + subfolder = 'transformer' + kwargs['quantization_config'] = model_quant.create_bnb_config() + if subfolder is None: + continue + shared.log.debug(f'Load: module={name} class={cls.__name__} repo={repo_id} location={subfolder}') + modules[name] = cls.from_pretrained(repo_id, subfolder=subfolder, cache_dir=cache_dir, torch_dtype=devices.dtype, **kwargs) + return modules diff --git a/modules/modelloader.py b/modules/modelloader.py index 8eab91597..ce36a739b 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -273,6 +273,7 @@ def load_diffusers_models(clear=True): place = os.path.join(models_path, 'Diffusers') if clear: diffuser_repos.clear() + already_found = [] try: for folder in os.listdir(place): try: @@ -303,7 +304,11 @@ def load_diffusers_models(clear=True): if (not os.path.exists(index)) and (not os.path.exists(info)) and (not os.path.exists(config)): debug(f'Diffusers skip model no info: {name}') continue + if name in already_found: + debug(f'Diffusers skip model already found: {name}') + continue repo = { 'name': name, 'filename': name, 'friendly': friendly, 'folder': folder, 'path': commit, 'hash': snapshot, 'mtime': mtime, 'model_info': info, 'model_index': index, 'model_config': config } + already_found.append(name) diffuser_repos.append(repo) if os.path.exists(os.path.join(folder, 'hidden')): continue diff --git a/modules/postprocess/yolo.py b/modules/postprocess/yolo.py index 44f72237e..44c7db20c 100644 --- a/modules/postprocess/yolo.py +++ b/modules/postprocess/yolo.py @@ -78,6 +78,10 @@ class YoloRestorer(Detailer): ) -> list[YoloResult]: result = [] + if isinstance(model, str): + model = self.models.get(model, None) + if model is None: + _, model = self.load(model) if model is None: return result args = { @@ -124,7 +128,9 @@ class YoloRestorer(Detailer): mask_image = None w, h = box[2] - box[0], box[3] - box[1] size = w * h / (image.width * image.height) - if (min(w, h) > shared.opts.detailer_min_size if shared.opts.detailer_min_size > 0 else True) and (max(w, h) < shared.opts.detailer_max_size if shared.opts.detailer_max_size > 0 else True): + min_size = (shared.opts.detailer_min_size if shared.opts.detailer_min_size > 0 else 0) * min(w, h) + max_size = (shared.opts.detailer_max_size if shared.opts.detailer_max_size > 0 else 1) * max(w, h) + if (min(w, h) > min_size) and (max(w, h) < max_size): if mask: mask_image = image.copy() mask_image = Image.new('L', image.size, 0) @@ -138,6 +144,7 @@ class YoloRestorer(Detailer): def load(self, model_name: str = None): from modules import modelloader + model = None self.dependencies() if model_name is None: model_name = list(self.list)[0] @@ -158,7 +165,7 @@ class YoloRestorer(Detailer): return model_name, model except Exception as e: shared.log.error(f'Load: type=Detailer name="{model_name}" error="{e}"') - return None + return None, None def restore(self, np_image, p: processing.StableDiffusionProcessing = None): if hasattr(p, 'recursion'): @@ -323,8 +330,10 @@ class YoloRestorer(Detailer): min_confidence = gr.Slider(label="Min confidence", elem_id=f"{tab}_detailer_conf", value=shared.opts.detailer_conf, minimum=0.0, maximum=1.0, step=0.05) iou = gr.Slider(label="Max overlap", elem_id=f"{tab}_detailer_iou", value=shared.opts.detailer_iou, minimum=0, maximum=1.0, step=0.05) with gr.Row(): - min_size = gr.Slider(label="Min size", elem_id=f"{tab}_detailer_min_size", value=shared.opts.detailer_min_size, minimum=0, maximum=1024, step=1) - max_size = gr.Slider(label="Max size", elem_id=f"{tab}_detailer_max_size", value=shared.opts.detailer_max_size, minimum=0, maximum=1024, step=1) + min_size = shared.opts.detailer_min_size if shared.opts.detailer_min_size < 1 else 0.0 + min_size = gr.Slider(label="Min size", elem_id=f"{tab}_detailer_min_size", value=min_size, minimum=0.1, maximum=1.0, step=0.05) + max_size = shared.opts.detailer_min_size if shared.opts.detailer_min_size < 1 and shared.opts.detailer_min_size > 0 else 1.0 + max_size = gr.Slider(label="Max size", elem_id=f"{tab}_detailer_max_size", value=max_size, minimum=0.1, maximum=1.0, step=0.05) detailers.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou], outputs=[]) classes.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou], outputs=[]) strength.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou], outputs=[]) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 346758851..967bd04cc 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -37,7 +37,6 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp image_ext.append(ext) shared.log.debug(f'Process: mode=batch inputs={len(image_folder)} images={len(image_data)}') elif extras_mode == 2: - assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled' assert input_dir, 'input directory not selected' image_list = os.listdir(input_dir) for filename in image_list: diff --git a/modules/script_loading.py b/modules/script_loading.py index f39394625..37f64b33f 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -14,7 +14,7 @@ def load_module(path): module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) try: - if '/sd-extension-' in path or '/Lora' in path: # safe extensions without stdout intercept + if 'sd-extension-' in path or 'Lora' in path: # safe extensions without stdout intercept module_spec.loader.exec_module(module) else: if debug: diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 7144a7be7..148e788d5 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -84,6 +84,8 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): if 'omnigen' in f.lower(): guess = 'OmniGen' pipeline = 'custom' + if 'sd3' in f.lower(): + guess = 'Stable Diffusion 3' if 'flux' in f.lower(): guess = 'FLUX' if size > 11000 and size < 20000: diff --git a/modules/sd_models.py b/modules/sd_models.py index dc0679c58..7743ce27d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -19,7 +19,7 @@ from modules import paths, shared, shared_state, modelloader, devices, script_ca from modules.timer import Timer from modules.memstats import memory_stats from modules.modeldata import model_data -from modules.sd_checkpoint import CheckpointInfo, select_checkpoint, list_models, checkpoints_list, checkpoint_titles, get_closet_checkpoint_match, update_model_hashes, setup_model, write_metadata, read_metadata_from_safetensors # pylint: disable=unused-import +from modules.sd_checkpoint import CheckpointInfo, select_checkpoint, list_models, checkpoints_list, checkpoint_titles, get_closet_checkpoint_match, model_hash, update_model_hashes, setup_model, write_metadata, read_metadata_from_safetensors # pylint: disable=unused-import model_dir = "Stable-diffusion" @@ -75,16 +75,6 @@ def read_state_dict(checkpoint_file, map_location=None, what:str='model'): # pyl return sd -def get_safetensor_keys(filename): - keys = [] - try: - with safetensors.torch.safe_open(filename, framework="pt", device="cpu") as f: - keys = f.keys() - except Exception as e: - shared.log.error(f'Load dict: path="{filename}" {e}') - return keys - - def get_state_dict_from_checkpoint(pl_sd): checkpoint_dict_replacements = { 'cond_stage_model.transformer.embeddings.': 'cond_stage_model.transformer.text_model.embeddings.', @@ -1052,6 +1042,15 @@ def clean_diffuser_pipe(pipe): def set_diffuser_pipe(pipe, new_pipe_type): + exclude = [ + 'StableDiffusionReferencePipeline', + 'StableDiffusionAdapterPipeline', + 'AnimateDiffPipeline', + 'AnimateDiffSDXLPipeline', + 'OmniGenPipeline', + 'StableDiffusion3ControlNetPipeline', + ] + n = getattr(pipe.__class__, '__name__', '') if new_pipe_type == DiffusersTaskType.TEXT_2_IMAGE: clean_diffuser_pipe(pipe) @@ -1060,7 +1059,7 @@ def set_diffuser_pipe(pipe, new_pipe_type): return pipe # skip specific pipelines - if n in ['StableDiffusionReferencePipeline', 'StableDiffusionAdapterPipeline', 'AnimateDiffPipeline', 'AnimateDiffSDXLPipeline', 'OmniGenPipeline']: + if n in exclude: return pipe if 'Onnx' in pipe.__class__.__name__: return pipe diff --git a/modules/shared.py b/modules/shared.py index ae94f26cf..342b5222c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -821,8 +821,8 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "detailer_conf": OptionInfo(0.6, "Min confidence", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.05, "visible": False}), "detailer_max": OptionInfo(2, "Max detected", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1, "visible": False}), "detailer_iou": OptionInfo(0.5, "Max overlap", gr.Slider, {"minimum": 0, "maximum": 1.0, "step": 0.05, "visible": False}), - "detailer_min_size": OptionInfo(0, "Min object size", gr.Slider, {"minimum": 0, "maximum": 1024, "step": 1, "visible": False}), - "detailer_max_size": OptionInfo(0, "Max object size", gr.Slider, {"minimum": 0, "maximum": 1024, "step": 1, "visible": False}), + "detailer_min_size": OptionInfo(0.0, "Min object size", gr.Slider, {"minimum": 0.1, "maximum": 1, "step": 0.05, "visible": False}), + "detailer_max_size": OptionInfo(1.0, "Max object size", gr.Slider, {"minimum": 0.1, "maximum": 1, "step": 0.05, "visible": False}), "detailer_padding": OptionInfo(20, "Item padding", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1, "visible": False}), "detailer_blur": OptionInfo(10, "Item edge blur", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1, "visible": False}), "detailer_strength": OptionInfo(0.5, "Detailer strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01, "visible": False}), @@ -1101,7 +1101,7 @@ profiler = None opts = Options() config_filename = cmd_opts.config opts.load(config_filename) -cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) +cmd_opts = cmd_args.settings_args(opts, cmd_opts) if cmd_opts.use_xformers: opts.data['cross_attention_optimization'] = 'xFormers' opts.data['uni_pc_lower_order_final'] = opts.schedulers_use_loworder # compatibility diff --git a/modules/ui.py b/modules/ui.py index 8ea565cfc..039ef6487 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -355,13 +355,20 @@ def create_ui(startup_timer = None): ui_onnx.create_ui() with gr.TabItem("Change log", id="change_log", elem_id="system_tab_changelog"): - with open('CHANGELOG.md', 'r', encoding='utf-8') as f: - md = f.read() - gr.Markdown(md) + def get_changelog(): + with open('CHANGELOG.md', 'r', encoding='utf-8') as f: + content = f.read() + content = content.replace('# Change Log for SD.Next', ' ') + return content - with gr.TabItem("Licenses", id="system_licenses", elem_id="system_tab_licenses"): - gr.HTML(shared.html("licenses.html"), elem_id="licenses", elem_classes="licenses") - create_dirty_indicator("tab_licenses", [], interactive=False) + with gr.Column(): + get_changelog_btn = gr.Button(value='Get changelog', elem_id="get_changelog") + with gr.Column(): + _changelog_search = gr.Textbox(label="Search", elem_id="changelog_search") + _changelog_result = gr.HTML(elem_id="changelog_result") + + changelog_markdown = gr.Markdown('', elem_id="changelog_markdown") + get_changelog_btn.click(fn=get_changelog, outputs=[changelog_markdown], show_progress=True) def unload_sd_weights(): modules.sd_models.unload_model_weights(op='model') diff --git a/package.json b/package.json index 2c0fa6b50..bf5a366ea 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/vladmandic/automatic/issues" }, "homepage": "https://github.com/vladmandic/automatic", - "license": "AGPLv3", + "license": "Apache-2.0", "engines": { "node": ">=14.0.0" }, diff --git a/requirements.txt b/requirements.txt index d376fe320..c6fcf97ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ pandas protobuf==4.25.3 pytorch_lightning==1.9.4 tokenizers==0.20.1 -transformers==4.46.0 +transformers==4.46.1 urllib3==1.26.19 Pillow==10.4.0 timm==0.9.16 diff --git a/scripts/prompt_enhance.py b/scripts/prompt_enhance.py index 2fb513e76..a02aea608 100644 --- a/scripts/prompt_enhance.py +++ b/scripts/prompt_enhance.py @@ -32,10 +32,10 @@ class Script(scripts.Script): def load(self): if self.tokenizer is None: - self.tokenizer = AutoTokenizer.from_pretrained('gokaygokay/Flux-Prompt-Enhance', cache_dir=shared.opts.diffusers_dir) + self.tokenizer = AutoTokenizer.from_pretrained('gokaygokay/Flux-Prompt-Enhance', cache_dir=shared.opts.hfcache_dir) if self.model is None: shared.log.info(f'Prompt enhance: model="{repo_id}"') - self.model = AutoModelForSeq2SeqLM.from_pretrained('gokaygokay/Flux-Prompt-Enhance', cache_dir=shared.opts.diffusers_dir).to(device=devices.cpu, dtype=devices.dtype) + self.model = AutoModelForSeq2SeqLM.from_pretrained('gokaygokay/Flux-Prompt-Enhance', cache_dir=shared.opts.hfcache_dir).to(device=devices.cpu, dtype=devices.dtype) def enhance(self, prompt, auto_apply: bool = False, temperature: float = 0.7, repetition_penalty: float = 1.2, max_length: int = 128): self.load() diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index e8f649182..e8ecb5bd4 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -48,10 +48,11 @@ class Script(scripts.Script): csv_mode = gr.Checkbox(label='Text inputs', value=False, elem_id=self.elem_id("csv_mode"), container=False) draw_legend = gr.Checkbox(label='Legend', value=True, elem_id=self.elem_id("draw_legend"), container=False) no_fixed_seeds = gr.Checkbox(label='Random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"), container=False) + include_time = gr.Checkbox(label='Add time info', value=False, elem_id=self.elem_id("include_time"), container=False) with gr.Column(): - no_grid = gr.Checkbox(label='Skip grid', value=False, elem_id=self.elem_id("no_xyz_grid"), container=False) - include_lone_images = gr.Checkbox(label='Sub-images', value=False, elem_id=self.elem_id("include_lone_images"), container=False) - include_sub_grids = gr.Checkbox(label='Sub-grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) + include_grid = gr.Checkbox(label='Include main grid', value=True, elem_id=self.elem_id("no_xyz_grid"), container=False) + include_subgrids = gr.Checkbox(label='Include sub grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) + include_images = gr.Checkbox(label='Include images', value=False, elem_id=self.elem_id("include_lone_images"), container=False) with gr.Row(): margin_size = gr.Slider(label="Grid margins", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) with gr.Row(): @@ -130,10 +131,9 @@ class Script(scripts.Script): (z_values_dropdown, lambda params:get_dropdown_update_from_params("Z",params)), ) - return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, no_grid, include_lone_images, include_sub_grids, margin_size] + return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size] - def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, no_grid, include_lone_images, include_sub_grids, margin_size): # pylint: disable=W0221 - shared.log.debug(f'xyzgrid: x_type={x_type}|x_values={x_values}|x_values_dropdown={x_values_dropdown}|y_type={y_type}|{y_values}={y_values}|{y_values_dropdown}={y_values_dropdown}|z_type={z_type}|z_values={z_values}|z_values_dropdown={z_values_dropdown}|draw_legend={draw_legend}|include_lone_images={include_lone_images}|include_sub_grids={include_sub_grids}|no_grid={no_grid}|margin_size={margin_size}') + def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size): # pylint: disable=W0221 if not no_fixed_seeds: processing.fix_seed(p) if not shared.opts.return_grid: @@ -307,38 +307,38 @@ class Script(scripts.Script): z_labels=[z_opt.format_value(p, z_opt, z) for z in zs], cell=cell, draw_legend=draw_legend, - include_lone_images=include_lone_images, - include_sub_grids=include_sub_grids, + include_lone_images=include_images, + include_sub_grids=include_subgrids, first_axes_processed=first_axes_processed, second_axes_processed=second_axes_processed, margin_size=margin_size, - no_grid=no_grid, + no_grid=not include_grid, + include_time=include_time, ) if not processed.images: - return processed # It broke, no further handling needed. + return processed # something broke, no further handling needed. z_count = len(zs) - processed.infotexts[:1+z_count] = grid_infotext[:1+z_count] # Set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids) - if not include_lone_images: - # Don't need sub-images anymore, drop from list: - if no_grid and include_sub_grids: + processed.infotexts[:1+z_count] = grid_infotext[:1+z_count] # set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids) + if not include_images: # dont need sub-images anymore, drop from list: + if not include_grid and include_subgrids: processed.images = processed.images[:z_count] # we don't have the main grid image, and need zero additional sub-images else: processed.images = processed.images[:z_count+1] # we either have the main grid image, or need one sub-images - if shared.opts.grid_save: # Auto-save main and sub-grids: - grid_count = z_count + ( 1 if not no_grid and z_count > 1 else 0 ) + if shared.opts.grid_save: # auto-save main and sub-grids: + grid_count = z_count + ( 1 if include_grid and z_count > 1 else 0 ) for g in range(grid_count): adj_g = g-1 if g > 0 else g info = processed.infotexts[g] prompt = processed.all_prompts[adj_g] seed = processed.all_seeds[adj_g] images.save_image(processed.images[g], p.outpath_grids, "grid", info=info, extension=shared.opts.grid_format, prompt=prompt, seed=seed, grid=True, p=processed) - if not include_sub_grids: # Done with sub-grids, drop all related information: + if not include_subgrids: # done with sub-grids, drop all related information: for _sg in range(z_count): del processed.images[1] del processed.all_prompts[1] del processed.all_seeds[1] del processed.infotexts[1] - elif no_grid: + elif include_grid: del processed.infotexts[0] return processed diff --git a/scripts/xyz_grid_classes.py b/scripts/xyz_grid_classes.py index 335d66186..202482157 100644 --- a/scripts/xyz_grid_classes.py +++ b/scripts/xyz_grid_classes.py @@ -84,13 +84,13 @@ class SharedSettingsStackHelper(object): axis_options = [ AxisOption("Nothing", str, do_nothing, fmt=format_nothing), - AxisOption("[Model] Model", str, apply_checkpoint, cost=1.0, fmt=format_value, choices=lambda: sorted(sd_models.checkpoints_list)), + AxisOption("[Model] Model", str, apply_checkpoint, cost=1.0, fmt=format_value_add_label, choices=lambda: sorted(sd_models.checkpoints_list)), AxisOption("[Model] UNET", str, apply_unet, cost=0.8, choices=lambda: ['None'] + list(sd_unet.unet_dict)), AxisOption("[Model] VAE", str, apply_vae, cost=0.6, choices=lambda: ['None'] + list(sd_vae.vae_dict)), - AxisOption("[Model] Refiner", str, apply_refiner, cost=0.8, fmt=format_value, choices=lambda: ['None'] + sorted(sd_models.checkpoints_list)), + AxisOption("[Model] Refiner", str, apply_refiner, cost=0.8, fmt=format_value_add_label, choices=lambda: ['None'] + sorted(sd_models.checkpoints_list)), AxisOption("[Model] Text encoder", str, apply_te, cost=0.7, choices=shared_items.sd_te_items), - AxisOption("[Model] Dictionary", str, apply_dict, fmt=format_value, cost=0.9, choices=lambda: ['None'] + list(sd_models.checkpoints_list)), - AxisOption("[Prompt] Search & replace", str, apply_prompt, fmt=format_value), + AxisOption("[Model] Dictionary", str, apply_dict, fmt=format_value_add_label, cost=0.9, choices=lambda: ['None'] + list(sd_models.checkpoints_list)), + AxisOption("[Prompt] Search & replace", str, apply_prompt, fmt=format_value_add_label), AxisOption("[Prompt] Prompt order", str_permutations, apply_order, fmt=format_value_join_list), AxisOption("[Network] LoRA", str, apply_lora, cost=0.5, choices=list_lora), AxisOption("[Network] LoRA strength", float, apply_setting('extra_networks_default_multiplier')), @@ -109,8 +109,8 @@ axis_options = [ AxisOption("[Process] Model args", str, apply_task_args), AxisOption("[Process] Processing args", str, apply_processing), AxisOption("[Process] Server options", str, apply_options), - AxisOptionTxt2Img("[Sampler] Name", str, apply_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), - AxisOptionImg2Img("[Sampler] Name", str, apply_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers_for_img2img]), + AxisOptionTxt2Img("[Sampler] Name", str, apply_sampler, fmt=format_value_add_label, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), + AxisOptionImg2Img("[Sampler] Name", str, apply_sampler, fmt=format_value_add_label, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers_for_img2img]), AxisOption("[Sampler] Sigma method", str, apply_setting("schedulers_sigma"), choices=lambda: ['default', 'karras', 'beta', 'exponential']), AxisOption("[Sampler] Timestep spacing", str, apply_setting("schedulers_timestep_spacing"), choices=lambda: ['default', 'linspace', 'leading', 'trailing']), AxisOption("[Sampler] Timestep range", int, apply_setting("schedulers_timesteps_range")), @@ -122,7 +122,7 @@ axis_options = [ AxisOption("[Sampler] eta delta", float, apply_setting("eta_noise_seed_delta")), AxisOption("[Sampler] eta multiplier", float, apply_setting("scheduler_eta")), AxisOption("[Refine] Upscaler", str, apply_field("hr_upscaler"), cost=0.3, choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), - AxisOption("[Refine] Sampler", str, apply_hr_sampler_name, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), + AxisOption("[Refine] Sampler", str, apply_hr_sampler_name, fmt=format_value_add_label, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), AxisOption("[Refine] Denoising strength", float, apply_field("denoising_strength")), AxisOption("[Refine] Hires steps", int, apply_field("hr_second_pass_steps")), AxisOption("[Refine] Guidance scale", float, apply_field("image_cfg_scale")), @@ -131,7 +131,7 @@ axis_options = [ AxisOption("[Refine] Refiner steps", float, apply_field("refiner_steps")), AxisOption("[Postprocess] Upscaler", str, apply_upscaler, cost=0.4, choices=lambda: [x.name for x in shared.sd_upscalers][1:]), AxisOption("[Postprocess] Context", str, apply_context, choices=lambda: ["Add with forward", "Remove with forward", "Add with backward", "Remove with backward"]), - AxisOption("[Postprocess] Detailer", str, apply_detailer, fmt=format_value), + AxisOption("[Postprocess] Detailer", str, apply_detailer, fmt=format_value_add_label), AxisOption("[HDR] Mode", int, apply_field("hdr_mode")), AxisOption("[HDR] Brightness", float, apply_field("hdr_brightness")), AxisOption("[HDR] Color", float, apply_field("hdr_color")), diff --git a/scripts/xyz_grid_draw.py b/scripts/xyz_grid_draw.py index 8f95e696b..9a9f2246c 100644 --- a/scripts/xyz_grid_draw.py +++ b/scripts/xyz_grid_draw.py @@ -4,23 +4,28 @@ from PIL import Image from modules import shared, images, processing -def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend, include_lone_images, include_sub_grids, first_axes_processed, second_axes_processed, margin_size, no_grid): # pylint: disable=unused-argument - hor_texts = [[images.GridAnnotation(x)] for x in x_labels] - ver_texts = [[images.GridAnnotation(y)] for y in y_labels] - title_texts = [[images.GridAnnotation(z)] for z in z_labels] +def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend, include_lone_images, include_sub_grids, first_axes_processed, second_axes_processed, margin_size, no_grid: False, include_time: False): # pylint: disable=unused-argument + x_texts = [[images.GridAnnotation(x)] for x in x_labels] + y_texts = [[images.GridAnnotation(y)] for y in y_labels] + z_texts = [[images.GridAnnotation(z)] for z in z_labels] list_size = (len(xs) * len(ys) * len(zs)) processed_result = None shared.state.job_count = list_size * p.n_iter t0 = time.time() + i = 0 def process_cell(x, y, z, ix, iy, iz): - nonlocal processed_result + nonlocal processed_result, i + i += 1 + shared.log.debug(f'XYZ grid process: x={ix+1}/{len(xs)} y={iy+1}/{len(ys)} z={iz+1}/{len(zs)} total={i/list_size:.2f}') def index(ix, iy, iz): return ix + iy * len(xs) + iz * len(xs) * len(ys) shared.state.job = 'grid' + p0 = time.time() processed: processing.Processed = cell(x, y, z, ix, iy, iz) + p1 = time.time() if processed_result is None: processed_result = copy(processed) if processed_result is None: @@ -30,13 +35,17 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend processed_result.all_prompts = [None] * list_size processed_result.all_seeds = [None] * list_size processed_result.infotexts = [None] * list_size + processed_result.time = [0] * list_size processed_result.index_of_first_image = 1 idx = index(ix, iy, iz) if processed is not None and processed.images: processed_result.images[idx] = processed.images[0] + if include_time: + processed_result.images[idx] = images.draw_overlay(processed_result.images[idx], f'time: {p1 - p0:.2f}') processed_result.all_prompts[idx] = processed.prompt processed_result.all_seeds[idx] = processed.seed processed_result.infotexts[idx] = processed.infotexts[0] + processed_result.time[idx] = round(p1 - p0, 2) else: cell_mode = "P" cell_size = (processed_result.width, processed_result.height) @@ -44,6 +53,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend cell_mode = processed_result.images[0].mode cell_size = processed_result.images[0].size processed_result.images[idx] = Image.new(cell_mode, cell_size) + return if first_axes_processed == 'x': for ix, x in enumerate(xs): @@ -93,7 +103,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend if (not no_grid or include_sub_grids) and images.check_grid_size(to_process): grid = images.image_grid(to_process, rows=len(ys)) if draw_legend: - grid = images.draw_grid_annotations(grid, w, h, hor_texts, ver_texts, margin_size, title=title_texts[i]) + grid = images.draw_grid_annotations(grid, w, h, x_texts, y_texts, margin_size, title=z_texts[i]) processed_result.images.insert(i, grid) processed_result.all_prompts.insert(i, processed_result.all_prompts[idx0]) processed_result.all_seeds.insert(i, processed_result.all_seeds[idx0]) diff --git a/scripts/xyz_grid_on.py b/scripts/xyz_grid_on.py index ac1bc4c2f..cdef01e60 100644 --- a/scripts/xyz_grid_on.py +++ b/scripts/xyz_grid_on.py @@ -57,9 +57,10 @@ class Script(scripts.Script): draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"), container=False) csv_mode = gr.Checkbox(label='Use text inputs', value=False, elem_id=self.elem_id("csv_mode"), container=False) no_fixed_seeds = gr.Checkbox(label='Use random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"), container=False) + include_time = gr.Checkbox(label='Add time info', value=False, elem_id=self.elem_id("include_time"), container=False) with gr.Column(): - include_grid = gr.Checkbox(label='Create main grid', value=True, elem_id=self.elem_id("no_xyz_grid"), container=False) - include_subgrids = gr.Checkbox(label='Create partial grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) + include_grid = gr.Checkbox(label='Include main grid', value=True, elem_id=self.elem_id("no_xyz_grid"), container=False) + include_subgrids = gr.Checkbox(label='Include sub grids', value=False, elem_id=self.elem_id("include_sub_grids"), container=False) include_images = gr.Checkbox(label='Include images', value=False, elem_id=self.elem_id("include_lone_images"), container=False) with gr.Row(): margin_size = gr.Slider(label="Grid margins", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) @@ -139,9 +140,9 @@ class Script(scripts.Script): (z_values_dropdown, lambda params:get_dropdown_update_from_params("Z",params)), ) - return [enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, margin_size] + return [enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size] - def process(self, p, enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, margin_size): # pylint: disable=W0221 + def process(self, p, enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, include_time, margin_size): # pylint: disable=W0221 global active, cache # pylint: disable=W0603 cache = None if not enabled or active: @@ -327,8 +328,10 @@ class Script(scripts.Script): second_axes_processed=second_axes_processed, margin_size=margin_size, no_grid=not include_grid, + include_time=include_time, ) + """ if not processed.images: active = False return processed # It broke, no further handling needed. @@ -354,6 +357,43 @@ class Script(scripts.Script): active = False cache = processed return processed + """ - def process_images(self, p, enabled, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, csv_mode, draw_legend, no_fixed_seeds, include_grid, include_subgrids, include_images, margin_size): # pylint: disable=W0221, W0613 + if not processed.images: + return processed # It broke, no further handling needed. + z_count = len(zs) + processed.infotexts[:1+z_count] = grid_infotext[:1+z_count] # set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids) + if not include_images: # dont need sub-images anymore, drop from list: + if not include_grid and include_subgrids: + processed.images = processed.images[:z_count] # we don't have the main grid image, and need zero additional sub-images + else: + processed.images = processed.images[:z_count+1] # we either have the main grid image, or need one sub-images + + if shared.opts.grid_save: # auto-save main and sub-grids: + grid_count = z_count + ( 1 if include_grid and z_count > 1 else 0 ) + for g in range(grid_count): + adj_g = g-1 if g > 0 else g + info = processed.infotexts[g] + prompt = processed.all_prompts[adj_g] + seed = processed.all_seeds[adj_g] + images.save_image(processed.images[g], p.outpath_grids, "grid", info=info, extension=shared.opts.grid_format, prompt=prompt, seed=seed, grid=True, p=processed) + if not include_subgrids: # done with sub-grids, drop all related information: + for _sg in range(z_count): + del processed.images[1] + del processed.all_prompts[1] + del processed.all_seeds[1] + del processed.infotexts[1] + elif include_grid: + del processed.infotexts[0] + + p.do_not_save_grid = True + p.do_not_save_samples = True + active = False + cache = processed + return processed + + + def process_images(self, p, *args): # pylint: disable=W0221, W0613 + if p.iteration > 0 and cache is not None and len(cache.images) > 0: + cache.images = [] # avoid returning same images multiple items return cache diff --git a/wiki b/wiki index 4360bc7fc..a64e3776f 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 4360bc7fcfd5dc301b825a6c5dcf4274c8eba983 +Subproject commit a64e3776fd357ccf85fa751a0efc65fead72c02a